推测解码(Speculative Decoding)
所属:01-fundamentals / 06-agent-infra
面试权重:★★★
来源:《深入理解 AI Infra:量化分析与系统设计》第 8.5 节(李博杰,github.com/bojieli/ai-infra-book);算例与实测均有实验条件,非本仓库测量
相关知识点:vllm-pagedattention.md(分页器如何回滚被丢弃的草稿 KV)、batching-parallelism.md(并发下权重读取的摊薄)、latency-cost.md(TPOT 的优化手段)、../01-llm-core/arch-variants.md(MTP 头)、quantization.md(同样是在带宽受限的 decode 上做文章)
一句话#
推测解码用一段便宜的草稿去换目标模型一次前向确定多个 token —— 关键在于它不改变输出分布(拒绝时按正差额重采样,严格还原 ),所以是「无损加速」;而它真正的取舍不是「草稿准不准」,而是**「每轮多输出了几个 token」与「每轮多花了多少时间」之比**。
为什么重要(面试角度)#
- 它是最经典的推理优化题之一,而且有一层几乎所有人都答不对的反直觉:草稿接受率低也能加速。因为验证的开销极小,而每轮至少确定一个 token —— 说得出这一点,就说明你理解了机制而不只是背了名词。
- 它的第二个反直觉更值钱:更快的草稿生成方式可能让整条请求更慢(并行生成草稿更快,但接受率下降反而增加每 token 均摊时间)。能讲清这个条件依赖,是「会算账」而不是「知道有这个东西」。
- 它是「低并发优化」的典型:低负载时多行验证吃闲置算力,高负载时普通批处理已经充分共享权重,草稿与验证反而争用资源。被问到「什么时候不该用」时,这是标准答案。
- 它和 KV / 内存管理强耦合:验证失败要回滚 KV 有效长度、分页器块引用、采样器与语法检查器状态 —— 这是面试官顺着往
vllm-pagedattention.md挖的接口。
⚠️ 边界声明:本篇是《AI Infra》第 8.5 节的知识梳理与算式复现,不是一手实测。文中 RTX PRO 6000、DFlash 等数据都来自书中的实验条件(batch 1、2K 上下文等)。面试时按知识 + 会算账讲,不要说成自己调过的。
核心内容#
1. 机制:草稿 → 验证 → 回退#
普通 decode 中,目标模型每次前向只确定一个 token。推测解码的思路:先为同一请求生成一段草稿序列,再由目标模型一次前向同时验证其中多个位置。 若多个草稿被接受,一次计算就确定多个输出。
贪心生成下的规则最直观。 草稿是 a b c d,目标模型在各位置分别给出自己的选择:
- 前两个与草稿相同 → 保留;
- 第三个目标模型选了
x(≠c)→ 保留前两个、在第三处输出x; - 第四个草稿 token 是在错误的第三个之后生成的,随之丢弃(即使目标模型已经算过它的验证结果也不能用)。
所以这一次验证最终留下 2 个匹配 + 1 个修正 = 3 个输出。若四个全部匹配,目标模型通常还能在它们之后再给一个 token。
采样生成下必须保证分布不变。 设目标分布 、草稿分布 。先从 采样得到草稿 token ,再以下列概率接受:
直接接受的概率是 ,距离目标 还差 (只取正差额)。因此拒绝后,把这个正差额归一化,从中重新采样 —— 两种情况合起来,最终输出的概率恰好是 。
两符号算例(可背):。
- 草稿恒为 A → 接受 A 的概率 1/4,其余 3/4 拒绝并改出 B;
- 草稿恒为 B → 接受 B 的概率 3/4,其余情况改出 A。
两种草稿都给出完全相同的目标分布,但接受率差三倍 —— 这就是「草稿质量」为什么重要,以及为什么它只影响效率、不影响正确性。
验证之后要清理状态(容易被漏掉的一步):KV 的有效长度调整到已经确定的输出位置,被丢弃的草稿 token 对应的 KV 不再参与后续计算;分页器按确定的输出长度更新块引用;采样器与语法检查器(按输出格式限制可选 token 的组件)恢复到对应状态。下一轮从修正后的序列继续。
2. 该用的指标:平均每输出 token 的耗时#
推测解码同时改变每轮的耗时和输出数,所以正确的度量是
必须用总时间除以总 token 数,而不是「先算每轮的平均值再对轮次取平均」。
为什么:两轮各 1.5 ms,分别输出 1 个和 5 个 token。正确的算法是 ms/token。若先算每轮的值(1.5 和 0.3 ms/token)再等权平均,会得到 0.9 ms —— 这等于让「只输出 1 个 token 的轮次」和「输出 5 个的轮次」占相同权重。这是一个很容易被追问的计算细节。
每轮平均输出多少? 设前面草稿都被接受时当前位置的接受概率为 ,草稿长度 ,则
(每一项对应「至少输出这么多个 token」的概率。) 时:
| 草稿 | 平均每轮输出 | |
|---|---|---|
| AAAA | 1/4 | 约 1.33 |
| BBBB | 3/4 | 约 3.05 |
3. 反直觉一:接受率低也照样加速#
这是全篇最值钱的一段。在 RTX PRO 6000、batch 1、2K 上下文下,普通 decode 一轮 26.26 ms(实验 8-1)。验证 5 个位置时,读取的权重和 KV 与普通 decode 相同,多出的 4 行矩阵运算约 65 GFLOP,按 BF16 峰值 62% 算只要约 0.2 ms —— 所以一轮验证仍按 26.26 ms 计。实验 8-5 也观察到同样现象:DFlash 每轮生成 7 个草稿、验证 8 个位置,中位耗时 15.2 ms,与同配置普通 decode 一步的 16.9 ms 相当。
于是加上草稿查询的 0.1 ms,一轮约 26.36 ms:
| 草稿 | 平均每轮输出 | 平均每 token |
|---|---|---|
| 普通 decode | 1 | 26.26 ms |
| AAAA(接受率 1/4) | 1.33 | 约 19.8 ms |
| BBBB(接受率 3/4) | 3.05 | 约 8.64 ms |
接受率只有 25% 的草稿,也把每 token 耗时从 26.26 降到 19.8 ms。 原因有两条:① 验证几乎不增加一轮的时间(权重和 KV 的读取量没变,多的只是几行矩阵运算);② 每轮至少确定一个 token,所以推测解码永远不会比普通 decode 更差 —— 最坏情况退化成普通 decode。
上限条件由此而来:一轮总耗时必须小于「平均输出数 × 26.26 ms」。扣除验证的 26.26 ms,留给草稿查询的预算是:
- AAAA:约 8.7 ms
- BBBB:约 53.9 ms
超过这个预算,草稿本身的成本就吃掉全部收益。
准备成本要摊。 若建立上下文索引需要 2 秒,而 BBBB 每个输出 token 省约 ms,则生成约 114 个 token 就能抵消。一个输出 256 token 的请求:普通 decode 约 6.72 s;先花 2 秒建索引再用 BBBB 草稿,期望约 4.22 s。如果索引能提前建立并被多个请求共用,这笔开销还能摊得更薄。
4. 反直觉二:更快的草稿可能让整条请求更慢#
草稿从哪来,决定了「每轮多花多少时间」,而这笔账不能只看草稿生成快不快。
| 草稿来源 | 做法 | 主要代价 |
|---|---|---|
| 上下文查找 | 从输入或已有回答里找重复片段 | 匹配与建索引的开销(可能要秒级) |
| 独立小模型逐步生成 | 单独的前向,逐步出草稿 | 额外一份权重与 KV;每 token 要前向一次 |
| EAGLE 类 | 把目标模型的中间表示送入较小的预测网络 | 额外网络 + 中间表示 |
| DFlash 类 | 并行一次生成整段草稿 | 额外网络;草稿分布与目标差异可能更大 |
| 模型自带 MTP 头 | 训练时联合学出的多 token 预测能力 | 需要模型本身支持(见 arch-variants.md) |
把每轮拆成生成草稿、准备验证、目标验证、确定输出四段,就能逐项比较。以 DFlash(权重约 1.95 GiB)为例,按 batch 1 的有效带宽(26.26 ms 读 15.44 GB,约 588 GB/s)算,该网络前向一次约 3.57 ms:
- 逐个生成 4 个草稿 = 4 次前向 = 14.28 ms,加验证 26.26 ms,平均输出 3 个 → 约 13.5 ms/token;
- 并行生成 整段草稿 = 1 次前向 = 3.57 ms,同样加验证,平均输出 3 个 → 约 9.9 ms/token。
并行赢了 —— 但只在这个接受率下成立。若并行草稿的接受率较低,平均输出降到 2 个:
14.9 > 13.5 —— 并行方式反而比串行慢。 草稿生成省下的 10.7 ms 被「每轮输出变少」抵消了。这就是「草稿快 ≠ 端到端快」的完整机制:分子变小,分母也变小,谁赢取决于两者谁降得多。
5. 草稿长度:边际收益递减,还有执行形状的阶跃#
边际判据。 长度为 的草稿平均输出 。从 增到 ,平均只多输出 个 token —— 接受概率连乘,越靠后的草稿 token 保留下来的概率越低。设为此多花 ,要让每 token 耗时下降,需要
即:为新增输出付出的平均时间,必须低于原来的平均每 token 耗时。
执行形状会造成阶跃。 若验证 4 或 5 个 token 都补齐为 8 行矩阵、复用预准备的 8 行计算图,多一个 token 的额外计算很少;待验证从 8 个增到 9 个就要补齐为 16 行、换计算图,计算量突然增加。 所以草稿长度不是连续可调的量 —— 选在分组边界内(如 4、8)比选在边界外(5、9)更划算。
实测印证。 Qwen3-8B / DFlash 的短提取任务、并发 1,完整耗时中位数:
| 配置 | 耗时中位数 |
|---|---|
| 普通 decode | 90.0 ms |
| K7(每轮 7 个草稿) | 30.6 ms |
| K15(每轮 15 个草稿) | 29.4 ms |
普通 → K7 省了约 59 ms,K7 → K15 只再省约 1.2 ms。 逐轮记录里还出现两种块长都只接受一个草稿 token 的轮次(K7 丢弃 6 个、K15 丢弃 14 个),输出长度相同,更大的验证块只增加了计算量。
动态调整的方向:对接受概率高、增加验证位置便宜的请求用更长的草稿;对前几个位置就经常不过、或会触发更大执行形状的请求缩短块长。定块长时还要给目标模型和草稿模型的 KV 留空间,再按常规方法决定接收多少请求。
6. 什么时候不该用:并发是决定性的#
低负载时,多行验证是在吃闲置算力,收益明显。高负载时,普通批处理已经把权重读取充分摊薄了,草稿生成和验证还要与其他请求争用加速器和内存 —— 收益下降甚至转负。
一句话记住:推测解码是延迟优化,不是吞吐优化。它的位置和
quantization.md里「weight-only 量化利好带宽受限的 decode」是同一类判断 —— 都在 decode 的带宽瓶颈上做文章,而 decode 恰好是 batch 小、算力空闲的那个阶段。
常见面试问法#
Q:推测解码是怎么保证输出不变的? A:靠接受—重采样这套组合。先按草稿分布 采样出 token ,以 的概率接受。直接接受的概率是 ,与目标 还差 ;拒绝后把这个正差额归一化再采一次,两条路径合起来正好还原 。所以草稿质量只影响速度,不影响正确性 —— 两符号算例最能说明:草稿恒为 A(接受率 1/4)和恒为 B(接受率 3/4),最终分布完全一样,但接受率差三倍。验证之后还要回滚状态:KV 有效长度截到已确定位置、分页器更新块引用、采样器与语法检查器恢复对应状态。
Q:草稿接受率只有 25%,还有加速吗? A:有,而且幅度不小。 RTX PRO 6000 / batch 1 / 2K 上下文下普通 decode 一轮 26.26 ms;验证 5 个位置的权重与 KV 读取量完全相同,只多约 65 GFLOP 的几行矩阵运算(约 0.2 ms),所以一轮验证仍约 26.26 ms。草稿恒为 A 时 ,平均每轮输出 个 → 加上草稿查询 0.1 ms 后约 19.8 ms/token,仍低于 26.26。两个原因:① 验证几乎不增加一轮时间;② 每轮至少确定一个 token,所以最坏情况退化成普通 decode,不会更差。上限条件是一轮总耗时 < 平均输出数 × 26.26 ms —— 留给草稿查询的预算 AAAA 约 8.7 ms、BBBB 约 53.9 ms。
Q:都用并行生成草稿不是更快吗? A:不一定,要看接受率。 并行一次前向生成整段草稿确实省时间:DFlash 网络前向一次约 3.57 ms,串行生成 4 个要 4 次前向约 14.28 ms。平均输出 3 个时,串行约 ms/token,并行约 ms/token,并行赢。但若并行的草稿接受率较低、平均输出降到 2 个,就变成 ms/token,反而慢于串行。机制是:并行让分子(每轮时间)变小,接受率下降让分母(每轮输出数)也变小 —— 谁赢取决于两者谁降得多。所以不能脱离接受率断言某种草稿方式更好。
Q:草稿是不是越长越好? A:不是,边际收益递减,而且有执行形状的阶跃。 从长度 增到 ,平均只多输出 个 token(接受概率连乘,越靠后越难留下);要让每 token 耗时下降需要 。执行形状还会造成不连续:验证 4 或 5 个 token 都补齐成 8 行矩阵、复用同一张计算图,但从 8 个增到 9 个就要补齐到 16 行、换计算图。实测上:Qwen3-8B/DFlash 短提取任务在并发 1 时,普通 decode / K7 / K15 的耗时中位数约 90.0 / 30.6 / 29.4 ms —— 普通到 K7 省了约 59 ms,K7 到 K15 只再省约 1.2 ms,而且出现过两种块长都只接受一个草稿的轮次(K7 丢 6 个、K15 丢 14 个),输出长度一样。动态策略是按接受率调:接受率高、加验证位置便宜的用长草稿;前几个位置就常挂、或会触发更大执行形状的缩短。
Q:什么场景不该上推测解码? A:高并发场景要谨慎。 低负载时多行验证利用的是闲置算力;高负载时普通批处理已经把权重读取充分摊薄,草稿生成和验证还要和别的请求争加速器与内存,收益下降甚至为负。另外:上下文查找型草稿要先摊准备成本 —— 建索引 2 秒、每 token 省 17.6 ms,要生成约 114 个 token 才回本,所以短输出请求不划算(256 token 的请求则是 6.72 s → 4.22 s,明显划算)。一句话:它是延迟优化,不是吞吐优化。
易错点#
| 易错说法 | 问题 | 应该说 |
|---|---|---|
| 「草稿不准就没意义」 | 漏了验证几乎免费的机制 | 接受率 25% 仍然加速(19.8 vs 26.26 ms/token)—— 验证的权重与 KV 读取量与普通 decode 相同,而每轮至少确定一个 token,最坏退化等于普通 decode |
| 「推测解码会损失精度」 | 机制理解错 | 采样路径的接受—重采样严格还原目标分布 ,是无损的;草稿只影响速度 |
| 「并行生成草稿一定更好」 | 忽略了接受率 | 并行更快,但若接受率下降、每轮输出变少,可能反而更慢(14.9 vs 13.5 ms/token) |
| 「草稿越长越好」 | 忽略了连乘衰减与执行形状阶跃 | 边际收益 递减;8→9 个 token 会补齐到 16 行换计算图。实测 K7→K15 只再省约 1.2 ms |
| 「先算每轮平均耗时再平均」 | 权重错了 | 用 (总时间 ÷ 总 token)。等权平均会让输出 1 个的轮次和输出 5 个的轮次同权,1.5/0.3 会错算成 0.9 而不是 0.5 ms |
| 「验证失败就整轮作废」 | 漏了保留规则 | 保留匹配的前缀并在分歧处输出目标模型的 token;只有分歧点之后的草稿才作废 |
| 「验证完直接继续生成」 | 漏了状态回滚 | 必须回滚 KV 有效长度、分页器块引用、采样器与语法检查器状态,否则被丢弃的草稿 KV 会污染后续计算 |
| 「高并发下也能拿到同样的加速」 | 场景判断错 | 高负载下批处理已摊薄权重读取,草稿与验证转而争用资源;它是延迟优化而非吞吐优化 |
| 把书里的 RTX PRO 6000 / DFlash 数字说成自己的实测 | 触碰仓库硬规矩 | 这些数字带实验条件(batch 1、2K 上下文、特定模型),引用即可,别讲成「我测过」 |
自测#
1. 解释推测解码为什么是无损的,并说明「草稿恒为 A」和「草稿恒为 B」在效率上差在哪、在正确性上为什么没有差别。
答案
无损的机制是「接受 + 重采样」的组合。 设目标分布 、草稿分布 :先从 采样出草稿 token ,以 的概率接受它。直接接受这条路径给出 的概率,距离目标 还差 (只取正差额,即 给多了的那部分)。拒绝后把这个正差额归一化、从中重新采样 —— 两条路径合起来,最终输出 的概率恰好是 。
两符号算例:。草稿恒为 A 时接受 A 的概率是 1/4,其余 3/4 拒绝并改出 B;草稿恒为 B 时接受概率是 3/4,其余改出 A。两者的最终分布完全一致(都是 ),所以正确性上没有差别 —— 草稿质量不影响输出,只影响速度。
效率上的差别来自接受率。 平均每轮输出 (): 时约 1.33 个 token, 时约 3.05 个。同样一轮约 26.36 ms,平均每 token 耗时约 19.8 ms vs 8.64 ms。但要注意:即使是接受率低的那个也比普通 decode(26.26 ms)快,因为验证几乎不增加一轮时间,且每轮至少确定一个 token。
还差一步:验证之后必须回滚状态 —— KV 有效长度截到已确定位置、分页器更新块引用、采样器与语法检查器恢复对应状态,否则被丢弃的草稿 KV 会污染后续计算。
2. 一个同事说「我们在低并发下测推测解码有 2 倍加速,所以打算全量开」。你要提醒他什么?
答案
核心提醒:推测解码是延迟优化,不是吞吐优化,高并发下收益会消失甚至转负。
原因:低负载时,多行验证消耗的是闲置算力,几乎白拿。高负载时,普通批处理已经通过增大 batch 把权重读取充分摊薄了 —— 这正是推测解码原本要补的那个缺口;而草稿生成和验证还要和其他请求争用加速器与内存,净收益下降甚至为负。
第二点:要按请求形态分。 ① 短输出请求不划算(上下文查找型草稿要先摊建索引成本:2 秒建索引、每 token 省 17.6 ms,要约 114 个 token 才回本);② 长输出请求划算(256 token 的请求:普通 6.72 s → 加 2 秒索引后用 BBBB 草稿约 4.22 s);③ 如果索引能提前建立并被多个请求共享,这笔开销能摊得更薄,那么门槛也降低。
第三点:块长要按接受率动态调,不能全量统一。 实测 Qwen3-8B/DFlash 短提取任务并发 1 时,普通 / K7 / K15 的耗时中位数约 90.0 / 30.6 / 29.4 ms —— 普通到 K7 省约 59 ms,K7 到 K15 只再省约 1.2 ms。无脑开大块长只增加计算量,还会因为执行形状补齐(8→9 个 token 要换 16 行计算图)产生阶跃成本。
最后:给一条可以量化的上线判据。 一轮总耗时必须小于「平均输出数 × 普通 decode 一轮时间」,扣除验证后留给草稿查询的预算 AAAA 约 8.7 ms、BBBB 约 53.9 ms。上线前应当按并发档位分别测 (不能对每轮取等权平均),只在高并发档仍成立的请求类型上开。
3. 为什么「先算每轮的平均每 token 耗时、再对轮次求平均」是错的?举一个数字例子。
答案
因为不同轮输出的 token 数不同,对轮次等权平均等于给「只输出 1 个 token 的轮次」和「输出 5 个 token 的轮次」相同的权重,而后者实际承担了 5 倍的产出。正确做法是总时间除以总 token 数:
数字例子:两轮各耗时 1.5 ms,分别输出 1 个和 5 个 token。总计 3 ms、6 个 token → 正确结果是 0.5 ms/token。若先算每轮的值(1.5 和 ms/token)再等权平均,得到 0.9 ms —— 几乎是正确答案的两倍。
这个错误在推测解码上特别容易犯,因为它的整个设计就是让每轮的输出数不相等(接受几个草稿决定本轮输出几个 token)。同样的口径问题在任何「每次产出数量不等」的系统里都存在(如批处理、投机执行、缓存命中与否的混合负载)。
4. 给你 26.26 ms 的普通 decode 一轮耗时、0.1 ms 的草稿查询开销、以及 的接受概率(草稿长 4)。请算出平均每 token 耗时,并求出「草稿查询最多能花多久还不亏」。
答案
① 平均每轮输出: 个 token。
② 一轮总耗时:验证仍约 26.26 ms(权重与 KV 读取量与普通 decode 相同,多出的几行矩阵运算按 62% 峰值算只需约 0.2 ms),加草稿查询 0.1 ms → 约 26.36 ms。
③ 平均每 token 耗时:,远低于普通 decode 的 26.26 ms/token。
④ 不亏的上限:条件是「一轮总耗时 < 平均输出数 × 普通 decode 一轮时间」,即
所以草稿查询只要不超过约 53.9 ms 就仍然划算。对照接受率低的情形(,):预算只有 。接受率低会把草稿预算压缩近 6 倍 —— 这就是为什么草稿来源的查询成本必须和接受率一起看。
⑤ 别忘了准备成本:若建索引要 2 秒,每 token 省 ms,需生成约 114 个 token 才回本。输出 256 token 的请求:普通约 6.72 s,先用 2 秒建索引再用该草稿约 4.22 s。
5. 推测解码和量化都在优化 decode,它们是什么关系?各自的适用条件有什么不同?
答案
相同点:都在「decode 阶段的显存带宽瓶颈」上做文章,因为 decode 每步只处理少量 token、却要把权重全部读一遍,是典型的带宽受限阶段(相比之下 prefill 是计算受限)。所以在 decode 上省字节、或者让一次读取服务更多 token,都能直接换成速度。
不同点在于它们缩短的是什么:
| 推测解码 | 量化(weight-only,如 W4A16) | |
|---|---|---|
| 改变的量 | 每轮输出的 token 数(分子分母同时变) | 每步要读的字节数 |
| 收益条件 | 一轮耗时 < 平均输出数 × 原一轮耗时;低并发更划算 | 瓶颈确实是带宽;精度损失可接受 |
| 主要代价 | 草稿模型/索引的额外开销与内存;高并发下争用资源 | 掉点(必须重跑评测);低精度 kernel 的硬件支持 |
| 场景冲突 | 高负载下收益下降(批处理已摊薄权重读取) | 与负载无关,是稳定收益 |
两者的收益会相互影响:量化减小了权重读取量,普通 decode 每步变快,推测解码要比较的基线也跟着变了;而量化让实例能装更多 KV、接收更多请求,batch 变大又进一步摊薄权重读取 —— 这同样削弱推测解码的相对收益。所以「组合优化」不能把各自单独测得的加速比相乘,必须在目标并发档位下一起测。
一句话:量化是降低每步成本,推测解码是让每步产出更多 —— 前者对负载不敏感,后者依赖低并发。