Lecture 5 · 推测解码:Speculative Decoding 与 SpecInfer · Speculative Decoding

AI Inference Infrastructure · Lecture 05。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:Stanford CS349D AI Inference Infrastructure(Spring 2026),#7 Mon Apr 20 Speculative Decoding。必读:Y. Leviathan, M. Kalman, Y. Matias, Fast Inference from Transformers via Speculative Decoding(ICML 2023);X. Miao, G. Oliaro, Z. Zhang, X. Cheng 等, SpecInfer: Accelerating Large Language Model Serving with Tree-based Speculative Inference and Verification(ASPLOS 2024)。 依据说明:原课没有公开 slides。本讲依据两篇必读论文的全文(SpecInfer 的两个定理的证明在它引用的技术报告里,本讲没有读到,正确性用代码精确枚举来核对)。§8”之后的发展”是我补充的。 配套代码 Code:code/lecture05/(numpy + 标准库;4 个脚本 + 1 个模块 spec_decode.py:推测采样、tiny_llm 上的真实推测解码与 KV 回滚、加速比公式、token 树与树形注意力、多步推测采样;最慢的脚本约 10 秒) 预计阅读时间:3–4 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标 Learning goals

学完这一讲,你应该能够:

  1. 说清推测解码的物理基础:batch 小时 decode 受限于读权重,一次验证 γ + 1 个 token 和 decode 1 个 token 差不多贵。
  2. 写出推测采样的接受规则和残差分布,证明它的输出分布精确地等于目标模型的分布;说出接受率 α=∑xmin⁡(p,q)=1−TV(p,q)\alpha = \sum_x \min(p, q) = 1 - \text{TV}(p, q) 的含义;指出几种常见的错误写法错在哪里。
  3. 推导期望产出 token 数 1−αγ+11−α\frac{1 - \alpha^{\gamma+1}}{1 - \alpha}、墙钟加速比 1−αγ+1(1−α)(γc+1)\frac{1 - \alpha^{\gamma+1}}{(1-\alpha)(\gamma c + 1)},解释最优 γ 怎样随 α 和代价比 c 变化,以及加速是用更多运算换来的。
  4. 实现带 KV 回滚的推测解码,理解两个 KV cache 的”不变量”。
  5. 解释 SpecInfer 的 token 树:为什么多条候选比一条好、拓扑感知的因果掩码怎样让一次 forward 验证整棵树、树上的**多步推测采样(MSS)**为什么要求候选是独立采样的。
  6. 用 roofline 解释:批越大,推测越不划算;长上下文是一个例外。

0.2 先修与本讲范围 Prerequisites & scope

默认你已经会 Assumed本讲只给直觉,后面再展开 Previewed only
第 1 讲:自回归生成、KV cache、roofline、batch = 1 时 decode 的 MFU 不到 1%概率论基础:离散分布、期望、几何分布(前置讲 P2 有简短回顾)
第 2 讲:迭代级调度、选择性批处理Medusa、EAGLE 等”草稿头”方法的训练细节(§8 只介绍思想)
第 3 讲:KV cache 的分配与释放多卡上的推测解码(第 6 讲讲并行;SpecInfer 的分布式部署在 §6.5 简单提到)
第 4 讲:注意力的强度、自定义掩码推测解码与 MoE、长上下文系统的结合(第 8、9 讲)

0.3 本讲地图 Map

§1  直觉:decode 时算力在闲着;处理器里的"推测执行"
§2  推测采样:接受规则、残差分布、证明、α 的含义、错误写法、宽松变体        ← 代码 01
§3  推测解码算法:猜 γ 个、一次验证、白送一个、KV 回滚                       ← 代码 02
§4  分析:期望 token 数、代价比 c、最优 γ、运算量、公式什么时候不准            ← 代码 02 演示 2、代码 03
§5  Leviathan 等人的实验与论文卡片
§6  SpecInfer:token 树、树形注意力、多步推测采样、系统设计、评测与论文卡片      ← 代码 04
§7  与批处理的关系:批越大越不划算,长上下文是例外                            ← 代码 03 演示 4、代码 04 演示 5
§8  之后的发展
§9–13  小结、常见误区、练习、延伸阅读、下一讲预告

0.4 记号 Notation

符号含义
MpM_p、MqM_q目标模型(target,大模型)、草稿模型(draft / approximation model,小模型)
p(x)p(x)、q(x)q(x)给定前缀时两个模型对下一个 token 的分布(已按采样方式”标准化”,见 §2.1)
γ\gamma每次迭代草稿模型猜几个 token
β\beta某一个位置上草稿被接受的概率;α=E[β]\alpha = \mathbb{E}[\beta] 是平均接受率
cc代价比:草稿模型跑一次的时间 ÷ 目标模型跑一次的时间
c^\hat c草稿模型每 token 的运算量 ÷ 目标模型每 token 的运算量
TV总变差距离 $\frac12 \sum_xp(x) - q(x)$
SSMSpecInfer 对草稿模型的叫法:small speculative model(不是状态空间模型;第 10 讲的 Mamba 也叫 SSM,别混淆)
MSS、NS多步推测采样(multi-step speculative sampling)、朴素采样(naive sampling)

0.5 关于数字 · About the numbers

本讲的数字有三类:(1) 精确计算(代码 01、代码 04 演示 3):用全概率公式或枚举直接算出输出分布,不含随机性;(2) tiny_llm 上的真实运行(代码 02、代码 04):接受率、每次验证产出的 token 数、目标模型调用次数都是实测的,但 tiny_llm 的权重是随机的,草稿模型是人为造的(“目标权重 + 噪声”、“只用前 1 层”、均匀分布),所以接受率只用来说明机制,不代表真实模型;(3) 纸上 GPU(代码 03、代码 04 演示 5):时间来自第 1 讲的 hw_model。论文里的数字写成”论文报告”。


1. 直觉:decode 时算力在闲着 · Intuition

1.1 每读一遍权重,只产出一个 token

回忆第 1 讲的结论:batch = 1 时,Llama-3-8B 每 decode 一个 token 都要把 16 GB 的权重从显存读一遍,而只做 16 GFLOP 的运算——算术强度约 1 FLOP/byte,远低于 H100 的屋脊点(约 295 FLOP/byte)。GPU 大部分时间在等数据,MFU 不到 1%。

这意味着一件反直觉的事:让目标模型一次处理 5 个 token,时间几乎不变。代码 03 演示 1 用 hw_model 算出:batch = 1、上下文 1024 时,Llama-3-8B 在 H100 上 decode 1 个 token 要 6.15 ms,一次处理 5 个 token 也是 6.15 ms(比值 1.000)。多出来的 4 个 token 只是让闲着的 Tensor Core 多干一点活,权重还是只读一遍。

问题在于:自回归生成要求第 t+1t+1 个 token 依赖第 tt 个,我们不知道接下来 5 个 token 是什么,没法一次喂给模型。

1.2 处理器里的”推测执行”

计算机体系结构里有一个老办法对付这种依赖:推测执行(speculative execution)。CPU 遇到条件分支时,不等条件算出来,先猜一个方向(分支预测)继续往下执行;猜对了,省下了等待的时间;猜错了,把错误路径上的结果丢掉,回到正确的方向。只要预测器猜得够准、撤销够便宜,平均下来就更快。

推测解码把同样的想法用在生成上:

  1. 猜:用一个便宜的小模型(草稿模型)一个一个地猜出接下来的 γ 个 token;
  2. 验:把这 γ 个猜测一次喂给大模型(目标模型),一次 forward 同时得到”每个位置上大模型本来会怎么选”;
  3. 收:从前往后核对,接受猜对的那一段,在第一个猜错的位置换成大模型的选择;猜错之后的全部丢掉。

猜对 3 个,就用一次大模型的时间生成了 4 个 token(3 个猜对的 + 1 个修正的)。

1.3 为什么小模型能猜对

语言里有大量”容易”的 token:the United 后面几乎一定是 States,代码里 for (int i = 0; i < 后面很可能是 n。这些地方不需要 700 亿参数的模型来判断,一个小得多的模型就能猜对。Leviathan 等人的论文开头就点明了这两个观察:(1) 难的语言建模任务里包含很多容易的子任务,小模型能近似得很好;(2) 推理常常受限于显存带宽而不是算力,有富余的算力可以用。

1.4 三个要求

要让这个想法真正有用,需要同时满足:

  • 猜得便宜:草稿模型跑一次的时间要远小于目标模型(代价比 cc 小);
  • 验得便宜:一次验证 γ + 1 个 token 不能比 decode 1 个 token 贵太多(批小时成立,§7 讨论批大时);
  • 不改变输出:这是最重要的一条。贪心解码时”不改变”很好定义(逐 token 相同);采样时要保证输出的分布和只用目标模型完全一样。§2 的推测采样就是为此设计的。

正是第三条让推测解码和量化、蒸馏、剪枝这类”有损”加速区别开:它不需要重新训练、不改模型结构,也不需要重新评估模型质量。


2. 推测采样 · Speculative sampling

2.1 先把各种采样方式”标准化”

贪心、温度采样、top-k、nucleus(top-p)在实现上各不相同,但它们都可以写成”从某个调整过的分布里采样”:贪心 = 把非最大值的概率全置 0 再归一化(one-hot);温度 T = softmax(logits/T)\text{softmax}(\text{logits}/T);top-k = 只保留前 k 个再归一化。论文 §2.2 因此只讨论”从分布 pp 里采样”这一种情况,下面的 pp、qq 都是调整之后的分布(代码里的 probs_from_logits)。

2.2 规则

我们想从目标分布 pp 里采一个 token,但手里有的是草稿分布 qq 的样本。推测采样这样做:

  1. 从 qq 里采一个 xx;
  2. 如果 q(x)≤p(x)q(x) \le p(x),接受 xx;
  3. 如果 q(x)>p(x)q(x) > p(x),以 1−p(x)q(x)1 - \frac{p(x)}{q(x)} 的概率拒绝 xx(也就是以 p(x)q(x)\frac{p(x)}{q(x)} 的概率接受);
  4. 拒绝时,从残差分布 p′(x)=norm(max⁡(0, p(x)−q(x)))p'(x) = \text{norm}\big(\max(0,\ p(x) - q(x))\big) 里重新采一个。

合起来就是:取 u∼U(0,1)u \sim U(0,1),当 u≤p(x)/q(x)u \le p(x)/q(x) 时接受。

直觉:q(x)≤p(x)q(x) \le p(x) 时草稿对 xx 是”保守”的,接受它不会让 xx 出现得太多;q(x)>p(x)q(x) > p(x) 时草稿对 xx “过度自信”了,按比例退回多出来的部分。退回的概率交给残差分布,只分给 pp 比 qq 高的那些 token——它们恰恰是草稿给少了的。

2.3 一个手算的例子

代码 01 演示 1 用一个 4 个 token 的词表把每一项都算出来:

tokenp(x)p(x)q(x)q(x)min⁡(p,q)\min(p,q)草稿为 xx 时接受率残差 p′(x)p'(x)P(输出 xx)
A0.500.300.301.0000.8000.500
B0.300.500.300.6000.0000.300
C0.150.100.101.0000.2000.150
D0.050.100.050.5000.0000.050

草稿最爱猜 B,可目标只给 B 0.30,所以猜到 B 时只以 0.6 的概率接受。接受率 β=∑min⁡(p,q)=0.75\beta = \sum \min(p,q) = 0.75。拒绝时(概率 0.25),只在 A、C 之间按 0.20 : 0.05 重新分配。最后一列恰好等于 pp。

2.4 证明:输出分布精确等于 pp

设 β=∑xmin⁡(p(x),q(x))\beta = \sum_x \min(p(x), q(x))。残差分布的归一化常数是 ∑x(p(x)−min⁡(p(x),q(x)))=1−β\sum_x (p(x) - \min(p(x), q(x))) = 1 - \beta。于是(论文附录 A.1):

P(输出 x)=q(x)⋅min⁡ ⁣(1,p(x)q(x))⏟草稿是 x 且被接受+(1−β)⋅p(x)−min⁡(p(x),q(x))1−β⏟被拒绝后从残差采到 x=min⁡(p,q)+p−min⁡(p,q)=p(x).P(\text{输出}\ x) = \underbrace{q(x) \cdot \min\!\Big(1, \tfrac{p(x)}{q(x)}\Big)}_{\text{草稿是 } x \text{ 且被接受}} + \underbrace{(1 - \beta) \cdot \frac{p(x) - \min(p(x), q(x))}{1 - \beta}}_{\text{被拒绝后从残差采到 } x} = \min(p, q) + p - \min(p, q) = p(x).

这是一个恒等式,对任何 pp、qq 都成立:草稿再差,也只影响速度,不影响正确性。代码 01 演示 2 对 1000 对随机分布精确地算出输出分布,与 pp 的最大偏差是 3.3×10−163.3 \times 10^{-16}(浮点舍入误差);20 万次蒙特卡洛采样的经验分布与 pp 的 TV 是 0.0008,与直接从 pp 采样(0.0016)在同一噪声水平;经验接受率 0.3337,理论值 0.3338。

2.5 接受率 = 两个分布的重叠

论文定理 3.5:一个位置上的接受率

β=Ex∼q[min⁡(1,p(x)q(x))]=∑xmin⁡(p(x),q(x))=1−TV(p,q).\beta = \mathbb{E}_{x \sim q}\Big[\min\Big(1, \frac{p(x)}{q(x)}\Big)\Big] = \sum_x \min(p(x), q(x)) = 1 - \text{TV}(p, q).

把 pp、qq 画成两组叠在一起的柱子,β\beta 就是重叠部分的面积。论文把 1−β1 - \beta 叫做 DLKD_{LK},它是一个对称的距离:p=qp = q 时为 0,两者完全不重叠时为 1。α=E[β]\alpha = \mathbb{E}[\beta](对所有位置取平均)就是衡量”草稿模型有多像目标模型”的指标。

一个容易误解的地方是温度的影响。代码 01 演示 4 在合成的 logits 上(草稿 = 目标 + 噪声)发现:温度越高(分布越平),α\alpha 越高——两个接近均匀的分布自然重叠得多;贪心时的”匹配率”最低。可 Leviathan 等人在真实模型上观察到的方向相反:分布越尖锐,α\alpha 越高(例如 T5-small 草稿配 T5-XXL 做英德翻译,贪心时 α=0.75\alpha = 0.75,温度 1 时 0.62)。原因是真实的小模型在”容易”的 token 上和大模型一致且很自信,在难的 token 上才分歧,合成噪声没有这种结构。α\alpha 是模型和任务的性质,必须在真实数据上测量。

2.6 几种”差不多”的错误写法

推测采样的规则看起来简单,实现时却容易写错。代码 01 演示 3 在 500 对随机分布上精确地比较了几种写法:

写法输出与 pp 的平均 TV平均接受率
正确:拒绝后从残差采0.00000.400
错误 A:拒绝后直接从 pp 重新采0.15040.400
错误 B:草稿 xx 以 p(x)p(x) 的概率接受,拒绝后从 pp 采0.03410.096
正确但低效:一轮拒绝采样(论文附录 A.2)0.00000.034

错误 A 最常见:拒绝之后”随便从 pp 采一个”,把 min⁡(p,q)\min(p,q) 那部分概率算了两遍,输出偏向”两个模型都喜欢”的 token。它照样能生成看起来通顺的文本,肉眼看不出来,只能靠分布检查发现。一轮拒绝采样(以 p(x)/(Mq(x))p(x)/(M q(x)) 的概率接受,M=max⁡xp/qM = \max_x p/q,失败就直接从 pp 采)输出是对的,但 MM 可能很大,接受率低得多。论文附录 A.2 证明推测采样的接受率总是不低于它:用残差修正,把拒绝的代价降到了最低。

2.7 贪心是特例

贪心时 pp、qq 都是 one-hot。草稿的猜测 xx 是 qq 的 argmax(q(x)=1q(x) = 1),接受的条件 u≤p(x)u \le p(x) 等价于”xx 也是 pp 的 argmax”;拒绝时残差就是 pp 的 one-hot。所以贪心的推测解码就是”草稿的 argmax 和目标的 argmax 相同就接受”,同一套代码两种情况都适用。

2.8 用一点偏差换更快:宽松参数

论文附录 A.5 讨论了一个”有损”的变体:比较之前把 q(x)q(x) 乘上一个宽松参数 l∈(0,1]l \in (0, 1]。l=1l = 1 是标准的推测采样;ll 越小越容易接受。代价是输出分布不再精确,但有一个保证:任何 token 的输出概率都不超过 p(x)/lp(x)/l。代码 01 演示 5:

ll接受率TV(输出, pp)max P(输出 xx)/p(x)p(x)
1.00.2800.00001.00
0.50.3800.10002.00
0.30.4550.17513.33
0.10.5970.316810.00

论文报告,T5-XXL 配 T5-small、l=0.1l = 0.1 时加速比从 2.5 倍提到 5 倍。但论文正文的所有结果都用 l=1l = 1。本讲其余部分只讨论无损的版本。


3. 推测解码算法 · The speculative decoding algorithm

3.1 一次迭代

论文 Algorithm 1 把推测采样串起来,一次迭代产出 1 到 γ + 1 个 token:

输入:目标模型 M_p,草稿模型 M_q,前缀
1. 草稿模型自回归地猜 γ 个:  q_i = M_q(前缀 + x_1..x_{i-1}),x_i ~ q_i        (γ 次串行 forward,但都很便宜)
2. 目标模型一次算出 γ+1 个分布:p_1, ..., p_{γ+1} = M_p(前缀), M_p(前缀+x_1), ..., M_p(前缀+x_1..x_γ)   (一次 forward)
3. 从前往后检查:x_i 以 min(1, p_i(x_i)/q_i(x_i)) 的概率被接受;第一个被拒绝的位置记为 n+1
4. 若 n < γ:从残差 norm(max(0, p_{n+1} − q_{n+1})) 采一个 t;若全部接受:从 p_{γ+1} 再采一个 t
5. 返回 前缀 + x_1..x_n + t

三个细节值得注意:

  • 第 2 步是一次 forward:把”上一个 token + γ 个猜测”一起送进目标模型,并让它给每个位置都算 logits(tiny_llm 里是 logits_for="all")。这就是第 2 讲 Orca 选择性批处理里”一个请求一次处理多个 token”的情形,和分块 prefill(第 7 讲)一样。
  • 白送一个 token:即使全部被拒绝,也至少产出 1 个(修正的)token,所以目标模型的调用次数永远不会多于普通解码;全部接受时,第 γ + 1 个位置的分布已经算出来了,再采一个是免费的。
  • 第 1 步是串行的:草稿模型也是自回归的,猜 γ 个要 γ 次 forward。这就是 §4 里代价比 cc 的来源。

3.2 KV cache 的回滚

目标模型在第 2 步里把 γ 个猜测的 K、V 都写进了 KV cache;被拒绝的那些必须撤销。第 1 讲的 KVCache.truncate 正是为此预留的:只要把 length 改回去,被拒绝位置的数据下次直接覆盖。草稿模型的 cache 同理。

代码里维持一个不变量:两个模型的 cache 里存着”除最后一个 token 以外”的整个序列;草稿模型可能还欠几个 token 没喂,记在 pending 里。代码 02 演示 4 打印了逐次迭代的追踪(贪心,σ = 0.1 的草稿,γ = 4)。第 1 次迭代:目标 cache 原有 44 个 token,验证时涨到 49,接受 3 个后截到 48——净增长 4 = 上一个 token + 3 个被接受的猜测,新产出的修正 token 留到下一轮再写入。第 6、8 次迭代 4 个猜测全被接受,一次验证产出 5 个 token;这时草稿模型还没处理过最后一个猜测,要和新 token 一起留到下一轮。

分页的 KV cache(第 3 讲)里,回滚就是释放块表尾部多出来的块;SGLang 的基数树里,就是不把被拒绝的 token 插入树。

3.3 在 tiny_llm 上真跑一遍

tiny_llm 的权重是随机的,没有”训练好的小模型”可用。代码 02 用三种办法造草稿:扰动副本(目标权重加上 σ 倍标准差的噪声,模拟”在同样数据上训练的小模型”:大方向一致、细节有差)、截断副本(只用目标的前 1 层,再接最后的 RMSNorm 和 lm_head,类似 early exit)、均匀分布(最笨的草稿)。三个提示各生成 120 个 token,γ = 4,贪心:

草稿输出与目标相同α实测 token/次验证公式 E(α,γ)E(\alpha, \gamma)目标 forward
扰动副本 σ = 0.1是0.7423.033.00122 / 360
扰动副本 σ = 0.3是0.3911.621.63225 / 360
扰动副本 σ = 0.5是0.2241.291.29283 / 360
截断副本(前 1 层)是0.4831.861.88197 / 360
均匀分布是0.0171.021.02357 / 360

五种草稿的输出都与目标模型自己的贪心输出逐 token 相同。草稿越像目标,α 越高,目标模型的调用次数越少:σ = 0.1 的草稿把 360 次降到 122 次。均匀草稿贪心时永远猜 token 0,几乎从不被接受,调用次数和普通解码一样——但也不会更多。

还有一个工程细节:贪心时理论上输出完全相同,但”一次处理 5 个 token”和”一次处理 1 个 token”的浮点求和顺序不同,logits 会有 10−610^{-6} 量级的差别;遇到几乎并列的最大值时,argmax 可能翻转。我们的实验里没有发生,真实引擎里偶尔会遇到,这是”数学上无损、浮点上不一定逐位相同”的又一个例子(第 4 讲 §4.3 的 ⊕ 合并顺序也是)。

3.4 采样时分布真的不变吗

采样(温度 0.5)时,代码 02 演示 3 精确地算出目标模型连续生成两个 token 的联合分布(44 × 44 个格子),再各做 6000 次推测解码和普通采样:

  • TV(推测解码的经验分布, 精确分布) = 0.1041;TV(普通采样的经验分布, 精确分布) = 0.0979(同样样本量下的噪声水平);
  • 第 2 个 token 的边缘分布:0.0257 vs 0.0262。

两者离精确分布一样近。第 2 个 token 有时来自”被接受的草稿”,有时来自残差分布,有时是”全部接受后白送的那一个”,三条路径加起来恰好还原了目标模型的分布。

(为什么用温度 0.5?tiny_llm 的随机权重在温度 1 时分布非常平,连均匀草稿的接受率都有 0.658,不能代表真实模型。)


4. 分析:能快多少 · Analysis

4.1 每次迭代期望产出几个 token

假设每个位置的接受概率都是 α 且相互独立(论文的简化假设)。前 ii 个猜测全被接受的概率是 αi\alpha^i;产出的 token 数 = 被接受的个数 + 1。于是(论文公式 (1)):

E(α,γ)=∑i=0γαi=1−αγ+11−α.E(\alpha, \gamma) = \sum_{i=0}^{\gamma} \alpha^i = \frac{1 - \alpha^{\gamma+1}}{1 - \alpha}.

它最少是 1、最多是 γ + 1,γ → ∞ 时趋于 1/(1−α)1/(1-\alpha)。α = 0.8 时上限是 5:平均连续猜对 4 个以后就会猜错一个,再多猜也没用。

代码 02 演示 2 在 tiny_llm 上扫 γ(σ = 0.3 的草稿,α ≈ 0.39):γ = 1、2、3、4、6、8 时每次验证分别产出 1.37、1.51、1.61、1.62、1.64、1.64 个 token,与公式几乎一致;与此同时,草稿模型每产出一个 token 要跑的次数从 0.73 涨到 4.74——被拒绝的猜测都是白跑的。

4.2 墙钟时间:代价比 cc

记目标模型跑一次的时间为 TT,草稿模型跑一次为 cTcT。一次迭代花 γcT+T\gamma c T + T(γ 次草稿 + 1 次验证),期望产出 E(α,γ)E(\alpha, \gamma) 个 token。于是墙钟加速比(论文定理 3.8)是

S(α,γ,c)=1−αγ+1(1−α)(γc+1).S(\alpha, \gamma, c) = \frac{1 - \alpha^{\gamma+1}}{(1 - \alpha)(\gamma c + 1)}.

这里假设”验证 γ + 1 个 token 和 decode 1 个 token 一样快”,§7 讨论它什么时候不成立。

cc 有多小?论文说,草稿比目标小两个数量级时,他们的实验里 cc 总是小于 0.05、常常接近 0。代码 03 演示 1 用 hw_model 估算了几对真实规格的模型(上下文 1024,batch = 1):

目标 / 草稿(GPU)目标 decode目标验证 5 个 token草稿 decodecc
Llama-3-8B / Llama-3.2-1B(H100)6.15 ms6.15 ms1.43 ms0.233
Llama-3-70B 8 位权重 / Llama-3.2-1B(H200)18.69 ms18.69 ms1.15 ms0.062
Llama-3-70B 8 位权重 / Llama-3-8B(H200)18.69 ms18.69 ms4.44 ms0.238

cc 比参数量之比大得多:1B 配 8B,参数量之比约 1/6.5,cc 却是 0.23。原因有二:草稿模型同样要付每步的固定开销(内核启动、调度,hw_model 取 0.5 ms);它的词表和目标一样大(128K),lm_head 占了它很大一部分权重。草稿模型的”小”必须是时间上的小,而不只是参数上的小——这也是 §8 里 Medusa、EAGLE 把草稿做成”几个额外的头 / 一层小网络”的原因。

4.3 γ 取多少

γ 是整数,直接枚举就能找到让 SS 最大的值(论文 §3.5、图 3)。代码 03 演示 2 先核对了论文的数字:c=0c = 0、α = 0.8、γ = 5 时速度 3.69×、运算量 1.63×(论文表 1);α = 0.75、γ = 7、cc = 0.02 时 3.16×(论文表 4 预测 3.2×)。然后对不同的 α 和 cc 求最优 γ:

αcc = 0.02cc = 0.05cc = 0.062(1B→70B-W8)cc = 0.233(1B→8B)
0.5γ = 4,1.79×γ = 3,1.63×γ = 3,1.58×γ = 1,1.22×
0.6γ = 6,2.17×γ = 4,1.92×γ = 4,1.85×γ = 2,1.34×
0.7γ = 8,2.76×γ = 6,2.35×γ = 5,2.25×γ = 2,1.49×
0.8γ = 11,3.82×γ = 8,3.09×γ = 7,2.91×γ = 4,1.74×
0.9γ = 19,6.37×γ = 13,4.67×γ = 12,4.29×γ = 6,2.17×

规律很直观:草稿越准越敢猜(最优 γ 随 α 增大),草稿越贵越少猜(随 cc 减小)。c→0c \to 0 时最优 γ 趋于无穷,加速比趋于 1/(1−α)1/(1-\alpha)。论文推论 3.9 给了一个简单的判据:只要 α>c\alpha > c,就存在能加速的 γ,而且加速比至少是 (1+α)/(1+c)(1+\alpha)/(1+c)(取 γ = 1 时)。

论文还指出,如果能预测每个位置的 β、动态选 γ,在有”神谕”的理想情况下期望 token 数能到 1/(1−α)1/(1-\alpha),加速比最多再提高约 60%。这为后来的动态草稿长度、动态草稿树(§8)留下了空间。

4.4 运算量增加,显存读写减少

加速不是免费的。每次迭代目标模型要算 γ + 1 个位置,平均只产出 EE 个 token,被拒绝的位置都白算了。论文定理 3.11 给出总运算量的倍数:

(1−α)(γc^+γ+1)1−αγ+1.\frac{(1-\alpha)(\gamma \hat c + \gamma + 1)}{1 - \alpha^{\gamma+1}}.

上表对应的运算量增加 1.3–2.9 倍(代码 03 演示 2 的 ops 一列)。batch = 1 时这些运算落在闲着的算力上,是”免费”的;批大时就不是了(§7)。

反过来,显存读写减少了:目标模型的权重和 KV cache 每次迭代只读一遍,却产出 EE 个 token,读的次数减少到 1/E1/E。SpecInfer 特别强调这一点:在把权重放在 CPU 内存里、每步都要搬到 GPU 的”卸载”(offloading)场景,减少步数就直接减少了 CPU → GPU 的传输。

4.5 公式什么时候不准:接受是”成片”的

公式假设每个位置独立地以概率 α 被接受。真实文本里,难易是成片出现的:一段模板化的代码里草稿连续猜对,一段需要推理的话里连续猜错。代码 03 演示 3 模拟了这种情况(容易位置 β = 0.95、困难位置 β = 0.3,各占一半,γ = 4):

接受模式实测平均 α模拟 token/次公式 E(α,γ)E(\alpha, \gamma)公式误差
独立(每个位置随机选难易)0.6252.4142.414−0.0%
段落平均 20 个 token0.5982.1912.296+4.8%
段落平均 100 个 token0.5882.1502.258+5.0%

我原本预期相反的方向:每次迭代的期望 token 数是 β 的凸函数,按 Jensen 不等式,成片时应该比”按平均难度”算更多。模拟否定了这个预期:成片时每次验证产出的 token 反而更少,把实测 α 代入公式会高估约 5%。

原因在于两种平均的权重不同。在容易段落里,一次迭代能前进约 4.5 个 token;在困难段落里只前进约 1.4 个——于是大约四分之三的迭代发生在困难段落里。“每次迭代产出多少”按迭代平均,被困难段落主导;而实测 α 按”被检查的猜测”平均,容易段落里检查的猜测多,权重更大。两种平均对不上,公式就有偏差。论文附录 A.3 也提到,实测与公式的差异部分来自”β 独立同分布”这个近似。

实践上的教训:直接测”每次验证产出的 token 数”,不要只测 α 再套公式。


5. Leviathan 等人的实验与论文卡片 · Experiments & paper card

实测加速:论文在一块 TPU-v4 上、batch = 1,用 T5-XXL(11B)做目标,与当时优化得很好的 T5X 实现比较墙钟时间。英德翻译:T5-small(77M)做草稿时贪心 3.4×(α = 0.75,γ = 7)、温度 1 时 2.6×(α = 0.62);摘要(CNN/DM):3.1× 和 2.3×。T5-small 在三种草稿里最好,因为它在 α 和 cc 之间平衡得最好——T5-large 的 α 更高(0.82),但太贵,只有 1.7×。论文附录表 4 显示公式预测与实测基本吻合。

不同模型和任务的 α(论文表 3,论文报告):草稿比目标小两个数量级时,α 通常在 0.5–0.9 之间;LaMDA 137B 配 LaMDA 100M / 2B / 8B 做对话,贪心时 α = 0.61 / 0.71 / 0.75。即使是 bigram 这种查表模型(c≈0c \approx 0),英德翻译上也有 α ≈ 0.2,γ = 3 时能快 1.25 倍。论文还提到,对于摘要、多轮改写这类输出大量重复输入的任务,“从上下文里复制”的无参数草稿可能有很高的 α——这就是后来的 prompt lookup decoding(§8)。

同期工作:DeepMind 的 Chen 等人独立提出了同样的算法(他们叫 speculative sampling),在 Chinchilla 70B 上报告 2–2.5 倍的加速。

项目内容
论文Fast Inference from Transformers via Speculative Decoding,Leviathan、Kalman、Matias,ICML 2023(Google Research)
一句话用小模型猜、大模型一次验证,用推测采样保证输出分布与大模型完全相同
问题自回归解码必须串行,batch 小时每生成一个 token 都要读一遍全部权重,速度受限于显存带宽
现有方案的缺陷蒸馏、量化、剪枝、自适应计算(early exit)要么改模型、要么重新训练,而且会改变输出;Blockwise Parallel Decoding、SAD 只支持贪心或特定任务
关键洞察很多 token 容易猜;decode 时有富余算力;把处理器的”推测执行”推广到随机的情形
机制推测采样(接受规则 + 残差分布);Algorithm 1(猜 γ 个、一次验证、白送一个);期望 token 数、加速比、运算量的公式;最优 γ
评测T5-XXL 上 2.3–3.4 倍墙钟加速、输出完全相同;多种模型和任务的 α 在 0.5–0.9;bigram 草稿也有 α ≈ 0.2(论文报告)
局限与假设需要有富余的算力(批大时收益下降,论文没有讨论批处理);需要一个词表相同、又足够便宜的草稿模型;公式假设各位置的接受独立同分布
与本课的关系把第 1 讲”decode 受限于带宽”变成了一个实用的优化;它和连续批处理(第 2 讲)争夺同一份富余算力,§7 讨论两者的取舍;后续的 SpecInfer、Medusa、EAGLE 都在它的框架里

6. SpecInfer:用 token 树一次验证多条候选 · SpecInfer

6.1 动机:一条候选的成功率随长度指数衰减

序列式推测解码只验证一条候选。要连续猜对 ii 个 token,概率是 αi\alpha^i,随长度指数下降;而草稿模型通常比目标小 100–1000 倍,能力差距决定了 α 不会太高。SpecInfer 的关键观察是:草稿的 top-1 猜错时,目标模型的选择常常就在草稿的 top-k 里。论文表 1 测了 LLaMA-68M 对 LLaMA-7B 的覆盖率:贪心时 top-1 约 62%–70%、top-5 约 82%–89%;随机解码(用 top-k 个候选做验证)时从 52%–57% 提高到 96%–97%。

代码 04 演示 2(a) 在 tiny_llm 上测了同样的量:σ = 0.3 的草稿 top-1 只覆盖目标贪心选择的 39.2%,top-5 覆盖 81.7%;σ = 0.1 时从 74.4% 到 99.2%。于是一个自然的想法:每一步多猜几个候选,把它们组织成一棵树,一次验证。

6.2 token 树

定义(论文定义 3.1):token 树的每个节点 uu 带一个 token tut_u,SuS_u 是从根到 uu 的 token 序列。根是上一轮最后确定的 token,每个节点代表一条候选序列。

SpecInfer 提出两种造树的办法:

  • 展开(expansion-based):用一个草稿模型,按预设的展开配置 ⟨k1,k2,…,km⟩\langle k_1, k_2, \ldots, k_m \rangle 造树——第 ii 层的每个节点展开 kik_i 个孩子(贪心时取草稿分布的 top-kik_i)。例如 ⟨2,2,1⟩\langle 2, 2, 1 \rangle 得到 4 条长度为 3 的候选(论文图 3)。论文实验用 ⟨1,1,3,1,1,1,1,1⟩\langle 1,1,3,1,1,1,1,1 \rangle:深度 8,在第 3 层展开 3 个。动态地决定怎么展开,论文说是留给未来的开放问题。
  • 合并(merge-based):用多个草稿模型,各自猜一条序列,再把它们合并成一棵树(定义 3.2:合并后的树包含所有原来的序列)。为了让这些草稿”各有所长”,SpecInfer 用类似 boosting 的方法无监督地微调:先把一个草稿微调到最好,标记它和目标模型预测一致的样本;用剩下的样本微调下一个草稿;如此反复。多个草稿可以放在不同 GPU 上并行跑,不增加猜测的延迟。

6.3 树形注意力:一次 forward 验证整棵树

定义 4.1(树形注意力):节点 uu 的注意力输出定义为把序列 SuS_u 当成普通序列算出的注意力。问题是怎样一次算完所有节点。

逐条序列验证有两个毛病:共享前缀的部分被重复计算;不同序列在同一位置的 K、V 不同(例如两条序列在第 3 个位置分别是 learning 和 translation),需要各自的 KV cache、各自的内核。SpecInfer 用两个技术解决:

  1. 按深度优先的顺序维护共享的 KV cache:所有节点的 K、V 放进同一个 cache;
  2. 拓扑感知的因果掩码(topology-aware causal mask):把整棵树的节点排成一列一起送进模型,每个节点只能看到上下文和它自己的祖先,兄弟和别的分支一律屏蔽。

这个掩码是普通因果掩码的推广:树退化成一条链时,“祖先”就是前面所有节点。还有一个细节:位置编码按深度算,同一深度的兄弟节点处在序列的同一个位置上。

代码 04 演示 1 用 σ = 0.3 的草稿按 ⟨2,2,1⟩\langle 2, 2, 1 \rangle 造了一棵 11 个节点的树,画出掩码(每一行的 ✓ 恰好是根到该节点的路径),并逐个核对:一次 tree forward 算出的每个节点的 logits,与”把根到它的路径单独当普通序列算”的最大差是 9.5×10−79.5 \times 10^{-7}(浮点误差)。共享前缀只算一次:4 条候选逐条验证要处理 16 个 token、维护 4 份互相冲突的 KV;合成树只要 11 个 token。

验证完以后,只把被接受路径上的节点的 KV 挪成连续的,其余丢弃(代码里的 compact_accepted)。第 4 讲 FlashInfer 的”自定义掩码”和块稀疏格式,正是为了让这种树形注意力也能高效运行。

6.4 验证:贪心与多步推测采样

贪心验证(Algorithm 2 的 VerifyGreedy)很简单:从根出发,若目标模型在当前节点的 argmax 恰好是某个孩子的 token,就走到那个孩子;否则输出这个 argmax,结束。代码 04 演示 2(b) 在 tiny_llm 上验证:所有配置的输出都与目标模型的贪心解码逐 token 相同。

随机验证难得多:一个节点有多个候选时,怎样保证输出分布不变?SpecInfer 提出多步推测采样(MSS):依次检查候选,第 jj 个以 min⁡(1,pj(xj)/q(xj))\min(1, p_j(x_j)/q(x_j)) 的概率被接受;每被拒绝一次,就把目标分布换成残差 pj+1=norm(max⁡(0,pj−q))p_{j+1} = \text{norm}(\max(0, p_j - q));全部被拒绝时,从最后的残差里采样。只有一个候选时,它就是普通的推测采样。论文定理 4.2 断言它保持目标分布,证明在技术报告里。

作为对照,**朴素采样(NS)**直接从 pp 采一个 token、看它在不在候选里——显然正确,但接受率低。论文定理 4.3 证明 MSS 的拒绝概率不会高于 NS。

代码 04 演示 3 在词表大小 5 上精确枚举了所有候选组合(候选从 qq 中独立采样):

候选数 kkMSS:max |输出 − pp|MSS 接受概率NS 接受概率确定性 top-k + 比值规则:TV
12.8×10−162.8 \times 10^{-16}0.4490.2020.2350
24.4×10−164.4 \times 10^{-16}0.5220.3160.2695
31.1×10−151.1 \times 10^{-15}0.5620.3780.3430

MSS 的输出分布精确等于 pp,接受概率在全部 600 对分布上都不低于 NS。论文表 3 在真实模型上测得 MSS 比 NS 每步多验证 1.26–1.28 倍的 token。

最后一列是一个值得记住的陷阱。MSS 的正确性依赖于”候选是从 qq 中独立采出来的”:证明里”草稿是 xx 的概率 = q(x)q(x)“这一项和 min⁡(1,p/q)\min(1, p/q) 相乘才得到 min⁡(p,q)\min(p, q)。如果候选是确定性地取 qq 的 top-k,却照搬同样的比值规则,输出分布就会偏离 pp(平均 TV 0.24–0.34)。候选是确定性的时候,正确的做法退化成 NS(接受概率 = 候选在 pp 下的总概率)。贪心验证不受这个问题影响。我们的代码在采样时让每个节点的孩子独立采样(重复的合并成一个节点),配 MSS 验证;论文正文描述的展开方法是取 top-k,它在随机解码下怎样保证正确,细节在技术报告里,本讲没有核对。

6.5 系统设计

SpecInfer 建在 FlexFlow(第 6 讲的必读论文之一)之上,组成如下(论文 §5、图 6):

  • 请求管理器(CPU):采用 Orca 的迭代级调度和连续批处理(第 2 讲),每一轮选一批请求做一次”推测 + 验证”;负责合并 token 树、执行验证。它和 GPU 之间只传 token,不传向量,开销可以忽略。
  • 草稿模型:小,一块 GPU 放得下,用数据并行把请求分给各 GPU 上的草稿副本。
  • 目标模型:用 Megatron 式的混合并行——节点内张量并行、节点间流水线并行(第 6 讲)。所有 GPU 用树形并行解码一次算完整棵树。
  • 内核:基于 FasterTransformer 的定制注意力内核,一个线程块算一个请求的一个头,用拓扑感知掩码保证因果性。

论文分析了开销:草稿模型比目标小 100–1000 倍,每个草稿增加的显存不到 1%;token 树的 KV 和注意力分数相比长序列的 KV cache 可以忽略;验证多出来的计算落在 decode 时闲着的算力上。

两个应用场景的收益来源不同:分布式推理里,每个 decode 步都要在 GPU 之间传激活,推测减少了步数,也就减少了通信轮数;卸载推理里,权重在 CPU 内存里,每步都要搬进 GPU,推测减少了步数,直接减少了搬运量。

6.6 评测

论文在 AWS 的 A10(24 GB)机器上评测(论文报告):

  • 分布式推理(LLaMA-7B、OPT-30B、LLaMA-65B,与 vLLM、HuggingFace TGI、FasterTransformer 比较):单节点多卡快 1.5–2.5 倍,两节点(LLaMA-65B,8 块 A10)快 2.4–2.8 倍,生成的 token 序列完全相同。关掉推测(每步只验证一个 token)时与现有系统持平,说明收益来自推测本身。相对序列式推测,树形推测再快 1.2–1.5 倍。
  • 卸载推理(OPT-13B、OPT-30B,单块 A10,与 FlexGen 比较):每 token 延迟降低 2.6–3.5 倍。
  • 树的宽度(表 2,LLaMA-7B + LLaMA-68M,深度 8,在第 3 层展开):宽度从 1 到 5,每次验证的 token 数在贪心时从 2.95 到 3.43、随机时从 1.79 到 2.38(Alpaca 数据集)。
  • 树形并行解码相对逐条解码:小批量时持平,大批量时最多快 1.8 倍。
  • 批大小的影响:批越大,相对现有系统的提升越小;宽树在 BS = 1、2 时最好,BS ≥ 4 时宽度 2–3 最好(图 10)。论文的结论是 SpecInfer 最适合低延迟的推理。

6.7 我们在玩具上的发现

代码 04 演示 2(b) 比较了两种展开位置(σ = 0.3 的草稿,贪心;格式为”每次验证产出的 token 数(树的节点数)”):

kk第 1 层展开 ⟨k,1,1,1⟩\langle k,1,1,1 \rangle第 3 层展开 ⟨1,1,k,1,1,1,1,1⟩\langle 1,1,k,1,1,1,1,1 \rangle
11.63(5)1.65(9)
21.88(9)1.70(15)
32.07(13)1.75(21)
42.15(17)1.75(27)
52.34(21)1.75(33)

在第 1 层展开时,宽度从 1 到 5 让每次验证的 token 数从 1.63 增加到 2.34;在第 3 层展开(论文的配置)只从 1.65 增加到 1.75。原因是这个草稿的接受率只有约 0.39,要连续猜对前两个才能走到第 3 层,概率约 0.15,在那里多放候选很少派上用场。论文的 LLaMA-68M 接受率高得多,路径经常走到第 3 层,所以那样展开有效。该在哪里展开取决于草稿的接受率——这正是论文说”动态展开是开放问题”、后来 EAGLE-2 按置信度动态造树的原因。

采样(温度 0.5)时,代码 04 演示 4 让每层的孩子独立采样、用 MSS 验证:⟨1,1,1,1⟩\langle 1,1,1,1 \rangle、⟨2,1,1,1⟩\langle 2,1,1,1 \rangle、⟨3,1,1,1⟩\langle 3,1,1,1 \rangle、⟨3,3,1,1⟩\langle 3,3,1,1 \rangle 每次验证分别产出 2.39、2.49、2.73、2.92 个 token;前两个 token 的联合分布与精确分布的 TV 是 0.1201,普通采样是 0.1236(噪声水平),分布不变的保证在树上依然成立。(论文里随机解码时树的相对收益比贪心时大;在我们的玩具里反而是贪心时更大:同样的 ⟨3,1,1,1⟩\langle 3,1,1,1 \rangle,贪心 +27%、采样 +14%。)

6.8 论文卡片

项目内容
论文SpecInfer: Accelerating Large Language Model Serving with Tree-based Speculative Inference and Verification,Miao、Oliaro、Zhang、Cheng 等,ASPLOS 2024(CMU 等)
一句话把多条候选组织成 token 树,用拓扑感知的因果掩码一次验证整棵树,用多步推测采样保证分布不变
问题序列式推测解码只验证一条候选,草稿与目标的能力差距让成功率随长度指数衰减
现有方案的缺陷一条候选、一个草稿;逐条验证多条候选会重复计算共享前缀,而且 KV 互相冲突
关键洞察草稿 top-1 猜错时,目标的选择常在草稿的 top-k 里;多条候选的共享前缀可以合成一棵树一次算
机制展开式 / 合并式造树(boost 微调多个草稿);树形注意力 + 拓扑感知掩码 + 深度优先的共享 KV;贪心验证与 MSS;请求管理器 + 迭代级调度;草稿数据并行、目标张量 + 流水线并行
评测分布式推理快 1.5–2.8 倍、卸载推理快 2.6–3.5 倍、输出完全相同;树比序列再快 1.2–1.5 倍;MSS 比 NS 多验证 1.26–1.28 倍的 token(论文报告)
局限与假设展开配置是静态的,最优配置依赖草稿的接受率和批大小;批大时收益下降,宽树甚至变慢;随机解码的正确性证明在技术报告里
与本课的关系树形注意力是”自定义掩码”的典型用例(第 4 讲 FlashInfer);验证多个 token 的强度分析见第 4 讲 §4.1;它的分布式部署用到第 6 讲的张量并行和流水线并行

7. 与批处理的关系 · Speculation vs. batching

7.1 两者争夺同一份富余算力

推测解码和批处理(第 2 讲)其实在做同一件事:让每读一遍权重做更多的有用功。批处理把读来的权重分给更多的请求;推测解码把它分给同一个请求的更多位置(其中一部分会被浪费)。富余的算力只有一份:批已经很大、decode 已经接近 compute-bound 时,再推测就是在 compute-bound 的区域里多算被浪费的 token,只会更慢。

代码 03 演示 4 用 hw_model 算了 Llama-3-8B + 1B 草稿(H100,α = 0.8)在不同批大小和上下文长度下的加速比:

batch上下文 128:γ = 4 / 最优上下文 1024:γ = 4 / 最优上下文 8192:γ = 4 / 最优
11.74× / 1.74×1.74× / 1.74×1.74× / 1.74×
161.74× / 1.74×1.73× / 1.73×1.71× / 1.71×
641.61× / 1.73×(γ = 3)1.72× / 1.72×(γ = 3)放不下
2560.73× / 1.03×(γ = 1)1.33× / 1.64×(γ = 2)放不下
5120.60× / 不推测放不下放不下

上下文短(128)时,batch 256 验证一步要处理 1280 个 token,变成 compute-bound,比普通 decode 慢 3.7 倍:γ = 4 反而变慢到 0.73×,最好的选择是 γ = 1,只快 3%;batch 512 时干脆不推测。这和 SpecInfer 的观察一致:批越大,收益越小。

7.2 长上下文是一个例外

上下文 1024 时,加速比下降得慢得多(batch 256 时最优仍有 1.64×)。原因是这时 decode 的时间大部分花在读 KV cache 上:batch 256 × 1024 个 token × 128 KB ≈ 34 GB,是权重的两倍多。第 4 讲 §4.1 说过,decode 注意力的强度约等于 GQA 的组大小,批处理提高不了它;而验证 γ + 1 个 token 时,每个序列的这份 KV 只读一遍,强度提高了 γ + 1 倍。所以 KV 占主导的长上下文场景里,推测在大批量下依然划算。这是 MagicDec(2024)等工作的出发点。

7.3 token 树的大小也要随负载变

树越宽,接受的 token 越多,但一次验证的 token 也越多。代码 04 演示 5 把演示 2(b) 实测的”每次验证的 token 数”代入 hw_model(Llama-3-70B 8 位权重 + 1B 草稿,H200,树 = ⟨k,1,1,1⟩\langle k,1,1,1 \rangle):

batchkk = 1kk = 2kk = 3kk = 4kk = 5
11.31×1.50×1.66×1.72×1.88×
161.30×1.08×0.87×0.71×0.64×
640.55×0.37×0.29×0.23×0.20×

batch = 1 时,验证 21 个节点的树和验证 5 个节点一样贵(都是 18.7 ms),越宽越好;batch 16 时只有最窄的树还有 1.30×,最宽的反而慢到 0.64×;batch 64 时任何树都比不推测慢。8 位权重让读权重的时间减半,compute-bound 来得更早。

7.4 生产系统的做法

所以生产系统里,推测解码是一个随负载调节的旋钮:低负载时开推测、γ 大、树宽,用富余算力换延迟;高负载时减小 γ、缩小树,甚至关闭推测,把算力留给批处理。vLLM、SGLang 都支持推测解码(草稿模型、n-gram / prompt lookup、EAGLE、MTP 等多种草稿)并提供相应的配置。

推测解码和连续批处理结合时还有一个调度上的细节:一批里不同请求这一轮接受的 token 数不同,下一轮的输入长度也不同——这正是第 2 讲选择性批处理(不同请求的 token 数可以不同)擅长处理的情形。


8. 之后的发展 · Since then

(这一节是我的补充,不是原课内容。)

  • 更便宜的草稿:把草稿做进目标模型里。Medusa(2024)在目标模型最后一层上加几个额外的”解码头”,第 ii 个头直接预测第 ii 个未来 token,用树形注意力验证;EAGLE(2024)用一层小网络在特征层面(目标模型倒数第二层的隐藏状态)自回归地预测,接受率比独立的小模型高得多;EAGLE-2 按草稿的置信度动态造树;EAGLE-3(2025)改进了训练方式并融合多层特征。这些方法的共同点:草稿的代价 cc 极小,而且与目标共享表示,α 更高——直接针对 §4.2 发现的”cc 并不小”的问题。
  • 不需要草稿模型:Lookahead decoding(2024)用 Jacobi 迭代并行地猜多个位置,再用一个 n-gram 池验证;prompt lookup decoding 在上下文里查找匹配的片段直接复制(Leviathan 论文 §3.6 已经提到这个思路);suffix decoding 用后缀树记住历史输出中重复出现的模式,对 agent 这类高度重复的负载特别有效(Arctic Inference 报告在 agent 负载上最多快 4 倍、对话和代码上 2.8 倍,结合了 suffix decoding 和轻量草稿)。
  • 自推测(self-speculative):用目标模型自己的前几层(early exit,如 LayerSkip)或跳过部分层(Draft & Verify)当草稿,不需要额外的模型和显存。代码 02 的”截断副本”就是这个思路。
  • 把多 token 预测训练进模型:DeepSeek-V3 训练时加了多 token 预测(MTP)模块,推理时可以直接用它做推测解码,报告第二个 token 的接受率约 85%–90%。
  • 多候选的理论:SpecTr(2023)把”多个草稿候选怎样验证最优”表述成最优传输问题,给出了比逐个检查更高接受率的方案。
  • 大批量与长上下文:MagicDec(2024)指出长上下文时 KV 读取占主导,推测解码在大批量下仍有收益(§7.2);也有工作根据负载动态决定推测长度。
  • 有损的推测:BiLD(2023)等方法放宽验证条件换取更高的接受率,输出不再与目标完全相同(类似 §2.8 的宽松参数)。

9. 小结 · Summary

  1. batch 小时 decode 受限于读权重,目标模型一次验证 γ + 1 个 token 和 decode 1 个 token 差不多贵(代码 03:6.15 ms 对 6.15 ms)。推测解码用便宜的草稿模型先猜、目标模型一次验证,把处理器的推测执行推广到随机的情形。
  2. 推测采样:以 min⁡(1,p(x)/q(x))\min(1, p(x)/q(x)) 接受草稿,拒绝时从残差 norm(max⁡(0,p−q))\text{norm}(\max(0, p - q)) 重采,输出分布精确等于 pp(代码 01:偏差 3.3×10−163.3 \times 10^{-16})。接受率 α=∑min⁡(p,q)=1−TV(p,q)\alpha = \sum \min(p, q) = 1 - \text{TV}(p, q)。拒绝后直接从 pp 重采是常见的错误(TV 0.15)。贪心是 one-hot 的特例。
  3. 推测解码:猜 γ 个、一次验证、白送一个、用 KVCache.truncate 回滚。在 tiny_llm 上,五种草稿的贪心输出都与目标逐 token 相同;σ = 0.1 的草稿把目标调用从 360 次降到 122 次;采样时分布核对通过(代码 02)。
  4. 分析:每次迭代期望产出 1−αγ+11−α\frac{1-\alpha^{\gamma+1}}{1-\alpha} 个 token,墙钟加速比还要除以 γc+1\gamma c + 1。最优 γ 随 α 增大、随 cc 减小;加速是用 1.3–2.9 倍的运算换来的。真实规格下 cc 并不小(1B 配 8B 约 0.23)。接受成片出现时,公式会高估约 5%(代码 03 演示 3,与我的预期相反),应直接测每次验证产出的 token 数。
  5. SpecInfer:token 树让一次验证覆盖多条候选;拓扑感知的因果掩码让每个节点只看自己的祖先,一次 forward 的结果与逐条路径一致(代码 04:差 9.5×10−79.5 \times 10^{-7});MSS 在候选独立采样时精确保持分布,接受率高于朴素采样;候选是确定性的 top-k 时照搬比值规则会出错。论文报告分布式推理快 1.5–2.8 倍、卸载推理快 2.6–3.5 倍。树该在哪里展开取决于草稿的接受率(代码 04 演示 2)。
  6. 与批处理争夺富余算力:批越大,验证越接近 compute-bound,推测越不划算,宽树更先变亏(代码 03 演示 4、代码 04 演示 5);KV 占主导的长上下文是例外。生产系统按负载调节 γ 和树的大小。

10. 常见误区 · Common pitfalls

  1. “推测解码会降低输出质量。” 标准的推测解码是无损的:贪心时输出逐 token 相同,采样时分布精确相同(§2.4)。有损的是宽松参数(§2.8)、BiLD 这类变体,以及写错的实现(§2.6)。
  2. “草稿模型越大越好,因为 α 更高。” α 更高,cc 也更高。T5-large 的 α(0.82)高于 T5-small(0.75),加速却只有 1.7 倍对 3.4 倍(论文表 2)。要看 S(α,γ,c)S(\alpha, \gamma, c) 的整体。
  3. “草稿模型的参数是目标的 1/8,所以 c≈1/8c \approx 1/8。” 不是。每步固定开销、同样大的词表和 lm_head 都让 cc 远大于参数量之比(代码 03 演示 1:1B 配 8B 的 cc 约 0.23)。
  4. “γ 越大越好。” 期望 token 数的上限是 1/(1−α)1/(1 - \alpha),而草稿的代价随 γ 线性增长;最优 γ 是有限的(§4.3)。
  5. “拒绝之后从目标分布里采一个就行了。” 这会把 min⁡(p,q)\min(p, q) 那部分概率算两遍,输出分布是偏的(§2.6 错误 A)。必须从残差分布采。
  6. “推测解码总能加速。” 只有在算力有富余时才行。批大、上下文短时,验证一步变成 compute-bound,推测反而更慢(§7.1:batch 256、上下文 128 时 0.73×)。
  7. “树越宽越好。” 只在 batch 很小时成立;批大时宽树最先变亏(§7.3)。而且该在哪一层展开取决于草稿的接受率(§6.7)。
  8. “有多个候选时,对每个候选都用 min⁡(1,p/q)\min(1, p/q) 检查就行。” 只有候选是从 qq 中独立采样、并且每次拒绝后都换成残差分布时才正确(MSS);候选是确定性的 top-k 时会出错(§6.4)。
  9. “推测解码只减少了时间,没有别的代价。” 它增加了总运算量(§4.4)、需要额外的显存放草稿模型和它的 KV cache,还让调度更复杂(每个请求每轮接受的 token 数不同)。

11. 练习 · Exercises

参考答案里的数字是用 spec_decode.py 和 hw_model.py 的函数实际算出来的。

练习 1(手算推测采样)。p=[0.6,0.3,0.1]p = [0.6, 0.3, 0.1],q=[0.2,0.5,0.3]q = [0.2, 0.5, 0.3]。求接受率、残差分布,并验证输出分布等于 pp。

参考答案

min⁡(p,q)=[0.2,0.3,0.1]\min(p, q) = [0.2, 0.3, 0.1],接受率 β=0.6\beta = 0.6。max⁡(0,p−q)=[0.4,0,0]\max(0, p - q) = [0.4, 0, 0],残差 =[1,0,0]= [1, 0, 0]。输出:[0.2,0.3,0.1]+0.4×[1,0,0]=[0.6,0.3,0.1]=p[0.2, 0.3, 0.1] + 0.4 \times [1, 0, 0] = [0.6, 0.3, 0.1] = p。直觉:草稿给 token 0 太少(0.2 < 0.6),所以拒绝时全部补给 token 0;token 1、2 草稿给多了,按 p/qp/q 的比例退回(token 1 以 0.6 的概率接受,token 2 以 1/3 的概率接受)。

练习 2(期望 token 数与加速比)。α = 0.7、γ = 3。(a) 每次迭代期望产出几个 token?(b) c=0c = 0 和 c=0.05c = 0.05 时的加速比各是多少?与论文表 1 核对。

参考答案

(a) E=(1−0.74)/(1−0.7)=(1−0.2401)/0.3=2.533E = (1 - 0.7^4)/(1 - 0.7) = (1 - 0.2401)/0.3 = 2.533。(b) c=0c = 0:2.53×,与论文表 1(α = 0.7、γ = 3 时速度 2.53×)一致;c=0.05c = 0.05:2.533/(3×0.05+1)=2.202.533 / (3 \times 0.05 + 1) = 2.20×。

练习 3(最优 γ)。α = 0.8、c=0.1c = 0.1。用 walltime_speedup 算 γ = 1…8 的加速比,最优 γ 是多少?

参考答案

γ = 1…8 依次为 1.64、2.03、2.27、2.40、2.46、2.47、2.45、2.41,最优 γ = 6,加速比 2.47×。注意曲线在最优点附近很平:γ = 5、7 也只差 0.01–0.02。实践中 γ 选得略小一些更稳妥(草稿浪费少,批变大时也不容易变亏)。

练习 4(推论 3.9 的下界)。(a) α = 0.6、c=0.1c = 0.1:推论 3.9 保证的加速比下界是多少?实际的最优是多少?(b) α = 0.1、c=0.2c = 0.2 时会怎样?

参考答案

(a) 下界 (1+0.6)/(1+0.1)=1.45(1 + 0.6)/(1 + 0.1) = 1.45,正是 γ = 1 时的加速比;最优 γ = 3,1.67×。(b) α < cc,任何 γ 都不能加速(γ = 1 时 0.92×),optimal_gamma 返回”不推测”。

练习 5(批大时验证还便宜吗)。用 hw_model 算 Llama-3-8B 在 H100 上、上下文 128 时,batch = 1、32、128 的 decode 一步和”每个序列验证 5 个 token”一步的时间,并说出各自是 memory-bound 还是 compute-bound。

参考答案
batchdecode 一步验证 5 个 token比值
16.11 ms(memory)6.11 ms(memory)1.00
326.30 ms(memory)6.31 ms(memory)1.00
1286.91 ms(memory)13.66 ms(compute)1.98

batch 128 时验证一步要处理 640 个 token,超过了 H100 的屋脊点(约 295 FLOP/byte 对应约 300 个 token),变成 compute-bound,时间翻倍。这时 α 要很高推测才划算(§7.1)。

练习 6(树的大小)。展开配置 ⟨2,2,1⟩\langle 2, 2, 1 \rangle 和 ⟨1,1,3,1,1,1,1,1⟩\langle 1,1,3,1,1,1,1,1 \rangle 的树各有多少个节点(含根)?各包含几条候选序列?逐条验证要处理多少个 token?

参考答案

⟨2,2,1⟩\langle 2,2,1 \rangle:1+2+4+4=111 + 2 + 4 + 4 = 11 个节点、4 条候选(每条连根长 4),逐条验证 16 个 token(代码 04 演示 1)。⟨1,1,3,1,1,1,1,1⟩\langle 1,1,3,1,1,1,1,1 \rangle:1+1+1+3×6=211 + 1 + 1 + 3 \times 6 = 21 个节点、3 条候选(每条连根长 9),逐条验证 27 个 token。宽度越大、分叉越早,树相对逐条验证省得越多。

练习 7(为什么贪心验证 top-k 没问题,随机验证 top-k 有问题)。用一两句话解释。

参考答案(要点)

贪心时目标的输出是确定的(argmax),验证只是检查”这个确定的 token 在不在孩子里”,候选怎么来的不影响输出,所以 top-k 候选完全正确,只影响接受率。随机时我们要保证输出的分布是 pp;比值规则 min⁡(1,p/q)\min(1, p/q) 的正确性依赖”候选是按 qq 随机出现的”,确定性的 top-k 破坏了这个前提(§6.4 表的最后一列)。

练习 8(分页 KV 的回滚)。块大小 16,一个请求已有 1000 个 token 的 KV(占 63 个块,最后一块用了 8 个槽位)。这一轮验证 γ = 7 个猜测(加上一个 token 共写入 8 个),只接受了 2 个。写入时需要新分配块吗?回滚后呢?如果 γ = 15 呢?

参考答案(要点)

写入 8 个 token 后共 1008 个,第 63 块(槽位 992–1007)刚好写满,不需要新块;回滚到 1000 + 1 + 2 = 1003 个,仍在第 63 块里,不释放任何块,只是把”已用长度”改小。γ = 15 时写入 16 个,共 1016 个,需要新分配第 64 块;接受 2 个后回滚到 1003,第 64 块整块释放(引用计数归零,还给块池)。分页让回滚只涉及”改长度 + 释放尾部的块”,与第 3 讲的写时复制、引用计数完全兼容。

练习 9(思考:推测解码遇到连续批处理)。一批 32 个请求同时做推测解码,γ = 4。这一轮有的请求接受 0 个、有的接受 4 个。(a) 下一轮每个请求的输入有多少个 token?(b) 为什么第 2 讲的选择性批处理正好适合?(c) 如果一个请求很”容易”、另一个很”难”,统一的 γ 好吗?

参考答案(要点)

(a) 每个请求下一轮都是”上一个 token + γ 个猜测” = 5 个 token 进目标模型(草稿模型那边,有的请求要多喂一个欠着的 token)。但各请求的 KV 长度增长不同(1 到 5)。(b) 选择性批处理里,非注意力的算子把所有请求的 token 拼在一起算,注意力按请求分开算,天然支持”每个请求的 token 数和 KV 长度都不同”。(c) 不好:容易的请求应该多猜,难的少猜。按请求、按位置动态决定 γ(甚至动态造树)是后续工作的方向(§8 的 EAGLE-2 等),代价是调度和内核更复杂。


12. 延伸阅读 · Further reading

本讲的必读论文

  • Y. Leviathan, M. Kalman, Y. Matias. Fast Inference from Transformers via Speculative Decoding. ICML 2023.
  • X. Miao, G. Oliaro, Z. Zhang, X. Cheng, Z. Wang, Z. Zhang, R. Y. Y. Wong, A. Zhu, L. Yang, X. Shi, C. Shi, Z. Chen, D. Arfeen, R. Abhyankar, Z. Jia. SpecInfer: Accelerating Large Language Model Serving with Tree-based Speculative Inference and Verification. ASPLOS 2024.

相关工作

  • C. Chen, S. Borgeaud, G. Irving, J.-B. Lespiau, L. Sifre, J. Jumper. Accelerating Large Language Model Decoding with Speculative Sampling. 2023(DeepMind 的同期工作)。
  • M. Stern, N. Shazeer, J. Uszkoreit. Blockwise Parallel Decoding for Deep Autoregressive Models. NeurIPS 2018(推测解码的前身,只支持贪心)。
  • T. Cai et al. Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads. 2024。
  • Y. Li, F. Wei, C. Zhang, H. Zhang. EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty. 2024;以及 EAGLE-2、EAGLE-3。
  • Y. Fu, P. Bailis, I. Stoica, H. Zhang. Break the Sequential Dependency of LLM Inference Using Lookahead Decoding. 2024。
  • Z. Sun et al. SpecTr: Fast Speculative Decoding via Optimal Transport. NeurIPS 2023。
  • J. Chen et al. MagicDec: Breaking the Latency-Throughput Tradeoff for Long Context Generation with Speculative Decoding. 2024。

背景

  • 前置讲 P2(Transformer 推理的账本):每 token 的 FLOPs 与字节数、采样方式。
  • 第 4 讲 §4.1:一次验证多个 token 时注意力的强度。

13. 下一讲预告 · Next lecture

到这里,Part I”单 GPU 推理引擎”就结束了:调度(第 2 讲)、显存(第 3 讲)、内核(第 4 讲)、以及用富余算力换延迟的推测解码(本讲)。可还有一个问题一直被我们回避:模型一块 GPU 放不下怎么办? Llama-3-70B 的 BF16 权重就有 141 GB,405B 的模型要 800 多 GB;即使放得下,也可能想用更多的卡来降低延迟。第 6 讲:并行读两篇论文:FlexFlow 把”怎样把一个神经网络切到很多设备上”表述成一个搜索问题,用执行模拟器 + 随机搜索在巨大的策略空间里找快的方案;Efficiently Scaling Transformer Inference(Pope 等)专门分析 Transformer 推理的分区方式,给出前馈层的 1D / 2D weight-stationary 与 weight-gathered 布局、多查询注意力按批切分的布局,以及低延迟和高吞吐下截然不同的最优选择。我们会在”虚拟设备”上实现 tiny_llm 的张量并行(列切分 + 行切分 + all-reduce),验证输出与单卡一致,并在纸上 GPU 上加入通信的代价模型。前置讲 P5(分布式与通信基础)是这一讲的预备知识。

← 返回课程目录