Skip to content

约 8 分钟阅读

15. 投机解码:先猜后验,一步推进多个 token ​

上一篇让每一步 Decode 搬更少的数据。这一篇换一个思路:每一步的成本基本不变,但让一步确定不止一个 token。做法是先用一个便宜的方法猜出后面几个 token,再让大模型一次性检查这些猜测,猜对的全部收下。这就是投机解码(speculative decoding)。读完你应该能解释它为什么能加速、为什么不会改变输出结果,算出一组参数下的预期收益,并判断它在什么场景下反而会变慢。

闲着的算力能不能用起来 ​

第 6 篇算过:单个请求的 Decode,搬一次 16 GB 权重要 4.8 毫秒,真正的计算只要 0.016 毫秒。GPU 的计算单元几乎全程在等数据。

换个角度看:既然搬一次权重的成本是固定的,那么这一次前向计算处理 1 个 token 还是处理 5 个 token,耗时几乎一样。Prefill 就是这样,一次处理几千个 token。

问题在于 Decode 是串行的:第 2 个 token 要等第 1 个生成出来才知道是什么。但如果事先猜出接下来的 4 个 token,就可以把“当前 token + 4 个猜测”一起送进大模型,像 Prefill 一样一次算出这 5 个位置各自的下一个 token 分布,然后逐个检查猜得对不对。

一轮投机解码 ​

一轮投机解码分三步:

  1. 起草:用一个便宜的方法(小模型、查表或模型自带的预测头)连续猜出 γ 个 token,比如 γ = 4。
  2. 验证:把这 4 个猜测和当前 token 一起送进大模型(目标模型),一次前向得到 5 个位置的概率分布。
  3. 接受:从第一个猜测开始逐个检查。猜对了就收下,继续检查下一个;遇到第一个猜错的,就用目标模型在这个位置的结果替换它,后面的猜测全部作废,因为它们是在错误的前文之后猜出来的。

一轮投机解码:草稿模型猜出 4 个 token,目标模型一次验证,接受前 2 个,第 3 个替换为目标模型的结果,第 4 个作废

无论猜得多差,每一轮至少确定 1 个 token(就是那个替换的 token),和普通 Decode 一样;猜得全对时,还能多得到目标模型在最后一个位置给出的 1 个 token,一共前进 γ + 1 个。

下面这个实验可以改变第几个猜测出错,观察接受和作废的范围:

先提议,再验证确定性 greedy 教学模型
草稿候选候选候选候选
目标路径?????
已提交尚未提交

等待提出 4 个候选。

这演示的是 greedy 匹配,未模拟随机采样分布。精确随机推测采样还需要接受概率和拒绝后的修正分布,不能只比较两个模型的最高概率词。

为什么结果不会变 ​

投机解码最重要的性质是不改变输出的分布:开启它以后,生成结果的统计分布和只用目标模型完全一样。

贪心解码时这很直观:每个位置都是把草稿和目标模型选出的 token 比较,相同才接受,不同就用目标模型的。最终每个 token 都是目标模型会选的那个。

随机采样时,不能简单地“两边选中同一个词就收下”。经典做法是:设目标模型在这个位置给草稿 token x 的概率是 p(x),草稿模型给它的概率是 q(x),以 min(1, p(x) ÷ q(x)) 的概率接受;被拒绝时,从修正分布 max(0, p − q)(归一化后)里重新采样。可以证明,这样得到的 token 恰好服从目标分布 p。草稿越接近目标,接受率越高:平均接受率正好等于两个分布逐个 token 取较小概率后的总和,两个分布完全相同时为 1。

“不改变分布”指的是数学上的保证。实际 GPU 计算中,批的组成不同会带来微小的数值差异(第 7 篇),所以不能指望开启前后逐字相同。

收益有多大 ​

设每个猜测被接受的概率都是 a,一轮最多猜 γ 个,一轮平均前进的 token 数是:

text
E[前进 token 数] = 1 + a + a² + … + a^γ = (1 − a^(γ+1)) ÷ (1 − a)

a = 0.8、γ = 4 时,平均每轮前进约 3.36 个 token。注意这个级数的特点:第 k 个猜测只贡献 a^k。a = 0.8 时,第 1 个猜测平均带来 0.8 个 token,第 8 个只带来约 0.17 个。猜得越长,边际收益越小,草稿的成本却在线性增加。

每一轮的耗时是“起草时间 + 验证时间”,所以:

text
加速比 ≈ 普通 Decode 每步耗时 × 每轮平均前进数 ÷ 每轮耗时

用一组接近第 6 篇 H100 例子的教学数字:普通 Decode 每步 5 毫秒;小草稿模型起草 4 个 token 共 2 毫秒,目标模型验证一次 6 毫秒(比普通一步略长,因为多了几个位置的计算),每轮 8 毫秒。

  • 平均前进 3 个 token:每个 token 约 2.7 毫秒,比原来快约 1.9 倍。
  • 平均前进 1.5 个 token:每个 token 约 5.3 毫秒,反而比原来慢。

接受率是收益的关键,草稿的成本是第二关键。

草稿从哪里来 ​

方法怎样起草优点代价
小草稿模型同系列的小模型自回归地猜,如 Qwen3-8B 配 Qwen3-0.6B通用,无需训练额外权重和 KV 占显存;词表必须兼容
N-gram / 提示查找在输入和已生成的内容里找相同的片段,照抄它后面的 token不需要额外模型,几乎零成本只在有大量重复的任务中有效
Medusa在目标模型上加几个预测头,分别预测后面第 1、2、3……个 token不需要单独的模型需要训练预测头;常配合树形验证
EAGLE 系列用目标模型内部的特征,训练一个很小的草稿网络接受率高,是目前的主流方案之一草稿网络与特定目标模型绑定
MTP模型训练时就带有多 token 预测模块,推理时拿来起草原生支持,接受率高只有带这种结构的模型才能用

几个具体数字可以帮助建立直觉,都来自各自的论文或技术报告:

  • DeepSeek-V3 的 MTP 只额外预测 1 个 token,这个 token 的接受率在 85%—90%,解码速度提高到约 1.8 倍。
  • EAGLE-3 去掉了 EAGLE 早期版本的特征预测目标,改为融合模型低、中、高三层的特征。论文报告的最高加速约 6.5 倍,出现在 Vicuna-13B、代码生成任务、温度为 0 的单请求延迟测试中;论文另一组在 SGLang 中的测试(Llama-3.1-8B、单张 H100、对话任务)里,批大小为 64 时吞吐只提高约 1.38 倍。

这两个数字来自不同的模型、任务和指标,不能直接对比,但它们指向同一个现象:投机解码在单请求、低并发时收益最大,批变大以后收益明显缩小。原因下面讲。

N-gram 方法在一些场景里特别好用:让模型修改一段代码,输出的大部分内容和输入一样;根据资料回答问题时,大段引用原文。这时照抄输入中的片段,接受率会很高。

树形验证 ​

一次只猜一条路线,猜错一个就全部作废。另一种思路是猜一棵树:第一个位置猜 2 个候选,每个候选后面再猜 2 个……然后让目标模型一次验证整棵树,选出被接受的最长路径。

验证一棵树需要特殊的注意力遮罩:每个节点只能看到它在树上的祖先,看不到兄弟分支。Medusa、EAGLE-2 都用了树形验证,EAGLE-2 还会根据草稿的置信度动态决定树往哪里长。

树形验证提高了每轮的期望前进数,但验证的 token 更多,计算量更大。各引擎的支持也不同:截至 vLLM 0.32.0,EAGLE、MTP 等方法都按单条链起草,不做树形验证;SGLang 支持通过参数构建草稿树。具体以所用版本的文档为准。

什么时候会变慢 ​

投机解码的前提是“GPU 的算力闲着”。这个前提在高并发时不成立:

  • 批变大以后,算力不再闲置。 第 6 篇算过,上下文较短时,批大小到几百 Decode 就接近计算受限(长上下文时 KV 读取仍占主导,但权重部分的富余算力同样被批占用了)。此时验证 5 个位置的成本,接近处理 5 个普通 token 的成本,省不下多少。一个批里有 64 个请求、每个验证 5 个位置,等于一次要处理 320 个 token。
  • 草稿模型占显存。 草稿模型的权重和它自己的 KV Cache 都要占空间,挤占了目标模型能同时服务的请求数。
  • 候选 token 也要 KV。 验证时每个候选位置都要临时写入 KV,被拒绝的要回退。
  • 采样温度高时接受率下降。 温度越高,分布越平,草稿越难猜中。
  • 和其他功能组合时容易出状态问题。 被拒绝的 token 要从 KV Cache、结构化输出的语法状态、停止条件判断里一起撤销。这些组合需要单独测试。

所以投机解码最适合低并发、对单用户延迟敏感的场景,比如个人助手、代码补全。高吞吐的批量服务中收益有限,甚至为负。vLLM 也提供了按批大小动态调整猜测长度的选项,批大时少猜或不猜。

怎样开启与验证 ​

在 vLLM 中,投机解码通过一个 JSON 配置开启,比如使用 EAGLE-3:

bash
vllm serve Qwen/Qwen3-8B \
  --speculative-config '{"method": "eagle3", "model": "<与目标模型配套的 EAGLE-3 草稿权重>", "num_speculative_tokens": 3}'

method 还可以是 ngram、draft_model、mtp 等,每种方法有自己的参数。从较小的 num_speculative_tokens 开始调。

验证时关注:

  • 接受率和平均接受长度。 引擎的指标里有被接受的草稿 token 数和总草稿 token 数,用它们算出实际的接受率,而不是凭感觉。
  • 不同并发下的 TPOT 和吞吐。 在并发 1 和业务的典型并发下分别测试,确认高并发时没有变慢。
  • 质量不变。 理论上输出分布不变,但要用固定的质量集确认实现没有问题,尤其是开启了量化、结构化输出等其他功能时。

投机解码利用 Decode 时闲置的算力:便宜地猜出几个 token,让目标模型一次验证,接受到第一个错误为止,每轮至少前进 1 个、至多 γ+1 个 token。正确的接受规则保证输出分布不变。收益由接受率和草稿成本决定,猜得越长边际收益越小;高并发时算力不再闲置,收益会明显下降。

第四部分到这里结束。第五部分把规模放大:一张卡放不下模型、扛不住流量时,怎样把工作分给多张卡。下一篇讲分布式推理。

别急,先让缓存热一下。