vLLM内核探秘-第12章 投机解码:以小博大

《vLLM 内核探秘》完整目录

第12章 投机解码:以小博大

"It is better to be approximately right than precisely wrong." -- Warren Buffett

:::tip 本章要点

  • 理解自回归瓶颈:为什么解码阶段 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 猜测-验证范式

投机解码分为两个阶段:

graph TB subgraph "阶段一:猜测(Draft)" D["小模型/n-gram
快速生成 k 个候选 Token"] end subgraph "阶段二:验证(Verify)" V["大模型
一次性验证 k 个 Token"] end D --> V V --> |"accept n ≤ k 个"| OUT["输出 n 个正确 Token
+ 1 个大模型采样 Token"] V --> |"reject (k-n) 个"| REJ["丢弃错误候选"] style D fill:#f59e0b,color:#fff,stroke:none style V fill:#3b82f6,color:#fff,stroke:none style OUT fill:#10b981,color:#fff,stroke:none style REJ fill:#ef4444,color:#fff,stroke:none
  1. 猜测阶段------用一个快速的方法生成 k 个候选 Token(如用 1B 参数的小模型,或 n-gram 统计)
  2. 验证阶段------将 k 个候选一次性送入大模型,计算每个位置的概率分布
  3. 接受/拒绝------从左到右逐个检查候选 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 分布与直接使用大模型采样完全一致------投机解码是精确的,不是近似的。

直觉解释:想象小模型和大模型在同时生成文本。如果小模型在某个位置给出的概率分布与大模型"基本一致"(小模型认为可能性高的 Token,大模型也认为可能性高),那么小模型的预测就很可能被接受。只有当两者"意见分歧"时(小模型认为可能性高但大模型不认同),才需要拒绝并用大模型重新采样。

这也解释了为什么同系列模型作为 Draft(如用 Llama-7B 为 Llama-70B 做投机)效果好------它们在同一数据上训练,输出分布相似度高,接受率自然高。而如果用一个完全不同架构的模型做 Draft(如用 GPT-2 为 Llama 做投机),即使它很快,接受率也会很低------因为两个模型的"语言观"不一致。

贪心采样的特殊情况 :当 temperature=0(贪心解码)时,拒绝采样退化为简单的比较------如果 Draft 模型和 Target 模型选择了相同的 Token,直接接受;如果不同,拒绝并使用 Target 的选择。此时接受率就是"两个模型在当前上下文下选择相同 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。

flowchart LR LLM["大模型\n前向传播"] -->|"hidden_states"| Eagle["EAGLE 预测头\n(1-2层, 轻量)"] Eagle -->|"k 个候选"| Verify["大模型\n一次性验证"] Verify --> Accept["接受 n ≤ k 个"]

优势:显存占用极小(预测头通常 < 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/。核心流程:

  1. 调度器分配投机预算------除了正常的 Token 预算,还分配投机步数 k
  2. Draft 阶段------Worker 调用 Draft 模型/n-gram 生成 k 个候选
  3. Verify 阶段------Worker 将 k 个候选送入大模型验证
  4. 接受/拒绝------根据拒绝采样算法确定接受多少个 Token
  5. 更新状态------接受的 Token 追加到请求上下文,更新 KV Cache

投机解码在 V1 初始 alpha 版中未被支持(因为 V1 的有状态 Worker 增加了投机解码的状态管理复杂度),在后续版本中逐步添加。

12.5 加速比分析

投机解码的理论加速比取决于两个因素:

接受率(α):候选 Token 被大模型接受的概率。接受率越高,每步有效生成的 Token 数越多。

猜测开销比(γ):Draft 阶段的计算时间 / Verify 阶段的计算时间。理想情况下 γ ≈ 0(Draft 几乎不花时间),此时加速比 ≈ 1/(1-α)。

scss 复制代码
理论加速比 = (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(零成本)
  • 适用场景------低并发、高重复性任务收益最大

源码导航

相关推荐
杨艺韬4 小时前
vLLM内核探秘-第14章 张量并行与流水线并行
agent
杨艺韬4 小时前
vLLM内核探秘-第7章 模型加载与权重管理
agent
storyseek4 小时前
拆解 DeerFlowd:一个开源 Super Agent Harness 是怎么做出来的
agent·harness
杨艺韬4 小时前
vLLM内核探秘-第13章 量化引擎:精度与速度的平衡
agent
杨艺韬4 小时前
vLLM内核探秘-第18章 设计模式与架构哲学
agent
杨艺韬4 小时前
vLLM内核探秘-第10章 前缀缓存:零开销的加速
agent
杨艺韬4 小时前
Harness Engineering-第4章 上下文工程:比 Prompt Engineering 更重要的事
agent
杨艺韬4 小时前
vLLM内核探秘-第9章 采样与输出处理
agent
杨艺韬4 小时前
Harness Engineering-前言
agent