Core optimizations
Speculative acceptance rate
Speedup is expected accepted tokens divided by draft-plus-verify cost. Sweep k. Measure alpha on your traffic.
Let α be the probability a drafted token is accepted (simplified i.i.d. model — reality is position-dependent). With draft length k, expected accepted drafted tokens before the first rejection is a geometric-like sum. You always emit at least the corrected token at the rejection point (or a bonus token if all k are accepted, depending on the variant).
E[tokens per round] ≈ (1 - α^(k+1)) / (1 - α) # under simplified assumptionsIf expected accepted tokens per round exceed 1 + cost_ratio, you win. Sweep k. Too small → little upside. Too large → wasted draft work after early rejection and a heavier verify. Typical sweet spots are small single digits unless the draft is extremely aligned.
Walking through two alphas
Treat each drafted position as succeeding with probability α, independently (a teaching lie that is still useful for sizing).
alpha, k = 0.8, 4
accepted = sum(alpha ** i for i in range(1, k + 1))
print(round(accepted, 4))Output:
2.3616P(accept at least 1 draft) = 0.8
P(accept at least 2) = 0.64
P(accept at least 3) = 0.512
P(accept all 4) = 0.4096
Plus correction / bonus → often ~3+ tokens delivered per target forwardIf drafting those 4 tokens costs 30% of a target forward, net speedup lands near 3 / 1.3 ≈ 2.3× before real-world inefficiencies.
Suppose α = 0.7, k = 5, and a target forward costs ~1.0 units while drafting five tokens costs ~0.25 units. Rough accepted length often lands around ~3 tokens/round in this regime (measure; do not trust the napkin blindly). Cost per round ≈ 1.25 → effective cost per token ≈ 1.25 / 3 ≈ 0.42 → about 2.4× vs baseline cost 1.0 per token.
Drop α to 0.4 and E[draft accepts] for k=4 becomes 0.4+0.16+0.064+0.0256=0.65 — barely worth it once overhead is counted. If α falls to 0.3 because draft and target disagree on code tokens, k=5 can become a net loss.
The engineering job is less "implement reject sampling" (engines have that) and more "raise α on our traffic" and "pick k from a sweep" at target concurrency.
Patterns you will see in vLLM, SGLang and TensorRT-LLM (names vary by version): enable speculative mode, point at a draft model or Medusa/EAGLE/MTP heads, set num_speculative_tokens (k). Benchmark TTFT (usually little changed) and TPOT at your concurrency. Measure acceptance rate if the engine exposes it. An acceptance rate that looks great on newsy English and terrible on JSON tool calls means you need a different draft or should disable speculation on those routes.
Common mistakes
- Picking k=8 because a paper did. Early rejection wastes the tail.
- Trusting a concurrency-1 demo. 2× at batch 1 often becomes 1.1× or 0.9× at concurrency 32.
- One global α. Chat, code and JSON are different drafts.
Check yourself
- For α = 0.8 and k = 4, what is E[draft tokens accepted]? Answer: 0.8 + 0.64 + 0.512 + 0.4096 = 2.3616.
- When does raising k start to hurt? Answer: when extra drafted tokens are usually rejected, so draft and verify cost grow faster than accepts.
Key takeaways
- Speedup is accepted tokens per round divided by draft-plus-verify cost.
- α and k dominate. Sweep k in {2,3,4,5,8} at production concurrency.
- Measure α by route. Disable where it is poor.
- Napkins assume i.i.d. accepts. Reality is position-dependent — still useful for sizing.
Further learning
Next, pick a draft source that can actually raise α.
Related
Speculative decoding
Draft-and-verify so one target forward accepts several tokens. Same output distribution. Real speedups when decode is memory-bound and acceptance stays high.
Draft and verify
A cheap draft proposes k tokens. The target scores them in one forward. Rejection sampling keeps the target's distribution.
Draft sources
Small LMs, prompt n-grams, Medusa heads, EAGLE and multi-token predictors. Pick for stack support and tokenizer match.