直觉先行:为什么 decode 慢
大模型 decode 每一步要跑完整个 forward pass,70B 模型一次 forward 大概 20ms——却只产出 1 个 token。瓶颈不是计算,是从 HBM 读权重的带宽:一次读 140GB,只用来算 1 个 token,极度浪费。
观察:如果这一步 forward 能一次性验证 4 个 token,"每 token 成本" 从 20ms 降到 5ms。关键是从哪儿来这 4 个候选 token。
投机解码工作流
Step 1 [Draft] 小模型(快)生成 4 个 token: t̂1 t̂2 t̂3 t̂4
Step 2 [Verify] 大模型 (贵但一次 forward) t1 t2 t3 t4 (并行 4 个位置)
Step 3 [Accept] 比较 p_draft 和 p_target,拒绝采样裁出最长前缀
──────
比如大小模型前 3 个一致,第 4 个不一致
→ 接受 t1 t2 t3,丢弃 t̂4
→ 用大模型自己算的 t4 继续
本轮产出 4 个正确 token,只花了 1 次大模型 forward + 4 次小模型 forward
使用推测采样(speculative sampling)——在 draft 模型分布和 target 模型分布之间做拒绝采样,数学上保证最终输出分布与直接从 target 采样完全相同。不是"近似",精度零损失。
三种 Draft 方案
1. Draft Model(独立小模型)
找个同 tokenizer 的小模型(Llama-3-8B 配 Llama-3.2-1B,Qwen2-72B 配 Qwen2-0.5B),它来 draft,大的来 verify:
vllm serve meta-llama/Llama-3-70B-Instruct \
--speculative-model meta-llama/Llama-3.2-1B-Instruct \
--num-speculative-tokens 5 \
--use-v2-block-manager
典型加速 1.5-2.5×。draft 模型越接近 target 分布,接受率越高,加速越明显。
2. Medusa(多个输出头)
不单独跑 draft 模型,在 target 模型顶部加 N 个 LM head,每个 head 预测未来第 1、2、3… 个 token。vLLM 直接加载预训好的 Medusa head 即可。
vllm serve meta-llama/Llama-3-70B-Instruct \ --speculative-model FasterDecoding/Medusa-Llama-3-70B \ --speculative-draft-tensor-parallel-size 1 \ --num-speculative-tokens 4
不用额外 draft 模型,显存占用小;但需要专门训练 Medusa head。
3. N-gram(零成本)
最朴素:从已生成的序列里抓最近出现过的 N-gram 模式,直接当 draft。适合代码、检索增强(context 里就有答案)、重复文本。
vllm serve meta-llama/Llama-3-70B-Instruct \ --speculative-model [ngram] \ --num-speculative-tokens 5 \ --ngram-prompt-lookup-max 4
不用额外模型、不用 GPU 算力,纯 CPU 字符串匹配。代码补全场景接受率能到 70%+,因为很多 token 就是在复制 context。RAG 场景也很受益——答案常常出现在检索段落里。
三种方案对比
| 方案 | draft 成本 | 接受率 | 典型加速 | 适合场景 |
|---|---|---|---|---|
| Draft Model | 小模型 forward | 60-80% | 1.5-2.5× | 通用对话 |
| Medusa | 几个 LM head | 50-70% | 1.8-2.3× | 通用,显存紧张 |
| N-gram | ~0 | 30-80%(分场景) | 1.3-3.5× | 代码 / RAG / 摘要 |
num-speculative-tokens 怎么选
投多少个候选(k)直接影响收益:
期望加速 ≈ (1 + α + α² + ... + α^k) / (1 + k × c) α = 接受率,c = draft 成本 / target 成本 例子:α = 0.7, c = 0.05 (1B draft for 70B target) k=1 加速 1.62 k=3 加速 2.07 k=5 加速 2.18 ← 甜点 k=8 加速 2.13 ← 开始下降 k=12 加速 1.96
经验值:Draft Model 取 k=5,Medusa 取 k=4,N-gram 取 k=5-10。太大没收益还增加验证成本。
什么时候投机解码不划算
- batch 已经很大:Continuous Batching 下 batch=32+ 时 GPU 已经接近 compute-bound,投机不再省。vLLM 日志里看
gpu_cache_usage长期 > 80% 就是信号 - target 模型不大:7B 以下模型 forward 本就快,draft 开销占比高,可能反而变慢
- 高 temperature 采样:temperature > 0.8 时分布越来越平,接受率掉,加速打折
- draft 模型太差:接受率 < 30% 时,失败的那些 token 等于白算
监控投机解码的指标
vLLM 的 Prometheus 指标:
| 指标 | 含义 |
|---|---|
vllm:spec_decode_draft_acceptance_rate | 草稿接受率,<50% 要调 draft 模型 |
vllm:spec_decode_system_efficiency | 系统效率(vs 普通解码的 speedup) |
vllm:spec_decode_num_accepted_tokens_total | 累计接受 token 数 |
vllm:spec_decode_num_emitted_tokens_total | 累计输出 token 数 |
实战 benchmark:Llama-3-70B
1×A100-80G(AWQ-INT4),batch=1 的延迟:
| 配置 | TPOT | TTFT | 生成 200 token 总耗时 |
|---|---|---|---|
| 无投机 | 22ms | 180ms | 4.6s |
| Draft 1B, k=5 | 10ms | 180ms | 2.2s(2.1×) |
| Medusa, k=4 | 11ms | 180ms | 2.4s(1.9×) |
| N-gram (代码补全) | 7ms | 180ms | 1.6s(2.9×) |
和 batching 的交互
投机解码在小 batch 低延迟场景收益最大(memory-bound),大 batch 高吞吐场景收益变小甚至反向。生产建议:
- 在线聊天服务(延迟敏感、batch 通常 < 16):开投机,用 Draft Model 或 Medusa
- 批量离线处理(吞吐敏感、batch 大):关投机,让 Continuous Batching 充分打满 GPU
- 代码补全 / RAG:开 N-gram,零成本白赚
本章小结
- 投机解码利用 decode memory-bound 特性,一次 forward 验证多个 draft token,大模型少跑几次
- 推测采样数学上保证输出分布与直接采样相同,精度零损失
- 三种 draft 源:独立小模型(通用)/ Medusa(省显存)/ N-gram(代码/RAG 神器)
- k=5 是甜点,更大反而拖慢;监控接受率 < 50% 就要调参
- 小 batch 低延迟场景用投机,大 batch 高吞吐场景关掉