42x faster prompt lookup drafting in llama.cpp
Optimizing Prompt Lookup Drafting in llama.cpp: A 42x Speedup
By Hayder Tirmazi | Originally published: 2026-09-26 [Homepage] | [GitHub] | [Twitter]
⚡ Executive Summary (TL;DR)
I have implemented a series of performance optimizations—heavily inspired by the research of Martin Ankerl and Daniel Lemire—that accelerate prompt lookup drafting in llama.cpp by up to 42x while simultaneously reducing memory consumption by up to 2.6x.
Update: Following my work, Daniel Lemire submitted a Pull Request that further boosts the speed of prompt lookup drafting by an additional 4.2x on top of my optimizations.
🔍 Understanding Prompt Lookup Decoding
Many high-performance inference engines (such as vllm and llama.cpp) and libraries (like Hugging Face's transformers) utilize prompt lookup decoding. This technique, also known as n-gram speculation, is designed to accelerate the generation of tokens.
Technically, this is a specialized form of speculative decoding. Instead of using a complex secondary model, it uses a sophisticated "really stupid" draft model: the n-gram model.
How it Works
The engine attempts to draft the next tokens. For a 3-gram model, it looks for sequences like:
- Generally: for any .
An n-gram model is probabilistic; it predicts the subsequent token based on the preceding tokens. By parsing a text corpus into n-grams, the model can simply select the token that most frequently followed that specific sequence in the training data.
The N-Gram Cache
The system relies on an n-gram cache, a data structure storing , which represents the count of how many times token follows the n-gram .
| Cache Type | Symbol | Description | Scope |
|---|---|---|---|
| Context Cache | Stores n-grams (sizes 1-4) for the current sequence . | Updated in real-time during generation. | |
| Dynamic Cache | Stores n-gram counts from previous model executions. | Persists across conversations. | |
| Static Cache | Stores 2-grams from a fixed text corpus. | Created via llama-lookup-create. |
⚙️ The Drafting Algorithm
To draft a new token, llama.cpp looks at the previous tokens, denoted as .
1. Scoring
For every token in the vocabulary, a score is calculated:
Where the weight is defined as:
Note: represents either the context cache () or the dynamic cache (). The weight ensures that tokens appearing in the static cache are heavily prioritized.
2. Selection and Validation
The token with the highest score is chosen: . Let be the total occurrences of .
The token is accepted as a draft only if it meets these two conditions:
Condition:
This means the sequence must appear at least times, and must follow it at least percent of the time.
3. Hard-coded Thresholds (Release b11182)
The values for (minimum count) and (minimum probability) are as follows:
- Context Cache:
- Dynamic Cache:
🛠️ Execution Flow
The drafting process follows a strict priority hierarchy:
Static Cache Fallback Logic:
If both context and dynamic caches fail, llama.cpp uses the static cache independently. It selects with the largest if:
🧪 Experimental Setup
To validate these optimizations, I utilized the example provided in the llama.cpp repository.
Tools Used:
# Create static cache from corpus
llama-lookup-create
# Benchmark the drafting performance
llama-lookup-stats
Methodology:
- Dataset: I used
WikiText-103to build the static cache. - Simulation:
llama-lookup-statstreats the tokens in the test file as if they were generated by a model. - Verification: It iterates through the drafting loop and tracks:
- Token match rate (Acceptance rate)
- Drafting latency
- Static cache load time
Since I did not alter the underlying algorithm, the acceptance rate should remain constant. I verified that my changes maintained nearly identical acceptance rates compared to the original implementation.
Key Metrics for Improvement:
Figure 1: Conceptual representation of latency reduction.