☰
推测解码实战:大模型推理加速300%的原理与踩坑指南
2026/10/14 6:58:32 网站建设 项目流程

在 LLM 推理这条路上,我踩过的最大一个坑就是“看起来很快,用起来很慢”。模型推理的延迟一直是上线前最头疼的问题,尤其是自回归生成,一个 token 一个 token 往外蹦,想快都快不起来。直到我认真把 Speculative Decoding(推测解码)这套思路吃透并在生产环境里落地,才真正体会到什么叫“换个思路,速度直接翻倍”——实测下来,在特定场景下 300% 的加速是实打实的。这篇文章不聊虚的,直接从原理到选型、从参数到踩坑,把我自己在落地推测解码过程中的完整经验整理出来,希望能帮你少走弯路。

这套技术最适合两类人:一是做大模型服务化、推理优化的工程师,二是想在自己应用里降低 Token 延迟的算法同学。只要你手里有一个能跑的训练/推理框架,哪怕只是调 API,理解推测解码的思想也能帮你在选型时更有底气。我会尽量用大白话讲清楚背后原理,再给出可以直接抄作业的实践方案。

1. 先搞清楚慢在哪:自回归解码的串行之痛

1.1 一个 token 一个 forward,谁也逃不掉

要理解推测解码为什么能提速,得先明白传统解码为什么慢。

大模型生成文本的方式是自回归(autoregressive):每生成一个 token,都要把当前已生成的全部 token 作为输入,跑一次完整的前向计算(forward),得到下一个 token 的概率分布,然后采样出 token,再把这个 token 拼回输入序列,继续下一轮 forward。也就是说,生成 N 个 token 就需要 N 次串行的 forward,每一步都依赖上一步的输出,完全没法并行。

这个过程的耗时主要由两部分决定:一是单次 forward 的计算量,二是显存带宽——也就是把模型参数从显存搬到计算单元的速度。对于大模型来说,显存带宽往往是更紧的约束。为什么?因为推理时 batch size 通常很小,计算单元(比如 GPU 的张量核心)大部分时间都在等数据搬运,而不是真正在算。这就是所谓的“memory-bound”问题。

我打个比方:自回归解码就像一辆只能坐一个人的小推车运货,每次只运一箱,还非要等这箱到了才能回去搬下一箱。明明仓库里堆着几百箱货,但路只有一条,车只有一辆,速度就卡死在“往返搬运”上。

1.2 为什么显存带宽成了第一瓶颈

很多人对“推理慢”有个误解,觉得是计算量太大。其实对于 7B、13B 甚至 70B 的模型,单次生成一个 token 的计算量并不高,瓶颈往往在带宽。举个例子,一个 13B 参数的模型,参数大约是 26GB(按 FP16 算),GPU 的显存带宽假设是 2TB/s,那么光是把参数从显存读一遍就需要大约 13ms。也就是说,无论计算多快,每生成一个 token 至少要 13ms。如果生成 1000 个 token,光等待参数搬运就超过 13 秒。

这个时间还只是参数读取,没算上 KV Cache 的读写、中间激活、采样开销等。所以在自回归解码下,单 token 延迟的“地板”被显存带宽死死钉住,怎么优化算子都很难突破。这也是为什么大家都在想办法减少 forward 次数,而不是单纯把单次 forward 做得更快。

那推测解码的核心思路就很清晰了:能不能一次 forward 验证多个 token,而不是只生成一个?如果能,哪怕每轮 forward 的耗时不变,生成同样数量的 token 所需的 forward 次数也大幅下降,等效的单 token 延迟自然就降下来了。

2. 推测解码的核心原理:草稿模型与并行验证

2.1 两阶段流水:小模型猜,大模型判

推测解码的思路其实非常朴素,就四个字:先猜后验。整个流程分成两个角色:

  • 草稿模型(Draft Model):一个非常小、速度很快的模型,用它自回归地“猜”出接下来 k 个 token。
  • 目标模型(Target Model):也就是你真正要部署的大模型,它不去逐个生成,而是拿到草稿模型猜出来的 k 个 token,一次性做并行验证。

具体流程是这样的:草稿模型先快速生成 k 个候选 token(比如 k=5),把这 5 个 token 接到已有序列后面,组成一个长度为“已有序列+k”的输入,目标模型对这个完整序列做一次 forward,一次性算出每个位置的预测分布。接下来关键的一步来了:目标模型会用拒绝采样(Rejection Sampling)逐一检查草稿模型的猜测。

怎么检查?从头开始,对第一个猜测 token,看目标模型给它的概率是否足够高。如果目标模型在该位置预测的分布里,这个 token 的概率排名靠前、足够“可信”,就接受它,继续检查下一个;如果某个 token 被目标模型给出较低的概率,就拒绝它,并从目标模型的分布里重新采样一个 token 作为替代,然后本轮结束。

这里有一个我最初理解错的地方:猜测 token 不是“猜对了就接受,猜错了就拒绝”这么简单。标准做法是“概率比较”——把草稿 token 在目标模型分布中的概率,和草稿模型自己给它的概率做比较。如果目标模型的概率更大,就接受;如果更小,就有一定概率拒绝。这个机制保证了最终采样分布和只使用目标模型时完全一致。

这套流程的收益在哪里?传统方法生成 k 个 token 需要 k 次大模型 forward,而推测解码只需要 1 次大模型 forward 外加 k 次小模型 forward。小模型 forward 速度快得多,所以总耗时大幅下降。

这里我个人的体会是:小模型 forward 也必须走显存带宽,但因为它参数少,带宽压力小。比如 100M 参数的草稿模型,参数只占 200MB,读一遍只需 0.1ms,比 13B 大模型的 13ms 快了两个数量级。

2.2 拒绝采样如何保证“结果不变”

这是推测解码最精巧的地方,也是很多人不敢用它上线的原因——他们担心加速会牺牲输出质量。实际上,标准推测解码在数学上保证了输出分布和原始大模型完全一致,不是“近似一致”,而是严格一致。前提是草稿模型的词汇表和目标模型一致,且拒绝采样的实现正确。

拒绝采样的逻辑我尽量不堆公式,用一个类比说明。假设你想从目标分布里采样,但目标分布太贵;你拿一个便宜分布(草稿分布)来采样,然后按一定规则决定留还是扔。

  • 如果草稿模型给的 token 在目标模型那里的概率更高,说明这个 token 在真分布里也很受青睐,直接接受;
  • 如果草稿模型给的 token 在目标模型那里概率较低,说明草稿模型“高估”了它,此时以概率 p 拒绝并重新从目标分布采样。

看上去像是“猜错了就换”,但它本质上是一个 Metropolis-Hastings 类的采样步骤,保证了最终留下来的 token 的分布恰好等于目标模型的分布。所以,无论草稿模型猜得多差,输出分布都不会偏离目标模型,最多是接受率低、加速效果变差而已。

这一点非常重要,也是我在向团队讲这个方案时反复强调的:推测解码不是“近似加速”,而是“无损加速”。理解这一点后,把它引入生产环境才没有心理负担。

2.3 加速比公式与接受率的关系

搞清楚接受率之前,先定义几个关键变量:

  • γ(gamma):草稿模型每轮猜测的 token 数量,也叫推测长度。
  • α:接受率,即草稿模型猜出的 token 被目标模型通过的概率(实际是平均每个位置通过的概率)。
  • c:草稿模型单次 forward 耗时与目标模型单次 forward 耗时的比值,通常远小于 1,比如 0.01~0.1。

每轮流程中,草稿模型需要串行跑 γ 次 forward,然后目标模型跑 1 次并行验证。如果接受率是 α,那么平均每轮能接受的 token 数大约是 (1 + α + α² + ... + α^γ) 的均值,近似为 (1 - α^(γ+1)) / (1 - α)。总耗时为 γ × c × T + T,其中 T 是目标模型单次 forward 耗时。加速比公式简化为:

speedup ≈ [(1 - α^(γ+1)) / (1 - α)] / (1 + γ × c)

把这个公式拆开看,两个因素决定成败:

  • 接受率 α直接决定每轮能“白捡”多少个 token。α 越接近 1,收益越大;α 太低,比如小于 0.3,那大部分 token 都被拒绝,等于白干。
  • 草稿模型成本 c决定了猜测本身的代价。如果 c 太大(草稿模型不够小),γ 次猜测的耗时就把收益吃掉了。

举个具体数字:α=0.7,γ=5,c=0.02,代入公式可得加速比约为 3.0 左右。这就是“300% 提速”的来源。当然这是理想平均情况,实际会受场景、模型对、batch 大小影响,但量级是对的。

我建议想做评估的朋友先把这套公式吃透,因为后续所有调优,本质上都是在调 α、γ、c 这三个变量。

3. 生产落地:草稿模型选型与参数调优

3.1 草稿模型不是越小越好

很多人一看“草稿模型要小、要快”,就下意识选一个参数最小的模型。我一开始也这么干,结果接受率惨不忍睹,加速比还不如直接跑大模型。原因很简单:草稿模型越小,它的分布和大模型差异越大,猜出来的 token 被拒绝的概率越高。草稿模型的“猜测质量”和“运行速度”是一对矛盾,必须找到平衡点。

我在实际项目中试过的经验是:草稿模型和目标模型的规模差距保持在 10 到 100 倍之间比较合适。比如目标是 13B 模型,草稿模型选 100M~1B 这个范围。选 100M 以下的通常是纯词汇级的统计模型,速度极快但基本猜不准;选 2B 以上的草稿模型,速度优势又变小了,整体收益会被吃掉。

另外,草稿模型最好和目标模型同源或同族,比如同一个预训练系列的不同尺寸版本。同源模型在能力分布上高度相似,接受率会明显比跨家族组合高。我在实验里对比过:同一家族 0.5B 草稿配合 13B 目标,α 能到 0.7 左右;换成同量级但不同家族的草稿,α 直接掉到 0.4。这个差距决定了加速比是 3 倍还是 1.5 倍,非常关键。

如果手头没有同源小模型,退而求其次的选择是:用目标模型的部分层(比如前几层)作为草稿。这种方案我在某些框架里看到过,效果不错,但工程复杂度更高。

3.2 词表对齐与 Tokenize 一致性

这是最容易被忽略、也最容易出大问题的坑:草稿模型和目标模型的词表必须完全一致。如果词表不同,token ID 对不上,拒绝采样根本没法做。你得把草稿模型的输出映射到目标模型的 token ID 空间,但不同模型的分词器可能对同一段文本切出不同的 token 序列,这就很麻烦了。

解决方案有两种:

  • 方案一:直接选择同词表的模型对。很多主流模型家族内部词表是一致的,比如同系列不同尺寸的模型通常共用 tokenizer。
  • 方案二:让草稿模型在目标模型的词表上重新训练一个输出层,或者用某种映射表把草稿模型的 token 映射到目标词表。这个方案实现复杂,我建议非必要不上。

另一个容易被忽略的细节是:草稿模型和目标模型的历史上下文 token 必须逐字对齐。也就是说,在开始推测之前,草稿模型必须“看到”和目标模型完全相同的上文。如果 tokenization 产生分歧,哪怕差一个 token,后续猜测全都会偏掉,接受率会断崖式下降。

我在生产环境里的检查习惯是:上线前写一个自动化测试,随机抽几百条数据,跑一下“草稿模型自回归输出”和“目标模型对应位置输出”,对比 token ID 序列是否一致,以及接受率的分布。如果发现异常,第一时间怀疑词表和 tokenizer 对齐,而不是去调 γ。

3.3 推测长度、批量验证与缓存设计

推测长度 γ 是推理速度和接受率之间的又一个调节旋钮。γ 太小,每轮验证的 token 太少,浪费了大模型的并行验证能力;γ 太大,草稿模型串行猜测的成本增加,而且越往后猜越容易偏,边际收益递减。

从我的实测看,γ 取 4~8 是比较合理的甜蜜区间。具体要结合目标模型的单次 forward 耗时和草稿模型的成本来定。一个粗略方法:在固定场景下,用不同 γ 跑同样长度的生成请求,画一条“γ vs 每 token 延迟”的曲线,取最低点。

工程上还要注意批量验证的实现。目标模型验证时,输入不是“只含草稿 token 的序列”,而是把原有序列加上 γ 个草稿 token 一起做一次 forward。此时需要保证 KV Cache 的拼接正确:

  • 目标模型在处理已有序列时已经把 key/value 缓存下来了;
  • 加入草稿 token 后,复用已有缓存,只对新增部分计算新的 KV 值;
  • 如果草稿 token 在验证中被拒绝到第 i 个位置,那么从第 i 个位置之后的 KV 缓存都要丢弃,回滚到接受位置。

这个“部分接受、部分回滚”的机制是工程实现里最容易出 bug 的地方。我见过不少实现在这里处理不当,导致 KV Cache 错位,生成结果莫名其妙地重复或者跳出低质量内容。我的建议是:先把“全接受”和“全拒绝”两个边角情况用单元测试覆盖掉,再处理中间状态。

另外关于批处理:推测解码在离线批量推理场景下收益更大,因为多个请求的草稿阶段可以合并成大 batch,GPU 利用率更高。在线低延迟场景也能用,但要注意 batch 增大后单次 forward 时间变长,需要重新评估 γ。

4. 实测与效果:哪些场景真能到 300%

4.1 代码生成与结构化输出:接受率最高

我最早尝试的场景是代码生成。代码是一种结构化很强的文本,规律明显,小模型很容易“学”到常见模式,所以草稿模型的接受率特别高。

在模拟项目 X 中,我用 0.5B 草稿模型配合 13B 目标模型,在代码补全任务上跑了一组对比。设置 γ=5,采样温度 0.2,接受率 α 稳定在 0.75~0.8 之间。按前面的公式估算,加速比在 3 倍左右,实测端到端延迟从平均每 token 35ms 降到了 12ms,接近 3 倍。如果把 γ 调到 6,还能再快一点,但涨幅已经不大了。

结构化输出(比如 JSON 生成、SQL 生成)也有类似特性。因为这类任务里 token 的分布高度集中,常见关键字、括号、引号出现的概率极高,草稿模型猜中的概率自然就大。如果你正在做 Agent、Function Calling、代码助手这类应用,推测解码是性价比最高的加速手段。

另一个有用的观察:采样温度越低,接受率越高。因为低温下目标模型的分布更尖锐,草稿模型的猜测更容易落在高概率区域。如果你的业务允许低温采样,推测解码的收益会更显著。

4.2 自由对话与创意生成:接受率下降怎么办

自由对话、故事生成这类任务就没那么乐观了。模型的输出高度发散,小模型很难猜到“用户下一秒想说什么”或者“模型下一步想编什么情节”。我在对话场景下测过,α 通常会降到 0.4~0.5,加速比缩水到 1.5~2 倍。更极端的创意写作场景,α 甚至可能低于 0.3,这时候收益就非常有限了。

面对这种场景,我有几个实践建议:

  • 降 γ:把 γ 从 5 降到 3,减少无效猜测的成本,加速比反而可能提升。
  • 换更大的草稿模型:比如从 0.5B 换成 1B,虽然单次猜测变慢,但接受率上去了,总体可能更划算。
  • 做场景分流:同一个服务里可以部署两套配置,对代码/结构化请求走 γ=6 的推测解码,对自由对话走 γ=3,避免一刀切。

这里要特别提醒:别期待推测解码在所有任务上都能到 300%。它是一个“锦上添花”的加速器,基础收益来自减少 forward 次数;如果任务本身发散度高,花再大力气调 γ 也挽救不了。把预期管理好,才能在合适的场景里用出最大价值。

4.3 与量化、PagedAttention 等技术的叠加

生产环境很少只做一种优化。我在实际部署中把推测解码和 INT8/INT4 量化、PagedAttention(分页注意力)一起用,这里有个非常重要的经验:优化手段是乘法关系,不是加法关系。

量化把单次 forward 的参数读取量降低,PagedAttention 让 KV Cache 内存使用更紧凑,推测解码则减少 forward 次数,三者互不冲突。实测下来,量化后的目标模型配合推测解码,加速比甚至比“非量化 + 推测解码”还高一点。原因是量化后目标模型单次 forward 更快,草稿猜测的相对成本微降,整体的等待开销更小。

但要注意:量化后的目标模型可能导致草稿模型接受率轻微下降。因为量化本身会引入数值误差,目标模型的分布会有一点偏移,草稿模型原来“猜中”的 token 在量化后的目标模型里概率可能略微降低。所以量化后一定要重新测接受率,不要沿用旧参数。我在一个项目里就吃过这个亏,量化后 α 从 0.72 降到 0.65,整体加速比从 2.9 掉到 2.4,后来把 γ 从 5 调到 6 才补回来。

还有一个小技巧:草稿模型也可以用量化版本,进一步压减它的单次 forward 开销。草稿模型精度损失对最终输出的影响很小,因为最后一步有目标模型的拒绝采样把关,量化误差不会累积到输出质量上。

5. 踩坑记录与排查经验

5.1 接受率低,第一排查方向不是调参

遇到加速效果不理想,绝大多数人第一反应是调 γ、换草稿模型。但我的经验是:先排查数据流和数据分布问题,再调参。接受率低的原因可能根本不是“草稿模型太弱”,而是:

  • tokenizer 不一致,导致上下文 token 序列错位;
  • 草稿模型输入的历史长度和目标模型不一致,比如目标模型用了更长的上下文窗口;
  • 采样参数不一致,比如目标模型用了核采样(top-p),而草稿模型用了贪心,分布差异会被放大。

我第一次上线时,加速比只有 1.2 倍,查了两天才发现是草稿模型的上下文长度只有 2048,而目标模型是 4096。长文本后面部分的上下文信息草稿模型完全看不到,猜测自然不准。把草稿模型的上下文长度对齐后,α 立刻从 0.35 升到 0.6。

排查的时候我建议先打日志:记录每轮推测的 token 数、接受 token 数、拒绝位置分布。如果拒绝位置大量集中在前 1~2 个 token,大概率是上下文对齐问题;如果拒绝位置均匀分布,说明草稿模型整体能力不足,这时候再去换模型或者调 γ。

5.2 显存占用翻倍的计算与应对

这是很多团队评估推测解码时忽略的成本。目标模型本身要占显存,草稿模型也要占一份;还有一个隐形成本:目标模型在做并行验证时,会把 γ 个草稿 token 的位置都计算一遍 KV Cache,这批缓存虽然短暂,但在批处理下会放大显存占用。

我用一个 13B 目标 + 0.5B 草稿的组合,显存增量大约 1.5~2GB。其中草稿模型参数占 1GB,额外的 KV Cache 缓冲占 0.5GB 左右。对 40GB 显存的卡来说还能接受,但如果你的卡本来就贴着显存上限部署,推测解码可能直接 OOM。

应对方案有几种:

  • 把草稿模型量化到 INT8,参数占用直接减半;
  • 限制草稿模型的最大推测长度,减少 KV Cache 的预分配;
  • 在推理框架里为验证阶段单独设置 KV Cache 的上限,超过就提前结束本轮推测。

另外要注意,PagedAttention 和推测解码搭配时,要确认框架是否统一管理草稿模型的 KV Cache。有些框架只给目标模型做分页管理,草稿模型的缓存还是朴素的整体分配,显存碎片化会更严重。

5.3 延迟不降反升的边界条件

不是所有场景都适合推测解码。我整理了几个“反向优化”的典型边界,如果你遇到类似情况,别怀疑自己,而是要考虑关闭这个功能:

  • 小模型 + 小 batch:目标模型本身很小(比如 1B 以下)时,单次 forward 已经非常快,草稿模型的猜测和验证流程反而带来额外开销。加速比公式里的 c 不占优,γ 次猜测的成本可能超过一次验证的收益。
  • 在线低延迟 + 单请求:推测解码的验证阶段需要把草稿 token 拼成大输入,计算量比单 token 生成大不少;在单请求低延迟场景,这会让首 token 时间变长。实测中,单请求的 TTFT(首 token 延迟)会略微增加,换取的是后续 token 吞吐提升。
  • 草稿与目标模型分布差异过大:比如目标模型是经过大规模指令微调的,而草稿模型还是原始预训练版本,两者分布可能差异很大。我遇到过 α 低到 0.2 的情况,加速比不到 1,果断关掉。

每引入一个新优化手段,都应该带着“是否在边界条件下有害”的视角去评估。推测解码不是银弹,但它在我目前的大多数生产场景里,都是“性价比极高”的一笔投入。

5.4 一个小彩蛋:树状推测解码

如果你已经熟练掌握了线性推测解码,可以关注一下它的进阶形态——树状推测解码(Tree-based Speculative Decoding)。它不再只猜一条 token 链,而是让草稿模型同时猜测多条分支,目标模型一次验证一棵“token 树”,从中选出最合理的一条路径。

这种方案对发散性强的任务特别有效。线性推测的 γ 个 token 只要中间错一个,后面的全白费;树状结构则让多个候选并行验证,接受率和单轮收益都更高。代价是实现复杂度明显上升,需要维护树结构、批量验证的掩码矩阵、动态回滚路径等。

我的建议是:先把线性版本吃透、跑通、测出基线,再考虑树状版本。生产环境最忌讳一上来就上高复杂度方案,因为调试成本会翻好几倍。我自己也是在线性版本稳定运行了半年之后,才开始在某跨平台系统里试验树状版本。

最后分享几个实操心得

这套技术真正帮我打通“速”与“稳”的时刻,是在我把草稿模型和目标模型的上下文长度对齐、并搭好自动化接受率监控之后。在那之前,我总觉得推测解码是个“碰运气”的加速方案;搞清楚数据流对齐和拒绝采样机制后,它在我眼中才变成一个可预测、可调优的工程组件。

最后再留三个小建议:第一,上线前一定要做“输出一致性”抽检,虽然理论上无损,但实现 bug 可能导致分布偏差,抽检能兜底;第二,把接受率作为核心监控指标接入告警,它比端到端延迟更能反映推测解码的健康度;第三,如果你的框架暂时没有现成的推测解码实现,可以先用伪代码把拒绝采样逻辑单独写个单元测试,验证数学正确性后再嵌入整体流程——这一步看起来费事,实际能省下后面大量联调时间。希望这些经验对你有用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询