如何让大语言模型提速3倍

每个 agent 本质上都在跑一个循环。而循环工程(Loop Engineering)是在 agent 外面再套一层循环,让它能够自我评估输出,工作不合格时重试,反复出现同类错误时自动修正指令。今天这个角色由你来扮演:检查成果、诊断问题、再次给 agent 下指令。本文通过一个可运行的例子演示如何把这套流程自动化,并探讨哪些环节仍然离不开人的判断。
一个 700 亿参数的模型,每次推理都要从 GPU 显存中读取约 140 GB 的权重。在现代数据中心 GPU 上,这一传输过程可能耗时几十毫秒,而对权重进行的实际计算只占其中一小部分。也就是说,在生成一个 token 的整个耗时里,处理器的运算单元大部分时间是闲置的。
投机解码(Speculative Decoding)正是把这些闲置算力转化为输出的技术。它用一个规模小得多的第二模型提前生成若干候选 token,然后让大模型在一次前向传播中同时验证所有候选,而不是每个 token 各跑一遍,从而将生成速度提升 2-3 倍。更妙的是,产出的文本与大模型单独运行时在统计上完全一致。
本文将介绍投机解码的工作原理,内容涵盖:
为什么 token 生成必须一步步进行
生成过程中 GPU 的时间花在了哪里
如何在一次前向传播中验证多个候选 token
接受-拒绝循环,以及候选出错时的处理方式
为什么输出质量能保持完全一致
接受率及其随任务类型变化的原因
草稿模型的四种来源
投机解码何时不再有效

免责声明:本文内容基于各大来源公开的信息整理,文末附有参考链接。如发现疏漏,欢迎评论指正。
自回归解码
文本生成是逐个 token 进行的。
模型会将已有的全部输出作为输入,计算词汇表上的概率分布,选出下一个 token,将其追加到输入末尾,然后重复这一过程。每一次循环称为一次前向传递(forward pass),每次前向传递都会让输入数据穿过模型的所有层。
例如,第 50 个 token 的生成依赖于第 49 个 token 已经出现在输入中;同理,第 49 个 token 依赖于第 48 个 token,依此类推。同时计算多个 token 会破坏这种依赖链,导致输出缺乏连贯性。
因此,生成一段 500 token 的回复需要进行 500 次串行前向传递,每次都必须在上一次完成后才能开始。由于单次前向传递的耗时取决于模型规模,总的生成时间就等于输出 token 数量乘以单次前向传递的时间。
这也就解释了为什么无论答案是一句简短的事实性回复,还是一大段代码,响应速度都大致稳定——因为每个 token 的消耗基本相同。这也解释了为什么在相同的硬件上,更大的模型生成文本会更慢。
现代推理系统使用 KV cache,存储已处理 token 的注意力状态,使得每次新的前向传递只需计算最新位置的注意力。这大大减少了每次前向传递内部的工作量,尽管「每个 token 需要一次前向传递」这一限制依然存在。
显存带宽
既然前向传递的次数由我们需要生成的文本量决定,那么方程的另一半就落到了这里:一次前向传递实际上把时间花在哪里了?
简单来说,一次前向传递的大部分时间都花在搬运数据,而不是进行算术运算。 模型权重存储在 GPU 显存(VRAM)中。要用这些权重进行计算,GPU 必须先将它们传输到负责乘加运算的计算单元。对于一个以 16 位精度存储的 700 亿参数模型,每生成一个 token,需要传输的数据量约为 140 GB。 相比之下,对这 140 GB 数据执行的算术运算量其实很小。每个 token 只意味着一个窄向量穿过每个权重矩阵。GPU 从显存中加载一个巨大的矩阵,将其与该向量相乘,然后丢弃结果,再加载下一个矩阵。 结果是:在处理提示词(prompt)时,计算利用率约为 90% 到 95%;但在 token 生成阶段,利用率会骤降至 20% 到 40%。在每一步操作中,计算单元大部分时间处于空闲状态,而内存总线却接近满载。 这种差异源于每次读取权重所支持的工作量不同:
提示词处理阶段:权重只读取一次,但同时应用于数千个输入 token。
Token 生成阶段:权重同样被读取,但每次仅应用于一个 token。
这部分容量已经付出代价,却未被充分利用。
但这在实际中为何重要?
具有更高内存带宽的 GPU,对生成速度的提升幅度大于拥有更多原始计算算力的 GPU。

然而,闲置容量只有在有实际工作可填充时才有用。关键问题是:单次前向传播能否产出超过一个 token 的输出。
并行验证
单次前向传播可以同时评估多个位置。
Transformer 模型并行处理整个序列。当输入一个 token 序列时,模型在同一轮前向传播中,会在序列的每个位置都计算出下一个 token 的预测。例如,一个五 token 的输入会产生五个预测结果。
得益于因果掩码(causal masking),这些预测始终保持有效。在注意力机制中,位置 5 可以访问位置 1 到 5,位置 6 及之后被掩码遮住,而位置 3 只能访问位置 1 到 3。因此每个位置都恰好以它前面的 token 为条件,和我们逐个生成序列时的条件完全一致。
正是这个特性让 prompt 处理变得很快。2000 个 token 的 prompt 一次前向传播就能跑完,而不是跑 2000 次,因为全部 2000 个位置是同时计算的。
用在验证上,结果很直接。比如,把 4 个候选 token 追加到上下文后面,跑一次前向传播,就能得到模型在这 4 个位置上各自的预测。
这里需要理解的一点是:验证和生成本质上是同一个操作。目标模型在每个位置上做的工作完全相同,省下来的成本来自于把这份工作合并到一次前向传播的多个位置上,而不是多次前向传播各处理一个位置。

起草与验证
完整的循环就是把一个快速的候选 token 来源和上面说的批量评估结合起来。
这个方案用到两个模型:
我们要其产出结果的大模型叫目标模型(target model)
旁边跑着一个参数量只有它 1/10 到 1/20 的小模型,叫草稿模型(draft model)。它通常来自同一个模型系列,并使用相同的 tokenizer。
每一轮有三个步骤:
草稿模型通过自己的串行循环产出 K 个候选 token。这些前向传播虽然也是串行的,但每次的成本只是目标模型一次传播的零头。
把候选 token 追加到上下文,目标模型用一次前向传播评估这个加长后的序列。
从左到右处理,每个候选词与目标模型在该位置的预测进行比对。保留匹配到的候选词,一旦出现首个不匹配,就丢弃剩余的所有候选。
不匹配点在此过程中很关键:验证阶段已经计算出了目标模型在该位置的预测,因此该 token 可以直接使用。这样就能保留匹配前缀,并额外免费获得一个正确 token。
这一特性为最坏情况设定了上限。
在最坏情况下,四个候选词可能全部不匹配,但我们仍会保留目标模型在第一个位置生成的那个 token,这正好等于普通解码经过一次前向传播得到的结果。此时浪费的计算量仅限于草稿模型的计算以及验证阶段的一些额外开销,两者都来自原本可用的算力。在典型场景下,四个候选中有两个匹配,我们会保留这两个加上免费的那个 token,从而在一次目标模型前向传播中产出三个 token。
草稿长度 K 是一个可调参数,通常设为 3 到 5。更大的值能提高节省的上限,因为完全接受的八词草稿比完全接受的三词草稿能带来更多收益。然而,更大的值也会降低后续候选通过的概率,因为草稿模型在向前推进时是以自身未经验证的输出为条件的。超过某个阈值后,新增候选被丢弃的频率足够高,以至于额外开销超过了收益。

此时的问题是,生成的文本是否仍是目标模型本来会产出的文本。
无损保证
投机解码产生的文本具有与目标模型单独运行时相同的统计特性,这一性质由接受规则来保证。
在贪婪解码中,我们始终选取概率最高的 token,规则非常简单:当候选 token 与目标模型的最优选择一致时,该候选将被保留。
