1. 推测解码到底是什么从“一个字一个字蹦”到“批量猜测再验证”1.1 为什么大模型推理这么慢自回归的枷锁用过vLLM做线上推理服务的朋友应该都有体感大模型生成token的速度跟它“咬文嚼字”的方式是强绑定的。自回归解码的本质是每生成一个新token都要依赖前面已经生成的所有token做一次完整的前向计算。也就是说模型生成第100个token时必须把前99个token重新过一遍网络。这个过程没法并行因为第k1个token必须以第k个token为条件。你算力再强也得一个token一个token地“挤”出来。这也是为什么很多团队在实际部署时发现无论把并发调多高、显存堆多大单条请求的延迟始终下不来——瓶颈压根不在吞吐而在串行生成路径本身。所以大家就开始琢磨能不能用某种方式“跳”过这种串行1.2 推测解码的核心思想小模型猜大模型验推测解码Speculative Decoding的思路很朴素既然大模型每生成一个token都得全量算一遍那能不能先让一个廉价的小模型快速生成一段候选人然后让大模型一次性“验收”这段候选验收通过的部分一次前向就拿到了好几个token等于把原本串行的多步计算压缩成了一步。具体来说整个流程分两个角色草稿模型Draft Model一个参数量小得多、速度远超目标模型的小模型负责“刹车前先冲几步”生成一段长度为K的候选token序列。目标模型Target Model就是你要加速的那个大模型它把草稿模型生成的K个token连同前面已有的上下文一起做一次前向计算然后逐个判断这些token是否“符合自己的采样分布”。那怎么判断“符合”呢最常用的机制叫作拒绝采样。目标模型在验证时会用自己的词表概率分布去核对草稿模型给出的每个token。某个位置上前k-1个token都被接受但第k个token的概率如果低于草稿模型给出的概率就从修正后的分布里重新采样一个token替换它然后停止继续接受后续token。这样一来即使草稿模型猜错了最后得到的采样分布也和目标模型单独自回归时完全一致。换句话说推测解码不会改变原始模型的输出分布代价最多是“白猜了一部分”。这个机制从理论上保证了无损也是它能被大规模应用到生产环境的原因。如果只是“大概差不多能加速”没法保证输出质量和单独跑目标模型一致那大家根本不敢在线上开这个功能。2. 在vLLM里开启推测解码从零到一的实操指南2.1 vLLM对推测解码的支持现状vLLM是目前对推测解码支持得比较完善的推理框架之一。它除了支持离线推理也支持在部署服务时直接通过命令行参数打开推测解码。早期版本的vLLM对推测解码的支持比较粗糙之后的版本陆续补上了采样兼容性检查、草稿模型并行、多token推测等能力也算是把这条路趟平了。这里有个前提必须先说清楚vLLM的推测解码需要指定一个草稿模型而且草稿模型和“目标模型”要共用同一个词表tokenizer否则没法做token级别的对齐验证。实际项目中最常见的搭配是同一系列的模型比如目标是7B/13B的模型草稿模型选同系列的几百M到1B级别的模型。跨系列的模型不是完全不行但词表一旦不一致对齐成本就会高到让你想放弃。2.2 按LLM服务启动推测解码一条命令的事如果你是用vLLM起一个OpenAI兼容的HTTP服务加推测解码其实就是在启动命令里多传几个参数。假设你的目标模型是“某公司发布的13B模型”草稿模型是同一系列的1B版本启动命令大概长这样python -m vllm.entrypoints.openai.api_server \ --model /models/target-13b \ --speculative-model /models/draft-1b \ --num-speculative-tokens 5 \ --max-model-len 4096 \ --served-model-name target-model这里有几个参数值得单独解释--speculative-model指定草稿模型的路径或名称vLLM会用它来做候选生成。--num-speculative-tokens指定草稿模型每轮最多推测多少个token。这个值直接决定了“一次前向能多拿到几个token”的上限。太大会导致草稿模型过度生成计算浪费严重太小则加速不明显。我见过很多新手直接把这里设成10甚至20然后发现延迟反而更高了。--max-model-len要同时给目标模型和草稿模型设置足够长的上下文如果草稿模型的max length不够推测过程会被截断这个坑容易忽略。启动之后你可以观察服务日志vLLM通常会打印出一些和推测解码相关的初始化信息。如果草稿模型加载失败或词表不匹配启动阶段就会直接报错。这其实是个好事说明框架把校验前置了而不是等服务调用到一半才出问题。2.3 在离线引擎里用推测解码适合测试和压测如果你不想起HTTP服务只是在脚本里做离线推理验证效果vLLM的LLM类也支持推测解码。关键还是那两个参数from vllm import LLM, SamplingParams llm LLM( model/models/target-13b, speculative_model/models/draft-1b, num_speculative_tokens5, ) params SamplingParams( temperature0.2, top_p0.9, max_tokens128, ) outputs llm.generate([介绍一下推测解码的原理], params)这段代码在推理内部就会自动走“草稿模型生成候选 目标模型验证接受”的流程。你不需要在自己的代码里维护两个模型的状态vLLM把调度过程封装好了。但要注意离线模式下推测解码和连续批处理之间有一些交互细节如果同时跑大批量请求收益会跟纯延迟不一样这个我后面细说。2.4 草稿模型的并行拆分显存大但算力分配不均时有些时候你手上有个特别大的目标模型比如70B甚至更大多卡部署已经是常态。这时草稿模型虽然小但如果所有卡都用来跑目标模型的前向草稿模型的生成就得排队。vLLM支持单独为草稿模型指定并行度--speculative-draft-tensor-parallel-size 1这个参数的意思是草稿模型用一张卡跑不跟目标模型的张量并行共享卡组。这样目标模型的多卡并行是独立调度的草稿模型单独占用一张卡做快速自回归生成。代价是草稿模型占用的那部分显存不能被目标模型使用。实际部署时你得根据线上请求的并发量和目标模型的显存剩余情况决定是否值得把草稿模型单独拆出去。3. 性能收益与参数调优别只盯着加速比3.1 为什么不同场景下加速比差别很大很多人在本地试推测解码时跑了个benchmark发现延迟降了20%~40%觉得很满意。但如果把同样的配置丢到高并发生产环境可能发现吞吐没怎么涨甚至偶尔还有轻微回退。这不是推测解码没用而是它解决的问题和连续批处理优化的是不同环节。连续批处理Continuous Batching解决的是“怎么把算力用满”的问题推测解码解决的是“怎么减少串行步数”的问题。如果GPU已经被大量并发请求占满每个batch里面的请求数量很多GPU的SM流式多处理器利用率本来就很高这时候草稿模型额外占用的计算资源反而会和目标模型抢算力。而如果单条请求的延迟是关键指标比如聊天场景里的首token延迟和每token生成速度推测解码带来的收益就会非常明显。所以我的建议是先搞清楚你优化的指标是什么。如果要压吞吐优先考虑增大batch、调优显存调度如果要压单请求延迟推测解码才是对症的药。3.2 草稿模型怎么选不是越小越好这是我在实践中踩过最深的一个坑。最初我想着草稿模型既然只是“猜”那越小越好越快越好。结果选了个参数量只有目标模型百分之一的模型推测出来的token接受率低得可怜经常是猜一个就被拒一个前向验证的收益完全被低效猜测抵消延迟比不开推测解码还高。后来我才慢慢意识到草稿模型的选择本质上是在“猜测速度”和“接受率”之间做权衡草稿模型越小生成候选token越快但跟目标模型的分布偏差大接受率低。草稿模型越大接受率越高但草稿模型本身的自回归速度可能接近目标模型推测节省的时间被草稿生成时间吃掉了。常见的匹配思路是选同一系列、参数量为目标模型1/10到1/20左右的模型然后看一眼接受率。接受率在70%以上说明搭配合理低于50%基本可以换模型了。3.3 num_speculative_tokens的调参规律在vLLM里num_speculative_tokens是最值得反复试的参数。我自己的经验是从小到大试探先设成4跑一批请求看平均延迟和接受率然后把参数加到6、8观察收益是否继续增长。这个参数并不是越大越好原因在于随着推测的token数变长草稿模型越到后面的token越不靠谱接受率呈现出很明显的边际递减趋势。假设草稿模型前几个token接受率很高越往后越容易猜偏那目标模型一次前向验证几近白算。所以vLLM内部实际接受的token数往往是推测总数的一个百分比而不是每个请求都能吃满这个上限。如果你想看真实接受率可以在vLLM的日志里打开对应的统计开关或者用vllm.LLM的--disable-log-requests之外的信息去观察。线上环境里我一般习惯把num_speculative_tokens控制在4到6之间兼顾延迟和计算开销。3.4 采样参数对推测解码的影响这里有个很多人不知道的细节推测解码的接受机制和采样参数强相关。如果你在目标模型上使用了非常激进的采样策略比如很高的temperature或者极端的top_p草稿模型的预测难度会被放大两个模型在同等条件下对下一个token的概率分布会产生很大的分歧接受率会明显下降。最明显的是temperature的影响。当temperature接近0的时候模型输出趋近于贪婪解码下一个token几乎是最确定的那个草稿模型也更容易“猜中”。当temperature升高到0.8以上每次生成都带有更强的随机性草稿模型和目标模型对同一个位置给出的概率分布可能差异巨大接受率自然掉得厉害。所以你在评估推测解码效果的时候一定要用跟线上一致的采样参数去测很多团队拿着默认温度去做压测上线时才发现收益没这么高就是吃了这个亏。4. 常见问题与排查技巧实录4.1 加速比不升反降先看接收率遇到推测解码开了之后延迟反而更高的情况别急着关功能。先确认三件事草稿模型和目标模型的词表是否一致。词表不一致时vLLM虽然可能不会直接报错但要做额外的token映射这个开销是隐形的。当前批次的并发请求数。如果并发很高GPU算力已经接近饱和推测解码带来的额外计算会让调度变慢。采样参数是否太“激进”。把temperature调低到0.1以下再压测一轮如果延迟明显下降说明采样随机性把接受率拖垮了。我遇到过好几个案例都是因为线上服务里其他团队把temperature设得比较高导致草稿模型接受率长期在40%以下。最后说服他们单独给用推测解码的模型调整采样参数收益才回到正常水平。4.2 显存溢出草稿模型也要占地方这是一个比较隐蔽的坑。当你给目标模型设置--max-model-len和--gpu-memory-utilization时往往只考虑了目标模型KV cache的显存开销忽略了草稿模型也需要显存。如果显存利用率设得过高比如0.9草稿模型加载之后可能直接OOM。解决思路有两个方向调低--gpu-memory-utilization给草稿模型留出空间。把草稿模型放到单独的GPU上用--speculative-draft-tensor-parallel-size控制它的设备分配。不过要提醒一句草稿模型虽然小但它的KV cache也在动态增长长上下文场景下占用不可忽视。我建议部署前先按草稿模型本身参数量加上上下文长度的KV cache估算一下显存开销别只看参数量。4.3 输出质量“似乎”变了先检查是否有损采样有些同学跑完推测解码后发现结果和不开推测解码时的输出不一致第一反应是“模型坏了”。实际上推测解码在理论上是无损的输出分布和原始模型一致。如果你发现输出变化很大尤其是使用低temperature时输出完全变成另一个结果那大概率不是推测解码的问题而是你用了非标准的采样器或者草稿模型和目标模型在EOS结束符处理上有差异。vLLM中如果检测到草稿模型过早生成了EOS token目标模型会在验证时拒绝这个token这本身是正常行为。但如果你用的草稿模型经常生成很短的回复猜测序列里频繁出现EOS那最终输出长度会受到影响。这种情况建议换一个更贴近目标模型训练分布的草稿模型或者检查草稿模型是否被微调过导致分布偏移。4.4 请求并发一大收益就变小合理混用我前面说过推测解码对单请求延迟的优化非常明显但对高并发吞吐的优化效果不稳定。实践中可以把部署拆成两条服务线低延迟优先的服务比如流式对话、代码补全开启推测解码且限制最大并发数不让GPU算力被撑满。高吞吐优先的服务比如离线批量打分、大规模数据标注关闭推测解码把batch size拉满。这种混用方式比试图用一套配置打天下要稳得多。说到底推测解码是一种“用计算换延迟”的策略当你没有延迟瓶颈纯粹在跑吞吐时它就不是最合适的工具。4.5 常见问题速查表现象可能原因排查方向延迟无改善甚至变高草稿模型接受率过低降低采样随机性或更换更大的草稿模型GPU显存OOM草稿模型与目标模型共用显存降低gpu-memory-utilization或单独分卡输出与不开启时差异明显采样分布被非标准采样器破坏检查EOS处理与草稿模型分布是否偏移首token延迟增加草稿模型前向计算成为新瓶颈减少num-speculative-tokens高并发下吞吐回落GPU算力被草稿模型挤占考虑关闭推测解码或用单独设备隔离草稿模型5. 最后聊聊我自己的使用感受从最开始在本地跑通推测解码到后来在线上服务里反复调参最大的体会是这个技术“入门容易调优难”。它的基本原理一句话就能说清小模型猜、大模型验证。但一旦进入真实场景模型组合、采样参数、并发规模、显存余量每个变量的微小变化都会让加速比大幅波动。我个人的建议路径是先用离线小批量把模型组合和num_speculative_tokens跑通记录接受率和延迟曲线再对照一个固定请求集比较不同配置下的输出一致性和延迟分布最后再上生产环境并且上线初期保留一键关闭开关便于随时回退。如果手头有几种不同规格的草稿模型建议多试几组组合别怕麻烦。不同目标模型和草稿模型的配合效果直觉和理论估算经常都不准确实测数据才是唯一的答案。推测解码这个方向在未来应该还会继续演进比如和投机采样、并行解码等技术的结合都会让大模型推理再快一截。但它终究不是银弹理解它解决什么、不解决什么才能真正把它用好。