
1. 慢在哪里自回归生成不是“写一行出一行”而是每一行都要重读一遍底稿1.1 串行生成的硬约束聊 Speculative Decoding推测解码之前得先把“大模型推理为什么慢”这件事掰开揉碎。很多人以为模型生成就像打字一样敲一个字出一句话实际上不是这样。主流大模型是自回归结构一次输出一个 token而且每个 token 的生成都依赖前面所有已经生成的 token。这个依赖关系是硬约束没法绕开你不能让第 100 个 token 和第 101 个 token 同时生成因为在模型眼里第 101 个 token 的分布必须建立在第 100 个 token 已经确定的基础之上。这种“一个接一个”的串行流程直接导致生成速度和序列长度强相关prompt 越长、要生成的 tokens 越多等待时间就线性上涨。我自己的项目里最早用的是约 7B 规模的通用底座单卡环境下生成一个 token 大约要 30 到 40 毫秒。这个速度在单用户聊天场景还勉强能忍但一到批量文档摘要、多轮 Agent 工具调用、或者需要生成上千 token 的代码补全等待时间就变得非常难受。你看着程序还在跑心里却很清楚地知道它只是在机械地一遍又一遍做同一件事没有任何“并行”的空间。1.2 真正的瓶颈是内存带宽不是算力这里有一个非常反直觉的结论大模型推理的瓶颈大多数时候不是显卡的算力不够而是显存带宽不够。简单说模型每生成一个 token都要把所有参数从显存里读一遍。一个 7B 模型如果用 FP16 存储光权重就是大约 14GB即便用 FP8/INT8也还有 7GB。每一次 forward 计算这些权重都要被搬运一次。显卡的算力再强如果数据搬运不过来计算单元也只能空转等待。用生活里的例子来类比算力就像一个厨师的做菜速度显存带宽则是传菜员的能力。模型参数是堆在仓库里的食材每生成一个 token 就要把整仓库的食材搬到厨房看一眼。哪怕厨师手里刀工再好传菜员一次只能搬一小筐整体效率就被卡死了。所以真正有效的加速思路不是让单次 token 生成更快而是想办法减少大模型 forward 的次数或者让一次 forward 干更多活。这也正是推测解码能把生成速度提升一大截的根本原因它本质上是在用一次大模型 forward 的钱换回多个 token 的产出把内存带宽这个瓶颈的利用率拉满。2. 推测解码的“先斩后奏”思路小模型打草稿大模型批量验收2.1 为什么小模型能替大模型写草稿推测解码的核心逻辑很简单既然大模型每一步都要从显存里读一遍权重那能不能让一个便宜又快的小模型先快速写出一段 candidate tokens也就是“草稿”然后让大模型一次性对这些草稿做校验如果草稿质量够好大模型一次 forward 就能同时“确认”好几个 token生成速度自然就上去了。这里的直觉依据是日常生活中大部分 token 的预测其实并不难。你读完“今天天气真好我们出去”这句话大概率能猜到下一个词是“散步”或者“玩”而不是什么冷门词汇。小模型虽然整体能力不如大模型但在很多“顺理成章”的上下文里它和大模型的判断高度接近。所以可以用一个小尺寸模型当“打草稿的人”它便宜、快走几步花不了多少时间大模型则扮演“验收员”它虽然每一步都慢但一次可以核查一整段草稿只要草稿里大部分内容合格这笔“先斩后奏”的买卖就划算。用一套非常极端但直观的对比假设大模型一次 forward 需要 10 毫秒小模型一次 forward 只需要 1 毫秒。传统方式生成 5 个 token大模型要跑 5 次总共 50 毫秒。推测解码方式下小模型先快速生成 5 个候选 token耗时 5 毫秒大模型跑一次批量校验比如需要 11 毫秒。如果 5 个候选全部被接受那么总耗时只有 16 毫秒却产出了 5 个 token速度提升超过 3 倍。这就是标题里“300%”的来源。2.2 拒绝采样既提速又保证输出分布不变这里有个技术细节值得展开讲大模型校验草稿时不是简单地“对就接受错就拒绝”。如果只做这种硬校验得到的结果分布会和大模型原本的生成分布不一致输出质量会悄悄劣化。正确的做法是采用一种“拒绝采样”机制。在实际实现中假设草稿模型在某个 token 位置给出的概率是 p_draft(x)目标模型在该位置给出的概率是 p_target(x)。如果 p_target(x) 大于等于 p_draft(x)那就直接接受这个 token如果 p_target(x) 小于 p_draft(x)则不是直接拒绝而是按 p_target / p_draft 的概率接受并且一旦拒绝需要从目标模型与草稿模型的概率差中重新采样再停止本次推测。这个设计的精妙之处在于它在数学上做到了“无偏”被接受的所有 token最终的概率分布和完全由目标模型自回归生成时的分布是一致的。换句话说推测解码改变的只是采样过程不是采样结果。这也是它和其他“投机取巧”的加速方法最本质的区别——你可以放心把生成结果交给下游任务而不必担心因为加速导致输出质量出现系统性的偏差。我第一版实现时没有严格做拒绝采样只是简单比较 argmax也就是在 greedy 模式下跑。某个模型上看起来没问题但换到带随机性的采样配置后生成文本的多样性和质量明显变差。后来才明白greedy 模式下的简化版本本质上只是拒绝采样在 temperature 趋近于 0 时的特例不能推广到所有场景。3. 生产落地草稿模型选型、采样对齐与加速收益实测3.1 选草稿模型的三个关键点草稿模型选得好不好直接决定推测解码是“如虎添翼”还是“鸡肋”。我自己的踩坑经验里有三个关键点最值得优先考虑。第一点是词表必须一致。如果草稿模型和目标模型使用不同的 tokenizer、不同的词表校验阶段根本对不上 token整个方案直接无法工作。这一点在前期调研时特别容易忽略因为很多开源小模型的 tokenizer 逻辑和大模型不一致哪怕都是中英混合模型词表也可能有细微差别。词表不一致时要么换草稿模型要么做词表映射但后者会引入性能损耗不推荐。第二点是模型规模不能太大。草稿模型的加速收益方程是收益 ≈ 平均接受长度 × 草稿模型速度 / 目标模型速度。如果草稿模型太胖它生成草稿的时间本身就快不了多少那大模型省下来的时间又被小模型浪费掉了。业内常见的经验是草稿模型和目标模型的规模差一个数量级左右比较合适。比如目标模型是 70B 量级草稿模型用 1B 到 7B 量级目标模型是 7B 量级草稿模型用几十 M 到几百 M 量级。第三点是最好和目标模型同源或者是经过蒸馏的关系。同源候选模型通常是同一个模型的较小版本权重分布天然接近命中率更高。如果没有同源版本也可以用通用小模型但我实测下来命中率比同源版本低不少尤其在代码和数学这类对精确性要求很高的任务上。3.2 采样参数必须对齐否则收益归零这是我在生产环境里踩过最深的坑之一。推测解码的接受率高度依赖草稿模型和目标模型在“同一个 token 位置”上的概率分布一致性。如果两个模型使用不同的 temperature、top_k、top_p那么即使它们本身能力相近输出分布也大概率错位草稿被拒绝的频率会飙升。举个例子某次我把草稿模型的 temperature 设成 0.6目标模型沿用默认的 0.8结果平均接受长度从 3.7 直接掉到 1.8加速收益几乎蒸发了一半。把两边参数完全对齐后接受长度立刻恢复。后面我干脆把采样参数统一收敛到一个配置对象里由服务启动时注入到两个模型中强制保证一致性。还有一个容易被忽略的点目标模型的采样方式如果是 beam search 这类非标准采样推测解码不能直接套用。因为 beam search 需要维护多个候选序列而拒绝采样的概率逻辑是基于标准自回归采样的。生产环境里如果确实要用 beam search我通常的做法是换回普通解码或者改用专门适配 beam search 的变体方案不要硬扛。3.3 我在模拟项目X上的一组实测数据为了让大家有个相对直观的认知我把自己在模拟项目X里的实测结果整理了一下。测试环境是单张消费级显卡目标模型是约 7B 量级的开源底座草稿模型选了一个约 400M 的轻量模型词表通过同源蒸馏方案对齐。场景普通解码速度 (tokens/s)推测解码速度 (tokens/s)平均接受长度加速比通用对话28743.42.64x中文摘要31793.12.55x代码生成24521.92.17x数学推理23411.41.78x从表格里可以清楚看到两个结论第一推测解码不是“每个场景都一样快”而是和任务可预测性高度相关。代码生成里变量名、库函数调用这些 token 相对好猜但中间突然出现一串“魔法数字”或复杂 API 签名时草稿模型就会频繁出错。数学推理更极端因为中间步骤的符号序列对上下文极其敏感小模型命中率很低所以加速比只有 1.78 倍没有达到标题里说的 300%。第二即使是最差的场景也比普通解码快没有出现负优化前提是草稿模型选得对、采样参数对齐了。4. 绕不过去的坑命中率、显存、batch 与框架兼容性4.1 接受率不稳定代码和数学题会打脸生产落地最大的挑战不是原理看不懂而是接受率不稳定。这里的“接受率”指的是草稿 token 被大模型确认的比例。接受率高的场景比如闲聊、通用问答草稿模型预测得很顺但到了代码生成、数学题、长文本生僻名词、多语言混写场景草稿模型经常“猜不中”导致平均接受长度断崖式下降。我在一次线上试运行时就遇到过这个问题同一个草稿模型处理用户闲聊请求时加速比能上 2.5 倍但只要请求里混进了大段代码生成速度立刻回落甚至接近普通解码。排查后发现不是推测解码框架出了问题而是小模型在代码任务上本来就不擅长。后来我的解决方案不是换一个更大的草稿模型而是做“动态窗口策略”通过实时统计最近若干次生成的平均接受长度动态调整单次推测的候选 token 数 k。任务简单时把 k 调大比如 6任务难时把 k 调小比如 2如果接受率太低就直接退化为普通解码。这套策略让整体平均加速比提升了约 20%比固定 k 值稳健得多。4.2 显存占用被低估小卡直接白干推测解码需要在显存里同时放下目标模型、草稿模型以及两份 KV cache。很多人只盯着目标模型的大小忽略了草稿模型也是实打实要吃显存的。我之前在一张显存偏小的卡上做测试目标模型加上量化之后已经占了将近 70% 显存再塞一个几百 M 的草稿模型和额外缓存直接 OOM。最后我只能把草稿模型也做量化又把 KV cache 通过 PagedAttention 这类显存复用机制压紧才勉强跑起来。这里给一个建议如果你的服务已经在目标模型的显存占用上卡得很紧最好先评估草稿模型的额外开销是否值得。有一种替代做法是“无草稿模型推测”也就是用 n-gram 或者检索历史 token 作为草稿来源完全不吃额外显存虽然命中率稍低但至少不会 OOM。对于显存资源特别紧张的小团队这条路反而更实用。4.3 批量服务场景不是简单的“越快越好”单条请求上推测解码效果很好不代表你直接把它塞进批量推理服务里就万事大吉。批量推理通常用 continuous batching多个请求共享一个 batch模型一次 forward 处理一批数据。推测解码引入后每个请求的草稿序列长度、接受率都不一样系统必须决定“按谁的 k 值来对齐 batch 内计算”。如果所有请求都按最长的草稿序列走短草稿请求就会产生大量 padding 浪费如果按短序列走长草稿请求又没法充分利用推测收益。我实际验证下来batch 越大推测解码的收益越容易被稀释因为 batch 填充和调度开销会吃掉一部分“单请求加速红利”。比较好的工程做法是让推理框架支持“按请求独立设定推测窗口”也就是每个序列可以有自己的候选长度而不是整个 batch 共享一个长度。但这依赖推理框架对动态形状和显存调度的支持不是所有开源框架都能轻松做到。如果你是在团队里做技术选型我建议先确认目标推理服务框架对推测解码的原生支持程度尤其是“动态 k 值”和“不同请求不同草稿序列长度”这两项能力。如果框架不支持强行在外层用一个统一 k 值硬套收益会打很大折扣。5. 还可以继续榨的优化空间5.1 树状推测解码一次验证多个分支标准推测解码是一条直线草稿模型生成一条候选序列大模型校验这条序列。但现实是很多 token 位置上草稿模型可能有多个“都不错”的候选。比如下一个词可能是“打开”也可能是“启动”草稿模型自己都拿不准。标准方案只能选其中一个去赌赌输了就退回。树状推测解码的思路是让草稿模型在每一步同时展开多个候选分支形成一棵小树然后让目标模型用一次 forward 同时校验所有分支。这就像考试时把多个可能的答案都写到草稿纸上让老师一次性批改整张纸批到哪一行就算到哪一行。这个方案的收益上限比标准推测解码更高尤其在草稿模型置信度不高的位置能够明显减少“被拒后从头来”的浪费。代价是实现复杂度上升需要改造 attention 掩码让树形结构的 token 之间不会互相越权看到未来信息。我目前只在模拟项目X里做了初步验证树状解码相比直线推测在代码生成场景能再提升 10% 到 15% 的吞吐但代码复杂度也确实翻倍。如果你团队的人力和算力都有限不建议一开始就上树状版本先把直线版本跑通、调好采样参数收益已经不小。5.2 草稿模型的热更新与蒸馏迭代草稿模型的命中率不是固定不变的它和业务数据分布强相关。如果你的线上流量集中在法律文书摘要而草稿模型是在通用语料上训练的命中率自然不高。长期运营之后可以拿线上真实 prompt 和 target 模型的历史生成结果蒸馏一个小模型作为专用草稿模型再通过热更新把它替换到推理服务里。这个流程类似“用大模型的输出去教小模型”唯一区别是这里的小模型不需要多高的通用能力只需要在线上数据分布里和大模型“想得差不多”就够了。我也试过用更大的草稿模型换取更高接受率结果发现收益并不线性。有些场景下草稿模型从 400M 增大到 1.5B接受率只提升了不到 8%但草稿生成耗时增加近一倍整体吞吐反而下降。所以选草稿模型别只盯着命中率一定要同时考虑“草稿阶段的生成速度”建议每次调整完模型后都跑一遍相对完整的吞吐基准别只看接受长度这一个指标。5.3 从工程角度留足观测指标最后说一个容易被忽略的点上线推测解码前最好把观测指标拆细。不要只记录“生成 tokens/s”还要记录平均接受长度、草稿模型耗时占比、拒绝后重采样导致的额外调用次数。我自己就把这些指标接到了监控面板里一旦发现某个模型版本升级后平均接受长度掉到阈值以下服务会自动告警。这个习惯帮我提前发现过一次测试环境草稿模型被误换成旧版本的问题不然在线上故障发生前根本不会注意到。推测解码不是一个“插上就能快三倍”的黑魔法它更像一台精密配合的双引擎小模型负责快速试探大模型负责精准验收。只要选对草稿模型、对齐采样参数、根据任务调 k 值再配好监控指标它确实是目前大模型推理提速里性价比极高的优化手段之一。我自己把整套流程跑完后的体会是不要一上来追求最快的加速比先保证输出分布不变、系统稳定再逐步调优各项参数这样推进起来心里最有底。