对应 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
学完这一讲,你应该能够:
- 说清推测解码的物理基础:batch 小时 decode 受限于读权重,一次验证 γ + 1 个 token 和 decode 1 个 token 差不多贵。
- 写出推测采样的接受规则和残差分布,证明它的输出分布精确地等于目标模型的分布;说出接受率 的含义;指出几种常见的错误写法错在哪里。
- 推导期望产出 token 数 、墙钟加速比 ,解释最优 γ 怎样随 α 和代价比 c 变化,以及加速是用更多运算换来的。
- 实现带 KV 回滚的推测解码,理解两个 KV cache 的”不变量”。
- 解释 SpecInfer 的 token 树:为什么多条候选比一条好、拓扑感知的因果掩码怎样让一次 forward 验证整棵树、树上的**多步推测采样(MSS)**为什么要求候选是独立采样的。
- 用 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
| 符号 | 含义 | ||
|---|---|---|---|
| 、 | 目标模型(target,大模型)、草稿模型(draft / approximation model,小模型) | ||
| 、 | 给定前缀时两个模型对下一个 token 的分布(已按采样方式”标准化”,见 §2.1) | ||
| 每次迭代草稿模型猜几个 token | |||
| 某一个位置上草稿被接受的概率; 是平均接受率 | |||
| 代价比:草稿模型跑一次的时间 ÷ 目标模型跑一次的时间 | |||
| 草稿模型每 token 的运算量 ÷ 目标模型每 token 的运算量 | |||
| TV | 总变差距离 $\frac12 \sum_x | p(x) - q(x) | $ |
| SSM | SpecInfer 对草稿模型的叫法: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 多干一点活,权重还是只读一遍。
问题在于:自回归生成要求第 个 token 依赖第 个,我们不知道接下来 5 个 token 是什么,没法一次喂给模型。
1.2 处理器里的”推测执行”
计算机体系结构里有一个老办法对付这种依赖:推测执行(speculative execution)。CPU 遇到条件分支时,不等条件算出来,先猜一个方向(分支预测)继续往下执行;猜对了,省下了等待的时间;猜错了,把错误路径上的结果丢掉,回到正确的方向。只要预测器猜得够准、撤销够便宜,平均下来就更快。
推测解码把同样的想法用在生成上:
- 猜:用一个便宜的小模型(草稿模型)一个一个地猜出接下来的 γ 个 token;
- 验:把这 γ 个猜测一次喂给大模型(目标模型),一次 forward 同时得到”每个位置上大模型本来会怎么选”;
- 收:从前往后核对,接受猜对的那一段,在第一个猜错的位置换成大模型的选择;猜错之后的全部丢掉。
猜对 3 个,就用一次大模型的时间生成了 4 个 token(3 个猜对的 + 1 个修正的)。
1.3 为什么小模型能猜对
语言里有大量”容易”的 token:the United 后面几乎一定是 States,代码里 for (int i = 0; i < 后面很可能是 n。这些地方不需要 700 亿参数的模型来判断,一个小得多的模型就能猜对。Leviathan 等人的论文开头就点明了这两个观察:(1) 难的语言建模任务里包含很多容易的子任务,小模型能近似得很好;(2) 推理常常受限于显存带宽而不是算力,有富余的算力可以用。
1.4 三个要求
要让这个想法真正有用,需要同时满足:
- 猜得便宜:草稿模型跑一次的时间要远小于目标模型(代价比 小);
- 验得便宜:一次验证 γ + 1 个 token 不能比 decode 1 个 token 贵太多(批小时成立,§7 讨论批大时);
- 不改变输出:这是最重要的一条。贪心解码时”不改变”很好定义(逐 token 相同);采样时要保证输出的分布和只用目标模型完全一样。§2 的推测采样就是为此设计的。
正是第三条让推测解码和量化、蒸馏、剪枝这类”有损”加速区别开:它不需要重新训练、不改模型结构,也不需要重新评估模型质量。
2. 推测采样 · Speculative sampling
2.1 先把各种采样方式”标准化”
贪心、温度采样、top-k、nucleus(top-p)在实现上各不相同,但它们都可以写成”从某个调整过的分布里采样”:贪心 = 把非最大值的概率全置 0 再归一化(one-hot);温度 T = ;top-k = 只保留前 k 个再归一化。论文 §2.2 因此只讨论”从分布 里采样”这一种情况,下面的 、 都是调整之后的分布(代码里的 probs_from_logits)。
2.2 规则
我们想从目标分布 里采一个 token,但手里有的是草稿分布 的样本。推测采样这样做:
- 从 里采一个 ;
- 如果 ,接受 ;
- 如果 ,以 的概率拒绝 (也就是以 的概率接受);
- 拒绝时,从残差分布 里重新采一个。
合起来就是:取 ,当 时接受。
直觉: 时草稿对 是”保守”的,接受它不会让 出现得太多; 时草稿对 “过度自信”了,按比例退回多出来的部分。退回的概率交给残差分布,只分给 比 高的那些 token——它们恰恰是草稿给少了的。
2.3 一个手算的例子
代码 01 演示 1 用一个 4 个 token 的词表把每一项都算出来:
| token | 草稿为 时接受率 | 残差 | P(输出 ) | |||
|---|---|---|---|---|---|---|
| A | 0.50 | 0.30 | 0.30 | 1.000 | 0.800 | 0.500 |
| B | 0.30 | 0.50 | 0.30 | 0.600 | 0.000 | 0.300 |
| C | 0.15 | 0.10 | 0.10 | 1.000 | 0.200 | 0.150 |
| D | 0.05 | 0.10 | 0.05 | 0.500 | 0.000 | 0.050 |
草稿最爱猜 B,可目标只给 B 0.30,所以猜到 B 时只以 0.6 的概率接受。接受率 。拒绝时(概率 0.25),只在 A、C 之间按 0.20 : 0.05 重新分配。最后一列恰好等于 。
2.4 证明:输出分布精确等于
设 。残差分布的归一化常数是 。于是(论文附录 A.1):
这是一个恒等式,对任何 、 都成立:草稿再差,也只影响速度,不影响正确性。代码 01 演示 2 对 1000 对随机分布精确地算出输出分布,与 的最大偏差是 (浮点舍入误差);20 万次蒙特卡洛采样的经验分布与 的 TV 是 0.0008,与直接从 采样(0.0016)在同一噪声水平;经验接受率 0.3337,理论值 0.3338。
2.5 接受率 = 两个分布的重叠
论文定理 3.5:一个位置上的接受率
把 、 画成两组叠在一起的柱子, 就是重叠部分的面积。论文把 叫做 ,它是一个对称的距离: 时为 0,两者完全不重叠时为 1。(对所有位置取平均)就是衡量”草稿模型有多像目标模型”的指标。
一个容易误解的地方是温度的影响。代码 01 演示 4 在合成的 logits 上(草稿 = 目标 + 噪声)发现:温度越高(分布越平), 越高——两个接近均匀的分布自然重叠得多;贪心时的”匹配率”最低。可 Leviathan 等人在真实模型上观察到的方向相反:分布越尖锐, 越高(例如 T5-small 草稿配 T5-XXL 做英德翻译,贪心时 ,温度 1 时 0.62)。原因是真实的小模型在”容易”的 token 上和大模型一致且很自信,在难的 token 上才分歧,合成噪声没有这种结构。 是模型和任务的性质,必须在真实数据上测量。
2.6 几种”差不多”的错误写法
推测采样的规则看起来简单,实现时却容易写错。代码 01 演示 3 在 500 对随机分布上精确地比较了几种写法:
| 写法 | 输出与 的平均 TV | 平均接受率 |
|---|---|---|
| 正确:拒绝后从残差采 | 0.0000 | 0.400 |
| 错误 A:拒绝后直接从 重新采 | 0.1504 | 0.400 |
| 错误 B:草稿 以 的概率接受,拒绝后从 采 | 0.0341 | 0.096 |
| 正确但低效:一轮拒绝采样(论文附录 A.2) | 0.0000 | 0.034 |
错误 A 最常见:拒绝之后”随便从 采一个”,把 那部分概率算了两遍,输出偏向”两个模型都喜欢”的 token。它照样能生成看起来通顺的文本,肉眼看不出来,只能靠分布检查发现。一轮拒绝采样(以 的概率接受,,失败就直接从 采)输出是对的,但 可能很大,接受率低得多。论文附录 A.2 证明推测采样的接受率总是不低于它:用残差修正,把拒绝的代价降到了最低。
2.7 贪心是特例
贪心时 、 都是 one-hot。草稿的猜测 是 的 argmax(),接受的条件 等价于” 也是 的 argmax”;拒绝时残差就是 的 one-hot。所以贪心的推测解码就是”草稿的 argmax 和目标的 argmax 相同就接受”,同一套代码两种情况都适用。
2.8 用一点偏差换更快:宽松参数
论文附录 A.5 讨论了一个”有损”的变体:比较之前把 乘上一个宽松参数 。 是标准的推测采样; 越小越容易接受。代价是输出分布不再精确,但有一个保证:任何 token 的输出概率都不超过 。代码 01 演示 5:
| 接受率 | TV(输出, ) | max P(输出 )/ | |
|---|---|---|---|
| 1.0 | 0.280 | 0.0000 | 1.00 |
| 0.5 | 0.380 | 0.1000 | 2.00 |
| 0.3 | 0.455 | 0.1751 | 3.33 |
| 0.1 | 0.597 | 0.3168 | 10.00 |
论文报告,T5-XXL 配 T5-small、 时加速比从 2.5 倍提到 5 倍。但论文正文的所有结果都用 。本讲其余部分只讨论无损的版本。
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 里代价比 的来源。
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/次验证 | 公式 | 目标 forward |
|---|---|---|---|---|---|
| 扰动副本 σ = 0.1 | 是 | 0.742 | 3.03 | 3.00 | 122 / 360 |
| 扰动副本 σ = 0.3 | 是 | 0.391 | 1.62 | 1.63 | 225 / 360 |
| 扰动副本 σ = 0.5 | 是 | 0.224 | 1.29 | 1.29 | 283 / 360 |
| 截断副本(前 1 层) | 是 | 0.483 | 1.86 | 1.88 | 197 / 360 |
| 均匀分布 | 是 | 0.017 | 1.02 | 1.02 | 357 / 360 |
五种草稿的输出都与目标模型自己的贪心输出逐 token 相同。草稿越像目标,α 越高,目标模型的调用次数越少:σ = 0.1 的草稿把 360 次降到 122 次。均匀草稿贪心时永远猜 token 0,几乎从不被接受,调用次数和普通解码一样——但也不会更多。
还有一个工程细节:贪心时理论上输出完全相同,但”一次处理 5 个 token”和”一次处理 1 个 token”的浮点求和顺序不同,logits 会有 量级的差别;遇到几乎并列的最大值时,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
假设每个位置的接受概率都是 α 且相互独立(论文的简化假设)。前 个猜测全被接受的概率是 ;产出的 token 数 = 被接受的个数 + 1。于是(论文公式 (1)):
它最少是 1、最多是 γ + 1,γ → ∞ 时趋于 。α = 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 墙钟时间:代价比
记目标模型跑一次的时间为 ,草稿模型跑一次为 。一次迭代花 (γ 次草稿 + 1 次验证),期望产出 个 token。于是墙钟加速比(论文定理 3.8)是
这里假设”验证 γ + 1 个 token 和 decode 1 个 token 一样快”,§7 讨论它什么时候不成立。
有多小?论文说,草稿比目标小两个数量级时,他们的实验里 总是小于 0.05、常常接近 0。代码 03 演示 1 用 hw_model 估算了几对真实规格的模型(上下文 1024,batch = 1):
| 目标 / 草稿(GPU) | 目标 decode | 目标验证 5 个 token | 草稿 decode | |
|---|---|---|---|---|
| Llama-3-8B / Llama-3.2-1B(H100) | 6.15 ms | 6.15 ms | 1.43 ms | 0.233 |
| Llama-3-70B 8 位权重 / Llama-3.2-1B(H200) | 18.69 ms | 18.69 ms | 1.15 ms | 0.062 |
| Llama-3-70B 8 位权重 / Llama-3-8B(H200) | 18.69 ms | 18.69 ms | 4.44 ms | 0.238 |
比参数量之比大得多:1B 配 8B,参数量之比约 1/6.5, 却是 0.23。原因有二:草稿模型同样要付每步的固定开销(内核启动、调度,hw_model 取 0.5 ms);它的词表和目标一样大(128K),lm_head 占了它很大一部分权重。草稿模型的”小”必须是时间上的小,而不只是参数上的小——这也是 §8 里 Medusa、EAGLE 把草稿做成”几个额外的头 / 一层小网络”的原因。
4.3 γ 取多少
γ 是整数,直接枚举就能找到让 最大的值(论文 §3.5、图 3)。代码 03 演示 2 先核对了论文的数字:、α = 0.8、γ = 5 时速度 3.69×、运算量 1.63×(论文表 1);α = 0.75、γ = 7、 = 0.02 时 3.16×(论文表 4 预测 3.2×)。然后对不同的 α 和 求最优 γ:
| α | = 0.02 | = 0.05 | = 0.062(1B→70B-W8) | = 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× |
规律很直观:草稿越准越敢猜(最优 γ 随 α 增大),草稿越贵越少猜(随 减小)。 时最优 γ 趋于无穷,加速比趋于 。论文推论 3.9 给了一个简单的判据:只要 ,就存在能加速的 γ,而且加速比至少是 (取 γ = 1 时)。
论文还指出,如果能预测每个位置的 β、动态选 γ,在有”神谕”的理想情况下期望 token 数能到 ,加速比最多再提高约 60%。这为后来的动态草稿长度、动态草稿树(§8)留下了空间。
4.4 运算量增加,显存读写减少
加速不是免费的。每次迭代目标模型要算 γ + 1 个位置,平均只产出 个 token,被拒绝的位置都白算了。论文定理 3.11 给出总运算量的倍数:
上表对应的运算量增加 1.3–2.9 倍(代码 03 演示 2 的 ops 一列)。batch = 1 时这些运算落在闲着的算力上,是”免费”的;批大时就不是了(§7)。
反过来,显存读写减少了:目标模型的权重和 KV cache 每次迭代只读一遍,却产出 个 token,读的次数减少到 。SpecInfer 特别强调这一点:在把权重放在 CPU 内存里、每步都要搬到 GPU 的”卸载”(offloading)场景,减少步数就直接减少了 CPU → GPU 的传输。
4.5 公式什么时候不准:接受是”成片”的
公式假设每个位置独立地以概率 α 被接受。真实文本里,难易是成片出现的:一段模板化的代码里草稿连续猜对,一段需要推理的话里连续猜错。代码 03 演示 3 模拟了这种情况(容易位置 β = 0.95、困难位置 β = 0.3,各占一半,γ = 4):
| 接受模式 | 实测平均 α | 模拟 token/次 | 公式 | 公式误差 |
|---|---|---|---|---|
| 独立(每个位置随机选难易) | 0.625 | 2.414 | 2.414 | −0.0% |
| 段落平均 20 个 token | 0.598 | 2.191 | 2.296 | +4.8% |
| 段落平均 100 个 token | 0.588 | 2.150 | 2.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 在三种草稿里最好,因为它在 α 和 之间平衡得最好——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 这种查表模型(),英德翻译上也有 α ≈ 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 动机:一条候选的成功率随长度指数衰减
序列式推测解码只验证一条候选。要连续猜对 个 token,概率是 ,随长度指数下降;而草稿模型通常比目标小 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 树的每个节点 带一个 token , 是从根到 的 token 序列。根是上一轮最后确定的 token,每个节点代表一条候选序列。
SpecInfer 提出两种造树的办法:
- 展开(expansion-based):用一个草稿模型,按预设的展开配置 造树——第 层的每个节点展开 个孩子(贪心时取草稿分布的 top-)。例如 得到 4 条长度为 3 的候选(论文图 3)。论文实验用 :深度 8,在第 3 层展开 3 个。动态地决定怎么展开,论文说是留给未来的开放问题。
- 合并(merge-based):用多个草稿模型,各自猜一条序列,再把它们合并成一棵树(定义 3.2:合并后的树包含所有原来的序列)。为了让这些草稿”各有所长”,SpecInfer 用类似 boosting 的方法无监督地微调:先把一个草稿微调到最好,标记它和目标模型预测一致的样本;用剩下的样本微调下一个草稿;如此反复。多个草稿可以放在不同 GPU 上并行跑,不增加猜测的延迟。
6.3 树形注意力:一次 forward 验证整棵树
定义 4.1(树形注意力):节点 的注意力输出定义为把序列 当成普通序列算出的注意力。问题是怎样一次算完所有节点。
逐条序列验证有两个毛病:共享前缀的部分被重复计算;不同序列在同一位置的 K、V 不同(例如两条序列在第 3 个位置分别是 learning 和 translation),需要各自的 KV cache、各自的内核。SpecInfer 用两个技术解决:
- 按深度优先的顺序维护共享的 KV cache:所有节点的 K、V 放进同一个 cache;
- 拓扑感知的因果掩码(topology-aware causal mask):把整棵树的节点排成一列一起送进模型,每个节点只能看到上下文和它自己的祖先,兄弟和别的分支一律屏蔽。
这个掩码是普通因果掩码的推广:树退化成一条链时,“祖先”就是前面所有节点。还有一个细节:位置编码按深度算,同一深度的兄弟节点处在序列的同一个位置上。
代码 04 演示 1 用 σ = 0.3 的草稿按 造了一棵 11 个节点的树,画出掩码(每一行的 ✓ 恰好是根到该节点的路径),并逐个核对:一次 tree forward 算出的每个节点的 logits,与”把根到它的路径单独当普通序列算”的最大差是 (浮点误差)。共享前缀只算一次: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):依次检查候选,第 个以 的概率被接受;每被拒绝一次,就把目标分布换成残差 ;全部被拒绝时,从最后的残差里采样。只有一个候选时,它就是普通的推测采样。论文定理 4.2 断言它保持目标分布,证明在技术报告里。
作为对照,**朴素采样(NS)**直接从 采一个 token、看它在不在候选里——显然正确,但接受率低。论文定理 4.3 证明 MSS 的拒绝概率不会高于 NS。
代码 04 演示 3 在词表大小 5 上精确枚举了所有候选组合(候选从 中独立采样):
| 候选数 | MSS:max |输出 − | | MSS 接受概率 | NS 接受概率 | 确定性 top-k + 比值规则:TV |
|---|---|---|---|---|
| 1 | 0.449 | 0.202 | 0.2350 | |
| 2 | 0.522 | 0.316 | 0.2695 | |
| 3 | 0.562 | 0.378 | 0.3430 |
MSS 的输出分布精确等于 ,接受概率在全部 600 对分布上都不低于 NS。论文表 3 在真实模型上测得 MSS 比 NS 每步多验证 1.26–1.28 倍的 token。
最后一列是一个值得记住的陷阱。MSS 的正确性依赖于”候选是从 中独立采出来的”:证明里”草稿是 的概率 = “这一项和 相乘才得到 。如果候选是确定性地取 的 top-k,却照搬同样的比值规则,输出分布就会偏离 (平均 TV 0.24–0.34)。候选是确定性的时候,正确的做法退化成 NS(接受概率 = 候选在 下的总概率)。贪心验证不受这个问题影响。我们的代码在采样时让每个节点的孩子独立采样(重复的合并成一个节点),配 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 数(树的节点数)”):
| 第 1 层展开 | 第 3 层展开 | |
|---|---|---|
| 1 | 1.63(5) | 1.65(9) |
| 2 | 1.88(9) | 1.70(15) |
| 3 | 2.07(13) | 1.75(21) |
| 4 | 2.15(17) | 1.75(27) |
| 5 | 2.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 验证:、、、 每次验证分别产出 2.39、2.49、2.73、2.92 个 token;前两个 token 的联合分布与精确分布的 TV 是 0.1201,普通采样是 0.1236(噪声水平),分布不变的保证在树上依然成立。(论文里随机解码时树的相对收益比贪心时大;在我们的玩具里反而是贪心时更大:同样的 ,贪心 +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 / 最优 |
|---|---|---|---|
| 1 | 1.74× / 1.74× | 1.74× / 1.74× | 1.74× / 1.74× |
| 16 | 1.74× / 1.74× | 1.73× / 1.73× | 1.71× / 1.71× |
| 64 | 1.61× / 1.73×(γ = 3) | 1.72× / 1.72×(γ = 3) | 放不下 |
| 256 | 0.73× / 1.03×(γ = 1) | 1.33× / 1.64×(γ = 2) | 放不下 |
| 512 | 0.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,树 = ):
| batch | = 1 | = 2 | = 3 | = 4 | = 5 |
|---|---|---|---|---|---|
| 1 | 1.31× | 1.50× | 1.66× | 1.72× | 1.88× |
| 16 | 1.30× | 1.08× | 0.87× | 0.71× | 0.64× |
| 64 | 0.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)在目标模型最后一层上加几个额外的”解码头”,第 个头直接预测第 个未来 token,用树形注意力验证;EAGLE(2024)用一层小网络在特征层面(目标模型倒数第二层的隐藏状态)自回归地预测,接受率比独立的小模型高得多;EAGLE-2 按草稿的置信度动态造树;EAGLE-3(2025)改进了训练方式并融合多层特征。这些方法的共同点:草稿的代价 极小,而且与目标共享表示,α 更高——直接针对 §4.2 发现的” 并不小”的问题。
- 不需要草稿模型: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
- batch 小时 decode 受限于读权重,目标模型一次验证 γ + 1 个 token 和 decode 1 个 token 差不多贵(代码 03:6.15 ms 对 6.15 ms)。推测解码用便宜的草稿模型先猜、目标模型一次验证,把处理器的推测执行推广到随机的情形。
- 推测采样:以 接受草稿,拒绝时从残差 重采,输出分布精确等于 (代码 01:偏差 )。接受率 。拒绝后直接从 重采是常见的错误(TV 0.15)。贪心是 one-hot 的特例。
- 推测解码:猜 γ 个、一次验证、白送一个、用
KVCache.truncate回滚。在 tiny_llm 上,五种草稿的贪心输出都与目标逐 token 相同;σ = 0.1 的草稿把目标调用从 360 次降到 122 次;采样时分布核对通过(代码 02)。 - 分析:每次迭代期望产出 个 token,墙钟加速比还要除以 。最优 γ 随 α 增大、随 减小;加速是用 1.3–2.9 倍的运算换来的。真实规格下 并不小(1B 配 8B 约 0.23)。接受成片出现时,公式会高估约 5%(代码 03 演示 3,与我的预期相反),应直接测每次验证产出的 token 数。
- SpecInfer:token 树让一次验证覆盖多条候选;拓扑感知的因果掩码让每个节点只看自己的祖先,一次 forward 的结果与逐条路径一致(代码 04:差 );MSS 在候选独立采样时精确保持分布,接受率高于朴素采样;候选是确定性的 top-k 时照搬比值规则会出错。论文报告分布式推理快 1.5–2.8 倍、卸载推理快 2.6–3.5 倍。树该在哪里展开取决于草稿的接受率(代码 04 演示 2)。
- 与批处理争夺富余算力:批越大,验证越接近 compute-bound,推测越不划算,宽树更先变亏(代码 03 演示 4、代码 04 演示 5);KV 占主导的长上下文是例外。生产系统按负载调节 γ 和树的大小。
10. 常见误区 · Common pitfalls
- “推测解码会降低输出质量。” 标准的推测解码是无损的:贪心时输出逐 token 相同,采样时分布精确相同(§2.4)。有损的是宽松参数(§2.8)、BiLD 这类变体,以及写错的实现(§2.6)。
- “草稿模型越大越好,因为 α 更高。” α 更高, 也更高。T5-large 的 α(0.82)高于 T5-small(0.75),加速却只有 1.7 倍对 3.4 倍(论文表 2)。要看 的整体。
- “草稿模型的参数是目标的 1/8,所以 。” 不是。每步固定开销、同样大的词表和 lm_head 都让 远大于参数量之比(代码 03 演示 1:1B 配 8B 的 约 0.23)。
- “γ 越大越好。” 期望 token 数的上限是 ,而草稿的代价随 γ 线性增长;最优 γ 是有限的(§4.3)。
- “拒绝之后从目标分布里采一个就行了。” 这会把 那部分概率算两遍,输出分布是偏的(§2.6 错误 A)。必须从残差分布采。
- “推测解码总能加速。” 只有在算力有富余时才行。批大、上下文短时,验证一步变成 compute-bound,推测反而更慢(§7.1:batch 256、上下文 128 时 0.73×)。
- “树越宽越好。” 只在 batch 很小时成立;批大时宽树最先变亏(§7.3)。而且该在哪一层展开取决于草稿的接受率(§6.7)。
- “有多个候选时,对每个候选都用 检查就行。” 只有候选是从 中独立采样、并且每次拒绝后都换成残差分布时才正确(MSS);候选是确定性的 top-k 时会出错(§6.4)。
- “推测解码只减少了时间,没有别的代价。” 它增加了总运算量(§4.4)、需要额外的显存放草稿模型和它的 KV cache,还让调度更复杂(每个请求每轮接受的 token 数不同)。
11. 练习 · Exercises
参考答案里的数字是用
spec_decode.py和hw_model.py的函数实际算出来的。
练习 1(手算推测采样)。,。求接受率、残差分布,并验证输出分布等于 。
参考答案
,接受率 。,残差 。输出:。直觉:草稿给 token 0 太少(0.2 < 0.6),所以拒绝时全部补给 token 0;token 1、2 草稿给多了,按 的比例退回(token 1 以 0.6 的概率接受,token 2 以 1/3 的概率接受)。
练习 2(期望 token 数与加速比)。α = 0.7、γ = 3。(a) 每次迭代期望产出几个 token?(b) 和 时的加速比各是多少?与论文表 1 核对。
参考答案
(a) 。(b) :2.53×,与论文表 1(α = 0.7、γ = 3 时速度 2.53×)一致;:×。
练习 3(最优 γ)。α = 0.8、。用 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、:推论 3.9 保证的加速比下界是多少?实际的最优是多少?(b) α = 0.1、 时会怎样?
参考答案
(a) 下界 ,正是 γ = 1 时的加速比;最优 γ = 3,1.67×。(b) α < ,任何 γ 都不能加速(γ = 1 时 0.92×),optimal_gamma 返回”不推测”。
练习 5(批大时验证还便宜吗)。用 hw_model 算 Llama-3-8B 在 H100 上、上下文 128 时,batch = 1、32、128 的 decode 一步和”每个序列验证 5 个 token”一步的时间,并说出各自是 memory-bound 还是 compute-bound。
参考答案
| batch | decode 一步 | 验证 5 个 token | 比值 |
|---|---|---|---|
| 1 | 6.11 ms(memory) | 6.11 ms(memory) | 1.00 |
| 32 | 6.30 ms(memory) | 6.31 ms(memory) | 1.00 |
| 128 | 6.91 ms(memory) | 13.66 ms(compute) | 1.98 |
batch 128 时验证一步要处理 640 个 token,超过了 H100 的屋脊点(约 295 FLOP/byte 对应约 300 个 token),变成 compute-bound,时间翻倍。这时 α 要很高推测才划算(§7.1)。
练习 6(树的大小)。展开配置 和 的树各有多少个节点(含根)?各包含几条候选序列?逐条验证要处理多少个 token?
参考答案
: 个节点、4 条候选(每条连根长 4),逐条验证 16 个 token(代码 04 演示 1)。: 个节点、3 条候选(每条连根长 9),逐条验证 27 个 token。宽度越大、分叉越早,树相对逐条验证省得越多。
练习 7(为什么贪心验证 top-k 没问题,随机验证 top-k 有问题)。用一两句话解释。
参考答案(要点)
贪心时目标的输出是确定的(argmax),验证只是检查”这个确定的 token 在不在孩子里”,候选怎么来的不影响输出,所以 top-k 候选完全正确,只影响接受率。随机时我们要保证输出的分布是 ;比值规则 的正确性依赖”候选是按 随机出现的”,确定性的 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(分布式与通信基础)是这一讲的预备知识。