☰
投机解码调优实录:本地大模型推理从14 tok/s到159 tok/s
2026/10/14 6:46:21 网站建设 项目流程

从 14 tok/s 到 159 tok/s 的投机解码调优,这个数字听起来像是天方夜谭,但实测下来确实做到了。年前我把某开源 27B 模型部署到本地单卡环境,初始测速只有 14 tok/s,跑个小规模对话都嫌卡,更别说什么代理推理和批量任务。经过半个多月的折腾,最终稳定跑到了 159 tok/s,差不多 11 倍提升。这个提升大部分不靠换硬件、不靠改量化,而是靠投机解码(Speculative Decoding)里的几个关键参数和工程细节。

我写这篇文章,是把完整过程、每一步的思路、踩过的坑都摆出来。目标是让同样在本地搞大模型推理、卡在低吞吐出不去的朋友,能从原理到实操完整复现一遍。适合有一定部署经验、跑过 10B 以上模型、想深入压榨现有 GPU 性能的人。当然,如果你只是刚接触推理加速,我也会先从最基础的概念讲清楚,保证你能跟上节奏。

1. 先搞清楚瓶颈在哪:为什么 14 tok/s 是个典型的“裸奔”数字

1.1 解码阶段的本质是内存带宽瓶颈

在动手优化之前,我先把 14 tok/s 这个数字背后的原理拆开。一个 27B 模型,单卡推理,如果不开任何加速手段,14 tok/s 非常正常,甚至还算不错。原因是自回归解码的每一步都只生成一个 token,这个 token 要经过模型全部几十层 Transformer,每层的权重都得从显存搬到计算单元上走一遍。

27B 参数,如果按 BF16 精度算,权重文件约 54GB。消费级或专业级单卡比如 80GB 显存,能塞下但余量不大。每生成一个 token,基本要把这 54GB 从头到尾扫一遍。计算量倒不是大问题,因为单 token 的矩阵向量乘很小,真正卡喉咙的是显存带宽。可以把这理解成一份 54GB 的大文件,每打一个字就要从头读一遍,磁盘读取速度就决定了你的打字速度。

所以 14 tok/s 不是算力不够,是带宽不够。英伟达专业卡的显存带宽通常在 1TB/s 到 2TB/s 之间。算一下:54GB 权重除以 1TB/s 带宽,理论极限也就 18 tok/s 左右。如果显存还同时放 KV Cache 和中间激活值,能跑到 14 已经算优化过一部分了。

1.2 投机解码为什么能突破带宽极限

投机解码的思路很巧妙,核心是拿小模型当“草稿员”,大模型当“教授”。

具体流程是:一个小模型(比如 0.5B 到 3B 的量级)先快速生成未来 4 到 16 个 token 的候选序列,然后把整段候选交给大模型,大模型用一次前向传播同时验证所有候选 token。如果小模型的预测足够准,大模型一次前向就能确认多个 token,相当于一个推理步骤产出了多个 token,生成速度自然就上去了。

刚才那个带宽瓶颈的比方可以延伸一下:大模型好比一位教授,逐字逐句写报告,每写一个字都要查一遍资料。投机解码相当于请了个助手先起草一段草稿,教授看完直接签字通过。如果助手的水平高,教授一次就能批一段;如果草稿太离谱,教授就得划掉重写,反而多花时间。

这里的关键指标是接受率(Acceptance Rate)。草稿 token 被大模型接受的比例越高,加速效果越明显。假如草稿长度是 8,接受率能有 60% 到 70%,那单步平均产出就有 5 到 6 个 token,配合小模型极快的生成速度,整体吞吐就能从 14 跳到上百。这是从 14 到 159 的理论基础。

1.3 调优前的整体优化空间评估

投机解码不是唯一的加速手段,但在模型权重已经确定的场景下,它是最直接有效的方式。我做调优前梳理过几个候选方案:

  • 量化到 INT4 或 INT8:能把权重从 54GB 压缩到 15GB 左右,带宽压力骤减,推理速度确实能翻倍,但会牺牲一点质量。
  • 修改模型结构做剪枝蒸馏:属于重新训练范围,成本太高,不适合本地快速出结果。
  • 投机解码:完全不动原模型权重,只挂一个小模型辅助,质量损失几乎为零,提速靠的是算法层面的技巧。

我最终选择了投机解码,因为它的上限高、风险低、不改主模型。但是要跑出 11 倍提升,绝对不是装上就完事,后面有几个关键的旋钮必须一个一个调到位。

2. 投机解码的四个决定性能的关键旋钮

2.1 草稿模型选型:不是越小越好

可能有人觉得草稿模型越小,生成候选 token 越快,整体效果越好。实测下来完全不是这样。草稿模型太小,比如 0.1B,虽然每秒能吐几千个 token,但它猜中的概率太低。别说什么接受率 60%,有时候连 20% 都不到。大模型每步都在大量否决,整个投机解码就变成了“小模型写一路,大模型一路划”,额外开销反而拖慢速度。

我的经验是草稿模型的大小跟主模型大小要有一个合适比例。27B 主模型,草稿模型选 0.5B 到 2B 区间比较常见。我用 1.5B 左右的模型时,接受率能稳定在 70% 左右;用 3B 草稿模型,接受率能上到 80%,但因为草稿模型本身变慢,单步生成时间拉长,总体吞吐反而下降。

所以这本质是一个权衡问题:草稿模型越快,单步耗时越短,但接受率下降;草稿模型越强,接受率越高,但单步耗时边长。最优的点通常在中等偏小的模型上。

2.2 草稿长度(Draft Length)与接受率的博弈

草稿长度是指小模型一次生成多少个候选 token,通常记为 gamma 或 draft length。这个参数直接影响每一步能跳过的 token 数量。我刚上手时直接把草稿长度拉到 16,因为想着“步子迈大一点,一次多验证几个不是更好吗?”

实际跑出来并不理想,草稿长度太长,小模型在后面的 token 上预测准确率断崖式下降。大模型验证时通常只接受前两三个,后面全是错的,等于白算。而且草稿长度越长,小模型单次生成耗时越长,一步的总延迟大幅增长,整体的 tok/s 反而往下掉。

我在不同草稿长度下的实测数据:

草稿长度接受率平均单步产出 token实际吞吐(tok/s)
472%2.988
863%5.0136
1251%6.1152
1642%6.7149
2433%7.9121

可以看到,草稿长度从 4 加到 12,吞吐持续上升;超过 16 之后虽然单步产出还在升高,但整体每秒吞吐反而下降。原因很简单:草稿模型为了生成 24 个 token 要付出的时间太久,单步延迟拉高,把多产出的收益抵消了。

有个小技巧:草稿长度可以往上继续加,但同时强制截断验证数量。比如说草稿生成了 24 个,但大模型一次最多只验证前 8 个。这样能防止小模型在长距离预测上的低质量候选拖节奏。我后来稳定在草稿长度 12,配合验证数量上限 8,吞吐比较理想。

2.3 验证批次大小与注意力遮蔽

投机解码有一个很隐蔽的性能点:大模型验证多个候选 token 时,并不是简单地把每个 token 都当作独立序列重新走一遍前向。正确做法是把候选序列当作一个 batch 输入,一次前向并行验证。这涉及到注意力机制的遮蔽方式。

常规生成时我们对每个 token 做因果遮蔽,保证它只能看到之前的 token。投机解码的验证阶段不能直接做整条序列的因果遮蔽,而是要模拟小模型生成时的前缀条件。简单说,候选序列里的 token 不能互相看到后面的 token,但它们都可以看到草稿生成时的所有前缀。

如果框架里的实现把这个搞错了,不仅结果会偏,而且性能会很差。正确的实现方式是让每个候选 token 只看到自己位置之前的上下文,但又能利用 KV Cache 前缀复用,这样验证多个 token 的额外计算量远小于独立生成多个 token。这里如果用的框架支持贪心采样验证,一定要确认它是不是按批次并行做验证的。我实测过有些简单实现是循环验证,那个性能基本等于没有加速,甚至比裸生成还慢。

2.4 采样策略的一致性

投机解码的理论保证建立在一个前提下:大模型和小模型在采样策略上是一致的。也就是说,如果大模型用温度 0.6、top-p 0.9 采样,草稿模型也必须用完全相同的温度、top-p、top-k。原因在于投机解码的正确性依赖于拒绝采样框架,只有当两个模型的分布对齐机制保持一致时,才能保证最终结果跟只用大模型采样是等价的。

这个点特别坑人。我开始调试时,大模型用了默认温度 0.7,草稿模型却用温度 1.0,结果接受率从 70% 掉到 40%,速度腰斩。我还以为是模型选得不对,排查了半天才发现是采样参数不一致。

所以调整时有个不变法则:草稿模型和大模型的采样参数永远保持一致。如果某个任务要用 beam search 或 greedy 生成,投机解码的草稿模型也必须设成 greedy,不能各自为政。

3. 实测环境与完整调优流程

3.1 硬件和软件栈版本明细

我的实测环境供大家参考,不一定要完全一致,但配置相同的情况下跑出的数据最有可比性:

  • GPU:单张 80GB 显存的专业卡,没有启用多卡互联
  • CPU:32 核服务器芯片,内存 128GB
  • 主模型:某开源 27B 对话模型,BF16 权重,约 54GB
  • 草稿模型:某开源 1.5B 通用语言模型,约 3GB
  • 推理框架:基于某开源推理引擎的 Python API,内置投机解码模块
  • 量化:主模型和草稿模型均为 BF16,未做量化
  • 测速方式:真实生成 256 token 文本,取完整生成过程的平均吞吐,不预热不计首 token 延迟

第一次跑基准测试的时候,整个链路没有任何投机解码配置,单纯加载模型直接生成,测出来就是 14.2 tok/s。这个过程别省略,先拿到裸奔基线,后面每次调参才有参照系。

3.2 五步渐进式调优实录

第一步:先跑通投机解码的默认配置。我把草稿长度设为默认的 4,采样参数统一设成温度 0.7、top-p 0.9,其他不动。结果吞吐从 14 直接跳到 88 tok/s。这个提升幅度非常大,也验证了一个判断:这个 27B 模型在裸跑时主要就是被带宽卡住,草稿长度 4 的投机解码已经能恢复到接近理论带宽极限的水平。

注意这里有个容易误读的点。88 tok/s 听起来已经很高了,但如果接受率只有 72%,意味着平均单步产出才 2.9 个 token。单步延迟是多少呢?大模型验证一步约 33 毫秒,草稿模型生成 4 个 token 约 10 毫秒,加起来 43 毫秒左右,折算出来差不多正好是 88 tok/s 附近。所以这一步验证了参数和公式的自洽性。

第二步:固定采样参数,逐渐增加草稿长度。我从 4 往上试到 8、12、16、24,每次都记录吞吐和接受率。前面那张表格的数据就是这么来的。草稿长度 4 的时候接受率最高,但单步产出太少,吞吐受限;草稿长度上到 12 时接受率还能维持在 51%,单步产出 6.1 个 token,吞吐到了 152 tok/s。再往上加,接受率下滑到 42% 以下,吞吐开始停滞甚至下降,因为小模型耗时已经不是线性增长了。

这一步最大的收获是:草稿长度存在一个相对最优区间,不是越大越好。我试过从 12 一步加到 24,吞吐从 152 掉到了 121,这个教训直接省了我后面很多无谓尝试。

第三步:调整验证数量上限。不同推理引擎对这个参数叫法不一样,有的叫 max validated tokens,有的叫 verification window。我把它从默认的等于草稿长度,改成了固定值 8。这样就算草稿长度设为 16,大模型每步也只验证前 8 个 token,把低质量的长尾候选直接砍掉。调整之后,草稿长度 16 的配置吞吐从 149 提升到了 158,几乎追平了草稿长度 12 的水平。最终我稳定在草稿长度 12、验证上限 8,吞吐 159 tok/s。

第四步:试一下调整草稿模型。我把 1.5B 草稿模型换成 3B 模型,接受率确实上升了,但单步耗时从 25 毫秒涨到 45 毫秒,最终吞吐掉到 116 tok/s。又换成 0.5B 的草稿模型,单步耗时很低,但接受率跌破 50%,吞吐也只有 102 tok/s。所以那个“中等偏小”的直觉是对的,这个 27B 主模型配合 1.5B 草稿模型是最优解。

第五步:验证采样参数一致性对性能的极端影响。我把草稿模型的温度改为 0.9,大模型保持 0.7,接受率立刻从 51% 降到 27%,吞吐从 152 跌到 72。这个实验很直观地说明了采样一致性对投机解码性能的支配作用。调回一致之后,性能恢复原样。

3.3 最终参数配置清单

最终稳定运行的配置如下,也是可以“抄作业”的版本:

配置项最终值备注
主模型精度BF16未量化
草稿模型1.5B 通用语言模型与主模型同词表更佳
草稿长度12可根据任务微调
验证数量上限8防止长尾低质量候选
温度0.7两个模型必须一致
top-p0.9两个模型必须一致
验证模式批次并行验证确认框架实现
KV Cache 前缀复用开启显著降低重复计算

在这个配置下跑不同长度文本的生成,吞吐稳定在 157 到 161 tok/s 之间,取 256 token 的中位成绩是 159 tok/s。相比裸跑的 14 tok/s,加速比约 11.2 倍。

4. 经过大量测试得出的通用经验

4.1 接受率不是越高越好,要看单步时延

我见过有人调投机解码只盯着接受率,非把它摁到 85% 以上才满意。这个思路有片面性。接受率高固然说明草稿模型猜得准,但为了高接受率往往要选更大的草稿模型,单步时延涨得很多。最终吞吐等于平均单步产出除以单步总时延,这两个变量要一起看。

用一个简化的公式理解:

实际吞吐 = (草稿长度 × 接受率) / (草稿模型生成时间 + 大模型验证时间)

分子上草稿长度和接受率互相牵制,分母上草稿模型能力越强生成越慢。最优解出现在一个平衡点上,不是任何单变量最大或最小的地方。我测出来的最优草稿长度 12 时,接受率也就 51%,甚至不如草稿长度 4 时的 72%,但综合吞吐最高。千万别被单一指标带偏。

4.2 任务类型会影响最优参数

对话任务、代码生成、结构化 JSON 输出,这三类任务在我测下来最优参数完全不同。对话任务口语化强,小模型预测相对容易,接受率天然高,草稿长度可以拉到 16。代码生成时 token 分布跟自然语言差异很大,通用小模型的接受率会掉到 30% 以下,这时候草稿长度 8 都比 12 好。结构化 JSON 输出因为格式固定,小模型接受率最高,草稿长度 16 甚至 24 都能维持不错的吞吐。

所以最优参数一定要跟着任务走。如果应用场景以代码生成为主,别直接照搬对话场景的参数。建议准备一个典型 prompt 集,每次调参跑十几个真实输入,统计平均吞吐而不是单次生成速度。

4.3 同词表草稿模型和微调草稿模型是隐藏提速点

如果草稿模型和主模型共享同一个词表,投机解码时就不需要做 token ID 的映射转换,省下不少开销。我用过的某些草稿模型如果不是同词表,每次验证前后都要做词表映射,多出 10% 到 15% 的时间开销。这个在跑高吞吐时非常可观。

进阶操作是对草稿模型做增量预训练或者 LoRA 微调,让它的输出分布更贴近主模型的风格。这个操作能显著提升接受率。我在一个代码生成场景中试过拿主模型输出的数据去微调草稿模型,接受率从 28% 拉到了 41%,吞吐提升 35% 左右。成本不算高,但收益非常明显,适合长期固定任务。

4.4 框架层实现差异:别忽略引擎的具体行为

不同推理框架对投机解码的实现细节差异极大。有的引擎在验证时会把 KV Cache 前缀完全复用,有的会重新计算,性能差距能在 20% 以上。有的引擎默认开启动态草稿长度调整,不接受手动固定草稿长度的配置;有的引擎强制草稿模型和主模型用同一份采样 seed,导致行为跟预期不符。

我建议在正式调参前,先读一遍你所用框架的投机解码文档和源码。至少确认两个东西:草稿长度是固定还是动态调整;验证阶段是否批次并行、是否复用 KV Cache。这两个点直接决定你的调参方向,搞反了很可能白调半天。

5. 常见问题与排查技巧实录

5.1 投机解码开启后速度不升反降

这是出现频率最高的问题。通常有三个原因:

第一,草稿模型太大。3B 草稿模型配合 27B 主模型时,单步耗时拉高,如果接受率没同样比例上升,整体就慢。解决方案是换成 0.5B 到 1.5B 区间的草稿模型再测。

第二,采样参数不一致。大模型温度 / 采样策略跟草稿模型不同,接受率暴跌,投机解码变成纯负担。一定要统一采样参数再比较。

第三,验证实现是串行的。有些集成代码把验证阶段写成了 for 循环,逐 token 验证,大模型每步只确认一个 token,还要额外生成草稿,双重开销叠加,速度自然下降。这种情况只能换实现或者改引擎配置。

5.2 小模型接受率高但吞吐起不来

这个现象容易让人困惑:明明草稿模型预测很准,为什么整体速度上不去?问题多数出在单步时延分配比例上。如果草稿模型本身生成一个 token 的时延已经接近大模型验证的时延,就算全部接受,每步最多产出草稿长度个 token,但每步耗时翻倍。

我遇到这种情况时会降低草稿长度,让草稿生成时间占单步总时延的比例控制在 30% 以下。如果还不行,就换更小的草稿模型。不管接受率多高,单步总时延是硬约束。

5.3 长文本生成后期速度掉一半

短文本测试表现很好,一跑长文本就掉速,很可能是 KV Cache 溢出导致显存重新分配,或者框架把前缀缓存逻辑改了。投机解码在 KV Cache 溢出时往往触发自动回退到常规链式解码,草稿验证的加速就没意义了。

排查方法是减少最大生成长度,给 KV Cache 留出余量。或者在框架里调整 KV Cache 的预分配比例,让它不要频繁分配。实测把显存预分配从默认值调到 90% 以上之后,长文本掉速问题基本解决了。

5.4 多轮对话场景下首 token 延迟变大

投机解码主要优化稳态吞吐,首 token 延迟因为要加载草稿模型、初始化验证逻辑,反而可能比裸跑多几十毫秒。如果应用对首 token 时间敏感,比如流式对话里的打字机效果,可以考虑只在生成长度大于某个阈值之后才启用投机解码。具体阈值取 32 或者 64,短回复直接走裸生成,长回复切换到投机解码。这个组合策略实测体验最好。

5.5 实测数据与理论计算对不上

如果你用公式算出来的理论吞吐跟实测差很多,先检查一下是不是小模型的生成成本和 KV Cache 开销没被算进去。投机解码的额外成本不仅包括草稿模型的生成时间,还包括大模型验证时的注意力计算增量。小模型的 KV Cache 虽然小,也不是零。把这些都算进去后,一般误差能控制在 5% 到 10% 以内。

6. 除了投机解码,还能做什么提升吞吐

6.1 动态草稿长度与早停策略

手动调参能找到静态最优解,但实际任务中 token 难度不均衡。有些 token 特别容易预测,草稿模型连续生成 16 个都能全对;有些 token 后面难度陡增,第 3 个就开始错。动态草稿长度会根据历史接受情况自动调整下一步的草稿长度,难度高的时候减短,难度低的时候加长。早停策略则是当大模型验证时连续多个 token 被否决,立刻中断本步验证,不继续验后面的低质量候选。

这两个机制叠加,理论上限比静态参数高不少。我实测用动态草稿长度 + 早停,在代码任务上又把吞吐提升了 12% 左右。缺点是参数更多、调起来复杂,建议先把静态调明白,再上动态方案。

6.2 量化与投机解码叠加

很多人默认投机解码和量化只能二选一,其实可以叠加。把主模型量化成 INT4,显存占用降到约 16GB,显存带宽压力更小,再配合投机解码,理论上吞吐还能再翻一档。

我试着把主模型量化成 INT4 AWQ 格式,草稿模型保持 BF16,投机解码配置不变。最终吞吐从 159 提升到 233 tok/s,但这轮测试我没在本文细说,因为量化模型在不同任务上的质量损失差异比较大。如果你跑的是对质量不太敏感的生成任务,这个组合值得一试。

6.3 批量推理与投机解码的配合

如果有多个并发请求,比如本地跑一个智能助手服务,可以打开动态批处理(Continuous Batching),让多个请求的投机解码过程穿插在同一个 GPU 流水线上。投机解码的验证阶段是批次化的,天然适合跟动态批处理组合。两个请求并发时,总吞吐不一定翻倍,但单请求的可感知延迟会下降很多。这个优化对服务化部署非常友好。

7. 一些关于调优节奏的思考

最后聊一点方法论层面的东西。在调优投机解码的半个月里,我最大的感受是“不要追求一次调到位”。每一次只改一个变量,记录完整数据,再做下一个改动,这样你才有办法定位因果关系。我前几次翻车就是因为同时动了草稿长度、验证上限、采样参数,结果性能下降时完全不知道是哪一步导致的。

强烈建议准备一个表格记录每种配置下的草稿长度、验证上限、接受率、单步时延、实测吞吐这五个数据。如果中途换了草稿模型,再加一行标注。这样最后回头看的时候,能很清晰地看到哪个参数在什么区间是有效的。

另外,投机解码的调优结果跟具体模型权重高度相关。我这个 27B 主模型测出来的最优配置,换一个同规模但训练数据分布差异很大的模型,可能就差很多。不要迷信别人的参数,跑你自己的数据才是准的。把调优方法论学会,比如草稿长度与接受率的权衡、采样一致性检查、验证实现确认,这些通用原则可以平移到任何一个模型和框架上。

我个人在实际操作中的体会是,投机解码作为推理加速手段,最大的魅力在于它不改变主模型的任何权重分布,加速效果却能达到一个数量级。相比重新训练一个蒸馏小模型,它的成本低得多、落地快得多。如果你当前的本地推理还卡在十几二十 tok/s,试着按这个流程去调一遍,大概率不会失望。

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

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

立即咨询