Fast Inference from Transformers via Speculative Decoding

通过“草稿模型先猜、大模型再批量验证”的两阶段解码,把自回归推理中的串行瓶颈部分摊薄,是现代低延迟推理的重要方向之一。

年份与会议

2023 · ICML

作者

Yaniv Leviathan、Matan Kalman、Yossi Matias

主题

推理优化

阅读时长

约 3 分钟

收录时间

2022/11/30

标签

原文链接

https://arxiv.org/abs/2211.17192

为什么推理加速最难的是“串行性”

大模型推理的一个基本痛点,是 decoder-only 模型生成文本时往往必须逐 token 前进:

  1. 先生成第一个 token。
  2. 再把它接回上下文,预测第二个。
  3. 再预测第三个。

即便有 KV Cache 与推理性能优化 这类技术,减少了历史前缀的重复计算,生成流程本身仍然带着很强的串行性。这就意味着:

  • 单次前向再怎么优化,也很难完全摆脱一步一步走的节奏。
  • GPU 很强,但 token 生成速度仍可能被解码流程本身卡住。

Speculative Decoding 之所以重要,是因为它没有试图改变模型本体,而是重新思考了解码过程:

能不能让一个更小、更快的模型先替大模型“打草稿”,再让大模型批量验证,而不是每一步都从零开始慢慢算?

核心直觉:先猜几个,再一起验

Speculative Decoding 的核心流程可以概括成两阶段:

  1. 使用一个更小、更快的 draft model 先连续生成若干个候选 token。
  2. 使用目标大模型一次性验证这些候选 token 中哪些可以接受。

如果候选 token 符合目标模型分布要求,就直接批量接受;如果不符合,就在某个位置回退,由大模型接管并重新生成。

这个想法的精妙之处在于:

  • 草稿模型便宜,能快速跑很多步。
  • 目标模型虽然贵,但可以一次验证多个位置,而不是逐个重新推。

因此,它试图把“严格串行”的一部分,转化成“草稿并行 + 验证批量化”的流程。

如果把这条解码协议画出来,Speculative Decoding 和普通逐 token 解码的差别会非常明显:

Speculative Decoding 草稿-验证流程图 小草稿模型先连续猜测多个 token,大模型再批量验证;可接受的 token 直接采纳,被拒绝的位置由大模型接管继续生成。 Speculative Decoding 不是改单步前向,而是重排解码协议
  <g>
    <rect x="42" y="120" width="148" height="68" rx="18" fill="#e8f1ff" stroke="#98b7e1" />
    <text x="116" y="149" text-anchor="middle" font-size="18" font-weight="700">当前前缀</text>
    <text x="116" y="171" text-anchor="middle" font-size="13" fill="#4b5563">已有上下文</text>
  </g>
  <g>
    <rect x="236" y="92" width="196" height="124" rx="22" fill="#eef6e8" stroke="#a8c48e" />
    <text x="334" y="124" text-anchor="middle" font-size="20" font-weight="700">Draft Model</text>
    <text x="334" y="149" text-anchor="middle" font-size="13" fill="#4b5563">小模型快速连续猜测</text>
    <rect x="266" y="170" width="42" height="28" rx="8" fill="#ffffff" stroke="#a8c48e" />
    <rect x="318" y="170" width="42" height="28" rx="8" fill="#ffffff" stroke="#a8c48e" />
    <rect x="370" y="170" width="42" height="28" rx="8" fill="#ffffff" stroke="#a8c48e" />
    <text x="287" y="189" text-anchor="middle" font-size="12" fill="#4b5563">t1</text>
    <text x="339" y="189" text-anchor="middle" font-size="12" fill="#4b5563">t2</text>
    <text x="391" y="189" text-anchor="middle" font-size="12" fill="#4b5563">t3</text>
  </g>
  <g>
    <rect x="478" y="92" width="210" height="124" rx="22" fill="#fff4dc" stroke="#e2c36f" />
    <text x="583" y="124" text-anchor="middle" font-size="20" font-weight="700">Target Model</text>
    <text x="583" y="149" text-anchor="middle" font-size="13" fill="#4b5563">一次性验证多个候选位置</text>
    <text x="583" y="178" text-anchor="middle" font-size="14" fill="#4b5563">接受前缀里高概率 token</text>
    <text x="583" y="199" text-anchor="middle" font-size="14" fill="#4b5563">在被拒位置重新接管生成</text>
  </g>
  <g>
    <rect x="734" y="98" width="192" height="52" rx="16" fill="#dff4f0" stroke="#8dc7bd" />
    <text x="830" y="129" text-anchor="middle" font-size="18" font-weight="700">接受的 token</text>
    <rect x="734" y="168" width="192" height="52" rx="16" fill="#fce7ef" stroke="#e2a8bd" />
    <text x="830" y="199" text-anchor="middle" font-size="18" font-weight="700">被拒后由大模型续写</text>
  </g>
  <g>
    <rect x="772" y="262" width="120" height="54" rx="16" fill="#ece8ff" stroke="#b5abef" />
    <text x="832" y="294" text-anchor="middle" font-size="18" font-weight="700">最终输出</text>
  </g>

  <line x1="190" y1="154" x2="236" y2="154" stroke="#5b6b7f" stroke-width="3" marker-end="url(#spec-arrow)" />
  <line x1="432" y1="154" x2="478" y2="154" stroke="#5b6b7f" stroke-width="3" marker-end="url(#spec-arrow)" />
  <line x1="688" y1="124" x2="734" y2="124" stroke="#5b6b7f" stroke-width="3" marker-end="url(#spec-arrow)" />
  <line x1="688" y1="194" x2="734" y2="194" stroke="#5b6b7f" stroke-width="3" marker-end="url(#spec-arrow)" />
  <line x1="830" y1="220" x2="832" y2="262" stroke="#5b6b7f" stroke-width="3" marker-end="url(#spec-arrow)" />
  <text x="490" y="318" text-anchor="middle" font-size="15" fill="#4b5563">核心变化:让“小模型先走几步,大模型集中裁决”,从而削弱纯串行解码</text>
</g>
Speculative Decoding 的价值不只是“更快”,而是它证明了解码流程本身可以被协议层重构,而不只是被动接受逐 token 串行。

为什么这件事不会改变输出分布

很多加速方法一旦引入近似,大家最担心的就是:模型会不会变味?Speculative Decoding 最漂亮的地方之一,是它在设计上追求分布等价,也就是:

在满足算法条件的前提下,最终生成结果仍然对应目标大模型原本的采样分布,而不是随便为了速度做粗暴近似。

这点非常重要,因为生产环境并不欢迎“快了很多,但回答风格和质量明显变了”的优化。Speculative Decoding 的吸引力就在于,它努力把加速建立在“正确性不明显牺牲”的前提上。

为什么草稿模型能帮上忙

直觉上看,草稿模型之所以有价值,是因为语言生成其实并不是每个 token 都同样难预测:

  • 很多常见 token、常见短语和高概率延续,本来就比较容易猜。
  • 真正需要大模型认真判断的,往往集中在少数不确定位置。

因此,让小模型先生成一串“高概率候选”,大模型再负责把关,就很像一个层级协作系统:

  • 便宜模型做大部分简单工作。
  • 昂贵模型处理关键裁决。

这和现实工程里“先粗筛、再精判”的思路非常一致。

它与 KV Cache、FlashAttention 的区别

Speculative Decoding 经常和其他推理优化一起被提到,但它解决的问题层级并不相同:

  • KV Cache:减少历史前缀重复计算。
  • FlashAttention:优化注意力算子本身的执行效率。
  • PagedAttention / vLLM:优化服务系统中的缓存管理与吞吐。
  • Speculative Decoding:优化解码流程的串行结构。

也就是说,它不是在算子级、省显存级做优化,而是在“生成过程怎么组织”这一级动手。正因为切入点不同,它常常能和其他优化叠加使用。

实验结果说明了什么

论文最有说服力的地方,是它证明了解码速度可以在保持输出分布一致性的前提下获得显著提升。对读者最值得记住的不是某个倍数,而是下面这件事:

  1. 串行解码不是绝对不可动。
  2. 只要有合适的草稿模型,大模型就不必每一步都独自扛全部开销。
  3. 解码策略本身是一大块独立优化空间,而不只是“模型前向有多快”。

这让推理优化从“更快 kernel、更快缓存”进一步扩展到“更聪明的生成协议”。

它最适合什么场景

Speculative Decoding 通常更适合以下场景:

  • 目标模型很大,单步生成成本高。
  • 有一个相对便宜、且和目标模型风格相近的草稿模型可用。
  • 服务目标很重视 token 吞吐或响应延迟。

在这些情况下,草稿模型的额外开销往往能被主模型验证时节省下来的成本覆盖。

工程难点:为什么不是所有场景都能轻松吃到收益

虽然这个想法很好,但工程上并不简单。主要难点包括:

1. 草稿模型选得不好,收益会很差

如果草稿模型和目标模型差异太大,候选 token 被拒绝的比例高,那么验证节省下来的收益会被反复回退抵消。

2. 系统实现更复杂

你不再只维护一个模型,而是要协调两个模型、候选缓存和验证逻辑。

3. 收益依赖任务分布

有些场景输出模式稳定、预测容易,speculative 非常有效;有些场景不确定性高,收益就没那么明显。

4. 与调度系统耦合

在线 serving 时,请求混跑、batch 组织和多租户策略都会影响 speculative 的实际增益。

因此,Speculative Decoding 是很强的方向,但它更像系统工程工具箱里的高级选项,而不是无脑默认开启的开关。

为什么这篇论文会长期重要

它的重要性不只在于具体算法,而在于它证明了一件此前很多人默认不太好动的事:

自回归解码虽然天然串行,但仍然可以通过“代理生成 + 主模型验证”的方式被重构。

一旦这件事成立,后续就自然会出现更多变体:

  • 更好的草稿模型选择
  • 分层 speculative
  • 与服务系统更紧的融合
  • 与量化、KV Cache、continuous batching 的联合优化

所以 Speculative Decoding 真正打开的是一整片解码协议设计空间。

读这篇论文时最该抓住什么

如果你只想抓住三点,请记住:

  1. 它解决的是解码流程串行性,而不是单步前向本身。
  2. 关键机制是“小模型先猜,大模型批量验证”。
  3. 它的价值在于加速同时尽量保持目标模型分布不变。

理解这三点,再去看各类推理框架中的 speculative 功能时,就不会把它和 KV Cache 或 FlashAttention 混为一谈。

延伸阅读

相关内容

沿着相近主题继续阅读,加深对方法边界与实践场景的理解。