Speculative Decoding#
前三节讲的是标准的 prefill + autoregressive decode 路径。这一节处理一种不同的执行模式:speculative decoding。它让 ModelRunner 在每一步不再只生成一个 token,而是先用一个轻量 draft model 猜多个 token,再用 target model 一次性验证,从而在不改变输出分布的前提下显著提升吞吐。
这一节解决什么问题#
这一节主要回答三件事:
- speculative decoding 在 SGLang 里怎样被表达成执行配置;
- draft + verify 两阶段怎样和
ForwardBatch/ModelRunner结合; - 为什么 speculative decoding 不改变输出分布,以及这个保证在 SGLang 里怎样落实。
一张图先看整体结构#
flowchart TB
subgraph Draft["Draft 阶段(轻量模型)"]
A["当前 token"] --> B["draft model forward x K 步"]
B --> C["候选 token 序列 [t1, t2, ..., tK]"]
end
subgraph Verify["Verify 阶段(target model)"]
C --> D["target model 并行 forward K+1 个位置"]
D --> E["接受/拒绝每个候选 token"]
end
subgraph Accept["输出"]
E --> F["接受的 token(可能 0 ~ K 个)"]
F --> G["最多接受 K 个 + 1 个 bonus token"]
end这张图最值得记住的一点是:target model 只需要跑一次 forward(K+1 个位置并行),就能完成 K 个 token 的验证。当 draft model 的猜测大部分正确时,一次 target forward 等于原本 K 次 autoregressive decode。
speculative decoding 怎样进入 ModelRunner#
SGLang 的 speculative decoding 通过 speculative_algorithm 参数进入执行层。从 ModelRunner 的初始化可以看到:
self.spec_algorithm = SpeculativeAlgorithm.from_string(
server_args.speculative_algorithm
)目前支持的主要模式包括:
EAGLE/EAGLE2:使用专门训练的 eagle draft modelDRAFT_MODEL:使用另一个小模型作为 draft model(如用 7B 草稿 70B)MEDUSA:多头 draft,不用单独的 draft model,在 target model 上加额外预测头
这个字段在 ForwardBatch 建立时会影响前向的执行路径,让 ModelRunner 在 decode 阶段进入 speculative 路径,而不是标准的单 token 路径。
两阶段执行:draft forward 和 verify forward#
Draft 阶段
Draft model 对当前 context 做 K 步 autoregressive 前向,得到 K 个候选 token。这 K 步通常很快,因为 draft model 参数量远小于 target model(EAGLE 的 draft 部分只有一层 transformer)。
Draft 阶段的输出不只是 K 个 token,还包括这 K 个位置的 logits 分布 q(t_i),用于后面的 rejection sampling。
Verify 阶段
Target model 对 [原始 context + K 个 draft tokens] 做一次并行 forward,计算 K+1 个位置的 logits 分布 p(t_i):
- 前 K 个位置对应 draft tokens 的验证;
- 第 K+1 个位置是在所有 draft token 都被接受时的 bonus token。
这次 forward 和标准 prefill 在计算上没有本质区别,差别只在于 K+1 个 token 是"待验证的候选",而不是"需要算 KV 的 prompt"。
接受/拒绝:为什么不改变输出分布#
这是 speculative decoding 最关键的性质:token 的最终分布和完全用 target model autoregressive decode 一样。
对第 i 个 draft token t_i,接受条件是:
uniform(0, 1) < min(1, p(t_i) / q(t_i))- 如果
p(t_i) >= q(t_i)(target 认为 draft 猜的这个 token 比 draft 自己更确信),直接接受; - 如果
p(t_i) < q(t_i)(target 没有 draft 那么确信),以p/q的概率接受; - 如果被拒绝,从修正分布
max(0, p - q) / Z中重新采样一个 token。
这套 rejection sampling 机制保证:无论 draft 猜的好不好,最终的输出分布都等价于直接用 target model 采样。
在 SGLang 里,这套逻辑在 speculative_algorithm.verify_step(...) 中实现,发生在 verify forward 之后、output processing 之前。
什么时候 speculative decoding 有收益#
收益来自 draft model 的接受率。如果 K 个 draft token 中有 k 个被接受,这次 target forward 等于完成了 k+1 次 decode(k 个接受的 + 1 个 bonus token),吞吐提升约为 (k+1) / 1。
收益受以下因素影响:
有利条件:
- Draft model 和 target model 在该分布上接近(例如同系列模型);
- 生成内容有较强规律性(代码、格式化输出),draft 容易猜对;
- Batch size 小(target verify forward 的 batch overhead 相对划算)。
不利条件:
- 高温度采样(随机性高,draft 难以准确预测);
- Batch size 很大时,K 个 draft forward 的总计算量接近跑 K 次 target forward。
这也是为什么 speculative decoding 在大 batch 高并发推理场景下收益有限,但在 interactive / low-latency 场景下(batch size 小、对 TTFT 和 token latency 敏感)价值最显著。
KV cache 在 speculative decoding 里怎样处理#
Speculative decoding 引入了一个 KV cache 管理上的复杂性:draft 阶段产生的 KV 可能最终不被 target 接受,这些"推测性 KV"不能直接 commit 到 RadixCache。
SGLang 的处理方式是:
- Draft forward 生成的 KV 写入
speculative_kv_buffer,而不是正式的token_to_kv_pool; - Verify 之后,只有被接受的 token 对应的 KV 才 commit 到主 KV pool;
- 拒绝的 token 及其 KV 直接丢弃,不需要回滚。
这个设计的代价是需要额外的 buffer 来暂存 draft KV,但避免了接受/拒绝结果出来之前就污染主 KV pool。
调试 speculative decoding 时先看哪里#
如果你看到的现象是:
- 开启了 speculative decoding,但吞吐没有明显提升;
- 某类请求开启 spec decoding 后输出质量下降(这通常意味着实现有 bug,不该发生);
- 延迟比普通 decode 更高;
更稳的顺序通常是:
- 先确认 draft model 的 acceptance rate 是否达到预期(通常需要 > 0.7 才有净收益);
- 如果 acceptance rate 低,检查 draft model 和 target model 是否来自同一 family;
- 如果延迟更高,看当前 batch size 是否太大,导致 draft K 次 forward 的开销超过了 target verify 的收益;
- 如果输出分布有疑问,回到
verify_step里确认 rejection sampling 的实现是否正确。
小结#
这一节真正要建立的是一个框架:
- speculative decoding 不改变输出分布——这是它能被用在生产上的前提;
- 它通过 draft + verify 两阶段实现"一次 target forward 等于多个 decode 步";
- 收益依赖 draft acceptance rate,而 acceptance rate 依赖 draft model 质量和采样参数。
理解了这个框架,再看 SGLang 里具体的 EAGLE、DRAFT_MODEL、MEDUSA 实现时,就能更快判断它们在哪个环节做了什么权衡。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。