The plain version
When one person chats with a local model, the graphics card spends most of each step waiting for weights to arrive from memory, not doing maths (see memory bandwidth). Reading those weights once and using them to check five tokens costs almost the same as using them for one. Speculative decoding exploits that spare capacity.
How it works. A small draft model from the same family (say, Qwen3-0.6B next to Qwen3-32B) quickly writes a guess of the next few tokens. The big target model then checks all of them in a single pass. It keeps every guess up to the first one it disagrees with, swaps in its own token there, and the round starts again.
An everyday analogy. A junior writer drafts the next sentence, and the editor reads it in one go, keeping the words they'd have written anyway and correcting the first one they wouldn't. If the junior is good, the editor gets through pages much faster. The finished text is still the editor's.
The output is the same as the big model on its own; only the speed changes. The original papers prove this for sampling, not just greedy decoding. Leviathan et al. report 2–3× on T5-XXL "with identical outputs". DeepMind measured 2–2.5× on the 70B Chinchilla. EAGLE, which drafts from the target's own internal features instead of a separate model, reports 2.7–3.5× on LLaMA2-Chat 70B.
Why your mileage varies. The gain depends on the acceptance rate: how often the draft guesses right. Code, structured output, and text that repeats your prompt are easy to guess, so speed-ups are large. Creative writing at high temperature is hard to guess, and the gain can shrink to nothing. A draft model also costs memory and some compute, so on a GPU that's already full, it can make things worse.
Picking a draft. It must share the target's tokenizer (the same family is the simple rule). It should be 10–50× smaller. And it should be trained on similar data: a chat-tuned draft for a chat-tuned target.
Why it's lossless
For each drafted token x with draft probability q(x) and target probability p(x), accept with probability min(1, p(x)/q(x)). On rejection, sample from the normalised residual max(0, p − q). The result is distributed exactly as p. In llama.cpp that's "within hardware numerics": floating-point differences between batched and single-token passes can still change a token now and then.
Expected speed-up
With acceptance rate α per token and k drafted tokens, the expected tokens per target pass is (1 − α^(k+1)) / (1 − α). At α = 0.8 and k = 4, that's about 3.4 tokens per pass, before subtracting the draft's own cost.
llama.cpp
llama-server -m Qwen3-32B-Q4_K_M.gguf -md Qwen3-0.6B-Q8_0.gguf \ -ngl 99 -ngld 99 --spec-draft-n-max 8 -c 16384 --port 8080-md(--spec-draft-model) loads the draft,-ngldputs its layers on the GPU, and--spec-draft-n-maxsets how many tokens to draft (default 3).-hfddownloads a draft from Hugging Face the same way-hfdoes for the main model. The older--draft-maxflag has been removed. Recent builds also list a--spec-typeoption (defaultnone), with draft-model, EAGLE-3, MTP and n-gram variants; runllama-server --helpon your build to see which apply.Draft-free variants
- n-gram / prompt lookup: guess by copying spans from the prompt. Free, and excellent for editing code or summarising.
- EAGLE and MTP heads: a small extra head trained on the target model's hidden states. Higher acceptance than a separate draft, but it must be trained for that exact model.
Pitfalls
- Batching kills it. With many concurrent users the GPU is busy with real work, so there's no spare capacity to spend on guesses. Servers often switch it off under load.
- Different tokenizers fail. A Llama draft can't speculate for a Qwen target.
- Measure acceptance, not vibes. Start llama-server with
--metricsand comparellamacpp:spec_decode_num_accepted_tokens_totalwithllamacpp:spec_decode_num_draft_tokens_totalon/metrics. Below about 50% acceptance, try a smaller k or turn it off.