Appearance
推测解码 — 概念
基本原理
自回归推理每步只生成 1 个 token,GPU 利用率很低。推测解码通过"猜"多个 token 来提高利用率:
数学原理
对于每个候选 token,计算接受概率:
accept_prob = min(1, p_target(token) / p_draft(token))- 如果
p_target > p_draft:总是接受 - 如果
p_target < p_draft:以概率p_target/p_draft接受 - 被拒绝的 token 后重新采样,保证分布不变
推测方法
N-gram Proposer
最简单的方法,不需要额外模型:
- 维护历史 n-gram 频率表
- 根据前 n-1 个 token 预测下一个 token
- GPU 加速版本使用 trie 结构高效查找
优点:零额外计算开销 缺点:准确率低,接受率有限
EAGLE
基于模型特征的方法:
- 利用目标模型的中间层特征
- 草稿模型很小(通常只有 1-2 层)
- 准确率高(85-95%),接受率高
Medusa
多头预测方法:
- 在目标模型上添加多个预测头
- 每个头预测不同未来位置的 token
- 不需要单独的草稿模型
- 训练成本低
方法对比
| 方法 | 额外模型 | 准确率 | 加速比 | 适用场景 |
|---|---|---|---|---|
| N-gram | 无 | 低 | 1.2-1.5× | 简单场景 |
| EAGLE | 小模型 | 高 | 2-3× | 通用 |
| Medusa | 多头 | 中高 | 1.5-2.5× | 不想用草稿模型 |
| DFlash | 小模型 | 高 | 2-3× | 大词草图模型(注意力掩码并行起草) |
| Suffix Decoding | 无 | 中 | 1.3-1.8× | 重复性文本 |
异构词表推测解码(TLI)
传统 draft_model 推测解码要求 draft 与 target 词表完全一致,限制了草稿模型的选择。开启 speculative_config.use_heterogeneous_vocab=True(仅 method='draft_model'、draft_sample_method='greedy' 可用)启用 Token-Level Intersection (TLI)(PR #38174):初始化时加载两侧 tokenizer、归一化 token 串(自动识别 BPE 的 Ġ / SentencePiece 的 ▁ 空格前缀,兼容混合分词器族)求交集,生成 draft↔target 双向 ID 映射与交集掩码(vllm/v1/spec_decode/vocab_mapping.py 的 VocabMapping)。采样前 constrain_draft_logits 把非交集位置置 -inf,采样后 map_draft_to_target_ids 把草稿 ID 翻译到目标词表空间再验证——从而在词表不同的草稿/目标模型间保持贪心解码无损(概率草稿采样暂未支持,因 draft_probs 重映射尚未实现)。
Rejection Sampling
验证阶段的核心算法:
python
def rejection_sample(draft_tokens, draft_probs, target_probs):
accepted = []
for i, token in enumerate(draft_tokens):
p_draft = draft_probs[i][token]
p_target = target_probs[i][token]
# 接受概率
accept_prob = min(1.0, p_target / p_draft)
if random() < accept_prob:
accepted.append(token)
else:
# 从调整后的分布中重新采样
adjusted = max(0, p_target - p_draft)
new_token = sample(adjusted)
accepted.append(new_token)
break # 后续 token 全部丢弃
return accepted上面的逐 token 独立判拒对应 rejection_sample_method='standard'(Leviathan et al. 2023)。v0.26 起拒绝采样支持三选一:'standard'、'synthetic'(按 synthetic_acceptance_rates 校准的衰减接受率),以及新增的 'block'——block verification(Sun et al. 2024 / arXiv 2403.10444)。'block' 不再逐 token 独立判拒,而是维护联合前缀比 p_i = Π q(x_j)/p(x_j),用 h = residual_mass / (residual_mass + 1 − p_i) 联合判定整个草稿块的接受长度,拒绝时从联合残差分布重采样(vllm/v1/worker/gpu/spec_decode/rejection_sampler_utils.py 的 _rejection_kernel 内 USE_BLOCK_VERIFICATION 分支)。注意:贪心请求仍走 target-argmax 精确匹配;block 模式不可与 synthetic acceptance rate 同用。该路径仅在 MRV2 (V2) runner 中实现。
推测解码与连续批处理
推测解码需要与 Continuous Batching 协同工作:
- 每个请求可能有不同数量的候选 token
- 调度器需要为推测解码预留足够的计算预算
- 被拒绝的 token 的 KV 缓存需要回滚
混合模型的推测解码
Mamba + Attention 推测解码
混合模型(如 Mamba + Attention)的推测解码面临 CPU-GPU 同步瓶颈:Mamba 状态在验证失败后需要回滚,传统方法需要将状态从 GPU 拷贝到 CPU 再恢复。
vLLM 引入了融合 Triton 内核 postprocess_mamba_fused_kernel,完全在 GPU 上执行 Mamba 状态复制:
关键组件:
- MambaSpecDecodeGPUContext:预计算内存布局元数据(基地址、步幅、元素大小、卷积宽度)
- postprocess_mamba_align_gpu():align 模式缓存回滚
- postprocess_mamba_all():
mamba_cache_mode="all"模式回滚 - MambaBuffers:从
MambaCopyBuffers重构,增加 postprocess 子对象
EAGLE-3 辅助隐藏层
EAGLE-3 在 DeepSeek 等模型上的适配集中在辅助隐藏层(aux hidden state layers)接口机制(fc_norm、norm_output 配置现已能正确透传到 speculator)。针对 DeepSeek EAGLE3 等大词本草稿模型,vLLM 优化了 TP 下的 logits 通信开销(通过 SupportsEagle 接口),避免草稿采样阶段的全词表 all-reduce 成为瓶颈。贪心草稿采样开启 speculative.use_local_argmax_reduction 后,每 token 的 TP 通信从全词表 all-reduce 降为两次仅含 2·tp 元素的小 all-reduce(每 rank 先做词表并行本地 argmax,再聚合金局 argmax),代价是仅兼容贪心采样、不兼容概率草稿采样。Sarvam MLA(MoE)也加入 EAGLE3 行列(#53052):SarvamMLAModel 混入 EagleModelMixin/SupportsEagle3,_maybe_add_hidden_state 做 stage 局部的 aux hidden state 捕获(PP 传输不带 aux states)。
每请求接受度指标(OpenAI API)
--per-request-spec-decode-metrics(ObservabilityConfig.per_request_spec_decode_metrics: none|summary|detailed,#48915):OpenAI 响应的 metrics.speculative_decoding 返回平均接受长度、draft 接受率与按 draft 长度的直方图;detailed 再加每步 accepted/proposed 有序数组。仅 n==1 请求;改动贯穿 chat/completion serving、vllm/outputs.py 与 Rust engine-core-client 协议。
Bailing 混合模型 MTP
Bailing-MoE-v2.5 这类「线性注意力 + MLA + MoE」混合模型现已支持 MTP(bailing_hybrid_mtp,BailingMoeV25MTPModel,PR #44880)。草稿层本身是常规 MTP 结构(enorm/hnorm/eh_proj → BailingMoeV25MLAAttention → MoE MLP),复用目标模型的 MoE 块与 MLA 注意力;真正的难点在于让带状态的线性注意力层适配推测解码——验证失败后需回滚线性注意力状态。新增融合 Triton 内核 _bailing_linear_attn_decode_spec_step_kernel 依据 num_accepted_tokens 在 GPU 端直接选取接受的状态槽位(与上面 Mamba 的 postprocess_mamba_fused_kernel 同思路,免 CPU 同步),并在 vllm/v1/attention/backends/linear_attn.py 新增 BailingLinearAttentionMetadataBuilder 注入 spec-decode 元数据。这把「混合(线性注意力)目标后端变 spec-decode-aware」这一类问题从 Mamba/GDN 扩展到了 Bailing 的线性注意力。
推测器架构(v1 Speculator)
vLLM v1 把各种推测方法抽象为统一的 BaseSpeculator / DraftModelSpeculator 接口(propose() / capture() / init_cudagraph_manager() 等标准方法),并按算法拆到 vllm/v1/worker/gpu/spec_decode/<algo>/ 子目录:
EAGLE 与标准 MTP 共用 AutoRegressiveSpeculator 基类(每步产生新 KV),通过 advance_draft_positions 属性区分。Gemma4 MTP 是 Q-only 架构(advance_draft_positions=False,跨模型共享目标模型 KV 缓存、位置与 seq_len 保持固定)。MTP 现已发展为一类方法族,内置支持近 20 种模型特定变体(涵盖 GLM、Qwen、MiniMax、盘古、Step、Gemma4、Ernie 等),是当前国产大模型推测解码的主流方案。
多模块 MTP(MultiModuleMTPSpeculator,spec_decode/multi_module_mtp/speculator.py,#48892/#50062):利用 checkpoint 里的多个 MTP 模块(num_nextn_predict_layers>1,如 Inkling),每步走不同 MTP 层;draft_input_id_overrides 处理 chunked prefill 时换入未来 prefill token。KV cache 侧配套:KVCacheCoordinator 新增 num_prefill_lookahead 与 num_reprefillable_tokens,并强制 scheduler_block_size >= num_speculative_tokens——draft lookahead token 会污染尾块,prefix cache 命中需按 scheduler 块对齐丢弃尾部。
DFlash:基于注意力掩码的并行起草
DFlash(method="dflash")是与 EAGLE/MTP 并列的推测方法,每步发射 1 + num_speculative_tokens 个查询 token(1 个 bonus + N 个 mask token),通过上下文 K/V 预计算与按 mask 展开的采样实现一次前向产生多个草稿 token,支持 causal / non-causal 模式,适用于大词表草稿模型。其 precompute_and_store_context_kv 把全层的 K/V 投影权重、K-norm、RoPE 各自堆叠成单次融合 GEMM / RMSNorm 一次性算完(qwen3_dflash.py),草稿模型的注意力后端也可经 speculative_config.attention_backend 独立指定;DFlash 现已覆盖 CPU(CPU DFlash SD)。
DSpark:半自回归并行起草
DSpark(spec_decode/dspark/speculator.py 的 DSparkSpeculator(DFlashSpeculator),DeepSeek 系「并行起草」speculator,checkpoint 如 DeepSeek-V4-Flash-DSpark、Kimi-K3-DSpark)在 DFlash 基础上改造:一次并行 forward 起草整个 num_speculative_tokens 块(复用 DFlash 的 context-KV 预计算 + query-block forward),再用轻量顺序 Markov head 从左到右注入块内依赖(前缀相关 Markov bias);sample_from_anchor 布局下 N 个 query 位全是预测位(anchor 预测第一个 draft token),FULL cudagraph 覆盖 backbone + Markov 采样;dspark_draft_topk 可缩小词表做概率化起草。DSpark 的并行 forward 走 DCP 式 attention:FlashInfer MLA 服务 DSpark drafting(#54277)、FlashInfer native CP 做 MLA decode(#54012),Kimi-K3 已打通 DCP+DSpark(#52188)。调度侧 token 预算自适应(#51725)令 K3 DSpark 场景 TTFT 降约 60%。
自适应验证(Adaptive Verification)
按 (request, position) 槽位的「生存概率」(confidence_probs.cumprod(dim=1))做全局 top-k 草稿预算分配(_assign_draft_token_budget,spec_decode/adaptive_verification.py,#47808):置信度高的请求多验、低的多砍,启动时 profiling step cost(VLLM_ADAPTIVE_VERIFICATION_PROFILE_CONTEXT_LEN 默认 8192);要求 FULL cudagraph,不兼容 LoRA/PP,目前仅 DSpark with confidence head。后续 #52242 让它与 logprobs 兼容:LogprobsTensors 新增设备端 cu_num_generated_tokens_tensor 边界,RejectionSampler 在 adaptive verification 下保留真实 per-request 边界在 GPU 上,compute_topk_scores 接受张量形式的 cu_num_logits。
动态推测解码(Dynamic SD)
Dynamic SD 根据实时并发量动态调整草稿 token 数 K(num_speculative_tokens):当 BS 增大、验证有效 batch(BS×K)超过临界值时自动降低 K 甚至关闭起草(K=0),避免推测解码在高并发下反而降低 TPOT。通过 num_speculative_tokens_per_batch_size 区间表(形如 [[start_bs, end_bs, K], ...])配置,典型用于 RL rollout 等并发波动大的场景(目前仅测试过 EAGLE/EAGLE-3)。
草稿前向的小优化
AutoRegressiveSpeculator._multi_step_decode() 在草稿 prefill 中,若模型返回的最后 token hidden state 已是独立张量(last_hidden_states is hidden_states,同一引用),则直接复用采样用的 hidden state,跳过多余的 hidden_states[last_token_indices] gather(PR #46968)。GLM-5/DSv3.2 的 MTP 草稿层也由 fused_eh_norm 内核把 position-0 置零 + 双 RMSNorm + eh_proj 输入拼接一次融合完成(见 topics/model-layers/ 的 GLM-5 算子融合)。
Mamba MTP align 尾部拷贝
Mamba 推测解码的 GPU 融合内核进一步支持 MTP align 模式下的状态尾部拷贝,并修复了 Mamba 状态缓冲区的内存指针溢出问题。
Mamba 内部 prefill checkpoint(部分前缀命中 + 推测解码)
MambaSpec 新增 prefill_checkpoint_alignment 与 get_mamba_prefill_checkpoint_position() / is_mamba_prefill_checkpoint_valid()(v1/kv_cache_interface.py,#52789/#53614):chunked prefill 中在「内部」对齐位置(而非仅 prompt 尾部)注册 Mamba state checkpoint,并删除原先 not use_eagle 的限制,使部分前缀缓存命中与推测解码可同时工作(single_type_kv_cache_manager.py 的 _needs_internal_checkpoint)——Mamba 前缀缓存 TTFT 提升 9%~25%。配套修复 #53945:在 EAGLE/MTP sibling 实际恢复的共享前缀交界处(block-grid 位置)注册 align checkpoint(需 mamba_cache_mode="align")。另有实验开关 disable_eagle_block_drop(#53388)可禁用 EAGLE 尾部 prefix-cache 块丢弃,用于度量 volatile 尾块对接受率的影响。Kimi K3 侧还用 --use-replayssm 的 KDA ReplaySSM(models/kimi_k3/nvidia/ops/recoverssm.py)做验证后状态恢复:门控重放 draft token(_kda_recurrent_step)+ _prepare_commit_plan/_compact_conv_state/_commit_kda_state 内核按接受长度提交状态。
相关概念
- Continuous Batching — 连续批处理
- KV Cache — 推测解码的 KV 缓存回滚
- CUDA Graph — 推测解码对图捕获的影响