通过阅读 vLLM 源码和业界最新资料,系统梳理投机采样的原理、方法对比和在 vLLM 中的实现。
目录
- vLLM 支持的所有方法
- Eagle vs Medusa
- Eagle vs DeepSeek MTP
- DFlash
- Suffix Decoding / N-gram
- Eagle 1 → 2 → 3 演进
- vLLM 投机采样完整调用流程
- 调度器中的关键计算
- 当前主流方法选型建议
vLLM 支持的所有方法
定义在 vllm/config/speculative.py 中,vLLM 目前支持 13 种实际实现 + 1 个预留方法 + 大量 MTP 变体:
Model-based 方法(需要训练/下载权重)
| 方法 | 说明 |
|---|---|
| Eagle | 小 transformer 接收 target hidden states,自回归逐 token 生成 draft |
| Eagle-3 | Eagle-3 版本,多层 feature fusion + TTT 训练 |
| MTP | Multi-Token Prediction,用于原生 MTP 模型(DeepSeek V3/V4、Qwen3、Gemma 4) |
| DFlash | 并行 block diffusion,一次 forward 生成所有 draft |
| Draft Model | 独立的完整小模型做 draft(如 Llama 3.2 1B) |
| Medusa | 多个并行 MLP head 从同一 hidden state 预测多个位置 |
| MLP Speculator | 预留方法,尚未实现 |
Model-free 方法(无需训练,零成本)
| 方法 | 说明 |
|---|---|
| N-gram | CPU/GPU 上匹配 prompt 中重复的后缀 n-gram |
| Suffix Decoding | 基于后缀树 + 频率计数的模式匹配 |
| Extract Hidden States | 直接从 target 中间层 hidden states 取 argmax 做 draft |
其他
| 方法 | 说明 |
|---|---|
| Custom Class | 用户自定义 proposer,动态加载 |
| Dynamic SD | 根据 batch size 动态调整 speculative token 数量 |
MTP 变体(通过 Eagle/MTP 路径路由)
deepseek_mtp, mimo_mtp, mimo_v2_mtp, glm4_moe_mtp, glm4_moe_lite_mtp, glm_ocr_mtp, ernie_mtp, nemotron_h_mtp, exaone_moe_mtp, exaone4_5_mtp, qwen3_next_mtp, qwen3_5_mtp, longcat_flash_mtp, minimax_m3_mtp, pangu_ultra_moe_mtp, step3p5_mtp, hy_v3_mtp, gemma4_mtp 等。
拒绝采样方式
standard:标准概率拒绝采样(greedy 或概率比测试)synthetic:合成接受率,用于 benchmark
Eagle vs Medusa
Medusa 架构
Medusa 在 target 模型顶部加多个独立的轻量预测头(Medusa heads),每个头对应一个 future position。所有头从 同一个 target 最后一层 hidden state 出发,一次 forward 并行预测。
target hidden state
├── ResidualBlock_0 → LMHead_0 → draft_token_0
├── ResidualBlock_1 → LMHead_1 → draft_token_1
├── ResidualBlock_2 → LMHead_2 → draft_token_2
└── ... → ... → ...
一个 head 生成一个 draft token,head 数等于每次 speculative 生成的 draft token 数。
Eagle 架构
Eagle 使用一个小的 draft 模型(通常 1-2 层 transformer),逐 token 自回归 生成 draft。每步用上一个 draft token 的 embedding + target hidden state 预测下一个。
Step 1: [token_0_embed, target_hidden] → all Eagle layers → token_1
Step 2: [token_1_embed, target_hidden] → all Eagle layers → token_2
Step 3: [token_2_embed, target_hidden] → all Eagle layers → token_3
为什么 Eagle 比 Medusa 好?
Medusa 的"瞎子猜谜"问题:所有 head 从同一个 target hidden state 出发,并行预测未来 K 个位置。head_2 猜 position+3 时完全不知道 head_0 和 head_1 猜了什么。如果前面猜错了,后面全部白费。
Eagle 每步都知道上一步的结果,预测更准。
| 指标 | Medusa | Eagle-1 | Eagle-3 |
|---|---|---|---|
| 接受率 | 60-70% | 70-75% | 75-80%+ |
| 加速比 | 1.8-2.5x | 2.5-3.0x | 3.0-3.5x |
| 额外显存 | ~0.8GB | ~1.5GB | ~1.8GB |
Medusa 的优势在于训练简单、部署轻量,但精度天花板低。业界普遍认为 Medusa 正在被 Eagle 取代。
Eagle vs DeepSeek MTP
两者在架构上其实很像——都用 feature-level 的 transformer 层 + LM Head。根本区别在训练方式。
Eagle 的训练方式
Eagle 是在已经训好的 frozen target 模型上,额外加一个 draft head 做后训练。draft head 只能看到 target 的输出,为了让它学到 target 的行为,必须用 feature loss 强迫它的 hidden state 去模仿 target 的 hidden state。
冻结的 target 模型 → 输出 hidden_state → Eagle draft head
├→ 目标1: 预测下一个 hidden_state(feature loss)
└→ 目标2: 预测下一个 token(cross-entropy loss)
DeepSeek MTP 的训练方式
MTP 是与 target 模型联合训练的。梯度贯穿 target 模型,MTP 的 loss 反向传播到 target 本身的参数上。
训练时:
target 模型 → MTP_layer_0 → MTP_layer_1 → MTP_layer_2
↑ (共享 embedding 和 LM Head)
└────── 梯度反向传播贯穿整个模型 ──────→
关键区别:
- 梯度贯穿 target 模型:target 会主动调整自己来帮助 MTP 层预测多个 token
- 不需要 feature loss:MTP 层不需要模仿任何人,直接学习"什么 hidden state 对预测 future token 最有帮助"
- 共享参数:embedding 和 LM Head 都是共享的
层结构差异
| Eagle | DeepSeek MTP | |
|---|---|---|
| 层数 | 固定小模型(如 2 层),所有步复用 | N 层 = N 个位置,一一对应 |
| 每步 forward | 跑全部层 | 只跑一个层 |
| 层间连接 | 无(每步从 target hidden 重新开始) | 有(每层的输出传给下一层) |
| 参数 | 同一组参数各步共享 | 每层独立参数 |
Eagle 像是"一个小模型反复跑多步";DeepSeek MTP 像是"为每个位置专门配了一个 transformer 层,串成一条链"。
DeepSeek MTP 预测的是 feature 还是 token?
架构上看,MTP 输出的是 hidden state(feature),再用 LM Head 转成 logits——这和 Eagle-1 一样。但从训练目标上看,MTP 只有 token cross-entropy loss,没有 feature loss。
| Eagle-1 | Eagle-3 | DeepSeek MTP | |
|---|---|---|---|
| 输出 | feature | 直接 token | feature |
| 训练目标 | 拟合 feature + 预测 token | 预测 token(TTT) | 预测 token(联合训练) |
| 约束 | feature 必须匹配 target | 无约束 | 无约束 |
MTP 是"Eagle-1 的架构 + Eagle-3 的训练哲学"——用 feature-level 的架构获得高表达能力,但训练时只关心最终 token 是否正确,不受 feature matching 的约束。这也是为什么 MTP 接受率能到 80-90%,比 Eagle-1 的 70-75% 高出一截。但也决定了它无法适用于已有模型——只有从头训练时才能做联合优化。
Feature tensor 的形状
target_hidden_states 的形状是 [num_tokens, hidden_size],其中 num_tokens 是 batch 内所有 token 的总和(sum(seq_lens)),不是 query len。
请求 A: [t0, t1, t2, t3] (seq_len=4)
请求 B: [t0, t1] (seq_len=2)
↓ flatten
target_hidden_states shape = [6, hidden_size]
↑ 6 = 4 + 2
实际用于 draft 的只是每个序列最后一个位置的那一行。Eagle-3 的多层融合则把多层的 [num_tokens, hidden_size] 在 hidden_dim 上拼接成 [num_tokens, hidden_size * N] 再做投影。
每个 token 位置 N 的顶层 hidden state 是通过 cross-entropy loss 训练来预测 token N+1 的。它只包含 position+1 的信息,这就是为什么 Eagle-3 要引入多层 feature fusion——底层的 hidden state 编码更丰富的语法/结构信息,没有"只对 N+1 优化"这个偏差。
DFlash
DFlash 是 2026 年新起之秀,两个核心创新使其超越 Eagle-3。
核心创新 1:KV Injection
Eagle 只在输入层把 target hidden states 和 token embedding 拼接融合。信息随层数递增而衰减。
DFlash 把 target hidden states 注入到每一层的 K/V 中:
对每个 draft layer:
Q = q_proj(draft_token_embedding)
K = concat([k_proj(fused_target_context), k_proj(draft_token_embedding)])
V = concat([v_proj(fused_target_context), v_proj(draft_token_embedding)])
target 的上下文信息通过 KV cache 持久存在于每一层,不会稀释。这使得加更多 draft 层能持续提升接受率,没有衰减。
核心创新 2:Block Diffusion(并行 drafting)
DFlash 不再自回归逐个生成,而是一次 forward 并行预测整个 token block:
输入: [anchor_token, MASK, MASK, MASK, MASK]
输出: [anchor_token, tok_1, tok_2, tok_3, tok_4]
关键设计:
- Non-causal attention:block 内部使用双向注意力,每个 MASK 可以看到 block 内其他所有 token
- Context K/V 持久缓存,Noise K/V 每步重新计算
- 每步只跑 1 次 draft forward(Eagle 需要 N 次)
DFlash 推理流程
Step 1: target 模型做完 prefill/verify
Step 2: 从 target 中间层提取 hidden states(多层均匀采样)
Step 3: 融合后注入 draft 模型所有层的 KV cache(KV Injection)
Step 4: block diffusion 一次并行生成所有 draft token
Step 5: target 模型验证
为什么 DFlash 的 lookahead 比 Eagle 多 1?
这是由 bonus token 的处理方式决定的。
Eagle:bonus 是最后一个 draft token 位置的 next-token 预测输出(logits),不是独立输入。KV 在下一步补算。
DFlash:bonus 是一个独立的输入位置(query_off=0),需要自己的 KV slot。
EAGLE:
bonus = 最后一个 draft 位置 LOGITS 的 next-token prediction
→ 不需要 KV slot(这是个输出)
→ KV 在下一步补算
lookahead = num_spec_tokens
DFlash:
bonus = 一个独立的输入位置
→ 需要 KV slot(这是个输入)
→ KV 必须现在算
lookahead = num_spec_tokens + 1
对应调度器代码(scheduler.py):
if speculative_config.use_eagle():
self.num_lookahead_tokens = self.num_spec_tokens
if speculative_config.use_dflash():
self.num_lookahead_tokens = self.num_spec_tokens + 1
DFlash 在 vLLM 中的实现
两个核心组件:
| 组件 | 文件 | 职责 |
|---|---|---|
| DFlashProposer | v1/spec_decode/dflash.py | 接收 target hidden states,预处理 context KV,管理 input/metadata |
| DFlashSpeculator | v1/worker/gpu/spec_decode/dflash/speculator.py | GPU 侧 draft 生成、CUDA graph、KV cache 管理 |
Step 1: set_inputs_first_pass()
→ 把 target_hidden_states 暂存
Step 2: build_model_inputs_first_pass()
→ precompute_and_store_context_kv()
→ target hidden states 投影成 K/V,写入 draft 模型的 KV cache
Step 3: propose() 中的 prepare_dflash_inputs()(Triton kernel)
对每个请求:
- last_valid_pos = 最后一个已验证的 token 位置
- query 0 = bonus_token(真实 token)
- query 1..N = parallel_drafting_token_id(MASK token)
- 计算 slot mapping、position、sample indices
Step 4: draft model forward(一次,非自回归)
→ non-causal attention: MASK 位置看到所有 context + 其他 MASK
Step 5: 从输出 hidden_states 采样 draft tokens
Suffix Decoding / N-gram
Suffix Decoding
利用**后缀树(Suffix Tree)**索引 prompt 和历史的 token 序列,通过频率计数找出最可能的后续 token。完全不需要神经网络。
当前 pattern: [45, 123, 67]
后缀树匹配到节点 → 历史中[45, 123, 67]后面最常跟的是:
token 89: 15次 ← 选这个
token 12: 5次
推测长度自适应:推测长度 = min(max_spec_tokens, max_spec_factor × 前缀匹配长度)。
全局缓存:除了每个请求自己的 prompt 后缀树,还有一个跨请求的全局缓存。其他请求之前生成的回复也会被加入缓存。对重复性高的场景特别有效。
与 N-gram 对比
| N-Gram | Suffix Decoding | |
|---|---|---|
| 候选来源 | 仅当前 prompt | prompt + 历史回复 + 跨请求缓存 |
| 候选排序 | 位置顺序 | 频率计数排序 |
| 推测长度 | 固定 | 自适应 |
| 跨请求复用 | 否 | 是 |
| 最佳场景 | 通用文本 | 代码编辑、agent、重复性任务 |
Cursor 就重度依赖这类 pattern-matching 方法做加速。
配置参数
| 参数 | 默认值 | 作用 |
|---|---|---|
suffix_decoding_max_tree_depth | 24 | 树的最大深度 |
suffix_decoding_max_cached_requests | 10000 | 全局缓存保留多少历史请求 |
suffix_decoding_max_spec_factor | 1.0 | 推测长度 = 该系数 × 前缀匹配长度 |
suffix_decoding_min_token_prob | 0.1 | 低于该频率概率的 token 被剪枝 |
Eagle 1 → 2 → 3 演进
Eagle-1(ICML 2024)
核心创新:Feature-level drafting
传统方法让 draft 模型预测 token。Eagle-1 改为预测 target 模型倒数第二层的 hidden state(feature),再用 target 自己的 LM Head 转成 token。
输入: concat([上一轮 feature, 上一个 token embedding])
→ 小型 transformer → 预测下一个 feature
→ target 的 LM Head → 得到 token 分布
为什么更好?Feature 层面比 token 层面更"平滑",不确定性更低。加上"把下一个 token 的 embedding 作为额外输入"这个技巧(论文称为 resolving uncertainty),极大降低了 draft 难度。
结果:2.7-3.5x 加速,~68K 数据训 1-2 天。
局限:
- 使用固定线性 draft 序列
- 只依赖 target 的顶层 feature
- 训练目标是"预测 feature 本身",而不是"预测正确的 token",引入了一个不必要的中间约束
Eagle-2(EMNLP 2024)
核心创新:Dynamic Draft Tree
不需要重新训练,直接复用 Eagle-1 的 head。关键发现:draft 模型的置信度可以很好地近似接受率。
固定树:
"10+2=" → [ "1", "3", "2" ] ← "3" 概率很低但浪费 budget
动态树:
"10+2=" → [ "1" ] ← 概率 99%,不需要其他候选
"10+2" → [ "1", "3" ] ← 难以预测,多展开分支
结果:比 Eagle-1 再快 1.3-1.4x,不需要额外训练。
Eagle-3(NeurIPS 2025)
两大核心改进:
改进 1:放弃 Feature Prediction → Training-Time Test (TTT)
为什么放弃 feature loss?论文的核心发现:
“For an LM head with a full-rank weight matrix, the top-layer features corresponding to the logits of the next token are unique, ensuring that the information contained in these features aligns directly with the logits of the next token. However, predicting the next-next token based solely on top-layer features—which are inherently limited to the next token—poses a significant challenge.”
顶层 hidden state 是专门为预测下一个 token 优化的。它只包含 position+1 的信息。让 draft 模型去模仿这个"只认识下一步"的特征,并用来预测后续多步——天花板天然就低。
更关键的是:feature loss 导致增加训练数据几乎不提升性能。
Eagle-1: 数据量 2x → 加速比几乎不变(遇到天花板)
Eagle-3: 数据量 2x → 加速比也约 2x(发现了 scaling law)
去掉 feature loss 后,draft 模型失去了"模仿 target feature 这一中间目标"。Eagle-3 的解法是 Training-Time Test(TTT)——训练时就用自己预测的 token 作为下一步输入,强迫模型学会从自己的错误中恢复。
传统训练:
step 1: 用真实 token_1 → 预测 token_2
step 2: 用真实 token_2 → 预测 token_3 (永远 ground truth)
TTT 训练:
step 1: 用真实 token_1 → 预测 token_2
step 2: 用自己预测的 token_2 → 预测 token_3 (即使错了也要走)
step 3: 用自己预测的 token_3 → 预测 token_4
↓
对所有步一起算 cross-entropy loss,反向传播整条链
改进 2:多层 Feature Fusion
去掉 feature loss 后,draft 模型的输入不再受限于"必须是 target 的顶层 feature"——可以是任何东西。
| 层 | 编码信息 |
|---|---|
| 底层(early layers) | 语法、词法、局部 token 上下文 |
| 中层(middle layers) | 语义关系、整体篇章结构 |
| 顶层(late layers) | 输出概率分布 |
Eagle-3 从这三层同时提取特征并融合,draft 模型得到了比 Eagle-1 丰富得多的上下文。
总结
| Eagle-1 | Eagle-2 | Eagle-3 | |
|---|---|---|---|
| 预测目标 | feature(hidden state) | feature | 直接预测 token |
| 输入特征 | 仅顶层 feature | 顶层 feature | 低/中/高层三层融合 |
| draft 结构 | 固定线性/固定树 | 动态 tree | 动态 tree(沿用 Eagle-2) |
| 训练 | ~68K 数据,1-2 天 | 复用 E-1 权重,无需重训 | ~532K 数据,TTT 训练 |
| 训练目标 | 拟合 target feature | 同左 | 模拟推理过程的 token 预测 |
| 加速比 | 2.7-3.5x | 3.0-4.3x | 4.1-6.5x |
一句话演进:Eagle-1 证明了"预测 feature 比预测 token 更好";Eagle-2 证明了"动态树比固定树更好";Eagle-3 证明了"直接预测 token + 多层特征融合 + 训练时模拟推理"才是最佳路径。
关于 Tree Attention
vLLM 目前没有实现 tree attention。所有方法生成的都是扁平线性链([batch, num_spec_tokens]),没有分支。代码中明确有 FIXME 标注待做。vLLM 的加速主要来自 feature-level drafting 的高接受率 + TTT 训练 + 多层 fusion,而非树形验证。
vLLM 投机采样完整调用流程
整体架构
EngineCore.step()
│
├── 1. scheduler.schedule() CPU:决定每步处理哪些 token
│
├── 2. model_executor.execute_model() GPU:target 模型 forward
│
├── 3. model_executor.sample_tokens() GPU:验证 + 生成下一批 draft
│ ├── RejectionSampler.forward() ← 验证
│ └── propose_draft_token_ids() ← 生成下一批 draft
│
├── 4. scheduler.update_from_output() CPU:回滚被拒 KV、更新状态
│
└── 5. scheduler.update_draft_token_ids() CPU:推入新的 spec tokens
各组件职责
| 组件 | 文件 | 职责 |
|---|---|---|
| EngineCore | v1/engine/core.py | 编排:schedule → execute → sample → update |
| Scheduler | v1/core/sched/scheduler.py | 决定每 step 推测多少 token,管理 spec_token_ids,回滚 KV |
| ModelRunner | v1/worker/gpu_model_runner.py | 组装输入、跑 target forward、调用验证、调用 proposer |
| RejectionSampler | v1/sample/rejection_sampler.py | Greedy 对比或概率比测试验证 |
| Proposer | v1/spec_decode/ | 生成下一批 draft token(方法特异的核心所在) |
| Speculator | v1/worker/gpu/spec_decode/ | GPU 侧自回归/并行生成 draft 的实现 |
不同方法的差异点
不只是在 propose() 这一步不同,至少 4 个环节有方法特异的逻辑:
- 初始化:不同方法实例化不同的 Proposer 类型(
model_runner.py:558-617) - Target forward 阶段:
use_aux_hidden_state_outputs控制是否返回中间层 hidden states(Eagle-3 / DFlash 需要,Eagle-1 / Medusa 不需要) - Propose 阶段:核心差异——自回归、并行、无模型、纯 CPU
- 调度阶段:
num_lookahead_tokens不同(Eagle:num_spec_tokens,DFlash:num_spec_tokens + 1)
验证阶段
def rejection_sample():
if greedy:
target_argmax == draft_token_id ? 接受 : 拒绝 + 替换
else:
p_target(draft) > u * p_draft(draft) ? 接受 : 从 residual 分布重采样
部分方法(Eagle / DraftModel / DFlash)可以获取 draft 模型的概率分布,支持 random rejection sampling;其他方法没有 draft_probs,只能做 greedy 比较。
调度器中的关键计算
num_scheduled_spec_tokens 计算
# scheduler.py:582-597
if request.spec_token_ids:
num_scheduled_spec_tokens = (
num_new_tokens
+ request.num_computed_tokens
- request.num_tokens
- request.num_output_placeholders
)
直观含义:
num_scheduled_spec_tokens = num_new_tokens - (num_tokens - num_computed_tokens) - num_output_placeholders
↑ ↑ ↑
这步总共能处理 backlog 的已提交 token async 已占位的 token
即:从这步能处理的 token 数里,先扣除 backlog 的旧账和 async 的占位,剩下的名额才给新的 spec token。
total_num_scheduled_tokens
total_num_scheduled_tokens = sum(num_new_tokens) 是包含 speculative tokens 的。在 _prepare_inputs 中:
total_without_spec = total_num_scheduled_tokens - total_num_spec_tokens
关于 bonus token 的 KV 分配
Eagle 的 bonus token 是最后一个 draft token 位置的 logits 预测输出,不是独立输入。它的 KV 不是在当前步计算的,而是在下一步补算——通过 num_new_tokens 公式中自动多出的 1 个名额来处理。
Step 1:
target forward 处理 5 个 draft(num_spec_tokens=5)
5 个 KV slot 被计算
bonus 作为 next-token prediction 输出
num_computed_tokens += 5(只算到 draft_5)
Step 2:
output_token_ids 有 6 个新 token(5 drafts + 1 bonus)
num_tokens_with_spec = len(prompt) + len(output) + 0 = P + 6
num_computed_tokens = P + 5
num_new_tokens = (P + 6) + placeholders - (P + 5) = 1 + placeholders
↑
这个 1 就是 bonus token——在下一步补算它的 KV
这解释了为什么 Eagle 只需要 num_spec_tokens 个 lookahead,而不是 num_spec_tokens + 1。
当前主流方法选型建议
2026 年业界共识
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 新部署通用模型 | Eagle-3 | 事实上的工业标准,社区最活跃 |
| 模型原生支持 MTP | MTP | DeepSeek V3/V4、Qwen3、Gemma 4 出厂自带,无需额外训练 |
| 追求极致性能 | DFlash | 非自回归并行,比 Eagle-3 高 ~50% 吞吐 |
| 零成本提速 | Suffix Decoding | 代码编辑、agent 场景尤其有效 |
| 大模型 70B+ | Draft Model | 独立小模型做 draft,开销相对小 |
| 简单部署 | Medusa | 受限于接受率低,正被 Eagle 取代 |
选型决策
模型原生支持 MTP?
├→ 是 → 直接用 MTP
└→ 否 → 有社区预训练 Eagle-3 head?
├→ 是 → 用 Eagle-3(直接下载即用)
└→ 否 → 有充裕显存(>10GB)?
├→ 是 → 自己训 Eagle-3 head
├→ 否 → Suffix Decoding(零成本)
└→ 代码编辑/agent → Suffix Decoding
是否需要注意避免重复工作?
在实施前,建议在 vLLM 仓库的 issue 和 PR 中搜索相关关键词,确认你的方法未被他人实现。
参考
- Eagle-1 (ICML 2024)
- Eagle-2 (EMNLP 2024)
- Eagle-3 (NeurIPS 2025)
- DFlash (2026)
- Medusa (2024)
- DeepSeek-V3 Technical Report
- Suffix Decoding (2025)
- vLLM 源码:https://github.com/vllm-project/vllm
- Speculators 项目:https://github.com/vllm-project/speculators