投机解码实战:本地27B模型推理从14 tok/s提速到159 tok/s 最近我把某个开源系列3.8版的一个27B参数模型搬进本地工作站折腾第一版推理服务的时候屏幕上那串每秒token数差点让我怀疑驱动装错了——14 tok/s。跑个稍长点的对话和等网页加载一样光标闪半天才蹦出一个字。后来用投机解码做调优同一个模型、同一张卡稳态吞吐直接拉到159 tok/s。这篇不聊虚的就把我是怎么从14一路调到159的完整过程写出来14到底慢在哪、投机解码的原理、参数怎么设、哪些坑别踩。适合想在自己机器上跑本地大模型、又受不了一个字一个字蹦出来的开发者和折腾党。1. 先算清楚账14 tok/s 到底慢在哪1.1 模型不是“算”出来的是“搬”出来的自回归生成模型有个天然特性每生成一个token都必须把模型的全部权重从头到尾读一遍。这不是比喻是实际的访存模式。以27B参数为例FP16精度下权重体积约54GBINT8约27GBINT4约14GB。每次decodeGPU都要把这些权重从显存搬到计算单元权重读完了才做那一点点矩阵向量运算。所以对于单卡部署模型生成速度主要取决于显存带宽或者说“每秒钟能搬多少字节的权重”而不是显卡的宣传算力。假设一张卡的显存带宽在1TB/s左右14GB权重全量读取一次理想情况下每秒最多也就七、八十个token如果模型有一部分挂在CPU内存上或者走了PCIe带宽掉到几十GB/s14 tok/s这个量级就完全合理了。我当时的基线就是这个状态权重虽然量化了但算子没优化、部分层还落在内存里实测14。这一段想告诉大家的是看到大模型推理慢先别急着怪显卡算力不够第一个要查的是权重放哪了、访存路线上有没有堵点。很多人把模型塞满显存后还慢那大概率是框架算子太差要是模型有一半权重在CPU上那慢就是必然的。投机解码之所以有效恰恰是在“访存”这个维度上动了刀。1.2 为什么“算力”不是关键很多人看到模型生成慢第一反应是显卡不够强换更大算力的卡。但在纯生成decode阶段大局势恰恰相反。每个token对应的矩阵乘法规模和读取权重相比小得可怜。GPU真正花时间的地方几乎全在搬运权重上FLOPs占用率可能低到个位数百分比。打个比方一辆载重10吨的卡车每次只运一箱货发动机再猛瓶颈也在“往返次数”上。这解释了为什么投机解码能带来这么大的提升它没有增加算力而是通过一次读取权重校验收多个候选token把“每趟只运一箱”变成了“一车装好几箱”。只要小模型猜得准等效的每token访存成本就会大幅下降。我自己之前也走过弯路一开始以为14 tok/s是因为量化精度太低、显存带宽不够换了两版量化参数速度几乎纹丝不动。后来真正把“每次生成一个token都要全量读一次权重”这笔账算清楚才明白问题不在精度和算力而在“读权重”的频次。投机解码本质上就是降低这个频次所以它对这种场景的杀伤力才这么大。2. 投机解码是怎么把“串行等结果”变成“批量批发”的2.1 实习生写稿专家审稿两个模型搭班干活投机解码的思路很直白用一个1B、3B级别的小模型先快速自回归生成K个候选token然后把这段候选送到27B大模型里大模型并行跑一次前向一次性算出K1个位置的概率分布从左到右逐一校验。连续被小模型猜中的token直接留下从第一个猜错的位置开始之后的候选全部扔掉换成大模型自己的采样结果作为新的起点。下一轮再让小模型从这个新起点继续往后猜。关键点在于大模型每轮只需要读取一次权重却可以验证K1个位置。常规解码生成K1个token要把27B的权重读K1次投机解码把同样的工作压缩成读一次。代价是额外跑一个小模型再加一份校验开销。只要草稿猜中率高、小模型跑得够快这个交易就非常划算。这里还要提醒一点K个候选token的校验不是自由并行最坏情况下可能全部被拒等于白跑一轮小模型。所以小模型的猜测质量直接决定收益上限。用实习生和专家来类比实习生先写一段草稿专家拿过来从头到尾批一遍批得顺利每页只扫一眼就签了效率极高批得满篇是错专家每一行都得重新改那还不如自己从头直接写。2.2 收益不是K倍而是由“接受率”决定投机解码的加速倍数其实由接受率α决定不是K。假设每一轮中草稿模型生成的K个token被大模型接受的概率都是α平均每轮最终能留下的token数大约是1 α α^2 ... α^K。如果K4α0.8期望约4.36个tokenK6α0.8期望约5.29。如果α只有0.4K6的期望只有2.2左右还得搭上小模型生成时间和目标模型验证开销提升非常有限。所以调优的第一目标不是把K调大而是提升接受率。接受率由小模型和大模型的分布匹配程度决定同系列小模型在相近语料上训练词表又一样猜中的概率就高。这也是我最终选择用同一系列1B模型当草稿的原因。有人可能会问159/14约等于11.4倍这个数字看起来比上面的期望公式推算出的值要高或者说不太对得上。原因是基线里模型部分权重在CPU/PCIe侧单个token解码还额外承担通信延迟投机解码把大多数token的生成交给了跑在显存里的草稿模型目标模型只做批量验证而批量验证的访存效率比单token解码高得多。也就是说它同时优化了“搬运次数”和“搬运效率”结果自然不是简单的一个期望公式能完全覆盖的。2.3 什么场景才适合投机解码投机解码不是万金油。它适合单用户、交互式、短文本、延迟敏感的场景你问我答每秒钟多蹦几十个token体验完全不一样。但如果你的场景是多路并发、大批量离线生成框架层面的连续批处理已经把GPU的算力和访存利用得很满投机解码的收益会被明显稀释甚至变成负优化。这也就是为什么很多线上服务默认不投机解码而本地单卡单路跑的人却应该重点试它。3. 实操记录从14 tok/s调到159 tok/s3.1 实验环境与测量方法先交代我自己的实验环境方便你对齐一张显存大概32GB的独显128GB内存系统层面关了交换分区。推理框架用的是一款支持投机解码的开源引擎目标模型就是标题里那个3.8版的27B模型草稿模型从同系列的1B和3B版本里选。为了控制变量我把上下文长度固定成4096提示词裁到约800 token每次固定生成256个token。测速前先跑5轮warmup预热CUDA kernel和缓存再取10轮的平均值。这里有一个很多人忽略的细节测tok/s要区分“端到端平均速度”和“纯decode稳定速度”。前者把prefill首轮计算也摊进去了提示词越长数字会被拉得越低。我报告的一律是纯decode阶段的稳态吞吐也就是在首token出来之后开始计时这样不同配置之间的对比才有意义。3.2 第一轮选对草稿模型从14到80第一次尝试投机解码我直接用3B模型当草稿K4结果只从14涨到22 tok/s。查了下运行日志发现问题出在草稿生成太慢3B模型虽然比27B小得多但每次要生成4个候选token花的时间比大模型验证一次还长而且它占用的显存和大模型抢KV cache验证批次的分配也受影响。后来的实测数据显示这种情况下接受率虽然不错但草稿阶段已经成了新瓶颈。换成同系列1B模型后效果立刻不一样草稿生成速度快了一个量级K4时稳态吞吐直接到80 tok/s以上。所以选草稿模型的关键不是“能力越接近目标越好”而是“速度和分布匹配度综合最优”。1B这种体量显存占用不到1GB生成K个候选的开销几乎可以忽略。小提示草稿模型和目标模型的词表最好一致。如果词表不一致框架要考虑token id对齐和额外的embedding映射不仅更慢还可能降低接受率。同系列小模型天然满足这个条件这也是我用同系列的一个重要原因。3.3 第二轮草稿长度K的收益曲线从80到159草稿模型定下1B之后我开始扫K值。K2时大概是75 tok/sK4时120K6时到了159K8反而掉回125左右。这个曲线说明两点第一K确实存在最优区间不是越大越好第二短草稿提升有限长草稿收益递减甚至反弹。为什么K8会变慢因为草稿模型生成K个token的时间随K线性增长而大模型验证一次的计算量也和K相关更关键的是候选序列越长后半段的接受率越差。草稿模型在前几个token上可能和大模型分布重合度很高但越往后它的错误会累积大模型就越容易在中间某个位置打断它导致后面的候选全部作废。K6在“期望token数”和“校验开销”之间刚好踩到甜点。3.4 第三轮量化、KV Cache和显存碎片的最后压榨速度来到159之后我再回头检查显存和整体配置。目标模型用INT4量化草稿模型用INT8KV cache在启动时就按4096上下文完整预分配避免运行中动态扩容反复触发显存碎片整理。这一套改下来虽然tok/s没再暴涨但稳定性明显好了长对话也不会跑到一半爆显存。我还做过一次逆向实验把一部分层有意放在CPU上跑模拟很多人“显存不够所以CPUGPU混跑”的用法。结果投机解码的提速能力几乎消失因为每轮目标模型验证都需要等CPU回传权重PCIe通信把批量验证的优势吃光了。所以我的建议很明确既然要投机解码目标模型和草稿模型就要完整放到卡里显存确实不够就先换更狠的量化或者减少上下文长度总之别用offload硬凑。3.5 各配置实测数据对比把整个调优过程的数据整理成一张表方便你对着看。数据来自我自己的固定测量流程同一模型、同一上下文长度、同一提示词模板唯一区别是每轮改一个变量。配置草稿模型投机长度K稳定tok/s备注基线无无14部分层offload算子未优化投机 · 3B草稿3B INT8422草稿生成变瓶颈投机 · 1B草稿1B INT8275接受率高但轮产出token少投机 · 1B草稿1B INT84120甜点区入口投机 · 1B草稿1B INT86159本轮最优投机 · 1B草稿1B INT88125长草稿收益衰减这张表不是让你照抄参数而是给你一个调参顺序样本先选草稿模型再扫K最后抠量化与显存细节。4. 调优中踩过的坑排障实录4.1 开了投机解码反而更慢第一步不是调参而是看日志我第一次把投机解码开关打开时差点直接关掉某些prompt下只有10 tok/s。后来看日志才发现框架根本没用上目标模型校验草稿的路径而是在CPU上跑了草稿模型再通过PCIe把候选token传回显卡。这种“伪投机解码”当然只会更慢。排查方法很简单看日志里有没有acceptance rate、每轮验证token数、草稿模型设备这几项。如果没有这些日志大概率功能没真正生效。另外如果草稿模型和设备不在同一张卡框架可能会静默退化。我帮一位朋友调同样的问题他爆显存后把草稿模型放在CPU上速度反而低于基线。投机解码的前提是草稿生成足够快放在CPU上还不如不开。4.2 输出质量变差拒绝采样没接对投机解码理论上不改变输出分布但前提是实现严格的拒绝采样。我中途换过一个分支版本为了省事用了“近似接受”策略结果同样温度下输出明显变傻逻辑混乱。网上有人抱怨投机解码让模型质量下降绝大多数是这一步实现有问题。如果你要用投机解码又关心输出一致性一定确认框架走的是标准规范采样大模型给草稿的每个token打分不通过就拒绝并回退而不是无脑收下草稿。我还试过在temperature0.6下对比开与不开投机解码的输出严格拒绝采样实现下结果一致不严格实现下连高频词偏好都会漂移。所以“质量不变”不是默认的是要靠实现保证的。4.3 显存爆掉草稿模型和KV Cache打架27B INT4权重约14GB草稿1B INT8不到1GB看起来很宽裕但实际跑起来框架预分配的KV cache、CUDA context、中间激活层会占掉不少。我在K8时爆过一次显存因为验证batch对应的中间激活随K涨而KV cache又是按最大上下文预分配的。解决方式是把K降到6、复用显存池、必要时减小最大上下文长度到4096。不要为了提速把K调到8以上至少在这套环境下不划算。4.4 长文本生成时收益缩水我只生成长文本时测过一组固定生成2000 token投机解码平均只比基线快约7倍而短文本256 token是11倍。原因是长输出过程中框架的缓存管理、显存换页、CPU采样开销逐渐不占主导平滑掉一部分投机解码的红利同时后期接受率会随对话变长出现波动。如果你的应用是长文生成别只拿短文本的159当预期。4.5 如何科学地“晒”tok/s最后聊一个很多人不问但很要命的问题tok/s统计口径。有人把prefill时间摊进全过程有人只算decode稳定段有人用temperature0贪心有人用随机采样有人测的是单次瞬时峰值有人取长窗口平均。同样一个配置不同口径可以差出两三倍。我建议跑分时固定几个条件提示词长度、生成长度、warmup轮数、统计窗口、相同采样参数。不然你看到的159和另一个人看到的159可能根本不是一回事。5. 参数速查与方案选型建议5.1 投机解码参数速查表整理一个速查表方便你直接抄作业。这里的数值以我实测的27B1B组合为基础迁移到其他模型时需要微调。参数推荐值说明草稿模型大小目标模型的1/10~1/30或1B~3B太大草稿生成慢太小接受率低草稿模型精度INT8兼顾速度与分布匹配目标模型精度INT4~FP8显存允许可以用更高精度K投机长度4~6从6开始扫找到吞吐拐点采样温度与关闭投机解码时一致配合严格拒绝采样上下文长度4096~8192注意KV cache显存占用5.2 不同场景的选型建议如果你和我一样是本地单卡、交互式对话那这套组合直接参考27B INT4目标模型 1B INT8草稿模型 K6。如果显存只有16GB可以用更小的目标模型、更短的上下文K先降到4。如果是多卡并行目标模型被切成多块后单卡访存压力下降投机解码的收益会打折扣建议重测。如果跑的是离线批量任务优先开连续批处理和KV cache复用不要迷信投机解码。总的来说先判断你的瓶颈是不是“单路decode访存受限”是才谈得上投机。5.3 最后再分享一个小技巧最后分享一个我后来每次调优都会做的事把推理框架里打印接受率日志打开观察草稿模型每一轮被目标模型接受了几个token。接受率低于0.5先换草稿模型别急着加K接受率在0.7以上再去扫K才有意义。我这次从14到159最大的分水岭其实是把草稿模型从3B换成1B——接受率只降了几个点草稿生成时间和显存占用却小了一大截。调优很多时候不是把所有参数都往上堆而是找到那个收益递减的拐点。