本文目录
自回归模型生成第 $t+1$ 个 token 之前,必须先知道第 $t$ 个 token。生成 100 个 token,目标模型通常要串行执行约 100 轮;每轮都读取大量权重,却只为很少的新位置计算,尤其在低并发 decode 中容易受显存带宽和 kernel 启动开销限制。
推测解码(Speculative Decoding)并没有打破自回归依赖,而是把工作分成“低成本串行提出候选”和“高成本并行验证候选”:一个更快的 proposer 先猜若干 token,目标模型再用一次 forward 同时评估这些位置。若前几个候选被接受,一轮目标模型调用就能正式推进多个 token。
常见比喻是助理先写草稿、专家批量审稿,但还要补上一点:随机采样下,专家不能只把错误词改掉;必须使用修正分布,才能保证最终样本仍来自目标模型。本文从这一步开始推导。
为什么目标模型能一次验证多个位置
标准 decode 在上下文 $x_{<t}$ 上计算目标分布:
\[p(x_t\mid x_{<t})\]采样出 $x_t$ 后,下一轮才能得到 $p(x_{t+1}\mid x_{\le t})$。串行性来自“下一位置的上下文必须包含刚才生成的 token”。
现在让草稿模型 $q$ 先按自回归方式生成 $k$ 个候选:
\[\tilde{x}_1,\tilde{x}_2,\ldots,\tilde{x}_k\]这些候选已经组成一条完整假设路径。将整条路径接到原上下文后输入目标模型,causal attention 会在一次 forward 中同时算出:
p(· | context)
p(· | context, x̃1)
p(· | context, x̃1, x̃2)
...
p(· | context, x̃1, ..., x̃k)
矩阵计算可以并行处理这些位置。目标模型并不是“相信”候选,而是暂时把它们当作已知输入来评分;一旦某个位置被拒绝,其后的分布都建立在错误前缀上,必须丢弃。
一轮推测解码
设目标模型为 $p$,草稿模型为 $q$,一轮最多提出 $k$ 个 token。
1. 草稿阶段
$q$ 串行采样候选,并保留每个候选在草稿分布下的概率:
\[\tilde{x}_i\sim q_i,\qquad q_i=q(\cdot\mid x_{<t},\tilde{x}_{<i})\]2. 验证阶段
目标模型并行计算对应条件下的 $p_i$,以及候选全部接受后再下一个位置的分布 $p_{k+1}$。
3. 逐位置接受
对候选 $\tilde{x}_i$,生成均匀随机数 $u_i\sim U(0,1)$,当
\[u_i\le \min\left(1, \frac{p_i(\tilde{x}_i)}{q_i(\tilde{x}_i)} \right)\]时接受。若被接受,就继续检查下一位置。
4. 首次拒绝后的修正
若第 $i$ 个候选首次被拒绝,从正残差分布采样一个修正 token:
\[p'_i(x)= \frac{\max(0,p_i(x)-q_i(x))} {\sum_y\max(0,p_i(y)-q_i(y))}\]本轮提交前 $i-1$ 个已接受候选和这个修正 token,候选 $i+1\ldots k$ 全部作废。
5. 全部接受
若 $k$ 个候选全部通过,再从目标模型已经算出的 $p_{k+1}$ 采样一个额外 token。于是一次目标验证最多可以推进 $k+1$ 个 token。
用一个三词词表手算接受过程
假设当前位置只有 {A, B, C} 三个 token:
| token | 草稿分布 $q$ | 目标分布 $p$ |
|---|---|---|
| A | 0.50 | 0.40 |
| B | 0.30 | 0.50 |
| C | 0.20 | 0.10 |
如果草稿采到了 A,接受概率为:
\[\min(1,0.40/0.50)=0.8\]如果草稿采到 B,$p(B)>q(B)$,接受概率为 1。采到 C 的接受概率是 $0.10/0.20=0.5$。
拒绝发生时,正残差为:
max(0, p - q)
A: max(0, 0.40 - 0.50) = 0
B: max(0, 0.50 - 0.30) = 0.20
C: max(0, 0.10 - 0.20) = 0
归一化后只能选择 B。这个修正补上了草稿对 B 分配不足的那部分概率质量。
为什么最终分布仍等于目标模型
对任意 token $x$,通过“草稿采到并被接受”这条路径产生的概率质量是:
\[q(x)\min\left(1,\frac{p(x)}{q(x)}\right) =\min(q(x),p(x))\]而目标分布尚缺的质量为:
\[p(x)-\min(q(x),p(x)) =\max(0,p(x)-q(x))\]首次拒绝后从归一化的正残差采样,恰好补齐这部分质量。因此,接受路径与修正路径相加仍是 $p(x)$。
这也解释了两个看似合理但错误的简化:
- 拒绝后直接从完整 $p$ 采样,会重复分配接受路径已经覆盖的概率质量;
- 只比较草稿与目标的 argmax,不能保持有温度采样时的目标分布。
论文中的“无损”指分布在数学上保持一致。有限精度、batch shape 与非确定性 kernel 可能造成数值差异,因此工程上不保证相同随机种子必然逐 token 复现同一条采样序列。
Greedy 模式更简单
当 temperature 为 0 时,基线目标模型每步选择:
\[x_i^*=\arg\max_x p_i(x)\]验证可以直接比较候选是否等于目标 argmax:
draft: A B C D
target: A B X ?
✓ ✓ ✗
提交 A、B 和目标 token X,D 作废。若全部候选相同,再提交目标模型额外算出的下一个 argmax。正确实现应与不开推测解码的 greedy 序列一致,数值 tie 和底层非确定性需要单独处理。
加速来自减少串行轮数
推测解码没有减少目标模型参数,也没有跳过目标验证。它利用一个实际硬件现象:在低 batch 下,目标模型一次验证几个连续位置的耗时,可能没有比验证一个位置高出同样倍数。
设:
- 一轮草稿耗时为 $T_d(k)$;
- 目标批量验证耗时为 $T_v(k)$;
- 本轮正式推进 token 数的期望为 $E[L]$;
- 缓存提交、采样等开销为 $T_o$。
平均每个输出 token 的时间近似:
\[\operatorname{TPOT}_{spec} \approx\frac{T_d(k)+T_v(k)+T_o}{E[L]}\]基线 TPOT 为 $T_p(1)$。只有上式明显更小,推测解码才有实际收益。
acceptance rate 不能单独代表加速。假设接受率很高,但草稿模型几乎和目标模型一样慢,总耗时仍可能增加;反之,一个几乎零成本的 n-gram proposer 接受率不算高,也可能在重复文本中划算。
草稿长度并非越大越好
若每个位置独立接受概率粗略为 $\alpha$,连续接受至少 $j$ 个候选的概率约为 $\alpha^j$。候选越往后,能被走到的概率越低;与此同时,草稿生成、目标验证宽度和临时 KV 都在增加。
例如 $\alpha=0.7$:
第 1 个候选被接受的概率约 0.70
前 2 个都被接受的概率约 0.49
前 4 个都被接受的概率约 0.24
前 8 个都被接受的概率约 0.06
真实接受事件并不独立,这个例子只说明边际收益会下降。最佳 $k$ 取决于 prompt、领域、采样温度、batch 和硬件。动态 speculative decoding 会根据近期接受情况或系统负载调整候选长度,而不是为所有请求固定一个值。
Proposer 不一定是另一台小模型
独立 draft model
使用与目标模型词表兼容的小模型,概念最接近原始算法。优点是通用,缺点是额外权重、KV Cache 和调度;草稿模型过小又可能接受率不足。
EAGLE 与特征级草稿
EAGLE 系列利用目标模型的特征来预测后续 token/特征,试图用较小的草稿头获得更贴近目标的候选。它需要配套训练与 checkpoint,不能把任意模型名称填入配置就启用。
Multi-Token Prediction
部分目标模型在训练时就包含 MTP 模块,可从当前状态预测多个未来 token。部署引擎需要理解对应权重结构和验证路径。
Medusa/多头预测
在目标模型上增加若干预测头并构造候选树。目标验证可能不只是一条线性候选,而要处理树形 attention 与接受规则。
N-gram、prompt lookup 与 suffix
从当前 prompt、已生成文本或全局后缀结构中查找重复模式,不需要额外神经网络。代码补全、模板文本和重复短语更容易命中,开放式创作中的覆盖会低一些。
这些方法不能只按论文中的最大 speedup 排序。需要同时考虑 checkpoint 可得性、额外显存、候选质量、运行时支持和目标流量。
KV Cache 是实现正确性的核心
目标模型批量验证 $k$ 个候选时,会为这些临时位置产生 K/V。验证完成后,cache manager 必须像事务一样处理:
reserve k (+ extra) slots
│
▼
target forward writes tentative KV
│
▼
verify candidates
│
├─ accepted prefix ─► commit corresponding slots
└─ rejected suffix ─► free / overwrite slots
草稿模型也必须回到相同的正式位置。若目标接受了 2 个候选并生成 1 个修正 token,下一轮的上下文只能包含这 3 个正式 token;被拒候选之后的草稿 K/V 不能继续使用。
下列状态也要按正式提交长度推进:
- EOS、stop token 与最大长度;
- grammar/FSM 状态;
- repetition penalty 和 logits processor 历史;
- beam/sequence 状态;
- 流式输出缓冲区。
如果先把候选流式返回给用户,之后才发现拒绝,协议就需要“撤回 token”;多数文本 API 没有这种语义。因此系统通常只流式发送已经验证并提交的 token。
何时容易加速,何时可能变慢
推测解码更适合:
- 中低 QPS,目标 decode 明显受带宽限制;
- 单请求或小 batch 的交互式延迟优化;
- 草稿便宜且与目标分布接近;
- 文本有较强可预测性或重复结构;
- 目标验证多个 token 能充分利用 GPU。
收益可能很小甚至为负的情况包括:
- 高 QPS 下基线 continuous batch 已把 GPU 填满;
- 草稿模型占用额外 GPU 或通信成为瓶颈;
- 高温度、跨领域 prompt 导致接受率下降;
- 上下文极长,草稿和目标都要读取昂贵 KV;
- 与 pipeline parallel、grammar 或某量化 backend 的组合尚不成熟。
因此,官方文档将其主要定位为降低中低 QPS、memory-bound workload 的 inter-token latency,而不是保证所有服务提升总吞吐。
在 vLLM 中配置时应注意什么
配置 schema 会随版本演进,下面只展示当前文档中的结构,不应复制后长期不核对:
vllm serve <target-model> \
--speculative-config '{
"method": "draft_model",
"model": "<draft-model>",
"num_speculative_tokens": 5
}'
上线前先从引擎日志确认 proposer 类型、草稿 checkpoint、候选数和并行配置确实生效。还要查目标版本的 known incompatibility:引擎支持“speculative decoding”不等于与任意 PP、量化、prefix cache、structured output 和模型架构组合都经过验证。
一组能解释收益的实验
准备对话、代码、摘要和推理四类 prompt,在完全相同的目标模型与 sampling 参数下比较基线。分别测试 greedy 和 temperature sampling:
正确性
- Greedy:比较输出 token 序列与无推测基线;
- Sampling:在小词表/可控 logits 上做拒绝采样统计检验,再做任务质量分布比较;
- 边界:EOS、stop string、最大长度、grammar 和取消;
- 状态:人工制造首个、中间和全部候选通过的情况,检查 KV 长度。
性能
扫描不同 num_speculative_tokens 和请求率,记录:
- 每轮 draft token 数、accepted token 数和平均推进长度;
- proposer、target verify、sampler 各自耗时;
- TTFT、TPOT/ITL、E2E;
- output tokens/s 与 SLO goodput;
- 目标/草稿 KV Cache 和峰值显存;
- 高并发下与基线的交叉点。
最终结论应类似“在这类流量和低于某请求率时降低 TPOT”,而不是脱离条件写成固定倍数。原始论文报告的 2—3 倍或 2—2.5 倍是其模型、硬件和实现上的实验结果,不是所有现代 serving 系统的承诺。
小结
推测解码把昂贵目标模型的串行步骤换成便宜 proposer 的串行草稿和目标模型的并行验证。它能够保持 greedy 结果或目标采样分布,关键在于逐位置接受、首次拒绝后的正残差修正,以及对临时 KV 状态的正确提交与回滚。
它是否更快则是另一件事。平均接受长度必须足以覆盖草稿、宽验证、额外缓存和采样开销;高并发时,基线 batch 本来已经充分利用 GPU,收益还可能缩小。正确的评估顺序始终是:先验证分布与状态,再分解每个阶段耗时,最后在真实 QPS 下比较 SLO goodput。
参考资料
觉得有帮助?
分享给同样关注系统性能的人。