AI AI infra · 推理侧
第 1 章
AI infra · 推理侧

第 8 章 投机解码:一次前向,验证好几个字

第 7 章让每一步变便宜,这一章让步数变少。

逐字生成的根本限制是串行:要生成第五个字,必须先算完第四个字;要算第四个字,必须先有第三个字。每一步只产出一个 token,一步就是一次完整的前向,一次前向就要把全部权重从显存读一遍。7B 模型在 A100 上,这一步 6.9 毫秒,其中真正用于计算的只有 45 微秒。

百分之九十九的时间,计算单元在等数据。投机解码的全部想法,就是把这部分空闲的算力用起来:既然多算几个 token 几乎不花额外时间,那就一次多算几个,然后让目标模型一次性判断这些 token 对不对。

这一章的机制和公式来自 Leviathan、Kalman、Matias 三人在 2023 年的原始论文《Fast Inference from Transformers via Speculative Decoding》,Inference Engineering 第 5 章给的是结论和工程取舍。凡是我自己算出来的数,下面会标出来。

这一章用一个具体的例子把机制走一遍,再算收益,最后说清楚它在什么情况下无效。

先看一个具体的例子

假设上下文是"今天天气很好,",目标模型接下来想写"我们去公园散步"。

普通的逐字生成要走五步:先算"我",再算"们",再算"去",再算"公",再算"园"。每一步都是一次前向,五步就是五次读权重。

投机解码换一种走法。先让一个小模型(叫草稿模型)快速写出四个候选:"我"、"们"、"去"、"公"。这四个字是猜的,可能对,也可能错。

然后,把"今天天气很好,我们去公"这一整段——原来的上下文加四个候选——一次性送进目标模型。一次前向,目标模型对每个位置都给出了自己的判断:在"今天天气很好,"之后它想写什么,在"我"之后它想写什么,一路到最后。

接着逐个比对。它认可"我",认可"们",认可"去",但到第四个位置,它认为该写"园"而不是"公"。于是前三个候选被接受,第四个作废,用目标模型自己的"园"顶上。

这一轮下来,输出是"我、们、去、园"四个字,只花了一次目标模型的前向,外加草稿模型写四个字的成本。而普通做法要四次前向。

如果四个候选全对,这一轮能出五个字,因为最后一个位置还能再给一个。这就是常说的"N 加 1"。

为什么一次前向能验证多个 token

这里有一个容易被跳过、但不讲清楚就永远想不通的点:为什么一次前向能同时判断四个位置?

因为语言模型本来就是这么工作的。给它一段长度为 L 的序列,它输出的不是一个预测,而是 L 个预测:每个位置都给出"下一个 token 是什么"的分布。这是训练时定下的:模型要学会在每一个位置预测下一个词。

因果掩码保证了这件事是合法的。位置 i 的输出只能看到位置 i 之前的内容,看不到后面的。所以在"今天天气很好,我们去公"这一段里,第三个位置的输出,就是"如果前三个字是'我们去',下一个该是什么"——正好是我们要验证的那个判断。它没有偷看第四个候选"公",因此这个判断是可信的。

换句话说,验证不是把四个候选分别送进去算四遍,而是把它们排成一条序列,让每个位置顺便给出自己的答案。一次前向,四个答案,成本几乎等于一次前向。

为什么输出不会变

有人会担心:草稿模型猜的东西,会不会把最终输出带偏?

不会,前提是比对时用对方法。目标模型在每个位置给出的不只是一个 token,而是一个概率分布。比对时不是简单判断"草稿的 token 是不是概率最高的那个",而是按概率决定接受还是拒绝。

具体规则叫投机采样。设目标模型的分布是 p,草稿模型的分布是 q。草稿给出的 token 如果满足 q 不超过 p,就直接接受;如果 q 大于 p,就以一减去 p 除以 q 的概率拒绝它。被拒绝时,从修正后的分布里重新采样,修正的办法是把 p 减 q 的负值截掉再归一化。

原始论文的附录给了一个证明:按这套规则采出来的 token,分布和目标模型单独生成时完全一致。也就是说,投机解码不改变模型的行为,只改变生成的速度。这一点和量化不同——量化会改变输出,只是改变得可能很小。

代价是采样参数必须对齐。温度、top-k、top-p 在草稿和验证两个阶段要按同一套规则处理,否则分布就偏了。这也是为什么一些自定义采样逻辑很难接投机解码。

多出来的那一个 token 从哪来

"N 加 1"经常让人困惑:四个候选,怎么出来五个字?

回到那个序列。输入是"今天天气很好,我们去公",一共十个位置(假设原上下文六个 token)。模型对每个位置都输出一个预测,包括最后一个位置——在"公"之后,它预测下一个是什么。

验证时,前四个预测用来判断四个候选;第五个预测没有对应的候选,它就是白送的那个新 token。

所以数量关系是:N 个候选产生 N 加 1 个预测位置,最多接受 N 个候选,再加 1 个新 token。如果中途某个候选被拒,后面的预测位置就用不上了,这一轮的总产出变少。

收益怎么算

这一节的两个公式来自 Leviathan 等人的原始论文,Inference Engineering 只给了结论,没给推导。论文里把每轮产出写成一个闭式:一减去接受率的 γ 加一次方,除以一减去接受率。

γ 是草稿长度,α 是接受率。这个式子和前面逐项相加的结果一样:α 等于 0.8、γ 等于 4 时,得到 3.36 个 token。

真正决定加速比的是还要除以成本。论文把成本系数记为 c,含义是草稿模型跑一次的时间和目标模型跑一次的时间之比。实验中 c 始终小于 0.05,经常接近零;草稿模型比目标模型小两个数量级时,效果最好。这一点和 Inference Engineering 的说法略有出入,它写的是小一个数量级,可以理解为进一步缩小的收益递减,真正的最优点在更小的一侧。

按 γ 等于 4、c 等于 0.02 算,成本因子是 1.08。把几个接受率代进去。

接受率每轮产出加速比
0.51.941.79
0.62.312.13
0.72.772.57
0.83.363.11
0.94.103.79

论文自己的实测能对上这个趋势。T5-XXL 翻译任务上,草稿模型 T5-small,温度 1 时接受率 0.62,加速 2.6 倍;温度 0 时接受率 0.75,加速 3.4 倍。摘要任务上分别是 2.3 倍和 3.1 倍。这些是端到端的实测,不是理论值。

论文还给了一组接受率的经验范围:草稿模型比目标模型小两个数量级时,α 大致落在 0.5 到 0.9 之间。同一个任务里,采样温度越低,分布越尖,接受率越高——这也解释了为什么贪婪解码下投机更划算。

这张表要你看的是曲线的陡度。接受率从 0.5 提到 0.8,加速比从 1.79 涨到 3.11。而从 0.8 提到 0.9,只多涨了零点七。所以优化投机的重点,是把接受率从"能用"提到"好用",而不是无脑加长草稿。

草稿长度也有最优值。论文给了求最优 γ 的方法:对加速比公式求最大值。γ 变大,产出涨得越来越慢,成本线性上涨,所以存在一个拐点。论文实验里 γ 取 3 到 10 之间,最优点随接受率和成本系数变化,没有固定值。

草稿从哪来

四种做法,本质区别是"谁在猜"。

做法谁来猜优点代价
草稿模型一个十分之一大小的小模型上手最快显存多一套权重和缓存
Medusa目标模型上加几个解码头不用第二个模型接受率有限
EAGLE用目标模型的隐藏状态训练出的草稿头接受率高,可到八个要专门训练
N-gram从输入里查重复片段不用模型,代码补全很强只适合输出复制输入

草稿模型方案最好理解,也最容易试:找同家族的小模型,接上去就能跑。代价是显存,本来紧张的卡更紧张。

Medusa 在目标模型最后一层挂几个额外的输出头,每个头预测不同距离的未来 token。省了第二个模型,但每个头能学到的信息有限,接受率上不去。

EAGLE 是目前效果最好的通用方案。它让草稿头看目标模型内部的隐藏状态,而不只是看 token,信息更丰富,猜得更准。代价是要训练。

N-gram 是特例:在输入里找出现过的片段,直接拿来当草稿。代码补全里,输出大量重复输入中的标识符和结构,命中率很高;通用对话里基本无效。

四个常见误解

第一个误解是它会有损。前面说过,正确实现的投机解码是无损的,输出分布不变。如果实测质量掉了,问题多半在采样参数没对齐,或者草稿和验证的对话模板不一致。

第二个误解是草稿必须是小模型。Medusa 和 EAGLE 都不需要独立模型,N-gram 连模型都不需要。草稿的来源可以是头、是字典、是缓存。

第三个误解是批越大收益越大。正好相反。批一大,算力被占满,验证就不再免费,草稿成本却照付。投机解码和吞吐优化是竞争关系,不是叠加关系。

第四个误解是草稿越长越好。草稿变长,成本线性上涨,靠后的候选接受率还更低,期望收益可能下降。

什么时候不该用

四种情况。

批很大。算力已经被批占满,没有空闲算力可供投机。服务如果靠大批量撑吞吐,上投机通常是负优化。

接受率低。任务发散、模型自由创作,草稿猜不中,每轮只前进一两个 token,还多付一份草稿成本。

显存紧。草稿模型要占显存,挤掉的是并发。并发掉一半,单条快一倍,总吞吐反而降。

输出很短。投机改善的是出字速度,不改善首字延迟。用户只问一句话、模型只回十几个字时,省下的时间抵不过启动开销。

Inference Engineering 有一句话值得记住:投机解码只改善 TPS 和 ITL,不改善 TTFT。首字延迟由预填充决定,而投机发生在预填充之后。这句话应该写进选型文档,因为很多人上完投机才发现"首字还是慢"。

和其他手段的关系

和量化:两者作用在不同维度。量化降低每步成本,投机减少步数。量化之后带宽压力小了,算力富余更多,投机的空间反而变大;但如果草稿和目标量化方案不同,接受率会掉。两者叠加通常有效,前提是都做过质量验证。

和批处理:前面说过,两者互斥。低并发时投机划算,高并发时批处理划算。一个服务如果流量有明显峰谷,可以按负载切换策略,但这需要引擎支持动态开关。

和缓存:没有直接关系,但共享前缀能降低预填充成本,让首字更快;投机只能让后面的字更快。两者一起用,用户感知的是"开头快、后面也快"。

下一章讲长上下文。它同时压两笔账:缓存随长度线性涨,注意力计算随长度平方涨。前面所有手段在长上下文下都要重新算一遍。