推测解码实战:vLLM 中让 LLM 推理速度翻倍的配置指南 做过推理服务的人都有这种体会模型参数量上去之后单 token 生成时间死死卡在几十毫秒用户问一句话要等好几秒。LLM 推理速度不够快这个问题卡住了不少人。推测解码Speculative Decoding是这两年开源社区里讨论度最高的加速手段之一而 VLLM 社区把这个原本停留在论文里的技巧做成了开箱即用的功能。这篇文章不打算堆公式而是用一个“实习生写初稿、专家一次性审阅”的视角把推测解码的原理讲明白再带你实际配通 vLLM 的推测解码服务。你会看到同样的模型、同样的显卡在合适场景下每秒吐词数量实打实翻倍。这篇文章适合三类人刚接触大模型推理的算法工程师想在不换卡的前提下提升服务响应速度的开发者以及所有被 LLM 逐字生成速度折磨过的玩家。我会把推理慢的根源、推测解码为什么省时间、vLLM 里怎么配置、常见坑怎么排按照从原理到实战的顺序讲清楚。你不需要先读一字不差的论文公式只要知道大模型每个 token 是怎么“蹦”出来的就能跟上后续所有内容。1. 从“一个字一个字蹦”说起LLM 推理为什么慢1.1 自回归解码的每一步代价比你想的大所有主流大语言模型都是自回归架构意味着它一次只生成一个 token然后把新词拼到已有序列最后继续作为输入来预测下一个词。这个机制有一个直观后果生成第 100 个 token 之前你必须先把前 99 个都生成完串行没有出口。你可以在接口里设置 max_tokens但解码过程本质上还是“链条式”的。但是每一环“链条”的成本远比想象中高。生成一个新 token 并不是只做一次简单预测而是要把当前的整段序列重新喂给模型穿过 Embedding 层、几十层 Transformer、最后一层投影到词表才能得到下一个词的分布。即便现在的推理引擎都实现了 KV Cache把历史 token 计算出的 Key 和 Value 缓存起来免去了历史部分的重算模型每一层的全部权重参数依然要从显存中完整读一遍参与计算。这一步是省不下来的。我见过很多人惊讶于一个现象不管 7B 还是 13B 的模型单 token 生成时间往往都在几十毫秒而且显卡利用率看起来并不高。原因在于 LLM 推理受限于显存带宽而不是计算算力。粗略估算一下7B 模型的 FP16 权重约有 14GB主流旗舰卡显存带宽大概 2TB/s即使不做任何计算光把权重读一遍就需要至少 7ms。所以生成一个 token 的时间在这种配置下很难低于这个门限计算单元大部分时间都在等待数据。1.2 卡脖子的不是算力是显存带宽有人会觉得KV Cache 不是已经把历史部分的重复计算都消掉了吗确实消掉了但它解决的是“同一个历史状态的 K/V 要不要重算”的问题解决不了“每一轮都要把权重读一遍”的带宽问题。权重就那么大显存带宽就那么多单 token 生成时间因此存在一个物理下限。只要解码还是逐 token 串行总延迟就得乘上 token 数量。这里可以做一个更直观的类比把显卡想象成一个图书馆模型权重是书架上的全部藏书生成一个 token 相当于把整套书从头快速翻一遍然后写下一个词。哪怕你只需要写一个词也必须把整本书翻完因为模型每一层都依赖这些权重。翻书的速度受限于书架的“带宽”而不是你写字的速度。所以模型越大这套书越多翻一遍花的墙钟时间就越长单 token 生成速度自然上不去。这个结论对后续理解推测解码非常关键。我们优化的本质不是让“翻一遍书”变得更快而是想办法尽量少“翻书”。自回归解码之所以昂贵是因为每生成一个 token 就要完整翻一遍书。如果有一天能一次翻书、同时写好几个词成本就被摊薄了。1.3 一个死结自回归模型没法一步跨两个 token既然串行是求解慢的直接原因一个自然的想法是让模型一次性多生成几个 token。但自回归模型的结构决定了它做不到——要预测第 n1 个 token必须先把第 n 个 token 的真实结果作为输入喂进去。这就陷入了一个死结想跳步却没有跳板。推测解码恰恰是通过引入一个“打草稿”的小模型把这个跳板搭了起来。2. 推测解码小模型打草稿大模型来验收2.1 核心思想草稿、验证、修正推测解码的核心思想可以概括成六个字草稿、验证、修正。流程分三步。第一步让一个轻量级草稿模型从当前上下文出发按照和正常解码一样的方式快速生成 K 个候选 token第二步把这 K 个 token 拼接到序列尾部一次性送入目标大模型做一次前向传播第三步大模型同时得到这 K 个位置各自对应的概率分布逐一判断草稿模型在当前位置猜得对不对对的留下不对的从第一个出错位置开始全部推翻并用自己的结果补上。这就是“实习生写初稿、专家一次性审阅”的由来。实习生写得快但水平有限专家不必每句话都单独找实习生重新写而是一次拿到一版初稿从头翻到尾把不合格的标记出来自己改。放在模型里专家只需要一次前向传播就能同时评估 K 个位置的答案这就是加速的核心来源。为什么大模型能“同时评估 K 个位置”因为这次前向传播的输入不是“只预测下一个 token”而是把草稿模型生成的 K 个 token 全部拼在序列后面。目标模型依次对每个位置做预测相当于同时完成了 K 个预测任务。这跟我们让目标模型自己一步一步生成不一样因为这里的 K 个输入 token 是草稿模型给的不需要等目标模型逐个产出来再喂回去所以可以并行。2.2 接受与拒绝为什么这不是偷工减料如果目标模型是贪心解码验收规则特别直白看草稿 token 是否等于目标模型在那个位置选出的最大概率 token。是就接受并继续比较下一个不是就从这个位置截断后面草稿全部作废改用目标模型自己的选择。这样一轮下来有效产出就是这个被截断的位置数量。如果 K5你运气好5 个全中那这一轮就一次生成了 5 个 token相当于把五轮自回归合成了一轮。采样的场景略有不同草稿 token 即使不是 argmax也可以根据目标分布和草稿分布的概率比值决定是否接受。这套接受-拒绝规则不是拍脑袋设计的它保证了最终采样结果和目标模型直接采样在分布上完全一致。所以我一直建议把推测解码理解成无损加速——你看到的内容风格和原本模型的随机性没有变化变的只是每一秒能吐出的 token 数量。有人会担心被拒绝的 token 是不是白算了不是。即便草稿前 5 个 token 里只接受了 2 个大模型也已经拿到了从第 2 个位置开始往后全部位置的概率分布接下来它完全可以在这个被拒绝的位置重新采样。也就是说草稿模型提供了一个“跳板”让大模型在这一次前向传播里同时看到了未来多个位置的信息。就算跳板短了也仍然省掉了一次大模型自回归的轮次只是收益变小而已。2.3 为什么两个模型相加反而比一个大模型跑得快有人会脱口而出这不是要跑两个模型吗算力开销更大了吧。确实如果草稿模型每生成一个 token 的时间不可忽略总开销会增加。但只要草稿模型足够小它单 token 生成时间比大模型低一个数量级整个账就划算。我算一个直观的例子。假设大模型单 token 耗时 10ms草稿模型单 token 耗时 2ms草稿长度 K 取 5且接受率理想情况下很高。原生生成 5 个 token 需要 5×1050ms。推测解码的流程则是草稿模型生成 5 个 token 花 5×210ms大模型一次前向传播验证存量序列加 5 个新 token 花大概 10ms加起来 20ms省了一半多。如果草稿模型再小一点、快一点K 再合适一点速度可以直接拉出去两三倍。但你要问接受率不高怎么办比如这 5 个里面只接受了 2 个相当于大模型一次前向后有效产出只有 2 个 token原生跑 2 个 token 只需要 20ms而推测解码花了 101020ms基本打平。如果接受率更低甚至变慢。所以推测解码省不省关键看两项草稿模型的对齐度接受率和草稿长度 K。这也是后面调参的核心。3. VLLM 社区的落地从论文到生产可用3.1 为什么是 vLLM架构带来的天然优势推测解码的概念很早就在学术圈出现了但真正把它推到生产环境靠的是以 vLLM 为代表的开源推理引擎。vLLM 在社区里口碑好不光是单元测试写得多而是它的架构天然适合承载这类“多模型协作”玩法。首先vLLM 用分页显存管理机制做 KV Cache按页分配避免了静态预分配带来的显存浪费这让同时加载目标模型和草稿模型成为可能。小模型虽然只有几百 MB但也是额外的显存开销显存管理不高效的话开这个功能还不够腾地方。其次vLLM 的调度器对多步推测做了专门支持。推测解码有一个跟普通自回归不一样的特性每一轮里草稿模型要连续生成 K 个 token这就涉及到一个“多步生成 一次性验证”的调度流。普通调度器处理不了这种“先连续走几步、再回头验证”的逻辑。VLLM 社区在调度层做了改造才能让用户只要传两个模型路径就能开启这个功能。另外vLLM 的动态批处理调度很成熟。多请求并发时一个新的请求进来不必傻等队头清空可以随时插入。推测解码和这种动态批处理结合后低并发场景下尤其受益因为单个请求的延迟可以大幅下降。这也是为什么很多人在本地搭 vLLM 服务而不是直接用闭源方案来做实验。3.2 草稿模型选型别只看参数量草稿模型不是随便拿一个语言模型就能用的社区里最常踩的坑就是词表对齐问题。推测解码机制要求草稿模型和目标模型在 tokenizer 的 vocabulary 上是兼容的否则你拿草稿模型输出的 token id 去大模型里算概率对不上号。最省事的做法是选同一系列、同一词表的小模型。比如你部署一个 7B 的模型优先考虑它作者自己放出的 0.5B/1B 小兄弟如果跨家族混搭先确认 vocabulary size 一致否则会出现概率分布维度不匹配直接跑不起来。其次是容量选择。草稿模型太小接受率低白干太大生成 K 个 token 的时间压不下来还是白干。社区里比较通用的经验是目标模型在 7B 左右配 0.5B 到 1B 的草稿模型13B 级别可以配 1B 到 3B更大的 70B 级别草稿模型也不宜超过 7B。这只是起步经验值具体还要看实测接受率。我的建议是代码里预留一个开关上线前用一小段评测集分别测不同草稿模型和不同 K 值把接受率和端到端延迟一并打出来不要让“感觉”替你做决定。还有一点容易被忽略草稿模型的结构越接近目标模型越容易产生“预测一致”。一个与目标模型同结构的缩小版通常比一个结构完全不同的通用小模型表现更好。因为它们的概率分布先验更接近。你在社区里看到的许多成功案例用的基本都是同构小模型。这不是强制要求但能降低很多调参成本。3.3 实测加速效果与适用边界VLLM 社区和各个自建用户放出的实测数据大致集中在以下规律单请求、低并发、长回复场景下加速比最明显常见 1.5 到 2.5 倍短回答和首字时间几乎没有帮助因为首字之前没有草稿可打高并发下加速比会回落原因是大模型本身就忙着处理多个请求显存带宽已经接近饱和草稿验证带来的边际价值变小。使用场景加速体感原因单请求长回答1.5 到 2.5 倍草稿模型用低成本预测大模型一次验证多位置多并发高吞吐接近 1 倍甚至无收益大模型带宽饱和草稿验证边际收益小短问答几乎无感首 token 前没有可验证的草稿加速被首字延迟淹没小模型很难超过 1 倍找不到更小的同词表草稿模型成本和收益倒挂我自己的体感也是这样。某个 7B 模型在单路下默认约 30 token/s开启推测解码用 1B 草稿模型后稳定在 55 到 65 token/s。但并发数拉到 8 以上后整体吞吐变成了另一种瓶颈推测解码的收益就不明显了。它不是银弹但适用场景恰好是很多个人开发者和中小团队最常用的场景一个推理服务低并发交互式聊天。4. 手把手配置在 vLLM 里开启推测解码4.1 环境准备与模型下载我默认你已经装好 vLLM。如果还没有Python 3.10 以上一张支持 CUDA 的显卡然后直接安装 vLLM 即可。这里特别提醒一句观察安装后的版本号推测解码这个功能在早期版本里名字和参数有过变动尽量用近半年的新版本老版本文档不一定对得上。接着准备好两套模型权重。目标模型用你计划部署的模型草稿模型按上面说的原则选。从模型分享平台下载两条命令就能拿到一条拉目标模型一条拉草稿模型。我用的是本地目录方式先分别下载再让 vLLM 加载本地路径。好处是断网环境也能跑也方便对比不同草稿模型。下载完务必检查一下 word embeddings 层的输出维度如果目标模型词表大小是 32000草稿模型也得是 32000否则后续会报 shape mismatch。4.2 启动参数与代码示例开启推测解码核心就是两个参数speculative-model 指向草稿模型num-speculative-tokens 指定每轮草稿长度。下面是一个最简命令行示例vllm serve /path/to/target-model \ --speculative-model /path/to/draft-model \ --num-speculative-tokens 5 \ --max-model-len 8192 \ --gpu-memory-utilization 0.8 \ --port 8000如果你更喜欢用 Python API 起服务等效的代码是from vllm import LLM, SamplingParams llm LLM( model/path/to/target-model, speculative_model/path/to/draft-model, num_speculative_tokens5, gpu_memory_utilization0.8, ) sampling_params SamplingParams(temperature0.0, max_tokens512) outputs llm.generate([写一份项目进度汇报的提纲], sampling_params)这里有个关键点vLLM 启动时如果显存不够同时放两个模型会先报显存错误这时候不要急着换显卡先把 gpu_memory_utilization 调低或者把 max-model-len 调小。草稿模型虽然小但推理时要保留自己的 KV Cache这部分显存经常被人忽略。还有一个小经验开推测解码后跟采样相关的参数尽量复制生产环境的值。比如生产环境用 temperature0.7那么压测和评测也都要用 0.7。因为采样温度会影响接受率温度高的时候草稿 token 被接受的概率会下降你在 temperature0 下测出来的加速比不代表线上真实效果。4.3 如何科学记录和解读性能指标开完功能很多人直接看终端里每秒生成速度觉得快了就完事。这不够。我建议做一组对照实验变量只有“是否开启推测解码”用同一批 prompt、同一个最大长度、同样的采样参数测三遍取中位数。记录下来三个观测首 token 延迟、端到端耗时、吞吐量。进一步可以看接受率。vLLM 的服务指标里会暴露 speculative 相关的统计不同版本字段名略有差异但基本上能看出每一轮草稿 token 里有多少被接受。这个数值是排查性能问题最重要的放大镜。如果接受率低于 50%先别折腾大模型侧换个草稿模型或者调小 K 往往立竿见影。如果追求更精细的调参把 num_speculative_tokens 从 1 依次调到 3、5、7每个值各压测一轮画出“K 值 vs 加速比”的曲线。超过某个点后接受率会随 K 增大而下降因为草稿模型越往后预测越不准加速比曲线就会出现拐点。找到拐点附近的值就是你这个组合的最佳参数。这种调参方法不仅适用于推测解码也适合很多带超参的推理优化记录下每轮配置和结论方便回溯。5. 掉坑记录与排查方法5.1 常见问题速查表把我在社区里和实际使用中见过的问题汇总一下按频率排现象可能原因排查方向启动时报 vocab size 不匹配草稿模型与目标模型词表不一致检查两个模型的 config 文件换同族模型显存不足无法启动同时加载两个模型KV Cache 预留不够降低 gpu_memory_utilization调小 max-model-len换更小草稿模型开启后速度反而变慢接受率太低草稿模型逐 token 耗时太高查看接受率换更小草稿模型调低 K 值确认并发数是否已很高响应结果与不开启时不一致采样参数不一致随机种子影响对比同一采样参数区分贪心采样和随机采样的噪声验证阶段显存溢出估算显存时漏了验证阶段的额外中间激活调小 num_speculative_tokens减小最大 batch token 限制这张表来自我自己的排查笔记。我想强调最后一行很多人开推测解码时只算了模型权重和 KV Cache忘了大模型在验证 K 个位置时中间激活值也会变大K 越大激活峰值越高。对付它的办法也很简单K 回调别贪。5.2 当接受率不高先分清草稿太弱还是 K 太大诊断思路其实不难。先固定 K3跑一个小批量评测如果接受率还是低基本可以确定是草稿模型和目标模型对齐度不够换草稿模型。如果 K3 接受率不错K7 接受率断崖那说明不是草稿模型本身差而是它做不了那么长远的预测把 K 调回 4 或 5 就好。有些情况下接受率不低端到端却没变快那问题出在草稿模型生成速度上。你可以单独给草稿模型计时看看它每生成一个 token 要多久。如果它跟大模型单 token 耗时差不到五倍以上整个流程收益就不会大。记住一个拍脑袋的参考线草稿模型单 token 耗时至少要比目标模型低一个数量级推测解码才划算。这里补充一个技巧开着服务看 GPU 利用率。推理过程受限于带宽时GPU 利用率不会忽高忽低但如果你发现 GPU 算力块一直很闲而整机速度没提升多半是调度层面的开销把 K 调大一些能摊薄每轮调度的固定成本。如果 K 调大后接受率下降明显那就需要换一个预测能力更强一点的草稿模型而不是继续跟 K 较劲。5.3 什么场景下不要开推测解码我也见过团队把推测解码供起来觉得未来所有推理都必须开。但实际工程里不是这样。如果你的服务已经跑在大 batch 高并发下GPU 带宽接近饱和推测解码的收益会被稀释甚至因为多跑一个小模型吞吐不升反降。这时候建议关闭。另外如果你的场景主要是短问答每次生成不超过几十个 token推测解码省下来的主要是后半段首 token 没有收益等于白忙。还有如果你用的目标模型本身已经非常小草稿模型很难选更小的同词表模型收益空间有限。我的建议是务实先上基准测试让数据说话别为了“用上新功能”而强行开启。我在实际使用中还有一个体会推测解码最好和别的加速手段搭配但不要叠太多。有人既开推测解码又开量化又开各种投机式优化结果排查问题时不知道瓶颈在哪。建议一次只改一个变量逐步叠加每次记录性能。这种做法看起来慢实际上最能帮你摸清当前组合的真实能力。我现在的习惯是每次更换目标模型或者更新推理引擎版本后都重新跑一遍上面这组基线测试。模型换代后最优的草稿模型和 K 值常常会变之前测出来的甜点位只能当参考坐标。把这个流程沉淀成脚本以后任何新模型上线都能快速找到自己的配置。这也是推测解码这个技术教会我的事不要凭感觉优化跑数据、看接受率、记录配置剩下的事情时间会告诉你。