Speculative Decoding in Production: A Practical Guide to Faster LLM Inference
Speculative decoding can cut LLM latency without changing model outputs, but only when the draft model, traffic shape, and metrics are tuned right.
Your LLM is probably wasting expensive GPU time generating one token at a time like it is typing with one finger.
This speculative decoding production guide shows how to make inference faster without swapping your main model, wrecking output quality, or pretending batching solves every latency problem. The trick is simple: let a smaller, cheaper model guess the next few tokens, then make the big model verify them in one pass.
When it works, it feels like free speed. When it is bolted on blindly, it burns memory, reduces throughput, and gives you a beautiful dashboard full of lies.
Let’s wire it up properly.
What Speculative Decoding Actually Does
Standard autoregressive decoding is painfully serial. To generate 100 tokens, the model usually performs 100 sequential decoding steps. Token 37 depends on token 36, token 36 depends on token 35, and so on. GPUs are powerful, but waiting on a chain of tiny sequential operations is not what they were born to do.
Speculative decoding attacks that bottleneck.
A smaller draft model proposes several candidate tokens. The larger target model verifies those candidates in parallel. Accepted tokens are committed. Rejected tokens fall back to the target model’s normal generation path.
In the original ICML 2023 paper, Leviathan, Kalman, and Matias showed 2x-3x acceleration on T5-XXL while preserving the output distribution. That last part matters. Proper speculative decoding is not “use a worse model and hope.” It is a verification scheme where the target model still decides what survives.
Think of it like this:
- Draft model: “The next five tokens are probably this.”
- Target model: “Yes, yes, yes, no. Keep the first three, fix the fourth.”
- Server: “Great, we skipped three expensive target-model decode steps.”
The speedup comes from accepted draft tokens. The cost comes from running the draft model and doing verification. Production work is mostly about making the first number bigger than the second.
When Speculative Decoding Is Worth It
Speculative decoding is not a universal turbo button. It is best for low-to-medium QPS workloads where decode latency matters and the target model has unused GPU capacity during token-by-token generation.
Use it when:
- Users wait on long responses.
- Your workload is decode-heavy, not just prefill-heavy.
- You serve a large model where each token is expensive.
- Your prompts produce predictable continuations, templates, summaries, coding output, support replies, or structured text.
- You can fit the draft model or speculator without pushing the server into memory panic.
Be skeptical when:
- You already run at high batch sizes and high GPU utilization.
- Most requests generate very short answers.
- Your prompts are chaotic and the draft model rarely matches the target.
- The draft model is too large relative to the target.
- You cannot measure accepted tokens per speculative step.
The boring truth: speculative decoding improves latency when the target model is memory-bound and underutilized during decoding. It can hurt throughput when your server is already packed.
Prerequisites
You need a working LLM serving setup before adding speculation. Do not debug speculative decoding and basic model serving at the same time unless you enjoy pain for sport.
You should have:
- A Linux server with NVIDIA GPU support.
- Python 3.10 or newer.
- A recent vLLM install.
- Access to the target model weights.
- Enough VRAM for the target model plus draft model, unless using n-gram, suffix, or built-in multi-token prediction.
- A benchmark prompt set that looks like real traffic.
- Basic metrics: time to first token, inter-token latency, total latency, output tokens per second, GPU utilization, and error rate.
For the examples below, we will use vLLM because it has production-oriented serving, OpenAI-compatible APIs, and current speculative decoding support through --speculative-config.
Install or update vLLM:
pip install -U vllm
Expected result: vllm serve --help should run without import errors.
Step 1: Measure Your Baseline First
Do not enable speculation before you know the current numbers. Otherwise you are just decorating vibes with JSON.
Start the target model normally:
vllm serve Qwen/Qwen3-4B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 1 \
--max-model-len 2048 \
--gpu-memory-utilization 0.8
Then send a normal OpenAI-compatible request:
from openai import OpenAI
import time
client = OpenAI(
api_key="EMPTY",
base_url="http://localhost:8000/v1",
)
prompt = "Write a concise technical explanation of KV cache reuse in LLM serving."
start = time.perf_counter()
response = client.completions.create(
model=client.models.list().data[0].id,
prompt=prompt,
max_tokens=300,
temperature=0.2,
)
elapsed = time.perf_counter() - start
print(response.choices[0].text)
print(f"total_latency_seconds={elapsed:.2f}")
Expected result: you have a baseline latency number for a realistic prompt and output length.
For production, run more than one prompt. Use at least 50-100 examples from actual traffic: support questions, agent traces, summaries, code completions, whatever your product really emits. A tiny toy prompt can make speculation look amazing or useless for completely fake reasons.
Track:
- Median latency.
- P95 latency.
- Generated tokens per second.
- Average output length.
- GPU memory.
- GPU utilization.
- Error rate.
If you cannot measure these, fix that before touching speculative decoding.
Step 2: Pick the Right Speculation Method
vLLM supports several speculative paths, including draft models, EAGLE-style speculators, MTP, n-gram, and suffix decoding. Pick based on your workload, not hype.
Option A: Draft Model
A draft model is a smaller LLM that predicts candidate tokens for the target model.
Use this when:
- You have a smaller model from the same family.
- The tokenizer is compatible, or your serving stack supports heterogeneous vocab handling.
- The draft model is much cheaper than the target.
- Your output style is predictable enough for decent acceptance.
Example pairing:
- Target:
Qwen/Qwen3-4B-Thinking-2507 - Draft:
Qwen/Qwen3-0.6B
Start with:
vllm serve Qwen/Qwen3-4B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 1 \
--max-model-len 2048 \
--gpu-memory-utilization 0.8 \
--speculative-config '{"method":"draft_model","model":"Qwen/Qwen3-0.6B","num_speculative_tokens":5}'
Expected result: the server starts with speculative decoding enabled. Your client code does not change.
Option B: N-Gram Speculation
N-gram speculation does not require a separate draft model. It proposes tokens by finding repeated token patterns from the prompt or recent context.
Use this when:
- Your workload has repeated text.
- You do summarization, extraction, translation, or templated generation.
- You cannot afford another model in VRAM.
- You want a low-risk first experiment.
Example:
vllm serve Qwen/Qwen3-4B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 1 \
--max-model-len 2048 \
--gpu-memory-utilization 0.8 \
--speculative-config '{"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":5}'
Expected result: lower memory overhead than draft-model speculation, usually with smaller speedups.
Option C: EAGLE or Dedicated Speculators
EAGLE-style methods use trained speculative heads or auxiliary models built for the target. These can outperform generic draft models because they are designed for token acceptance, not standalone chat quality.
Use this when:
- A compatible EAGLE, EAGLE-3, DFlash, or other speculator exists for your target.
- You care enough about latency to run a proper benchmark.
- You are willing to use model-specific serving config.
Example from vLLM-style usage:
vllm serve RedHatAI/Qwen3-8B-FP8-dynamic \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--speculative-config '{"method":"eagle3","model":"RedHatAI/Qwen3-8B-speculator.eagle3","num_speculative_tokens":5}'
Expected result: better acceptance than a random small draft model, assuming the speculator matches the verifier model and your workload.
Option D: Built-In MTP
Some models include multi-token prediction support. In that setup, the model has internal machinery for predicting future tokens, so you may not need a separate draft model.
Example:
vllm serve XiaomiMiMo/MiMo-7B-Base \
--tensor-parallel-size 1 \
--speculative-config '{"method":"mtp","num_speculative_tokens":1}'
Expected result: speculation without loading a generic external draft model, but only for model families supported by your inference engine.
Step 3: Keep the Client Path Boring
A good production optimization should not force every application team to rewrite their API calls.
With vLLM, the client keeps using the same OpenAI-compatible endpoint:
from openai import OpenAI
client = OpenAI(
api_key="EMPTY",
base_url="http://localhost:8000/v1",
)
completion = client.completions.create(
model=client.models.list().data[0].id,
prompt="Explain speculative decoding in one paragraph.",
max_tokens=200,
temperature=0.2,
)
print(completion.choices[0].text)
Expected result: the response shape is unchanged. The serving layer handles speculation behind the curtain.
That is the correct abstraction boundary. Product code should not care whether the backend used vanilla decoding, draft speculation, n-gram lookup, or a trained EAGLE head.
Step 4: Tune num_speculative_tokens
The most tempting knob is num_speculative_tokens. It controls how many candidate tokens are proposed per speculative step.
Start small:
3for cautious first tests.5for a normal draft-model test.8+only after acceptance metrics justify it.
The tradeoff is blunt:
- Too low: you leave speed on the table.
- Too high: the draft model guesses too far ahead, tokens get rejected, and you waste compute.
- Way too high: latency gets worse and someone declares speculative decoding “doesn’t work” after one lazy benchmark.
Run a sweep:
for N in 2 3 5 8; do
echo "Testing num_speculative_tokens=$N"
vllm serve Qwen/Qwen3-4B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 1 \
--max-model-len 2048 \
--gpu-memory-utilization 0.8 \
--speculative-config "{\"method\":\"draft_model\",\"model\":\"Qwen/Qwen3-0.6B\",\"num_speculative_tokens\":$N}"
done
In practice, you will run each config separately, benchmark it, stop the server, then test the next value.
Expected result: one value gives the best latency without wrecking throughput. It is often not the largest value.
Step 5: Measure Acceptance, Not Just Speed
The key metric is accepted draft tokens per target verification step.
If the draft proposes five tokens and the target accepts four, excellent. If the target accepts one, you are doing extra work for almost nothing.
Track:
- Average accepted tokens per speculative cycle.
- Rejection rate by route or prompt type.
- Latency by output length.
- Throughput at different concurrency levels.
- GPU memory pressure.
- Time to first token.
- Inter-token latency.
The acceptance rate will vary by task. Boilerplate customer support replies may accept many tokens. Creative writing with high temperature may reject more. Code generation can be surprisingly good when the draft model knows the syntax and library patterns, but brittle when exact identifiers matter.
Do not average everything into one number and call it science. Segment by workload.
Step 6: Roll Out Behind a Flag
Speculative decoding should be a server-side feature flag, not a personality trait.
Use routes like:
baselinedraft_3draft_5ngram_4eagle3_5
Then route a small slice of traffic:
- 1% internal traffic.
- 5% low-risk production traffic.
- 25% if P95 and error rate look clean.
- 50-100% only after peak-load testing.
Expected result: you can disable speculation without redeploying application code.
Also keep the baseline server available during testing. You want fast rollback, not a 40-minute model-loading ceremony while users stare at spinning UI.
Common Pitfalls
Pitfall 1: The Draft Model Is Too Big
A draft model should be cheap. If it is half the size of the target, you may add more compute than you save.
Bad setup:
- Target: 8B
- Draft: 7B
Better setup:
- Target: 8B
- Draft: 0.5B-1.5B
- Or a dedicated speculator trained for that target
The draft model does not need to be a brilliant chatbot. It needs to guess tokens the target will accept.
Pitfall 2: Tokenizers Do Not Match
Classic speculative decoding works cleanly when the draft and target use the same tokenizer. Different tokenizers make alignment harder. Some serving stacks now support heterogeneous vocabulary techniques, but this is not a free-for-all.
If you mix model families, verify that your engine explicitly supports it. Otherwise use same-family models or n-gram speculation.
Pitfall 3: You Benchmark Only One Prompt
One prompt tells you nothing. Speculation is workload-sensitive.
Test at least:
- Short answer prompts.
- Long answer prompts.
- Structured outputs.
- Summaries.
- Tool-call-like JSON.
- High-temperature creative requests if your app allows them.
- Peak concurrency.
A config that wins on long deterministic summaries may lose on short chat responses.
Pitfall 4: You Ignore Memory Headroom
Draft models consume VRAM. KV cache consumes VRAM. Longer contexts consume more. Higher concurrency consumes more.
If speculative decoding forces a lower batch size or causes cache pressure, the latency win can vanish under real traffic.
Watch:
- GPU memory utilization.
- KV cache capacity.
- OOM errors.
- Request queue time.
- Prefix cache hit rate if you use prefix caching.
Pitfall 5: You Expect Better Answers
Speculative decoding is for speed. It is not a quality upgrade.
With exact verification, accepted outputs should follow the target model’s distribution. If your answers suddenly get better or worse, inspect your sampling parameters, model IDs, tokenizer handling, and request path. Something else changed.
Pitfall 6: You Use Deprecated Flags
Current vLLM documentation points users toward --speculative-config as the main configuration path. Older flags such as passing the speculative model separately may be deprecated depending on version.
Use this shape:
--speculative-config '{"method":"draft_model","model":"<draft-model>","num_speculative_tokens":5}'
Not a pile of stale blog-post flags from six releases ago.
Production Checklist
Before calling this done, answer these:
- What is the baseline P50 and P95 latency?
- What is the speculative P50 and P95 latency?
- What happens at peak concurrency?
- How many draft tokens are accepted on average?
- Which workloads reject most draft tokens?
- Does GPU memory headroom remain healthy?
- Does output formatting remain stable?
- Can you disable speculation without changing client code?
- Are model IDs, revisions, and configs pinned?
- Do logs clearly identify which speculative config served each request?
Pin your model revisions where possible. “Latest” is not a deployment strategy. It is how quiet infrastructure changes become loud incidents.
Recommended Starting Configs
For a first production experiment, use one of these three paths.
Safest First Test: N-Gram
Use this when you want minimal moving parts:
--speculative-config '{"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":5}'
Best for summarization, extraction, repeated templates, and memory-constrained servers.
Standard Draft-Model Test
Use this when you have a smaller same-family model:
--speculative-config '{"method":"draft_model","model":"Qwen/Qwen3-0.6B","num_speculative_tokens":5}'
Best for general serving where the target model is meaningfully larger than the draft.
Best Serious Setup: Dedicated Speculator
Use this when a trained speculator exists for your verifier:
--speculative-config '{"method":"eagle3","model":"RedHatAI/Qwen3-8B-speculator.eagle3","num_speculative_tokens":5}'
Best for teams that care about latency enough to benchmark properly and keep model-serving configs tidy.
What Good Results Look Like
A healthy speculative decoding rollout usually looks like this:
- P50 latency drops noticeably on medium and long generations.
- P95 improves or stays flat.
- Output quality remains unchanged.
- Error rate stays flat.
- GPU memory stays within planned limits.
- Throughput does not collapse under normal concurrency.
- The best config differs by workload.
A bad rollout looks like this:
- One cherry-picked prompt gets faster.
- Real traffic gets slower at peak.
- Memory usage jumps.
- Logs do not expose acceptance behavior.
- The team cannot explain why
num_speculative_tokenswas set to 12. - Someone says “AI optimization is mysterious.” No. The benchmark was lazy.
Final Takeaway
Speculative decoding is one of the rare LLM infrastructure tricks that can cut latency without asking users to accept worse answers. But it rewards measurement, not enthusiasm.
Start with a baseline. Pick the simplest speculation method that fits your workload. Tune num_speculative_tokens with real prompts. Track accepted tokens, not just wall-clock latency. Roll it out behind a flag.
The goal is not to say you use speculative decoding. The goal is to make your LLM feel faster while your GPU bill stops smirking at you.
Sources
> Want more like this?
Get the best AI insights delivered weekly.
> Related Articles
AI Agent Approval Workflows: Put Humans at the Right Control Points
Human approval can make an agent safer—or merely slower. Design checkpoints around irreversible actions, changing risk, and evidence people can actually review.
LLM Trace Redaction in Production: Debug Without Logging Private Data
LLM traces are debugging gold and privacy dynamite. Capture structure, decisions, and timing while removing secrets and personal data before storage.
Secret Management for AI Agents: Stop Leaking Credentials Into Prompts
An agent needs tools, not a backpack full of API keys. Keep secrets outside model context, issue short-lived capability tokens, and audit every use.
Tags
> Stay in the loop
Weekly AI tools & insights.