Appearance
第12章 投机解码:以小博大
"It is better to be approximately right than precisely wrong." -- Warren Buffett
本章要点
- 理解自回归瓶颈:为什么解码阶段 GPU 利用率低
- 掌握投机解码的核心思想:猜测-验证范式
- 深入多种投机策略:Draft Model、EAGLE、n-gram
- 理解验证算法:如何保证投机解码的输出与标准解码数学等价
- 认识投机解码的适用场景与局限
12.1 自回归的枷锁
LLM 的解码是**自回归(Autoregressive)**的——第 N 个 Token 的生成依赖于第 N-1 个 Token。这意味着:
- 每步只能生成 1 个 Token
- 每步都要执行一次完整的模型前向传播
- GPU 大部分时间在等待 KV Cache 数据从显存搬运到计算单元
在一张 A100 上,Llama-2-70B 的解码速度约为每秒 30 Token(单请求)。模型有 140 GB 的参数需要读取,即使 A100 有 2 TB/s 的显存带宽,也需要约 70ms 读一遍——这就是解码延迟的理论下限。
投机解码的核心洞察:既然每步都要读一遍参数,为什么不在一次读取中"顺便"验证多个候选 Token?
12.2 猜测-验证范式
投机解码分为两个阶段:
- 猜测阶段——用一个快速的方法生成 k 个候选 Token(如用 1B 参数的小模型,或 n-gram 统计)
- 验证阶段——将 k 个候选一次性送入大模型,计算每个位置的概率分布
- 接受/拒绝——从左到右逐个检查候选 Token:如果候选的概率足够高,接受;否则拒绝,并用大模型自己的采样结果替代
关键点:验证阶段只需要一次前向传播(处理 k 个 Token),而不是 k 次。因为模型可以并行计算所有位置的注意力——这正是预填充阶段的工作方式。
如果 k 个候选全部被接受,一步就生成了 k+1 个 Token(k 个接受 + 1 个大模型新采样)。即使只接受了 n 个(n < k),也比标准解码的 1 个 Token 多。
数学保证
投机解码不是"近似"——它可以保证输出分布与标准解码完全相同。这通过**拒绝采样(Rejection Sampling)**实现:
对于每个候选 Token x_i:
- 如果
p_big(x_i) >= p_draft(x_i)(大模型的概率 ≥ 小模型的概率),直接接受 - 否则,以概率
p_big(x_i) / p_draft(x_i)接受 - 拒绝时,从修正分布
max(0, p_big - p_draft)中重新采样
这个算法保证了最终的 Token 分布与直接使用大模型采样完全一致——投机解码是精确的,不是近似的。
12.3 猜测策略
vLLM 支持多种猜测策略:
Draft Model(草稿模型)
用一个小版本的同系列模型做猜测。例如用 Llama-2-7B 为 Llama-2-70B 做猜测。小模型与大模型共享词表和分词器,推理速度快很多。
优势:猜测质量高(同系列模型的输出分布相似),接受率通常在 60-80%。 劣势:需要加载两个模型,额外占用 GPU 显存。
EAGLE / EAGLE3
源码:
vllm/v1/spec_decode/eagle.py
EAGLE 使用一个轻量级的"预测头"替代完整的 Draft 模型。这个预测头直接在大模型的隐藏状态上工作,不需要独立的模型前向传播。
python
# vllm/v1/spec_decode/eagle.py:22-39 (简化)
class EagleProposer:
def __init__(self, vllm_config, device):
self.num_speculative_tokens = (
vllm_config.speculative_config.num_speculative_tokens)
# ...
def propose(
self,
target_token_ids: torch.Tensor, # 大模型已生成的 token
target_hidden_states: torch.Tensor, # 大模型最后一层隐藏状态
next_token_ids: torch.Tensor, # 当前步大模型采样的 token
sampling_metadata: SamplingMetadata,
# ...
) -> torch.Tensor:
"""用大模型的隐藏状态预测接下来的 k 个 token"""EAGLE 的核心洞察:大模型最后一层的隐藏状态已经包含了丰富的语义信息——用一个小的预测头(通常只有 1-2 层 Transformer)就能从中高效地预测后续 token。
优势:显存占用极小(预测头通常 < 500MB),猜测速度快(复用大模型的隐藏状态,不需要独立前向传播)。EAGLE3 进一步改进了预测头的架构。
劣势:需要额外训练预测头(针对特定的大模型)。
n-gram
源码:
vllm/v1/spec_decode/ngram_proposer.py
最简单的策略——基于已生成文本的 n-gram 统计做猜测:
python
# vllm/v1/spec_decode/ngram_proposer.py:10-26
class NgramProposer:
def __init__(self, vllm_config):
self.min_n = vllm_config.speculative_config.prompt_lookup_min
self.max_n = vllm_config.speculative_config.prompt_lookup_max
self.k = vllm_config.speculative_config.num_speculative_tokens
# 预热 Numba JIT 编译(< 1秒)
self.propose(np.zeros(1024, dtype=np.int32))
def propose(self, context_token_ids: np.ndarray) -> Optional[np.ndarray]:
"""在上下文中查找 n-gram 匹配,返回匹配后的 k 个 token"""
# 例:context = [1,2,3,4,2,3], min_n=2
# 最后 2 个 token [2,3] 在位置 1-2 出现过
# 返回位置 2 之后的 token: [3,4,...]n-gram 的实现使用了 Numba JIT 编译(@jit 装饰器),将 Python 循环编译为原生机器码,在 CPU 上高效执行 n-gram 查找。
优势:零 GPU 开销(纯 CPU),不需要额外模型。 劣势:猜测质量依赖文本重复性——代码生成(大量重复模式)效果好,创意写作效果差。
使用方式:--speculative-model "[ngram]"。
三种策略对比
| 策略 | 接受率 | GPU 开销 | 显存占用 | 适用场景 |
|---|---|---|---|---|
| Draft Model | 60-80% | 中(小模型前向) | 大(需加载小模型) | 通用,高质量 |
| EAGLE | 50-70% | 低(预测头) | 小(< 500MB) | 有预训练头的模型 |
| n-gram | 30-60% | 零(纯CPU) | 零 | 代码生成、翻译 |
12.4 在 vLLM 中的实现
V1 的投机解码模块位于 vllm/v1/spec_decode/。核心流程:
- 调度器分配投机预算——除了正常的 Token 预算,还分配投机步数 k
- Draft 阶段——Worker 调用 Draft 模型/n-gram 生成 k 个候选
- Verify 阶段——Worker 将 k 个候选送入大模型验证
- 接受/拒绝——根据拒绝采样算法确定接受多少个 Token
- 更新状态——接受的 Token 追加到请求上下文,更新 KV Cache
投机解码在 V1 初始 alpha 版中未被支持(因为 V1 的有状态 Worker 增加了投机解码的状态管理复杂度),在后续版本中逐步添加。
12.5 加速比分析
投机解码的理论加速比取决于两个因素:
接受率(α):候选 Token 被大模型接受的概率。接受率越高,每步有效生成的 Token 数越多。
猜测开销比(γ):Draft 阶段的计算时间 / Verify 阶段的计算时间。理想情况下 γ ≈ 0(Draft 几乎不花时间),此时加速比 ≈ 1/(1-α)。
理论加速比 = (1 + α + α² + ... + α^k) / (1 + γ × k)
≈ 1/(1-α) / (1 + γ × k) (当 k 较大时)实际数字:
- α = 0.7, k = 5, γ = 0.1:加速比 ≈ 2.1×
- α = 0.8, k = 5, γ = 0.1:加速比 ≈ 2.8×
- α = 0.5, k = 5, γ = 0.3:加速比 ≈ 1.2×(几乎没有收益)
这说明投机解码需要高接受率 + 低 Draft 开销才能有效。n-gram 方法的 γ ≈ 0(纯 CPU 查表),但 α 通常只有 0.3-0.5(除非文本高度重复)。Draft Model 的 α 可以到 0.7-0.8,但 γ 不为零(小模型也要 GPU 计算)。
12.6 何时使用投机解码
投机解码不是银弹,它的收益取决于:
- 接受率——猜测的准确度。接受率越高,加速越明显
- Draft 成本——猜测的计算开销。如果 Draft 模型太大,猜测本身就很慢
- batch size——大批次下,验证阶段的额外 Token 可能挤占其他请求的预算
经验法则:
- 单请求/低并发 → 投机解码收益大(GPU 利用率低,有空间做投机)
- 高并发 → 收益减小(GPU 已经满负荷,投机的额外计算反而成为负担)
- 文本重复性高的任务(如代码生成、翻译)→ n-gram 策略效果好
- 通用对话 → Draft Model 或 EAGLE 更稳定
12.7 本章小结
- 自回归瓶颈——解码每步只生成 1 个 Token,GPU 利用率低
- 猜测-验证范式——小模型快速猜测 k 个候选,大模型一次验证
- 数学等价——拒绝采样保证输出分布与标准解码完全相同
- 多种策略——Draft Model(高质量)、EAGLE(低开销)、n-gram(零成本)
- 适用场景——低并发、高重复性任务收益最大
源码导航
- V1 投机解码:
vllm/v1/spec_decode/- 投机解码文档:https://docs.vllm.ai/en/latest/features/speculative_decoding/