投机解码:EAGLE / MTP / n-gram 在 SGLang
谁该读这一篇? 想把 TPOT 再降一截、但 CUDA Graph + FlashInfer 已经用满了的工程师。 前置阅读:
03-code-walkthrough/04-model-runner.md、00-prerequisites.md(Decode 是什么)。 耗时: 40 分钟 学完能: 1. 讲清投机解码的核心思想(draft → verify); 2. 区分 EAGLE / EAGLE3、MTP、独立 Draft、DFLASH / DSPARK 与 n-gram; 3. 算"接受率 + draft 步数 → 实际加速比"的公式; 4. 在 SGLang 启动时配 EAGLE; 5. 排查"投机开了但 TPOT 没降"的常见原因。
1. 核心思想
普通 decode:每步 LLM forward 算 1 个 token,N 个 token 就 N 步。 投机 decode:
1) "Draft"(轻量模型 / 算法)一次猜 K 个 token
2) "Target"(主模型)一次 forward 验证这 K 个
3) 接受前 m 个连续匹配的 token,第 m+1 个被采样替换
4) 剩下 K-m 个丢弃
如果接受率高,一次 target forward 出 m+1 个 token,远比一次出 1 个划算。
2. 当前 Draft 方案
| 方案 | Draft 来源 | 当前 CLI 值 | 适用条件 |
|---|---|---|---|
| EAGLE-2 / EAGLE-3 | EAGLE draft checkpoint 预测 feature,再经 LM head 产生候选 | EAGLE / EAGLE3 |
目标模型与 draft checkpoint 必须匹配 |
| MTP (Multi-Token Prediction) | 模型内置 next-token / multi-token head | 常用 EAGLE 或 NEXTN,以模型说明为准 |
checkpoint 必须带兼容的 MTP 层 |
| Standalone | 一个更小的普通语言模型逐 token 起草 | STANDALONE |
需要额外 draft checkpoint |
| DFLASH / DSPARK | 专用 draft checkpoint,走线性 block verify 路径 | DFLASH / DSPARK |
模型、设备和并行方式有额外限制 |
| N-gram | 从已有 token 构建的 n-gram cache 检索候选 | NGRAM |
无额外模型,收益高度依赖文本重复度 |
内置枚举还包含内部使用的 FROZEN_KV_MTP 和关闭态 NONE,插件也可以注册额外算法;以
SpeculativeAlgorithm 和
--speculative-algorithm 帮助文本 为当前事实源。
NEXTN 是 CLI alias:通常归一化为 EAGLE,检测到 Gemma 4 assistant draft 时会转为
FROZEN_KV_MTP(speculative_hook.py:35)。
MTP 和旧的 STANDALONE_NGRAM 都不是当前有效的内置 CLI 值。
源码:srt/speculative/。
3. EAGLE 流程
sequenceDiagram
participant U as User
participant T as Target Model<br/>(70B Llama)
participant D as Draft Model<br/>(EAGLE head)
U->>T: prompt
T->>T: prefill → hidden_states_last
T->>D: hidden_states_last + last_token
loop K 步 draft
D->>D: predict next hidden state
D->>D: target.lm_head(hidden) → token
end
D-->>T: K 个 draft token + 对应 hidden
T->>T: verify forward (K token + tree attention)
T->>T: compare logits with draft, accept m
T-->>U: m+1 个 token
要点:
- EAGLE draft 通常显著小于 target,并复用 target 的 embedding / LM head;具体规模和复用方式取决于 checkpoint 架构。
- Verify 阶段用 tree attention:K 个 draft token 一起送回 target 做一次 forward(每 token 看不同的因果 mask)。
- 接受过程用 rejection sampling 保持采样分布正确(Leviathan 2023 论文)。
4. 数学:实际加速比
下面是单路径、固定接受概率的估算模型,用于理解变量关系;EAGLE tree、batch size、 CUDA Graph、memory bandwidth 和 kernel 效率都会让实测偏离这个公式。设:
- $K$ = draft 步数(一次猜几个)
- $\alpha$ = 平均接受率(每步接受概率)
- $T_{target}$ = target 一次 forward 时间
- $T_{draft}$ = draft 一次 forward 时间(通常 $\ll T_{target}$)
每次"target verify"产出的期望 token 数:
每次产出的耗时:
实际 TPOT:
普通 TPOT = $T_{target}$,加速比:
4.1 数值例子
$T_{target} = 20$ ms, $T_{draft} = 2$ ms, $K = 5$, $\alpha = 0.8$:
- $E[\text{tokens}] = (1 - 0.8^6)/(1 - 0.8) = 3.69$
- $T_{\text{per verify}} = 5 × 2 + 20 = 30$ ms
- $\text{TPOT}_{\text{spec}} = 30 / 3.69 = 8.13$ ms
- 加速比 = $20 / 8.13 = 2.46×$
接受率从 0.8 降到 0.5:
- $E[\text{tokens}] = 1.94$
- $\text{TPOT} = 30 / 1.94 = 15.5$ ms
- 加速比 = $20 / 15.5 = 1.29×$
接受率是关键变量之一,但必须和 draft / verify 成本一起看。
5. 在 SGLang 启动 EAGLE
sglang serve meta-llama/Llama-3.1-70B-Instruct \
--speculative-algorithm EAGLE \
--speculative-draft-model-path yuhuili/EAGLE-LLaMA3.1-Instruct-70B \
--speculative-num-steps 5 \
--speculative-eagle-topk 8 \
--speculative-num-draft-tokens 32 \
--tp 4
关键参数:
| 参数 | 含义 |
|---|---|
--speculative-algorithm |
EAGLE / EAGLE3 / NEXTN / STANDALONE / NGRAM / DFLASH / DSPARK,也可使用插件注册值 |
--speculative-draft-model-path |
Draft 模型权重(HF 上有公开 checkpoint) |
--speculative-num-steps |
K:draft 步数 |
--speculative-eagle-topk |
每步 draft 保留多少候选(tree-attention 宽度) |
--speculative-num-draft-tokens |
整个 draft tree 的总 token 上限 |
EAGLE 训练好的 draft model 在 HuggingFace 上有现成的(yuhuili 仓库)。
6. n-gram speculative
不需要 draft model:
sglang serve MODEL_PATH \
--speculative-algorithm NGRAM \
--speculative-num-draft-tokens 16
实现:在已生成 token 流里查 n-gram 匹配。 适合"重复结构高"的任务(代码、JSON、模板化输出)。
源码:speculative/cpp_ngram/ 是 C++ 加速版本。
7. MTP(模型内置 Draft 层)
MTP checkpoint 在训练时加入额外的 next-token / multi-token prediction 层,推理时可直接把这些层当 draft,
因此很多模型不需要独立 draft checkpoint。但启用值和所需参数是模型相关的,不能把 MTP 当成通用算法名。
上游通用文档当前用 MiMo 的单层 MTP 路径作为示例:
sglang serve XiaomiMiMo/MiMo-7B-RL \
--trust-remote-code \
--speculative-algorithm EAGLE \
--speculative-num-steps 1 \
--speculative-eagle-topk 1 \
--speculative-num-draft-tokens 2
有些内置 MTP 模型使用 NEXTN,有些仍使用 EAGLE,多层 head 还可能要求
--enable-multi-layer-eagle。直接采用对应模型的当前 cookbook 参数,并用 acceptance 与端到端 benchmark 调参。
8. 实战 cheatsheet
| 现象 | 排查 |
|---|---|
| 投机开了 TPOT 没降 | 看 sglang:spec_accept_rate / sglang:spec_accept_length,再与同负载的非投机基线对比;没有跨模型通用阈值 |
| 接受率很低 | Draft 模型不匹配;任务过于发散;试不同 K |
| 启动 OOM | 外置 EAGLE / STANDALONE / DFLASH / DSPARK 要加载 draft 权重;降低静态显存占比或并发上限 |
| 长 prompt 时优势消失 | 投机主要优化 decode,不会缩短 target prefill;分别看 TTFT 与 TPOT |
| 输出质量下降 | rejection sampling 不应该影响分布;检查 draft 模型是否同 tokenizer |
9. EAGLE-2 与 EAGLE-3
EAGLE-2 相比初版 EAGLE 使用置信度驱动的 dynamic draft tree,并在有限 token budget 下保留更有希望的候选。
在 SGLang 中用 --speculative-algorithm EAGLE。EAGLE-3 使用多层 target feature 训练 draft,
需要匹配的 EAGLE-3 checkpoint,并用 --speculative-algorithm EAGLE3。
这与下一节的 runtime adaptive 模式不是一回事:EAGLE-2 的 dynamic tree 在一次 draft 中分配候选,
runtime adaptive 则根据近期接受长度在请求批次之间调整 num_steps。
10. Adaptive speculative decoding
当前 EAGLE / EAGLE3 还可以用接受长度的 EMA 在运行时调整 draft steps:
sglang serve TARGET_MODEL \
--speculative-algorithm EAGLE3 \
--speculative-draft-model-path DRAFT_MODEL \
--speculative-adaptive
默认配置按 padded batch size 选择候选集合,例如 bs=1 可在 [1, 3, 7] 间切换,
bs=64 可降到 0(暂时不 draft)。AdaptiveStepSlot(adaptive_spec_params.py:140)根据接受 token 数更新 EMA,经过 warmup、update interval 和 hysteresis 后升降档;--speculative-adaptive-config FILE.json 可以覆盖每个 batch 档位的 candidate_steps 和阈值。
限制:目前只支持 EAGLE / EAGLE3 与 topk=1,不支持 DP attention、multi-layer EAGLE、TBO 或 PDMux;不满足时会告警并回退静态 speculative 参数。启动时 ModelRunner.max_decode_logits_rows(model_runner.py:824)会按所有候选 steps 的最大 logits 行数分配共享 buffer,不能只按初始档位估算。
11. 源码结构
speculative/
├── spec_info.py ← 内置算法枚举与统一 dispatch
├── spec_registry.py ← 插件算法注册表
├── eagle_worker_v2.py ← EAGLE / EAGLE3 worker
├── standalone_worker_v2.py ← 小模型 token-level draft
├── frozen_kv_mtp_worker_v2.py ← Gemma 4 Frozen-KV MTP
├── multi_layer_eagle_worker_v2.py ← 多层 MTP / EAGLE
├── ngram_worker.py / cpp_ngram/ ← n-gram worker 与 C++ corpus
├── dflash_worker_v2.py ← DFLASH worker
├── dspark_components/ ← DSPARK 组件
├── eagle_draft_cuda_graph_runner.py ← Draft 的 CUDA Graph
├── eagle_disaggregation.py
├── draft_utils.py
├── adaptive_runtime_state.py
├── adaptive_spec_params.py
└── base_spec_worker.py
算法先由 SpeculativeAlgorithm dispatch;EAGLE 家族的主 worker 是 EagleDraftWorker
(eagle_worker_v2.py)。
12. 性能监控
sglang:spec_accept_rate{...} # 接受率
sglang:spec_accept_length{...} # 平均每次接受多少 token
sglang:spec_num_steps{...} # 当前 speculative steps
sglang:spec_num_draft_tokens{...} # draft token 数
sglang:spec_verify_calls_total{...} # verify 调用计数器
这些指标在 metrics_collector.py:421
注册。它们没有通用“健康范围”:同一个 acceptance 在不同 target/draft 成本、batch size 和算法下可能对应不同收益。
用固定模型、采样参数和请求分布做 A/B benchmark,比较 TPOT、吞吐和显存,才是开关或调档依据。
13. 小结
- 投机解码 = draft 猜 K → target 验证 → 接受 m+1。
- 加速比由"接受率 × draft/target 比例"决定。
- adaptive 模式按 batch size 与接受长度动态调 steps,并预分配所有候选档位的 buffer 上界。
- 当前内置路径包括 EAGLE / EAGLE3、模型内置 MTP、STANDALONE、NGRAM、DFLASH 与 DSPARK。
- 投机主要优化 decode;采用精确 rejection sampling 的路径保持 target 分布。
- 监控 acceptance,但最终用相同负载下的 TPOT / 吞吐 / 显存 A/B 结果决策。
14. 自检
- 用自己的话说清"为什么投机解码不影响输出分布"。
答案
关键是 **rejection sampling**(Leviathan 2023 论文):draft 模型给出候选 token $x$ 时,target 模型也算出自己的概率 $p_t(x)$;draft 概率为 $p_d(x)$。 接受规则:以概率 $\min(1, p_t(x) / p_d(x))$ 接受 $x$;拒绝时按 $\max(0, p_t(x) - p_d(x)) / Z$ 重采样。 数学上可证明:最终采样的边际分布 == target 模型的原始采样分布。 直观:当 draft 倾向某 token 而 target 不倾向,接受概率低;反过来 draft 不倾向但 target 强烈倾向,拒绝时重采样补回。结果完全等价于直接从 target 采样,只是用了 draft 的"猜测"做加速。- 接受率 0.3 时还应该开投机吗?
答案
**通常不应该**。用 §4 的公式:$T_{target}=20$ms, $T_{draft}=2$ms, $K=5$, $\alpha=0.3$: $E[\text{tokens}] = (1-0.3^6)/(1-0.3) = 1.43$ $T_{\text{per verify}} = 5×2 + 20 = 30$ms $\text{TPOT} = 30/1.43 = 21$ms > 20ms 直接 decode 在这一组**示例成本参数**下是负优化;这不是通用的 0.5 阈值。 例外:(a) `K` 调小(如 2)能在低 $\alpha$ 时仍小赚;(b) draft 模型极轻($T_{draft} \ll 1$ms);(c) target 模型超大($T_{target} > 100$ms),任何加速都值。- n-gram 投机在什么任务上最有用?
答案
重复结构高的任务: - **代码生成**:变量名、关键字、import 路径容易重复。 - **JSON / 结构化输出**:字段名、标点符号是重复字面量。 - **文档生成 / 总结**:用户内容里的关键名词被复述。 - **多轮 chat 回引用上文**:用户 prompt 里的实体在 assistant 回答里再次出现。 不适合:开放创作、对话头脑风暴(每 token 都新鲜)。 优势:免训练、零部署成本,C++ 实现([`speculative/cpp_ngram/`](../sglang/python/sglang/srt/speculative/cpp_ngram/))速度快。EAGLE、EAGLE3、NEXTN和MTP是什么关系?
答案
`EAGLE` 和 `EAGLE3` 是当前用户可选的 EAGLE-2 / EAGLE-3 CLI 值,必须与 draft 架构匹配。 MTP 是模型训练和 draft head 的类别,不是当前通用 CLI 值;不同模型可能要求 `EAGLE` 或 `NEXTN`。 `NEXTN` 是启动参数解析阶段处理的 alias:通常转为 `EAGLE`,Gemma 4 assistant draft 则转为内部的 `FROZEN_KV_MTP`。- MTP 的
num_steps应该怎么选?
答案
没有跨模型固定答案。先采用该 checkpoint 的当前 cookbook 或模型卡组合;然后固定请求分布和采样参数, 联合观察 `sglang:spec_accept_length`、TPOT、吞吐和显存,对 `num_steps`、`topk`、`num_draft_tokens` 做成组 A/B benchmark。若模型带多层 MTP head,还要确认是否要求 `--enable-multi-layer-eagle`。15. 下一步
03-quantization.md— 另一个 TPOT 杀手锏。09-advanced-features/02-structured-output.md— 结构化输出与投机的协同。- 源码:
srt/speculative/。 - 参考:EAGLE 论文、EAGLE-2 论文。