观文听傑

返回

上一篇用 Chunked Prefill(分块预填充)限制长 prompt 对 decode 的阻塞,但生成仍有一条硬约束:第 t+1t+1 个 token 要把第 tt 个 token 当作输入。普通自回归解码生成 4 个 token,目标大模型就要串行执行 4 次。

Speculative Decoding(投机解码)引入一个便宜的 draft model(草稿模型)连续猜若干 token,再让 target model(目标模型)用一次前向并行检查这些位置。猜对得多,就用一次昂贵前向推进多个 token;猜错时通过接受—拒绝修正,仍保持目标模型的采样分布。

01 瓶颈为什么是串行轮数,而不只是 FLOPs?#

设当前前缀为 x1:tx_{1:t}。普通解码依次执行:

p1=p(x1:t),xt+1p1,p_1=p(\cdot\mid x_{1:t}),\quad x_{t+1}\sim p_1, p2=p(x1:t+1),xt+2p2.p_2=p(\cdot\mid x_{1:t+1}),\quad x_{t+2}\sim p_2.

第二个分布必须等第一个采样结果产生。即便 GPU 在单轮 decode 中没有吃满,也不能直接把未知的未来 token 放进同一批。

普通 decode: Target -> 1 token -> Target -> 1 token -> Target -> 1 token
投机 decode: Draft  -> a,b,c -> Target 一次验证 -> 接受若干 token
text

目标不是减少所有计算:系统额外运行了 draft。真正要换取的是更少的 target 串行调用,以及更高的单次验证算术强度。

02 Draft 与 Target 各自产生什么?#

给定 batch size NN、草稿长度 γ\gamma、词表大小 VV

  1. draft 自回归提出 γ\gamma 个 token,draft_ids[N,γ]
  2. 同时保存每一步的草稿分布 qiq_idraft_probs[N,γ,V]
  3. target 把原前缀与草稿 token 一起前向,得到验证分布 pip_i 以及一个 bonus 位置,target_probs[N,γ+1,V]
  4. verifier(验证器)逐位置决定接受、拒绝与替换。
flowchart LR
  C[已确认前缀 x1:t] --> D[Draft 串行提出<br/>y1,y2,y3]
  C --> T[Target 一次前向]
  D --> T
  T --> V[逐位置验证]
  V -->|接受前缀| A[提交 y1..yk]
  V -->|首次拒绝| R[采样修正 token<br/>丢弃其后草稿]
  V -->|全部接受| B[再提交 1 个 bonus token]
mermaid

“一次验证”并不代表 verifier 可以乱序接受。位置 2 只有在位置 1 已接受后才仍处于 target 评估过的那个前缀上;首次拒绝之后,后面的草稿条件链已经失效。

03 Greedy 模式先看最直观版本#

当 temperature 为 0,可把流程理解为:

draft 提议:       [A, B, C, D]
target 对各位置:  [A, B, X, ...]
提交:             [A, B, X]
text

前两个 token 与 target 的 argmax 相同,可以接受;第三个不同,就提交 target 的 X 并丢弃 C,D。一次 target 前向推进了 3 个 token,而不是 1 个。

若四个草稿全部匹配,还可从 target 的第 5 个位置提交 bonus token,因此一次验证最多推进 γ+1\gamma+1 个 token。

04 精确采样为何按概率比接受?#

在某一步,draft 从 q(x)q(x) 抽到候选 yy,target 希望最终服从 p(x)p(x)。接受概率是

α(y)=min(1,p(y)q(y)).\alpha(y)=\min\left(1,\frac{p(y)}{q(y)}\right).

p(y)q(y)p(y)\ge q(y),draft 对 yy 没有“提议过量”,直接接受;若 q(y)>p(y)q(y)>p(y),只以 p(y)/q(y)p(y)/q(y) 接受,削掉多出来的概率质量。

拒绝后不能直接再从 pp 采样,否则被拒候选的质量会重复计算。修正分布为

p(x)=max(0,p(x)q(x))zmax(0,p(z)q(z)).p'(x)=\frac{\max(0,p(x)-q(x))}{\sum_z\max(0,p(z)-q(z))}.

接受分支贡献 min(p(x),q(x))\min(p(x),q(x)),拒绝分支再补上 (p(x)q(x))+(p(x)-q(x))_+;两者相加恰好回到 p(x)p(x)。这就是“无损”指向的数学含义:在相同采样变换与数值实现假设下,输出分布不因 draft 改变。

05 用两个词手算一次接受与修正#

词表只有 A/B。第二个位置的分布为:

tokendraft qqtarget ppp/qp/q
A0.20.63.0
B0.80.40.5

若 draft 提议 B,接受概率为 0.5。抽到均匀随机数 u=0.7u=0.7 时拒绝。残差为:

(pq)+=(0.4,0),(p-q)_+=(0.4,0),

归一化后必然选择 A。于是该轮在首次拒绝处提交 A,后续草稿全部作废。

从总体概率看:B 被提议的概率 0.8,乘接受率 0.5,最终质量为 0.4;其余 0.6 全由修正分支给 A,正好得到 target 的 (0.6,0.4)

06 一轮算法的透明伪代码#

实际实现还要在同一位置应用完全一致的 temperature、top-k、top-p、禁用 token、重复惩罚等 logits transforms。若 ppqq 经过不同处理,精确性证明不再成立。

07 一个可单元测试的 PyTorch 验证器#

下面只实现“一条序列的一轮验证”。输入是已归一化概率,不依赖模型封装。

可测试例输入:draft_ids=[B]draft_probs=[[0.2,0.8]]target_probs=[[0.6,0.4],[0.3,0.7]]uniforms=[0.7]。输出应为 [A]。再用大量随机样本做频率检验,确认结果逼近 (0.6,0.4)

生产代码还需处理 logits 为 -inf、零概率、浮点舍入、批内不同拒绝位置与 EOS;教学代码的 clamp_min 只防除零,不能替代这些策略。

08 Target 为什么能并行验证未来位置?#

draft 已经把候选 y1..yγ 填进输入,target 可像 prefill 一样对这段做 causal forward。位置 ii 的 hidden state 只看确认前缀和 y1..y{i-1},正好给出验证 yi 所需的条件分布。

对每层,若历史 KV 为 [N,Hkv,L,Dh],验证块新产生 KV 为 [N,Hkv,γ,Dh];验证 logits 为 [N,γ+1,V]。最后一维词表通常不必全部长期保存,kernel 可融合采样或只保留所需统计量。

被拒位置之后的候选 KV 必须回滚或不提交:它们依赖已被替换的错误前缀。常见做法是先暂存 speculative slots,再按每条请求的接受长度更新有效 context length。

09 速度取决于接受长度,不只取决于 Draft 大小#

令一次验证接受的草稿 token 数为 A[0,γ]A\in[0,\gamma],本轮推进量为:

S=A+1,S=A+1,

其中 +1 是首次拒绝的修正 token,或全部接受后的 bonus token。平均每次 target 调用推进 E[S]\mathbb E[S],但墙钟收益还要扣掉 draft、验证更长序列、采样与状态管理的成本。

近似可写为:

speedupE[S]Ttarget,1Tdraft,γ+Ttarget,γ+Tverify.\text{speedup}\approx \frac{\mathbb E[S]\,T_{target,1}} {T_{draft,\gamma}+T_{target,\gamma}+T_{verify}}.

它不是可直接代入的硬件公式,却说明三个杠杆:draft 要便宜、与 target 足够一致、验证 γ\gamma 个位置不能比单 token target 慢太多。

10 Draft 长度为什么不是越大越好?#

若每个位置条件接受率粗略为 aa,到第 kk 个候选仍全部接受的概率约为 aka^k。低一致性时,长草稿的大部分尾部都会被计算后丢弃。

情况增大 γ\gamma 的可能结果
aa 高、低并发、target memory-bound一次推进更多 token,ITL 下降
aa被拒后的尾部浪费快速增加
高并发验证块抢占 batch token 预算,吞吐可能下降
draft 很大draft 本身接近一次 target 成本,收益消失

因此需要按 prompt 类型、采样参数、batch size 与负载动态测量,不能只在单请求 greedy 上宣布加速比。

11 当前 vLLM 中怎样落地与核实?#

当前官方文档把投机方法分为 draft model、EAGLE、Multi-Token Prediction(多 token 预测,MTP)、n-gram/suffix 等。配置入口为 --speculative-config,具体字段与模型兼容性会随版本演进。

vllm serve your-org/target-model \
  --speculative-config \
  '{"method":"draft_model","model":"your-org/draft-model","num_speculative_tokens":4}'
bash

上线前必须用所安装版本的 vllm serve --help 与官方版本页核对方法名;不要从旧文章复制参数。vLLM 文档也明确提醒:投机解码主要针对中低 QPS、decode 偏显存带宽受限的负载,并不保证所有 prompt 与采样设置都降低 ITL。

12 应记录哪些指标?#

至少同时记录:

  • proposal length:每轮实际提议数;
  • accepted tokens 与 acceptance rate;
  • mean accepted length / target step;
  • draft、target verify、sampler 各自耗时;
  • TTFT、ITL/TPOT、E2E 的 p50/p95/p99;
  • output tokens/s 与 requests/s;
  • batch size、prompt/output 长度、采样参数和拒绝位置分布。

只报 acceptance rate 会误导:接受率 80% 但 draft 占了一半时间,可能仍更慢;只报单请求 ITL 也会掩盖高并发吞吐退化。

13 正确性怎样验证?#

确定性模式可做逐 token 对账:关闭投机与开启投机,在相同模型、tokenizer、greedy 设置下输出 token IDs 必须一致。

随机模式不能要求相同 seed 后逐 token 完全一致,因为随机数消耗顺序可能不同。应做分布检验:构造极小词表或可枚举模型,运行大量样本,比较首 token 与短序列频率、总变差距离或卡方统计,并覆盖第一位拒绝、中间拒绝、全部接受、EOS 和零概率边界。

还要断言:

committed_length = accepted_draft_count + 1
rejected_suffix KV 不可见
每个请求独立维护 RNG 与接受位置
停止条件只检查已提交 token
text

14 常见错误与最短调试路径#

症状常见原因最短检查
Greedy 输出不同position、KV 回滚或 bonus 对齐错γ=2 下逐位置比较 target logits
随机输出分布漂移拒绝后仍从原 pp 采样枚举二词表,检查残差分布
接受率高但更慢draft/验证开销超过串行轮数收益分段计时并与相同 batch 基线比较
EOS 后仍提交 token对草稿尾部一次性应用停止规则只对已提交前缀逐 token 检查
batch 内串线每条请求拒绝位置与 KV 长度未独立构造一条全接受、一条首位拒绝的 batch
top-p 下无法复现p,qp,q 使用了不同 logits transforms记录过滤后的支持集和概率和

15 它与相近方法的边界#

Chunked prefill 是把一个长输入拆到多轮,保护 decode 延迟;speculative decoding 是让一次 target decode 尝试推进多个输出。Prefix Caching(前缀缓存)复用其他请求已计算的 prompt KV,只减少重复 prefill。

EAGLE、MTP 与独立小模型都可充当 proposer(提议器),差别在如何低成本预测未来 token,不改变“提出—验证—提交”的核心契约。Medusa 等多头方案可形成候选树;候选越多,target 验证与调度形状也越复杂。

16 失败场景#

  • 代码生成、罕见领域或高温采样使 draft 与 target 分歧大;
  • target 很小,额外调度和 sampler 开销不可忽略;
  • 高 QPS 已通过大 continuous batch 吃满 GPU,验证块反而挤压吞吐;
  • draft 占用额外权重显存,使 KV 容量下降并增加抢占;
  • tokenizer、词表或 logits processor 不兼容,无法定义逐 token 概率比;
  • 多租户服务没有隔离草稿状态、RNG 与 metrics。

17 今天真正需要记住什么?#

  1. 投机解码减少的是昂贵 target 的串行调用次数,不保证减少总 FLOPs。
  2. target 可以并行验证草稿块,但只能提交首次拒绝之前的连续前缀。
  3. 随机采样必须用 min(1,p/q)\min(1,p/q) 接受率和 (pq)+(p-q)_+ 残差修正,才能保持目标分布。
  4. 性能由接受长度、draft 成本、验证成本和并发负载共同决定,必须同时看延迟、吞吐与正确性。

18 思考题与小练习#

  1. 词表为 A/B/Cq=(0.5,0.3,0.2)q=(0.5,0.3,0.2)p=(0.2,0.5,0.3)p=(0.2,0.5,0.3)。draft 提议 A 时接受率是多少?若拒绝,写出归一化残差分布。
  2. γ=4\gamma=4,一轮在第 3 个候选拒绝。哪些 token 与 KV slots 可以提交,下一轮 context length 增加多少?
  3. verify_one 增加 EOS 处理和确定性 RNG;用 100,000 次抽样验证输出频率逼近 target 分布。

相关工作#

  1. Leviathan et al., Fast Inference from Transformers via Speculative Decoding,给出保持目标分布的经典 speculative sampling 算法。
  2. Chen et al., Accelerating Large Language Model Decoding with Speculative Sampling,独立系统化研究草稿—验证加速。
  3. Cai et al., Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads,用多解码头构造候选树。
  4. Li et al., EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty,从特征层改进草稿质量。
  5. vLLM, Speculative Decoding 官方文档,列出当前支持方法、配置入口与适用负载。

19 下一篇预告#

投机解码减少一条请求的 target decode 轮数,但大量请求若共享同一段 system prompt 或长文档,仍会反复做相同 prefill。下一篇将拆解 Automatic Prefix Caching:怎样用链式块哈希找到最长可复用前缀,又如何避免错误共享与多租户侧信道。

生成 4 个 Token 为何不一定要跑 4 次大模型?Speculative Decoding 的提议、验证与接受
https://zwjcode.cn/blog/speculative-decoding-draft-verify-acceptance
作者
发布于 2026年9月19日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。