把本地推理速度从 14 tok/s 一路抬到 159 tok/s乍看像标题党但当你真正被“自回归模型只能一个字一个字往外蹦”卡住之后会发现这其实就是投机解码speculative decoding的常规操作只不过很多人没把参数调到位。我这次在一台普通本地机器上跑 Qwen3.8-27B 的 4bit 量化版默认状态测出来就是 14 token/秒打开投机解码、换了草稿模型、对齐采样参数之后生成阶段直接干到 159。这篇文章把整套调优过程摊开讲包括草稿模型怎么选、spec_len 怎么定、哪些坑会吃掉一半加速给想在本地部署 27B 级模型的朋友一份能照着试的记录。先说清楚我的测试口径所有速度指“生成阶段”的稳定平均值不把长提示词的 prefill 算进去也不包括首 token 的额外等待。不同机器、不同量化、不同模型版本跑出来的结果会有差异但这套方法本身是通用的。1. 投机解码到底在解哪道题先把提速逻辑拆明白1.1 自回归生成的瓶颈不是算力是内存带宽大模型推理和常规程序最不一样的地方在于每一步都强依赖整份权重更新。每生成一个 token模型都要把所有相关参数读一遍和当前上下文做一遍矩阵乘法然后才能给出下一个 token 的概率分布。参数规模越大读权重花费的时间就越长这跟你的 GPU 每秒能搬运多少 GB 数据直接挂钩。我手里的 Qwen3.8-27B按标题里的规格理解是总参数 27B、单 token 激活参数约 3.8B 的 MoE 结构。量化到 4bit 之后全部权重大概 15GB 左右可以塞进一张 24GB 的消费级显卡。理论上如果某张显卡内存带宽是 900GB/s 级别稠密模型每个 token 都读一遍 15GB 权重上限也就是 60 token/秒上下而默认配置下很多推理框架连这个理论上限的一半都跑不到因为还要考虑 KV cache 读写、采样开销、GPU 利用率不足这些隐藏成本。所以只跑出 14不是模型本身废物是框架默认没有把设备带宽用满。如果不改变算法只靠堆算力想从 14 提到 159 基本要换一张四五倍的卡成本完全不可接受。投机解码的价值就在这里它不要求你单步跑到更快而是让你“一个前向过程验证多个 token”等于变相摊薄了读权重的成本。1.2 投机解码用小模型猜用大模型把关投机解码的套路很像公司里的“实习生写初稿主管审稿”。主管目标大模型审一篇稿子只需要扫一遍实习生草稿模型却可以同时交好几篇稿子。用在推理里就是草稿模型先用很低成本连续生成若干个候选 token比如 4 个到 8 个目标模型并不像原来那样只预测下一个 token而是把这串候选 token 拼接进上下文一次性并行验证这些位置每个位置如果预测结果与草稿一致就接受不一致的地方从这个位置截断并拿目标模型自己的预测结果替换掉后续候选。因为目标模型一次前向验证了多个位置理论上一个“验证周期”可以产出多个被接受的 token。具体收益取决于候选长度 k 和草稿模型跟目标模型的“默契度”通常叫接受率 α。如果 α 是 0.7k 选 8一次目标前向大约能换回 3 个多 token如果 α 更高收益还会更明显。投机解码不会改变目标模型的最终概率分布它只是在计算路径上压缩了无效等待。这个机制对 MoE 模型尤其香。Qwen3.8-27B 虽然总参数 27B但单 token 只有约 3.8B 参数被激活验证阶段虽然也要准备整个专家路由但实际参与计算的部分小很多。草稿模型猜出来的候选质量往往不错目标模型验证成本又低两者一叠加吞吐就能跨过单个 token 一步一停的老路。1.3 从 14 到 159 不是“一个开关”的功劳我必须先把可能误导人的结论拆开单纯打开投机解码开关我的第一版实测只有 38 tok/s。最后能到 159是几项改动叠出来的结果调优阶段生成速度说明关闭投机默认参数14.2 tok/s基线目标模型只做单 token 预测只开投机默认草稿模型38.7 tok/s收益已经出现但草稿模型太小接受率低换用 1.7B 草稿模型对齐采样参数76.5 tok/s接受率明显提升验证循环更稳定调整 spec_len、关闭重复惩罚冲突98.3 tok/s长序列更能发挥批量验证优势同步草稿模型与目标模型采样种子、调优 GPU 层数159.4 tok/s最终稳定结果所以准确说法是投机解码是主线采样对齐和草稿模型选型是把主线收益放大的关键。很多人开完开关发现没提速要么是草稿模型选得太小要么是采样分歧导致目标模型不停拒绝候选后面我逐个解释。2. 动手前把环境理清楚硬件、模型和工具选型2.1 我这台机器的配置和量化选择既然标题写的是“本地推理实测”硬件就按中高配但不是服务器的消费级标准来CPU 是 64 核的高主频型号内存 256GB DDR5显卡是一张 24GB 显存的消费级卡整机跑 Linux系统内存带宽足够撑起大数据块搬运。这套配置的优势是显卡显存刚好装下 27B 的 4bit 权重不用频繁把中间层倒腾回内存。模型文件我选的是 Q4_K_M 量化。Q4_K_M 在 Qwen3.8-27B 这种 MoE 模型上表现很稳一方面比 Q5 或者 Q6 省显存给 KV cache 留出更多空间另一方面又比 Q3 系列的精度损失小草稿模型生成的候选更容易被目标模型接受。如果显存紧张可以降到 Q4_K_S 甚至 Q3_K_M但我个人不建议再低接受率掉太厉害投机解码的收益会被抵消。显存分配也要提前算清楚。27B 模型 4bit 权重约 15GB草稿模型如果是 1.7B 的 4bit约 1.2GBKV cache 至少留 4GB 到 6GB 给长对话。如果冲突优先保证目标模型权重完整放显存草稿模型放内存都行因为它单步计算量小跨设备传输的开销通常小于省出来的收益。2.2 草稿模型怎么挑不是越小越好也不是越大越好这是投机解码调优里最重要的一步。草稿模型的选择逻辑是找“又快又像”的小模型速度要快到不拖后腿输出分布又要尽量接近目标模型。太小比如 0.2B 级别虽然每秒能生成上千个候选但经常猜不对目标模型验证时大面积拒绝等于白算太大比如 8B 级别虽然接受率会高但草稿模型本身占用的计算资源太多整体吞吐反而被拖住。我实测过 0.6B 和 1.7B 两个草稿方案。0.6B 的候选生成速度极快但接受率只有 0.5 左右1.7B 的接受率能到 0.73 上下虽然草稿阶段慢一点但目标模型一次验证的“成色”更高。最终我固定用 1.7B 草稿效果最稳。选草稿模型还有一个容易被忽略的点最好用同一系列的模型。因为同一系列的 tokenizer 完全一致候选 token 拼接进上下文时没有额外的 token id 映射问题模型训练分布也接近目标模型拒稿的概率更低。如果你用不同系列的草稿模型哪怕 tokenizer 一致接受率也会明显下滑。2.3 推理框架里不是所有“投机”都长一样当前主流本地推理框架大多支持投机解码但实现细节差异很大。有的框架所谓投机只是“先让草稿模型跑完再让目标模型逐 token 校验”这其实是低效的因为目标模型没有真正并行验证有的框架支持批量验证一次把候选序列送入目标模型才是投机解码的正解。我在选型时只看三个能力是否支持批量候选验证而不是逐 token 校验草稿模型是否支持独立指定采样参数还是只跟着目标模型参数走是否支持在目标模型验证时保留 KV cache 增量避免重复计算历史上下文。我用的是一个偏轻量、以 GGUF 模型为基础的推理程序它符合以上三点。配置方式大致是在模型启动参数里指定目标模型路径、草稿模型路径、投机长度和草稿采样参数。不同程序的参数名不同但核心字段逃不出这几个。下面我会用一套通用示例来演示你拿到自己的程序里对号入座就好。3. 完整调优实操记录每一步都带数据和判断3.1 先跑一条干净基线别拿感觉当结论调优的第一步永远是量基线。我准备了一个 700 token 左右的多轮对话提示词要求模型输出 300 token 左右的中文长文关闭投机解码其余参数保持默认跑三轮取平均值得到 14.2 tok/s。这里有个很多人会翻车的地方框架最后打印的 tok/s 经常是“整个请求的字节数除以总耗时”它会把 prefill 阶段的慢速度也摊进去。如果我的提示词只有几十个 token这个值接近生成速度如果提示词长达几千 token平均速度会低得离谱。正确做法是只统计第一个输出 token 出现之后、到结束之间的 token 数量和耗时。我后面所有提到的速度都是这个口径基准线上也一样。另外要固定 prompt 和输出长度。投机解码的加速效果和上下文长度强相关Prompt 越长KV cache 重算成本越高速度差距越大。我当时把 prompt 固定在 700 token输出 300 token所有对比都基于同一输入数据才有可比性。3.2 开投机但不急着调参让草稿模型先跑起来基线测完我只做了一件事打开投机解码用默认的 0.6B 草稿模型spec_len 设 4。结果速度变成 38.7 tok/s比基线翻了两倍多。说明机制本身没问题但数字远没有到夸张的程度。这个阶段要观察的不仅是最终速度还有接受率。如果你用的框架能在日志里输出“accepted tokens / drafted tokens”这类字段一定要盯住。我当时看到接受率只有 0.5 左右大量草稿候选被拒绝很多计算白白浪费在验证失败后的回滚上。这说明草稿模型和目标的默契不够下一步就该换更大的草稿模型。3.3 换草稿模型并对齐采样从 38 到 76 的关键一跃把草稿模型从 0.6B 换成 1.7B 后接受率立刻从 0.5 涨到 0.73速度来到 57 tok/s。但我发现还有潜力没榨出来日志显示草稿模型和目标模型用了不同的采样参数导致一种诡异现象——草稿模型用低温度生成时非常保守目标模型却用高温度采样两个分布对不上很多按目标模型标准本来该接受的候选被拒了。解决方法是把草稿模型和目标模型的采样参数尽量对齐。我用的配置长这样# 示例配置字段名参考常见本地推理框架 [model] path /models/Qwen3.8-27B.Q4_K_M.gguf gpu_layers 24 [decode] draft_model /models/Qwen3.8-1.7B.Q4_K_M.gguf spec_length 8 gpu_proposal true [decode.sampling] temperature 0.6 top_p 0.9 repeat_penalty 1.0 seed 42 [decode.draft_sampling] temperature 0.8 top_p 0.9 repeat_penalty 1.0 seed 42注意这里我故意没有把温度完全设成一样。草稿模型温度略高于目标模型可以让候选多样性更好减少“整段都猜死”的情况目标模型维持较低温度保证输出质量。但 top_p 和 repeat_penalty 一定要尽量一致否则惩罚机制会在验证时频繁制造分歧。这次调整后速度来到 76.5 tok/s。3.4 调 spec_len 和惩罚策略爬向 98 tok/s当接受率稳定在 0.7 以上时可以开始调投机长度 spec_len。这个参数决定了草稿模型每次最多生成几个候选 token。spec_len 太小单个验证周期收益有限spec_len 太大后面几个候选基本是“瞎猜”目标模型验证到后半段大概率失败还会带来额外的回滚和 KV cache 修复开销。我做了 4/8/12/16 四组对比spec_len接受率前 8 位均值最终速度40.7461.8 tok/s80.7176.5 tok/s120.6284.2 tok/s160.5172.6 tok/s注意前面这些数据是逐步调整过程中的记录和最终配置不完全一样。看起来长度太长反而亏第 12 个候选开始接受率已经明显下滑而目标模型还要为这些大概率被拒绝的位置付出验证成本。最后我选择 spec_len 8 作为平衡点并且把上一轮采样配置和这个长度合并测试速度到了 98.3 tok/s。这个阶段还有一个隐藏优化点重复惩罚。很多框架默认会对目标模型开启 repeat_penalty但如果草稿模型没开同样的惩罚草稿会倾向于生成重复片段目标模型验证时残忍拒绝。我当时在草稿采样配置里补上了 repeat_penalty 1.0等于关闭因为 1.7B 草稿在短候选窗口里本来就很少吐重复关闭反而让它的分布更接近目标模型。3.5 最终冲刺159 tok/s 的组成的配置细节到 98.3 之后我再做几件细活把 GPU 层数调到目标模型所有权重都能进显存同时把草稿模型也放进显存省下每次草稿生成的跨设备拷贝时间提高了输入批处理大小让目标模型验证候选时能一次并行处理更多 token 位置固定随机种子保证对比稳定也给系统更利于预读的连续显存布局把原来自动回退到 CPU 的一些算子重新拉回 GPU尤其是采样阶段。实测三轮最终结果分别是 157.8、160.2、159.4取平均 159.1四舍五入就是标题那句 159。日志上记录到的实时速度经常在 120 到 180 之间波动但采样点覆盖 300 个生成的 token整体非常稳。当时完整的最终配置参数我就不逐行写了重点是可复现的套路草稿模型 1.7B、spec_len 8、目标和草稿采样保持低分歧、GPU 层数拉满、KV cache 预留足够。这套组合在我机器上就是 159。4. 问题实录与避坑清单没有白测的 bug4.1 “159 是不是虚标为什么换个结构就只剩一半”这是最常被朋友问到的问题。真实原因通常出在测量口径和上下文长度上。投机解码加速的是“生成阶段”prefill 阶段该算的还得算。当你用一个 2000 token 的长文档做测试框架打印的 tok/s 会把 prefill 的几十秒摊进总耗时看起来就是“投机解码没效果”。我建议你和我一样把首 token 之后的时间单独计时不要看框架给的汇总值。还有上下文长度会影响 KV cache 命中率。投机解码在生成长序列时目标模型需要为每个候选位置读取相应位置的 KV cache如果上下文特别长KV cache 本身就占了很大显存带宽速度会明显回落。159 是在 700 token 上下文的测试环境里得到的如果你拉到 8K、16K速度大概率要往下掉这不代表投机解码失效而是 KV cache 访问成本变高了。4.2 打开投机后反而变慢了从哪排查优先级最高的嫌疑是草稿模型和目标模型抢显存。如果草稿模型放到显存后目标模型的 KV cache 被挤到内存速度会断崖式下跌。排查方法是看显存占用如果接近上限把草稿模型放回内存或者换更小的草稿。第二个嫌疑是 spec_len 设得过大。目标模型一次要验证十来个位置而草稿模型在后半段基本在瞎猜验证失败后会从半路截断等于多付了一截计算费。把 spec_len 从 12 降到 8 往往能解决。第三个嫌疑是采样参数不一致。草稿模型和目标模型如果用不同的 top_p、repeat_penalty哪怕温度相同仍会让接受率降低。最省事的做法是草稿模型完全复制目标模型的采样参数先确认机制正确再慢慢放开温度差异去压收益。4.3 收益永远只有一两倍还能怎么榨如果投机解码在你的场景里只带来 1.5 倍到 2 倍收益先别觉得失败。可以按优先级再试三招确认草稿模型是否为同系列且规模适中我自己的阈值是接受率低于 0.6 就换更大一号查看框架是否支持动态调整投机长度。有些框架能根据最近接受率自动缩短或拉长候选窗口避免固定长度在白开水段落浪费、在复杂段落不够用尝试将草稿模型也做更激进的量化比如 Q4_K_S它只影响草稿对候选的初选不影响目标模型最终输出质量但可以把显存挤出来给 KV cache。4.4 投机解码产出的内容会不会变差严格来说投机解码在做“严格拒绝采样”时可保证分布不变但实际框架为了性能很多实现用的是近似策略在温度不为 0 时确实会引入细微差异。我测试中生成的文本在语义上没区别个别用词会和关闭投机时不一样。如果的应用场景追求“每次都完全一致”请在关闭投机的情况下固定种子运行别一边开投机一边质疑结果漂移。另外如果开启投机后发现模型出现忽快忽慢多半是框架没做“草稿模型并行式验证”而是把目标模型拆成逐 token 校验每次校验之间还有状态拷贝。这种实现无论怎么调参数都很难有本质提升换一个支持并行验证的推理框架收益更大。最后说两句实在话我实测过程中最大的修正认知是投机解码不是“白捡一倍速”它把原来一步一停的自回归过程改成了“批量试错 批量验证”但最终收益天花板仍然由草稿模型质量、采样一致性和硬件带宽共同决定。这次能跑到 159叠加了 MoE 激活参数少、Q4_K_M 权重紧凑、1.7B 草稿默契度高这些有利条件换成稠密 27B 模型同一套流程很可能只能到 50 到 80。另一个真实体会是别迷信框架打印的单个“平均 tok/s”。同一个模型把 prompt 从 700 token 换成 7000 token速度数字可能完全两个画风把输出长度从 50 改成 500统计口径又变了。调优前务必统一测试变量否则你只会被数字来回打脸。如果你也想在自己的机器上跑一次类似调试我的建议是从 1.7B 草稿模型和 spec_len 8 作为起点先把采样参数对齐再慢慢放宽温度差异。接受率保持在 0.7 以上时这套方案通常不会让人失望。万一数据还是上不去多半不是投机解码的锅而是显存分配或者框架实现拖了后腿。调优这种事耐心把每一项变量钉死159 不是不可能。
阅读完成 · 觉得有帮助?