Writing
Speculative Decoding: Making LLMs 3x Faster with Zero Quality Loss
A small, fast draft model proposes the next few tokens and the large one verifies them in a single batched pass. The speedup is 2-3x, and the output distribution is provably identical to running the large model alone.
Why Text Generation Is Slow
If you have ever run a local model like Llama 3 or Mistral, or watched ChatGPT think, you have felt the latency: you send a prompt and the text arrives one word at a time.
That is not a flaw in the implementation. It is the architecture. Modern
LLMs are autoregressive: to generate a sequence of
K tokens, the model runs its entire network K
times, in series. It cannot begin word five until the arithmetic for word
four has finished. It is the slowest possible way to produce a sequence, and
every transformer does it.
The Memory Wall
Here is where it gets frustrating. Measure a GPU running a large transformer and you find the compute cores sitting idle for most of the time.
The reason is that loading the model's weights out of high-bandwidth memory into the cores takes longer than the arithmetic those cores then perform. The memory bus is the traffic jam; the engine is revving, waiting for the road to clear. In the standard terminology, single-stream inference from a large model is memory bandwidth bound, not compute bound.
Spare compute is sitting there, already paid for. Any technique that converts idle arithmetic into finished tokens is free speed, and that is precisely the trade speculative decoding makes.
Not Every Token Is Hard
Yaniv Leviathan, Matan Kalman and Yossi Matias at Google Research started from an observation that is obvious once stated: not all inference steps are alike. Some genuinely need a very large model. Others are approximated perfectly well by a small one.
- Easy. The prompt is "The sky is". The next token is almost certainly "blue". A child could guess it; so could a model a hundred times smaller.
- Hard. The prompt is "The chef added a pinch of". It could be salt, sugar, paprika, cumin. This one needs the context depth the big model has.
So why spend an eleven-billion-parameter forward pass on "blue"? Why not let something small and fast do the guessing, and keep the large model for what it is uniquely good at judging whether the guess was right?
The Professor and the Intern
Two models do the work, and it helps to name them.
p(x_t | x_<t).
q(x_t | x_<t).
The Intern proposes. The Professor disposes. Everything else is the machinery that makes that exchange lossless.
Alpha and c: The Two Numbers That Decide
Whether this pays off comes down to two quantities.
| Symbol | Name | What it measures |
|---|---|---|
| α | Acceptance rate |
How often the Professor accepts the Intern's guess, formally
the expected overlap between the two distributions. At
α = 0.7 the Intern is right seven times in ten.
How smart is the Intern?
|
| c | Cost coefficient |
How long the Intern takes relative to the Professor. Professor at
100 ms, Intern at 1 ms gives
c = 0.01. How cheap is the Intern?
|
As long as α > c, meaning the Intern is right more often than it is expensive, you get a speedup. Not usually, not on average: mathematically guaranteed.
When the Intern is a hundred times smaller, c is around 0.01.
An Intern that guesses right one time in fifty still clears the bar.
Section 11 pushes that idea to its limit.
The Five Steps
Every generation step runs the same loop.
M_q for γ
tokens (gamma, typically three to seven). It does this autoregressively,
one at a time, but it is small enough that this happens in a blink. From
the prefix "The recipe calls for" it might offer salt,
and, pepper, to.
M_p on all γ + 1 positions in a single
batched pass: the bare prefix, prefix + [x₁],
prefix + [x₁, x₂], and so on. Because GPUs
are built for exactly this shape of matrix multiply, five runs cost
about what one run costs in wall-clock time.
q(xᵢ) against p(xᵢ). If
q ≤ p, the Intern was more cautious than the
Professor and the token is accepted outright. If q > p
it was overconfident, and the token is rejected with probability
1 − p/q. The first rejection stops the walk and
discards everything after it.
n accepted tokens,
and the one corrected or bonus token from the Professor.
Every iteration emits at least one token, so this is never slower than plain decoding. At best it emits γ + 1.
Why the Output Is Identical
This is the part that makes the technique a breakthrough rather than a trick. Usually, speeding up a model means trading away accuracy. Quantisation and distillation both give you outputs that are similar, not identical. Speculative decoding gives you the same distribution, exactly.
The mechanism is rejection sampling, and the intuition is short. You want to
sample from p, the Professor. You sample from q,
the Intern, instead.
-
Where
q ≤ p. The Intern was cautious. Accept always. You have captured exactlyq(x)of the mass, the part ofpthatqcovers. -
Where
q > p. The Intern was overconfident. Accept with probabilityp/q, which keeps exactlyp(x). On the rejection, sample from the residualmax(0, p − q).
Sum the accepted-via-q mass and the rejected-via-residual mass
and the q terms cancel. The algebra lands on p(x).
The output distribution is provably unchanged. A specific generation is not bit-for-bit reproducible against a non-speculative run with the same seed, because the dice rolls consume randomness differently. If you have tests pinned to exact token sequences, they will need rewriting around the distribution, not the path.
The Algorithm in Python
The paper's description translates almost directly into code. This assumes
Mp and Mq are callables returning probability
distributions over the vocabulary.
import numpy as np
def speculative_decoding_step(Mp, Mq, prefix, gamma):
# 1. Sample gamma guesses x1..x_gamma from Mq, autoregressively.
x, q = [], []
for _ in range(gamma):
qi = Mq(prefix + x)
xi = np.random.choice(len(qi), p=qi)
q.append(qi)
x.append(xi)
# 2. Run Mp on every position. One batched call in production.
p = [Mp(prefix + x[:i]) for i in range(gamma + 1)]
# 3. Walk the guesses in order and find n, the number accepted.
n = gamma
for i in range(gamma):
r = np.random.uniform(0, 1)
if r > p[i][x[i]] / q[i][x[i]]:
n = i
break
# 4. Corrected token, or a free bonus one if nothing was rejected.
if n == gamma:
p_prime = p[n]
else:
p_prime = np.maximum(p[n] - q[n], 0)
p_prime = p_prime / p_prime.sum()
# 5. n accepted draft tokens, plus exactly one token from Mp.
t = np.random.choice(len(p_prime), p=p_prime)
return prefix + x[:n] + [t]
The full annotated version is in this post's
codes/python/ directory.
What each block is doing
-
The draft loop. Both the tokens and their distributions
are kept, because
qis needed again at verification time and recomputing it would throw away the saving. -
The comprehension over
Mp. Written serially for readability, but this is the one line that must become a single batched forward pass in a real implementation. Run as written, it is slower than plain decoding. -
The dice roll.
r > p/qrejects. Whenq ≤ pthe ratio is at least one,ris drawn from [0, 1), and acceptance is automatic: the two branches of the maths collapse into one line of code. -
The residual.
max(0, p − q)strips out what the Intern already claimed, and renormalising makes it a distribution again.
Empirical Results
The paper tests T5-XXL (11B) as the Professor against three Interns (T5-Small at 77M, about 100× smaller; T5-Base at 250M, 40×; and T5-Large at 800M, 13×) on English-to-German translation and on CNN/DailyMail summarisation.
| Task | Intern | Mode | Optimal γ | α | Walltime speedup |
|---|---|---|---|---|---|
| Translation (En→De) | T5-Small 77M | Greedy | 7 | 0.75 | 3.4× |
| Translation (En→De) | T5-Base 250M | Greedy | 7 | 0.80 | 2.8× |
| Translation (En→De) | T5-Large 800M | Greedy | 7 | 0.82 | 1.7× |
| Translation (En→De) | T5-Small 77M | Standard | 7 | 0.62 | 2.6× |
| Translation (En→De) | T5-Base 250M | Standard | 5 | 0.68 | 2.4× |
| Translation (En→De) | T5-Large 800M | Standard | 3 | 0.71 | 1.4× |
| Summarization (CNN/DM) | T5-Small 77M | Greedy | 5 | 0.65 | 3.1× |
| Summarization (CNN/DM) | T5-Base 250M | Greedy | 5 | 0.73 | 3.0× |
| Summarization (CNN/DM) | T5-Large 800M | Greedy | 3 | 0.74 | 2.2× |
| Summarization (CNN/DM) | T5-Small 77M | Standard | 5 | 0.53 | 2.3× |
| Summarization (CNN/DM) | T5-Base 250M | Standard | 3 | 0.55 | 2.2× |
| Summarization (CNN/DM) | T5-Large 800M | Standard | 3 | 0.56 | 1.7× |
What the Numbers Say
The dumbest Intern wins
T5-Small is the least accurate draft model and the
fastest overall. T5-Large is measurably smarter, with α of
0.82 against 0.75, and finishes well behind it, because the extra
accuracy costs more in c than it returns in α. Speed beats
cleverness here, and by a wide margin.
Greedy beats sampling
Temperature zero always outruns stochastic sampling. Forced onto the single most likely token, the two models agree far more often. Add randomness and the Intern starts picking creative tokens the Professor will not endorse, and every one of those is a rejection.
Translation beats summarisation
English to German is close to a deterministic mapping, so a small model tracks it well. Summarisation requires deciding what matters in an article, which is exactly the judgment a 77M-parameter model does not have.
Do not reach for the best small model you have. Reach for the fastest one that clears α > c, then tune γ from there. The optimum is usually smaller than intuition suggests.
The Bigram Intern
What if you have no small transformer at all, just the weights of the giant one? The authors tried an Intern that is not a neural network in any sense: a bigram model. A lookup table that takes the previous token and returns whatever most often followed it in the training data. No grammar, no syntax, no meaning. Counting.
It guessed right twenty percent of the time. And because a table lookup is
free, with c effectively zero, that
α = 0.2 still bought a 1.25× speedup.
You do not need a good draft model. You need a draft model that is right one time in five and costs nothing. That is a much easier thing to find, and it is why this technique generalises so well.
When to Use It, and When Not To
Reach for it when
- Memory bandwidth is the bottleneck. True of nearly any large model at batch size one on a single GPU, the case where the compute cores are idle and you have capacity to spend.
- A smaller sibling exists. T5, GPT, LaMDA and Llama all ship distilled or smaller variants. That is your Intern, already trained.
- The sequences are long. More tokens means more chances to accept a draft and amortise the setup.
Leave it alone when
- Compute is the bottleneck. Serving a hundred concurrent users on one GPU already saturates the cores. There is no idle capacity left to spend, and the extra parallel runs will cost you throughput rather than buy it.
-
The Intern is not cheap enough. A draft model only twice
as fast as the target puts
cat 0.5, and α rarely clears that. - You need bit-exact reproducibility. The distribution is identical; the specific path through it is not.
This is the failure people hit in production. Speculative decoding is spectacular in local and single-user settings and can be actively counterproductive under heavy batched serving. Benchmark it at your concurrency, not at batch size one.
Arithmetic Traded for Wall Clock
Be clear about what is being spent. Standard decoding runs
M_p once per token. Speculative decoding runs it
γ + 1 times per iteration. That is strictly more total
arithmetic, and when the Intern guesses badly, most of it is discarded.
It still wins, because the arithmetic was never what you were waiting for. The weights had to be read out of memory regardless; the extra multiplications ride along in cycles that were going to be idle. You are not buying speed with accuracy. You are buying it with work the hardware was going to waste anyway.
Using It Today
This is not a research curiosity waiting on productionisation.
- vLLM and ExLlamaV2 both ship variants of speculative decoding already.
- Medusa takes a different route, attaching fine-tuned draft heads to the main model rather than running a separate one.
- DeepMind (Chen et al., 2023) arrived at the same technique independently and confirmed 2–2.5× on Chinchilla 70B shortly after the original paper.
If you run models locally, look for the speculative flags in your inference engine. If you are building a serving system, a small draft model on the side is a low-cost addition for a large return.
Final Thoughts
What makes this elegant is where it aims. The obvious target is the serial nature of autoregressive generation, and the obvious attack is to make the model smaller or cheaper, which costs quality. Leviathan and colleagues went after the observation underneath it instead: the bottleneck is not the arithmetic, it is the memory. Spend the idle compute on parallel verification and let a tiny model guess ahead.
And they did it with a proof that the output distribution is untouched. No retraining, no new models, no change to what comes out. Where memory bandwidth is the limit and compute is going spare, it is close to a free default.
We are moving into a period where the interesting question is not how smart a model is, but how efficiently it can be run. This is a large step in that direction.
References
- Leviathan, Y., Kalman, M., & Matias, Y. (2023). Fast Inference from Transformers via Speculative Decoding. Proceedings of the 40th International Conference on Machine Learning.
- Chen, C., Borgeaud, S., Irving, G., Lespiau, J., Sifre, L., & Jumper, J. (2023). Accelerating Large Language Model Decoding with Speculative Sampling. arXiv.
- Raffel, C., et al. (2020). Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer. JMLR.