投机解码:LLM推理无损加速的核心原理与工程实践指南

发布时间:2026/9/8 17:27:27
投机解码:LLM推理无损加速的核心原理与工程实践指南 1. 从一次卡顿说起LLM推理的瓶颈到底在哪里如果你调过任何大模型的API或者本地跑过开源模型一定有这种感觉prefill预填充阶段还挺快一大段prompt啪一下就吃进去了但到了生成阶段一个字一个字往外蹦快的时候二十几个token每秒慢的时候三五个。尤其在70B这种大模型上哪怕用了A100/H100解码速度依然让人着急。这件事的本质是自回归。模型每生成一个token都要把它接到上下文末尾再重新算一次条件概率。它没法像CNN处理图像那样一张图一次前向就把所有分类结果全出来。生成文本的过程天生是串行的第n个token没算出来第n1个token就无从谈起。所以大家想尽办法在这个串行链条上动刀而Speculative Decoding投机解码就是这几年被验证最有效、且能做到数学上无损的解法之一。这篇文章不打算复述论文而是想站在工程落地和实际部署的角度把投机解码的原理、2025年主流的算法变体、以及我在真实业务场景里踩过的坑一次说清楚。适合正在做LLM推理优化、服务压测、或者想给自家模型省GPU卡的人看。先说结论投机解码的核心思想是让一个又小又快的小模型先“打草稿”然后让大模型一次性地批量“批改”。如果草稿质量够高一次前向就能多吐出好几个token解码速度成倍提升。更关键的是这个过程不是近似优化而是精确采样只要工程实现不出错输出分布和原生模型完全一致这就是标题里那个“Lossless”的真正含义。2. 投机解码的核心原理让草稿模型先探路2.1 基本流程两步走草稿与验证投机解码的整体流程并不复杂通常分为两个角色草稿模型draft model或叫approx model和目标模型target model就是我们要部署的大模型。每一轮循环分成两个阶段第一阶段给定当前上下文context草稿模型以标准自回归方式连续生成K个token。这K个token只是“猜测”质量取决于草稿模型的能力通常用比目标模型小一两个数量级的模型比如7B主模型配68M的草稿模型。第二阶段把这K个草稿token拼到context后面一次性喂给目标模型。注意这里不是跑K次前向而是只跑一次前向但输入序列长度是K1K个草稿token再加上原本的下一个预测位置。由于Transformer在解码阶段利用了KV Cache对多个位置同时算logits代价远小于串行跑K次。接下来就是最关键的一步逐token检查决定“接受”还是“拒绝”这些草稿token。如果第一个token就被拒了那就从修正分布里重新采样一个真实token本轮结束如果全部接受则还能多拿一个目标模型自己预测出来的额外token。每一轮循环结束后无论接受多少个token最终都会得到至少1个由目标模型分布钦定的token然后继续下一轮。这就保证了生成方向不会跑偏。2.2 拒绝采样无损的关键机制很多第一次接触投机解码的人会问小模型猜出来的token大模型凭什么敢直接收下答案是拒绝采样这也是整个算法“Lossless”属性的数学根基。我们简化场景。假设草稿模型在某个位置给出的概率分布是q(x)目标模型在同一位置的真实分布是p(x)。投机解码不会机械地“全部信”或“全部不信”而是按如下规则决策对于草稿序列中的第j个token计算接受概率alpha min(1, p(x_j) / q(x_j))然后从均匀分布U(0,1)中采样一个随机数。如果这个随机数小于alpha那么接受这个token继续检查下一个如果大于等于alpha就拒绝这个token并且从修正分布中重新采样一个真实输出p_fix(x) max(0, p(x) - q(x)) / sum(max(0, p(x) - q(x)))注意这个修正分布不依赖被拒绝的那个具体token而是整条概率曲线的“差额”目的是把草稿模型猜过头的位置拉回来。这个过程和统计学里经典的rejection sampling一脉相承。它的精妙之处在于被接受的token保证了在原分布中存在足够的概率质量而被拒绝时的修正采样又把那部分被小模型带偏的概率质量补偿回来。两者合在一起最终采样出来的序列分布上严格等于直接从目标模型p(x)里采样一个不多一个不少。所以“无损”不是一个营销词。它说的是投机解码不会像量化、剪枝、蒸馏那样改变模型行为也不像beam search那样改变解码策略。它就是目标模型自己的采样只不过用草稿模型做了一次“重要性采样”的加速。2.3 加速比到底怎么算期望收益的量化理解了无损机制更要紧的问题是它到底能快多少这个收益用数学表达非常直观。假设草稿模型生成K个token需要耗时t_draft(K)目标模型做一次验证前向耗时t_target(1)。如果没有投机解码产出同样数量token需要(E[L]1)次目标模型前向耗时就是(E[L]1) * t_target(1)。而投机解码一轮的总耗时是t_draft(K) t_target(1)。那么加速比近似为speedup ≈ ((E[L] 1) * t_target(1)) / (t_draft(K) t_target(1))其中E[L]是期望接受长度就是每轮平均有多少个草稿token被大模型批准。从这个公式能看出几个重要结论第一加速比的上限是K1。如果草稿模型“神预测”每个位置都和目标模型分布重合那么E[L]K一轮就出K1个token加速接近K1倍。但现实中这个值打折扣。第二草稿模型的时间成本不是免费的。如果草稿模型太大太慢t_draft(K)本身就会吃掉大量收益。这也是为什么草稿模型通常要选得极小并且最好开启CUDAGraph这类优化。第三K值不是越大越好。K越大单轮收益上限越高但草稿模型生成K个token的时间线性增长而且越往后的token越是“瞎猜”接受率断崖式下跌。实际工程里K4到6是最常见的区间后面章节我会展开讲怎么调。从实测数据看对一个7B主模型配68M草稿模型K4接受率在0.6到0.8之间时端到端吞吐提升普遍落在1.8到2.5倍。如果草稿模型是从主模型蒸馏来的接受率能到0.8以上跑到3倍也不稀奇。2.4 草稿模型的质量为什么很重要整个过程里草稿模型是唯一需要额外“养”的东西。它的质量直接决定接受率而接受率决定了加速比曲线能跑到多高。所谓质量首先是指词汇表对齐。目标模型和草稿模型的tokenizer必须是同一个或至少vocab size一致、token id映射一致。否则q(x)和p(x)根本没法做逐位置比较很多框架会直接拒绝加载配置。其次是分布接近度。草稿模型最好和目标模型同源——用目标模型的checkpoint做蒸馏或者至少是同一个数据域训练出来的。我见过有人拿一个通用小模型去配Llama-3.1-70B前几个高频虚词还能偶尔命中一旦遇到推理、代码、数学这类对上下文敏感的token接受率直接掉到0.3以下K值再大也救不回来。换句话说投机解码的本质是“用算力换延迟”加上“用预测换并行”。草稿模型猜得越准一次性验证的并行收益就越高。如果草稿模型猜得太离谱那还不如老老实实让大模型自己一步一步走。3. 2025年的投机解码版本全景从草稿模型到无草稿方案投机解码这个方向从2022年底被提出到2025年已经演化出好几个流派。如果你要在这个方向选型得先搞清楚各家方案在“是否需要额外模型”“改造难度”“加速上限”上有很大差异。3.1 草稿模型派最朴素、最灵活的落地方式这一派就是上一节讲的经典框架。你额外带一个小的自回归模型它和主模型共享tokenizer独立前向。典型代表是论文Fast Inference from Transformers via Speculative DecodingLeviathan et al.和Accelerating Large Language Model Decoding with Speculative SamplingChen et al.。工程上的优点是草稿模型可以与主模型解耦换草稿模型不需要动主模型任何参数推理框架集成度高。缺点是草稿模型本身要占显存而且它要串行跑K步延迟开销始终存在。3.2 Medusa与多头预测绕过草稿模型Medusa的思路很不一样它不给主模型配外部草稿模型而是在主模型最后一层隐藏状态上额外挂几个并行的“解码头”head。每个头负责预测往后偏移1步、2步、3步...的token。这样主模型一次前向不仅算出当前token还捎带把未来几个位置的候选token也预测出来最后用树形注意力tree attention一次性验证多个分支。这个方案的直接好处是省掉了单独维护小模型的麻烦而且因为解码头是在主模型的自身上微调出来的预测分布天然和主模型更接近接受率一般比独立小模型高。代价是需要对模型结构做手术以及要做一轮微调训练。Medusa系列现在已经迭代到Medusa-2、Medusa-3在部分框架里已经是一行参数就能启用的程度。3.3 EAGLE系列特征级预测EAGLE是我个人比较看好的方向。它和Medusa同属无外部草稿模型的路线但预测的单位从“token”提升到了“特征”。EAGLE的原型做法是利用主模型某层通常是倒数第二层的hidden state加上已经生成的token embedding训练一个轻量的自回归头用来预测下一层的特征向量然后再过主模型的LM Head得到候选token。因为特征比token携带的信息更稠密预测相似度更高接受率通常明显优于直接预测token的方案。EAGLE现在已经出到EAGLE-3实测在多种模型规模上都能跑到2.5到3.5倍的解码加速在SGLang等框架里有比较成熟的接入支持。如果让我给新项目做选型EAGLE通常是我优先试跑的对象。3.4 Lookahead Decoding与自投机还有一类方案走的是“零额外模型”路线比如Lookahead Decoding。它的核心观察是文本中大量n-gram是重复出现的可以先从历史生成中提取高频片段当作“草稿”再用上一步验证。这种方法的好处是完全不用训练额外模型缺点是对随机性强的自由文本收益有限模板化程度高的任务比如JSON输出、代码补全的固定片段会更有效。属于“可以了解一下但未必适合通用场景”的选项。另外还有一些利用主模型自身早层输出early exit来生成草稿的尝试原理类似但对模型结构改动要求高工程化程度相对低。3.5 主流方案对比我把目前常见的几类方案放在一张表里方便选型时快速决策方案核心思想是否需要额外模型典型加速比工程复杂度适合场景经典投机解码草稿模型小模型草稿 大模型批量验证是轻量草稿模型1.8~2.5x低大部分通用场景最稳妥Medusa多头解码头预测未来token否需微调主模型2.0~3.0x中有训练资源想极致加速EAGLE系列基于特征层回归预测否需微调主模型2.5~3.5x中高性能要求框架支持好Lookahead Decoding从历史n-gram提取草稿否1.2~2.0x低模板化、重复文本多自投机early exit用主模型早层输出做草稿否需改结构1.5~2.5x高对模型结构有完全掌控上面所有加速比都是基于吞吐型场景的实测经验值具体数值受模型规模、K值、batch大小影响很大。选型的基本逻辑是如果只求低成本快速见效经典草稿模型方案是最稳的如果愿意花算力微调且框架支持到位EAGLE和Medusa能获得更高上限。4. 工程落地的实操要点从论文到生产线4.1 先别自己造轮子在vLLM和SGLang里低成本启用2025年这个节点主流推理框架都已经内置了投机解码支持很多情况下你不需要自己从零实现。以vLLM为例启用经典草稿模型投机解码只需要在启动命令里加两个参数python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-3.1-8B-Instruct \ --speculative-model JackFram/llama-68m \ --num-speculative-tokens 5 \ --max-model-len 8192--speculative-model指定草稿模型的路径或名称--num-speculative-tokens对应K值。如果你用的是MedusaVLLM也支持类似--speculative-config medusa的写法。SGLang则提供了--speculative-algorithm EAGLE --draft-model ...这类参数。我的建议是在接自己业务之前先用现成框架把基准速度测一遍确认框架版本、草稿模型、K值都对了再考虑要不要自己写定制实现。自己实现投机解码的坑远比想象中多后面我会专门讲。4.2 自己实现的核心流程一份可复现的伪代码如果你需要把投机解码集成到自研的推理引擎里或者想在框架之外做定制核心流程其实不复杂。下面是一份伪代码逻辑等价于标准投机解码的单轮过程def speculative_decoding_step(target_model, draft_model, context, K): # 阶段一草稿模型自回归生成K个token draft_tokens [] draft_probs [] # 每个位置的q分布 for _ in range(K): logits draft_model.predict_next(context draft_tokens) prob softmax(logits, temperatureT) token sample(prob) draft_tokens.append(token) draft_probs.append(prob) # 阶段二目标模型一次前向验证K个草稿位置 1个额外位置 target_logits target_model.forward(context draft_tokens) # 长度为K1 target_probs [softmax(s, temperatureT) for s in target_logits] # 阶段三顺序拒绝采样 accepted [] for j in range(K): p target_probs[j] q draft_probs[j] token draft_tokens[j] alpha min(1.0, p[token] / q[token]) if random.random() alpha: accepted.append(token) else: # 拒绝从修正分布采样本轮结束 fix_dist max(p - q, 0) fix_dist fix_dist / fix_dist.sum() accepted.append(sample(fix_dist)) return context accepted # 如果K个草稿全部接受额外从目标模型位置K1的分布采样一个token extra_token sample(target_probs[K]) return context accepted [extra_token]单轮循环结束后返回的序列长度至少是1拒绝发生在第一个位置时修正采样补了1个最多是K1。下一轮再以这个新序列作为context继续。这里有两个工程细节需要特别提醒第一草稿模型和目标模型的logits必须在同一个温度下计算。投机解码的无损性推导基于同一个采样策略很多实现bug都出在草稿模型用了默认温度、目标模型用了业务温度导致概率分布对不上。第二KV Cache要分两套管理。草稿模型和目标模型是两套独立的TransformerKV Cache完全不能共用。草稿模型的KV Cache每轮要重算K步目标模型的KV Cache每轮追加新接受的token。如果框架没有自动处理这个内存管理会非常容易出错。4.3 参数调优K值、草稿模型规模的取舍K值是最直接可调的超参数。我的调参方法论是看接受率曲线。首先固定草稿模型跑一个包含各种任务类型的测试集统计接受率r E[L]/K。如果接受率在0.7以上说明草稿模型质量够好可以考虑把K从4往上调每次加1观察端到端吞吐变化。如果接受率掉到0.5以下盲目加大K只会让草稿模型空转——因为它猜的后半段基本全被拒收益趋近于零还得倒贴草稿模型的生成时间。草稿模型规模的选择经验上建议控制目标模型的1%到3%参数量。比如7B主模型配68M到200M的小模型70B主模型配0.5B到1B的草稿模型。草稿模型太大单步前向时间过长分摊到每个接受token上的成本就不划算了。太小则接受率上不去同样没收益。这个区间是我在多个模型上试出来的平衡点。还要注意量化对草稿模型的友好度。草稿模型本身精度要求不高用int8甚至int4量化接受率下降通常不超过两三个百分点但显存占用和访存时间都能降一大截。在显存紧张的部署环境里这是最划算的一笔优化。4.4 性能测量的正确姿势很多人跑投机解码测出来效果不好最后发现是测量方法有问题。我给出一套我常用的评估口径核心指标是端到端吞吐生成的token总数除以墙钟时间单位tokens/s。普通解码和投机解码之间用这个指标直接对比。首token延迟单独测。投机解码对prefill阶段没有帮助甚至因为草稿模型加载首次请求可能更慢。不要把TTFT混进per-token延迟里。接受率必须单独记录。很多框架在日志里会输出acceptance或speculative acceptance rate如果没输出自己打点记录。一定要做预热。GPU在冷启动、CUDA kernel加载、显存分配等阶段性能不稳定建议先跑足20轮以上的生成再进入正式统计区间。我自己踩过的一个坑在一台共享GPU上测速度旁边有别的任务抢带宽测出来的加速比忽高忽低。正确的做法是隔离环境至少保证测试期间没有其他大任务占显存和算力。5. 常见问题与避坑实录5.1 加速比没提升甚至变慢先查这四件事这是群里被问烂的问题。如果你开了投机解码加速比反而不如普通解码我建议按这个顺序排查第一草稿模型是不是太快/太小。如果它跑K步的时间加上目标模型验证时间比目标模型自己跑K步还慢那就没有加速可言。草稿模型的decode速度至少要比目标模型快5倍以上否则直接放弃这个方案。第二接受率到底是多少。VLLM等框架会提供metrics如果你用的不是K4/5这个主流区间先调K值。接受率低于0.5就直接换草稿模型别浪费时间死磕。第三CUDAGraph有没有打开。投机解码引入了大量短序列前向调用如果框架没有用CUDAGraph把kernel launch开销压掉小模型的优势会被启动开销吃掉一大半。检查日志里的graph capture状态。第四batch大小和持续批处理continuous batching的开销。草稿模型在batch较大时也会变慢而且调度器如果不把草稿模型的前向纳入计算resource accounting会失真导致并行度下降。5.2 llama-server返回HTTP 500的一个真实案例热搜词里有个llama-server inference returned http 500 (internalservererror)这个我确实遇到过。有一回我给llama.cpp的server配投机解码加载完模型、第一个推理请求直接500服务端日志没有任何有效报错。查了几个小时最后定位到问题是草稿模型的词汇表不匹配。草稿模型过于冷门tokenizer和主模型不是同一个框架虽然允许加载但在计算接受概率时q和p的token id对不上导致运行时异常。解决办法很粗暴换一个和主模型同系列、同tokenizer的草稿模型比如Llama-3.1-8B配Llama-3.2-1B问题立刻消失。这个案例给我们的教训是投机解码对tokenizer对齐有硬要求加载阶段不会给你详细报错但推理阶段各种诡异问题全冒出来。在上线前一定要用脚本检查两个模型的tokenizer vocabulary是否完全相同。5.3 显存不足与部署形态的取舍投机解码需要同时驻留两个模型显存占用确实会上升。如果显存不够有几种常见解法草稿模型量化到int4显存占用能压到原来的四分之一接受率损失通常可控。把草稿模型放到另一张空闲GPU上通过跨卡通信传递logits。跨卡延迟会吃掉一部分性能但在多卡机器上是可行的。CPU offload草稿模型靠CPU跑小模型推理。这个方案只适合吞吐型任务延迟会明显变差不推荐。说到底投机解码适合的场景是“GPU算力没打满、但显存还有余量”的服务。如果你的GPU显存已经紧巴巴或者算力本身已经跑满了那它就不是最优解。5.4 哪些场景不适合投机解码投机解码不是万金油这四类场景我一般不建议硬上输出只有一个token的任务如分类、打分草稿模型的前向开销纯属浪费。输出特别短的任务比如一两句话的摘要每轮草稿都还没进入稳定状态收益极低。非采样式解码路径beam search、contrastive search这类解码策略和投机解码的采样式证明不搭强行结合容易破坏搜索语义。如果要兼容通常只能在贪婪模式greedy下做修剪后的验证。延迟极度敏感的小请求服务投机解码在大batch、持续流式场景收益最大单请求小batch时优势不明显反而可能因为额外前向增加响应时间。6. 一点个人体会投机解码这个方向我从最早的论文研究一直跟到它在主流框架里变成默认选项。如果要说一个最深的体会那就是它看起来像是“取巧”实际是对解码过程本质理解得很透的方案。它并没有让语言模型“变聪明”也没有让数学上不可能并行的自回归链条凭空消失而是换了一个角度把串行步骤交给便宜的小模型把并行验证留给昂贵的大模型。在工程上我比较推荐的落地路径是先用经典草稿模型方案跑通流程拿到基线加速比和接受率如果效果不理想再上EAGLE或Medusa这类需要训练的更激进方案。不要第一步就搞最复杂的否则你根本分不清性能瓶颈到底出在算法原理还是基建和推进顺序。另外无损这件事数学上很漂亮工程上还是留了个心眼。浮点计算的微小差异、并行采样随机数种子不同、以及中间层的数值截断都可能在长序列生成中产生肉眼可见的输出差异。所以即使框架声称无损上线前拿业务prompt集做一次AB对比永远是值得的。

相关新闻