投机解码(Speculative Decoding)是一种让 LLM 在不损失质量的前提下更快生成文本的技术。一个小草稿模型快速猜出接下来几个 token,大模型则用一次运行把这一批猜测全部校验完。
本文我们来讲清楚投机解码:它是什么、没有它时 LLM 生成为什么慢、小草稿模型与大目标模型如何配合更快地产出 token、保证质量不降的拒绝采样数学、2 到 3 倍加速的真实数字、它在生产环境中的用武之地,以及需要留意的取舍。
我们将覆盖以下内容:
- 投机解码解决了什么问题?
- 全局图景
- 为什么 LLM 生成很慢?
- 投机解码的核心思路
- 逐步走一遍
- 校验步骤
- 真实数字与加速比
- 用在哪些地方
- 权衡
- 快速总结
投机解码解决了什么问题?
当我们使用 ChatGPT、Claude 或 Gemini 这类内部跑着 LLM 的工具时,会注意到模型是一个词一个词地写。它不是一口气写完整段,而是写一个 token,再写下一个,再下一个。
这很慢。
模型越大越慢。每一个 token 都逼着整个模型做一次完整的前向传播。为了产出一个 token,几十亿参数要从显存里读一遍。然后下一个 token,同样的事再来一遍。一遍,又一遍。
投机解码就是一项让这个过程变快、同时既不改模型、也不损失任何质量的技术。
全局图景
在深入细节之前,先看全局。
投机解码用两个模型。一个又小又快但不那么聪明,一个又大又慢但非常聪明。小模型快速写出一份接下来几个 token 的草稿,大模型则一口气把这些 token 全部校验完。
用一句话说:
投机解码 = 小模型起草 token + 大模型并行校验这些 token。
如果草稿写得好,我们用一个「大模型步」的代价换来好几个 token。如果草稿写坏了,我们扔掉错的 token,从草稿正确的地方接着往下走。
输出结果和只用大模型时完全一致。没有任何质量损失。
为什么 LLM 生成很慢?
要理解投机解码,必须先理解普通的 LLM 生成为什么慢。
LLM 以自回归(autoregressive)方式生成文本。也就是说,模型产出一个 token,再把这个 token 作为输入,产出下一个。
假设我们让模型写出「I love teaching AI and Machine Learning.」,它会这样做:
步骤 1:输入 "I" -> 输出 "love" 步骤 2:输入 "I love" -> 输出 "teaching" 步骤 3:输入 "I love teaching" -> 输出 "AI" ... 以此类推每一步,整个大模型都要完整跑一次。如果模型有 700 亿参数,那么每产出一个 token,这 700 亿参数都要从显存里读一遍。
有意思的地方在这里。GPU 算得并不慢,它慢在搬数据。生成单个 token 的时间里,绝大部分花在把模型权重搬进计算单元上,而不是真正做数学运算。
这意味着 GPU 有大量空闲的算力,在等内存的时候闲置着。
看到问题了吗?我们为每一个 token 都付出了完整的内存代价,却几乎没用上 GPU 的算力。
投机解码填的就是这道缝隙。
投机解码的核心思路
投机解码背后的想法非常简单。
假设我们有两个模型:
一个叫草稿模型(draft model)的小模型。它很快,比如说 10 亿参数。
一个叫目标模型(target model)的大模型。它是聪明的那一个,比如说 700 亿参数。
计划是这样的:
草稿模型快速写出接下来 4 到 5 个 token,一个接一个。
目标模型把这些草稿 token 一起接过来,用单次前向传播全部检查完。
对每个草稿 token,目标模型给出判断:「是,我也会这么写」或者「不,这是错的」。
我们保留目标模型接受的全部 token,扔掉其余的,然后从那里继续。
整体流程大致如下:
[ 草稿模型 ] | 依次写出 4 个 token | v +--------------------------+ | 草稿:t1 t2 t3 t4 | +--------------------------+ | v [ 目标模型 ] | 对全部 4 个 token 做一次前向 | v +----------------------------------+ | 接受 t1,接受 t2,拒绝 t3, | | 修正 t3,丢弃 t4 | +----------------------------------+ | v 从已接受的前缀继续神奇之处在第二步。目标模型用一次前向同时检查了 4 到 5 个 token,用的是原本闲置的那部分 GPU 算力。加载模型权重的内存代价只付了一次,却拿回了多个 token。
打个现实中的比方。
假设有一位资深工程师,非常严谨也非常慢;还有一位初级工程师,手快但会犯些错。没有投机解码时,资深工程师自己一行一行写完所有代码。有了投机解码,初级工程师先快速写出 5 行草稿,资深工程师把这 5 行一起读完,批准正确的那些,改掉第一个错的,然后从那里继续。大多数情况下,初级工程师至少前几行是对的。于是资深工程师只需要审一批稿,而不用每行都从零开始写。
整个思路就是这些。
逐步走一遍
现在,我们把一轮投机解码完整走一遍,看看实践中是什么样子。假设目前已有的 prompt 是「I love teaching AI」,草稿长度为 4。
步骤 1:草稿模型拿着「I love teaching AI」,一个一个生成 4 个 token:
草稿 token:"and"、"Machine"、"Learning"、"today"草稿模型很小,所以这一步非常快。
步骤 2:目标模型把 prompt 加上这 4 个草稿 token 一起接过来。在一次前向传播中,目标模型给出每个位置的概率分布:
位置 1("AI" 之后): 目标预测 "and" -> 与草稿一致 位置 2("AI and" 之后): 目标预测 "Machine" -> 与草稿一致 位置 3("AI and Machine" 之后):目标预测 "Learning" -> 与草稿一致 位置 4("AI and Machine Learning" 之后):目标预测 "everyday" -> 与草稿的 "today" 不一致步骤 3:我们从前往后接受 token,直到遇到第一个被拒绝的。
- “and” 被接受
- “Machine” 被接受
- “Learning” 被接受
- “today” 被拒绝。目标模型想要的是 “everyday”。
步骤 4:我们把被拒绝的 token 换成目标模型的选择。于是我们接受「and Machine Learning everyday」。
步骤 5:我们带着「I love teaching AI and Machine Learning everyday」这个新 prompt,开始下一轮。
在看校验过程的可视化之前,先看看进入目标模型的全部输入——它们在一次并行前向中同时处理:
全部 8 个输入并行进入目标模型(一次前向): 位置: 1 2 3 4 5 6 7 8 输入: "I" "love" "teaching" "AI" "and" "Machine" "Learning" "today" \____________ prompt ____________/ \______ 4 个草稿 token ______/ | | v v +----------------------------------------------------+ | 目标模型(一次并行前向传播) | +----------------------------------------------------+ | v v v v v v v v 预测 预测 预测 预测 预测 预测 预测 预测 (每个位置各预测"下一个 token")8 个位置同时被处理。在每个位置上,目标模型都预测「下一个 token 应该是什么」。于是一次并行前向,我们就拿到了 8 个「下一 token」预测。我们把这些预测与 4 个草稿 token 比对,决定接受哪些——4 个全部同时校验,而不是一个一个来。
同一轮的可视化如下:
| 位置 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 草稿 | “and” | “Machine” | “Learning” | “today” |
| 目标 | “and” | “Machine” | “Learning” | “everyday” |
| 判定 | 通过 | 通过 | 通过 | 修正 |
发生了什么:
- 位置 1、2、3:接受(草稿与目标一致)
- 位置 4:拒绝,替换成目标的 “everyday”
本轮新增的 token:“and” + “Machine” + “Learning” + “everyday” = 4 个 token
目标模型前向次数:1 次
一轮下来,我们拿到的是 4 个 token 而不是 1 个,而目标模型只跑了一次。这就是加速的来源。
如果 4 个草稿 token 全部被接受,一轮就能拿到 5 个 token——4 个草稿 token 加上 1 个奖励 token。这个奖励是白送的:目标模型的单次前向在每个位置都产出概率分布,包括最后一个草稿 token 之后的那一个位置。所以不额外花代价,又多得一个 token。
完美运转。
校验步骤
现在自然会有个问题:目标模型凭什么决定接受还是拒绝一个草稿 token?
最简单的版本是:如果草稿 token 和目标模型本来会选的一样,就接受。
但这不是全部故事。LLM 是概率性的,它并不总是挑那个最可能的 token,而是从概率分布里采样。所以我们不能只检查「草稿 token 是否等于目标的最高选择」。我们需要一条更聪明的规则。
于是,拒绝采样(rejection sampling)登场了。规则是这样的:
对每个草稿 token,我们有两个概率:
q(x)= 草稿模型会选这个 token 的概率p(x)= 目标模型会选这个 token 的概率
接受规则如下:
如果 p(x) >= q(x): 总是接受 否则: 以 p(x) / q(x) 的概率接受用具体数字看一看。
例 1:草稿 token “Machine”——目标强烈同意。
q("Machine") = 0.6(草稿模型有 60% 的概率选 “Machine”)p("Machine") = 0.8(目标模型有 80% 的概率选 “Machine”)
因为p >= q(0.8 >= 0.6),我们总是接受 “Machine”。目标模型同意,而且比草稿模型更坚定,没有任何理由拒绝。
例 2:草稿 token “today”——目标不同意。
q("today") = 0.5(草稿模型有 50% 的概率选 “today”)p("today") = 0.1(目标模型只有 10% 的概率选 “today”)
因为p < q(0.1 < 0.5),我们不能总是接受,而是以如下概率接受:
p / q = 0.1 / 0.5 = 0.2也就是说,20% 的情况我们接受 “today”,80% 的情况拒绝它。我们靠抛一枚有偏的硬币来定。
直觉很简单。当目标模型同意草稿(p >= q)时,草稿没问题——接受。当目标模型不同意(p < q)时,草稿有风险——我们只在部分情况下接受,比例取决于目标模型还相信它多少。
如果 token 被拒了,我们不会就此作罢。我们会从一个「修正分布」里重新采样一个 token,这个分布填补了两个模型之间的差距。差距按max(0, p(x) - q(x))计算,然后归一化。
用数字看看。假设在那个被拒绝的位置上,有三个 token 可选:“everyday”、“today”、“tomorrow”。草稿与目标的概率如下:
| token | q(x) | p(x) |
|---|---|---|
| “everyday” | 0.2 | 0.5 |
| “today” | 0.5 | 0.1 |
| “tomorrow” | 0.3 | 0.4 |
草稿选的是 “today”,但它被接受规则拒了。现在我们对每个 token 计算修正分布max(0, p - q),再归一化:
"everyday":max(0, 0.5 - 0.2) = 0.3 "today": max(0, 0.1 - 0.5) = 0 "tomorrow":max(0, 0.4 - 0.3) = 0.1 合计 = 0.4 修正后("everyday") = 0.3 / 0.4 = 0.75 修正后("today") = 0 修正后("tomorrow") = 0.1 / 0.4 = 0.25所以替代 token 从这个修正分布中采样:75% 概率是 “everyday”,25% 概率是 “tomorrow”。概率质量向那些「目标模型比草稿模型更想要」的 token 转移。“today” 一点都分不到,因为目标根本不想要它——草稿把它抬得太高了。
这里的数学是精心设计的,使得投机解码的最终输出,与「只用目标模型逐 token 生成」具有完全相同的概率分布。这不是近似,而是数学上精确相等的取值。
注意:为了保证本文简短易读,我们有意跳过了完整的数学证明。该证明表明,无论算法走哪条路径,最终得到某个 tokenx的概率都恰好等于p(x)。如果好奇细节,可以在投机解码的原始论文里找到完整证明。
这是投机解码最重要的性质。我们拿到了速度上的好处,质量上一无所失。这就是投机解码的精妙之处。
不过,并非所有技术都会让这个分布原封不动。有些技术会有意做极小的偏移——LLM 水印(watermarking)就是靠这种微小的偏移,把一个信号藏进生成的文本里。
真实数字与加速比
把这些放进真实数字里,就能清楚看到我们省了多少时间。
假设:
- 目标模型每个 token 花 50 毫秒
- 草稿模型每个 token 花 5 毫秒
- 草稿长度取 4 个 token
没有投机解码时(基线):
每个 token 都要逼着目标模型完整跑一次。
1 个 token = 1 次目标前向 = 50 ms所以生成任意 N 个 token 需要N × 50 ms。
有投机解码时,一轮的代价是:
4 个草稿 token × 5 ms = 20 ms(起草) 1 次目标校验 = 50 ms(并行校验全部 4 个) 每轮合计 = 70 ms每轮代价固定为 70 毫秒。变化的是这一轮产出多少 token,取决于有多少草稿 token 被接受。
来看三种情况。
最好情况——4 个草稿 token 全部被接受。我们得到 4 个被接受的 token,外加目标模型白送 1 个奖励 token,也就是 70 毫秒拿到 5 个 token。
无投机解码:5 个 token × 50 ms = 250 ms 有投机解码: = 70 ms 加速比: 250 / 70 = 3.57 倍平均情况——4 个草稿 token 中有 3 个被接受。前 3 个草稿 token 被接受,第 4 个被拒并被目标模型的选择替换。于是我们得到 3 个接受 + 1 个修正 = 向前推进 4 个 token(正是上面逐步演示的那一轮)。
无投机解码:4 个 token × 50 ms = 200 ms 有投机解码: = 70 ms 加速比: 200 / 70 = 2.86 倍最差情况——0 个草稿 token 被接受。即便如此,我们仍然向前推进 1 个 token。当第一个草稿 token 就被拒时,我们在该位置从修正分布max(0, p - q)中采样一个替代 token,所以进展有保证,绝不会是零。但起草的时间白花了。
无投机解码:1 个 token × 50 ms = 50 ms 有投机解码: = 70 ms 加速比: 50 / 70 = 0.71 倍(更慢了!)所以投机解码是一场下注。草稿模型与目标模型意见一致时,我们大赚;不一致时,我们付一点小罚金。只要平均接受率足够高,这笔下注就划算——对同一模型家族来说,通常足够高。
再看看对完整回答的用户可见影响。
假设用户问了一个问题,目标模型需要生成 200 个 token 的回答。沿用上面平均情况——每轮向前推进 4 个 token(3 个草稿被接受,加上 1 个在被拒位置被修正的 token)。在真实负载中这个数字可能在 1 到 5 之间,这里取 4 仅作示例。算式如下:
无投机解码: 200 个 token × 50 ms = 10,000 ms = 10.0 秒 有投机解码: 每轮代价:4 个草稿 token × 5 ms + 1 次目标前向 × 50 ms = 70 ms 每轮 token 数(平均):4 所需轮数:200 / 4 = 50 轮 总时间:50 轮 × 70 ms = 3,500 ms = 3.5 秒 节省时间:6.5 秒(2.86 倍加速)同样的对比放到时间轴上:
时间(秒):0 1 2 3 4 5 6 7 8 9 10 无投机解码:|==================================================| 10.0s (200 次目标前向,每 token 一次) 有投机解码:|==================| 3.5s (50 轮,每轮 4 个 token) <-------- 节省 6.5 秒 -------->同样的回答,用户等 3.5 秒,而不是 10 秒。这就是用户能感知到的收益。
实践中,大多数负载的加速比在 2 到 3 倍之间。具体数字取决于:
- 小草稿模型与大目标模型有多契合
- 草稿有多长
- 输入风格与草稿模型见过的内容有多相似
草稿模型的行为越接近目标模型,接受率越高,加速比越大。
用在哪些地方
今天很多生产级 LLM 系统都在用投机解码。一些真实例子:
- vLLM、SGLang 和 TensorRT-LLM 都开箱支持投机解码,用于大模型服务。
- Llama 系列常常把一个小 Llama 和一个大 Llama 配对,以加速推理。
- DeepSeek-V3 在训练时加入了名为多头预测(Multi-Token Prediction,MTP)的辅助目标,即额外加一个轻量模块(一层 transformer 加一个输出头),让它学会预测未来位置上的下一个 token。推理时,这个模块就成了内置的起草器,因此不需要单独的草稿模型。DeepSeek 报告由此获得约 1.8 倍的生成加速。
这个想法之所以有这么多变体,是因为它是在不重新训练大模型的前提下,让 LLM 服务提速 2 到 3 倍的最便宜手段之一。
权衡
投机解码让生成变快,但也要付出代价:
- 我们需要第二个模型。草稿模型要占用额外的显存。对超大规模部署来说,这不是小事。
- 两个模型必须共享同一个 tokenizer(分词器)。如果它们的词表不同,草稿 token 就无法与目标模型期望的输入对齐。
- 接受率取决于草稿与目标有多一致。如果草稿模型与目标差得太远,大部分草稿 token 都会被拒,时间就白费了。
- 对非常短的输出,跑两个模型的开销可能把加速比抵消掉。
- 草稿长度必须调。太短拿不到多少加速,太长会浪费在被拒的 token 上。
- 在低批量(batch size)下加速效果最明显——那时解码受显存带宽约束,GPU 算力闲置。批量很大时,GPU 已被连续批处理(Continuous Batching)占满,投机解码赖以取用的空闲算力不复存在,收益缩小,某些情况下甚至会反转。
对大多数大模型服务负载来说,这笔取舍是值得的。用户看到更快的响应,GPU 也用得更有效率。
后来的方法干脆不再需要第二个模型,而是让大模型自己给自己起草。比如 Medusa 用额外的解码头(decoding heads)来完成起草。
EAGLE 更进一步,起草的不是模型带噪声的输出 token,而是它平滑的内部特征。
N-gram 投机(N-gram Speculation)走了另一条路,完全不需要模型来起草:它在 prompt 里查找最后几个 token,把它们之后出现过的内容复制过来当草稿,再交给大模型校验——当回答重复了输入内容时,这招非常好用。
快速总结
回顾一下我们学到的:
- 问题:LLM 一次只生成一个 token,每个 token 都逼着整个大模型跑一次。在加载内存的同时,GPU 的算力闲着。这很慢。
- 核心思路:用一个小而快的草稿模型写出几个 token,用大而慢的目标模型在一次并行前向里把它们全部校验完。
- 接受规则:每个草稿 token 依据一条叫拒绝采样的数学规则被接受或拒绝。这套数学保证最终输出与大模型独自生成的结果一致。
- 收益:2 到 3 倍加速,且无质量损失。
- 代价:草稿模型要额外占显存,且加速幅度取决于两个模型有多一致。
用一句话说:投机解码 = 小模型起草 token + 大模型并行校验这些 token。
投机解码是那种罕见的、能给我们速度却不牺牲质量的技术。它不改模型,不改输出,只是把 GPU 用得更聪明。
这就是为什么今天几乎每一个现代 LLM 服务系统都支持它。
常见问题
投机解码会降低输出质量吗?
不会。拒绝采样规则的设计目标就是让最终输出与「只用目标模型逐 token 生成」具有完全相同的概率分布。这不是近似。我们拿到速度上的好处,质量上一无所失。
投机解码会让生成变慢吗?
在某些情况下会。如果一个草稿 token 都没被接受,一轮的代价就比单次目标前向更高,比如 70 毫秒而不是 50 毫秒。在批量非常大时收益也可能缩小甚至反转,而非常短的输出因为两个模型的开销,可能根本享受不到好处。
草稿模型和目标模型需要同一个 tokenizer 吗?
需要。两个模型必须共享同一个 tokenizer。如果它们的词表不同,草稿 token 就无法与目标模型期望的输入对齐。
我们总是需要单独的草稿模型吗?
不需要。DeepSeek-V3 用多头预测(MTP),由一个轻量的额外模块充当内置起草器。Medusa 让大模型用额外的解码头给自己起草。N-gram 投机则完全不需要起草模型,它直接从 prompt 里复制草稿。