☰
大小模型接力加速LLM推理:投机解码原理与工程实践
2026/10/22 8:06:25 网站建设 项目流程

很多人在线上跑过大模型服务后都有一个共同感受:显存占得满满的,GPU 利用率看着也不低,但输出就是一个字一个字往外蹦。尤其是问答、摘要这类对延迟敏感的场景,几十个字的回复能让用户等上好几秒。问题不一定出在硬件不够猛,而是大模型天生只能“一次想一个词”,整个生成过程被自回归的串行逻辑钉死了。大小模型接力(业内常叫投机解码或草稿验证)就是冲着这个瓶颈来的:让一个又小又快的小模型先快速打草稿,再让大模型一次性“验收”一小段草稿,对的直接收下,不对的从出错位置重写。这样既能保住大模型的输出质量,又能真正实现又快又准的 LLM 推理。这篇文章我会从原理讲到工程落地,把我实际调过的参数和踩过的坑都摊开说。

1. 先看清瓶颈:大模型为什么只能一个字一个字往外蹦

1.1 自回归的本质:今天想什么,取决于昨天说了什么

大模型做生成,本质上是一个“给定前文、预测下一个词”的模型。这句话说起来简单,但它限制了整个推理过程的结构。比如生成“今天天气很好,我打算去公园散步”这句话,模型要先根据“今天天气很好,我打算去”预测出“公园”,接着把“公园”接到文末,再预测“散步”,然后再补一个标点,最后预测结束符。

每一步都必须等上一步的结果落定才能开始。想同时让模型把 100 个位置的下一词都预测出来?不可能,因为后面的位置还没有前文,模型无从下手。你可以把整个 GPU 集群都堆上去,但一条链路上的前后依赖关系没法拆成并行任务,这就是自回归生成的硬约束。

这里有个很值得注意的点:推理时的“下一步依赖上一步”和训练时并不完全一样。训练时可以拿到一整段标注好的文本,老师给模型出题:“看到这个句子的开头,猜下个词是什么”,于是所有位置的题目可以同时做。但推理时,后半段文本不存在,只能先生成第一个词、再生成第二个词、再生成第三个词。所以生成天然是串行的,而验证天然可以并行。这个差异是后面所有接力的地基。

1.2 更扎心的是访存瓶颈:权重被反复搬运

如果只看计算量,大模型生成一个 token 其实“没干多少活”。以常见规模的大模型为例,它需要把模型权重完整加载到片上,和已有的上下文做一次矩阵运算,最后算出一个词的概率分布。计算量不小,但相比加载几个 GB 权重所花费的访存时间,计算时间往往反而短。

当请求的并发很低、batch 很小时,比如在线问答经常就是 batch=1,GPU 的大部分时间其实是在“等数据从显存搬到片上”,真正做矩阵乘法的时间占比很低。这个问题叫访存密集。模型越大,搬运一遍权重的成本越高,单 token 延迟也就越高。

KV cache 可以省掉一部分重复计算,但它没有改变“一步只能出一个 token”的串行结构。更形象一点:模型每生成一个 token,都相当于把整本参考书从头翻一遍,只为查一个词。书越厚,翻书越慢;翻了很多遍,但每次只查一个词。

1.3 一个关键观察:多数 token 并不需要“大模型级”的智力

如果我们把生成的每个 token 都看作一次小决策,会发现问题没那么均匀。写“今天天气很好,我打算去____”这种位置,答案几乎是确定的,一个小模型也能给出很接近的下文;真正需要大模型“动脑子”的,往往是一段话里的少数几个转折点:“但是”“然而”“最好的方案其实是”这类地方,以及需要复杂推理的短句。

我做过一个粗糙的统计:在开放域闲聊和摘要类任务里,小模型和大模型在超过七成位置上的选择完全一致。这意味着大模型有相当多的时间在“陪跑”——处理一些随便找个模型都能答对的简单位置。既然大量 token 都是低难度决策,那为什么每一棒都必须让大模型亲自跑?让便宜的小模型先跑几棒,只在关键时刻把接力棒交给大模型,是不是可行?这就是大小模型接力要解决的问题。

2. 接力赛怎么玩:小模型先跑,大模型验收

2.1 草稿加验证:实习生先写,主编后审

大小模型接力的基本过程分三步:

  1. 小模型(也叫草稿模型)快速自回归生成一段候选文本,通常一次生成 4-8 个 token。
  2. 大模型(主模型)把这整段候选文本一次性过一遍,同时检查每一个位置上“如果是我,我会选什么词”。
  3. 小模型和大模型意见一致的 token 直接收下;从第一个意见不一致的 token 开始,后面的候选全部丢弃,大模型在分歧位置亲自生成一个 token,然后继续循环。

用画面来记就是:小模型是实习生,先写一大段初稿;大模型是主编,拿到稿子从头往下扫,连续正确的地方直接放行;一旦发现跑偏,就从跑偏的那个词开始重写。实习生跑得便宜,主编只负责把关和改错,最终稿的质量仍然是主编水平。

这个“主编最后拍板”的细节非常重要。接力不是让小模型和大模型各写一半然后拼接,也不是让小模型的输出直接当作最终结果,而是始终由大模型对每个词做最终裁决。因此,输出质量的决定权还是在大模型手里。

2.2 为什么一次前向就能检查多个位置

前面刚说过自回归只能逐个生成,那大模型为什么能一次性检查整段候选?关键在于“生成”和“验证”的性质不同。

自回归生成时必须等前文确定,所以第 n 个 token 没出来,第 n+1 个位置就不能算。但验证阶段,小模型已经把整段候选完整写出来了,大模型手里有一段“未来的草稿”,它就可以像训练时那样,对每个候选位置同时做一次预测:第 3 个位置依赖前两个位置,而前两个位置已经有候选词了,不需要等真实结果。

这么说可能更直观:自回归生成像排队过闸机,每个人必须等前面的人刷完票才能往前走;验证像保安拿到一整队人的证件,一眼扫过去,谁的票有问题当场就能看出来。整队人的证件是“已经存在”的,所以保安可以同时检查所有人。

2.3 为什么验收比生成便宜

一次“验收”本质上是一次并行前向传播,输入的 token 数比单步生成多,计算量和访存量确实会增加,但远没有增加 K 倍。换句话说,花大约一倍多一点的成本,有机会换回 K 个 token。如果这 K 个 token 全部被接受,那就相当于一次大模型前向顶掉了 K 次大模型前向。

即便只接受了其中一部分,比如 8 个候选里接受了 5 个,那么接力的成本是“1 次大模型验证 + 8 次小模型生成”,换来 5 个 token;而同样 5 个 token 如果不接力,需要 5 次大模型自回归。小模型通常比大模型便宜一个数量级以上,这笔账怎么算都是赚的。

这里还有一层理论保证:如果小模型候选被接受的概率严格按大模型的分布来判断,不一致时再从大模型分布重新采样,那么最终输出的分布和直接用大模型自回归是等价的。也就是说,接力不是“牺牲质量换速度”,它可以在不改变输出质量的前提下加速。工程实现里一旦把采样参数弄乱,等价性就会被打破,后面会专门讲。

3. 这笔账到底划不划算:匹配率与候选长度的账

3.1 匹配率是速度的第一变量

接力的加速效果,最核心的指标是“接受率”,也叫匹配率——小模型写出来的候选 token 里,有多少比例是大模型在同一位置也会选择的。

匹配率越高,一次验收能换回的 token 就越多,加速比自然越高。匹配率 0.9 和 0.5 的差距不是 1.8 倍,而是被反复放大:因为每一轮生成的草稿都要重新跑一遍小模型,匹配率低意味着经常要“白跑一叠草稿再重写”,小模型的成本没有被充分利用。

匹配率不是模型单独决定的,它和任务类型强相关。开放闲聊、摘要这类语言自由度高的任务,小模型猜中的概率很高;代码、数学、带专业术语的问答,token 的选择更“敏感”,匹配率会明显下降。我后面会给一组粗糙的经验值,但不同模型组合之间差异很大,最好以自己线上的统计为准。

3.2 候选长度 K:不是越长越好

候选长度 K 指小模型每次连续生成的 token 数。K 太小,一次验证换回的 token 太少,接力的意义被削弱;K 太大,至少有两个副作用。

第一,草稿越往后越不可靠。小模型顺着前文继续编,编到第 10 个 token 时已经偏离大模型思路的概率,远高于第 3 个 token。第二,大模型验证时输入的候选段变长,单次前向的计算量和显存占用都会上升。于是 K 值存在一个边际收益递减的区间。

我在实际项目里常用的起步值是 6,然后观察接受率和加速比。如果显存紧张,就用 4;如果匹配率确实很高、显存也宽松,可以试到 10-12,但超过 16 我基本不会再往上涨。下面这张表可以帮你快速定位:

K 值范围单次验证成本典型收益我的建议
2-3低安全但偏保守低显存或对首 token 敏感的场景
4-8中等多数场景的甜点区推荐从这里起步
9-16偏高高匹配率场景有增益看接受率决定
>16高收益基本递减一般不碰

3.3 期望收益的粗算

假设一个小模型单 token 生成延迟是大模型的 1/20,匹配率 0.7,K=8。理想情况下,如果 8 个候选全部被接受,一次大模型前向换 8 个 token,理论加速接近 8 倍;实际因为匹配率折减、部分轮次要由大模型在分歧位置多生成一个修正 token,端到端通常落到 2-3 倍。

不要小看这个数字。很多线上服务瓶颈不是单卡算力,而是单请求延迟和总吞吐,能稳定拿到 2 倍以上已经非常可观。我也见过一些宣传材料把投机解码吹到 5-10 倍,那些通常是理想实验环境、高匹配率数据集下的结果,真实业务里最好按 1.5-3 倍做预期,超过就当惊喜。

4. 落地实操:从现成接口到自己写验证器

4.1 先跑通现成流程,再考虑魔改

当前主流开源推理库大多已经集成了 assisted decoding,也就是把“草稿生成 + 验证”包装成了一个高层接口。想快速看效果,通常只需要额外传入一个小模型和几个参数:

output_ids = model.generate( input_ids, assistant_model=small_model, num_assistant_tokens=8, do_sample=True, temperature=0.6, )

这一行代码背后发生的事情是:用 small_model 生成候选草稿,用大模型并行验证,持续接受匹配 token、丢弃不匹配部分,循环直到结束。对于第一次接触接力的团队,我建议先跑通这个流程,把加速比测出来,再决定要不要深入定制。

这里有两个立刻要检查的点:两个模型必须共用同一个 tokenizer;生成相关的所有参数必须显式传给两侧,不要依赖默认值。默认采样参数不一致是第一个很大的坑,后面会展开。

4.2 自己写一个精简版验证器

如果用的是不支持 assisted decoding 的自研框架,或者想彻底理解每一步在做什么,可以自己实现一版核心逻辑:

def draft_and_verify(target, draft, input_ids, k): outputs = [] is_done = False while not is_done: # 1) 小模型自回归生成一段候选草稿 candidates = draft.generate(input_ids, max_new_tokens=k) draft_tokens = candidates[:, input_ids.shape[1]:] # 2) 大模型一次性验证整个候选段 logits = target.forward(input_ids + draft_tokens) # 3) 逐位置比对,找第一个不一致点 accept_count = 0 for i in range(k): target_token = sample(logits[:, input_ids.shape[1] + i - 1]) if target_token == draft_tokens[i]: accept_count += 1 else: break # 4) 接受连续一致部分;分歧位置由大模型亲自补一个token accepted = draft_tokens[:accept_count] outputs.append(accepted) if accept_count < k: next_token = sample(logits[:, input_ids.shape[1] + accept_count - 1]) input_ids = input_ids + accepted + [next_token] outputs.append([next_token]) else: input_ids = input_ids + accepted is_done = check_stop(input_ids, outputs) return outputs

关键在第 3 步和第 4 步:比对的是“大模型在第 i 个候选位置预测的 token”和“小模型实际写下的第 i 个 token”;一旦发现不一致,后面的候选全部作废,由大模型在分歧位置亲自生成一个 token 并作为新的真实输入。这个“大模型亲自补一个 token”的动作是保证输出质量不下降的关键,不能省。

如果全部候选都被接受,可以像上面代码一样直接进入下一轮草稿,也可以让大模型多预测一个 token 再继续,两种方式都能跑通。另外要注意:这是伪代码风格,具体实现要按你所用框架的 tensor 接口调整,比如拼接时要处理好维度。

4.3 参数一致性:比想象中更重要的工程细节

自己写验证器之后,最容易忽略的就是采样参数。小模型生成草稿时如果用 temperature=1.0、top_p=0.9,大模型验证时如果用 temperature=0.3、top_p=1.0,两边在同一个位置的选择很可能不一样,接受率会直线下跌。

工程上的做法是抽一个公共采样配置对象,同时传给草稿模型和主模型:temperature、top_p、top_k、repetition_penalty、no_repeat_ngram_size,全部保持一致。还有一点容易被忽略:模型内部的 EOS 处理逻辑也要一致,否则小模型提早结束候选,大模型还在继续验证,行为就会错位。

如果主模型使用了 beam search、contrastive search 这类非采样解码,assisted decoding 的接受条件和标准实现会有差异,不是简单改几个参数就能套用的。我的经验是,接力模式基本只和采样解码搭配,beam search 场景单独处理。

5. 实测中真正会踩的坑

5.1 选草稿模型:词表对齐是第一优先

草稿模型不是随便选一个“看起来小的”就行。第一优先级是词表一致、分词器一致。

如果小模型和大模型来自不同组织、不同词表,同一个词在两边的 token id 可能完全不同。验证环节比对的是 token id,id 对不上,即使语义完全一致也会被判为不匹配,接受率会被打到极低。更隐蔽的是,即使你知道两个词表内容相同,但词表顺序不同,也要做 id 映射后再比较。

除了词表,我建议从同一训练家族里挑草稿模型。同一家族模型在 token 偏好、语言风格上天然相似,匹配会更高。能力差距也要控制:主模型 70B 级别时配 1B-7B 比较合适,主模型 7B 级时配 0.2B-1B;草稿模型太大,成本优势被抹平;太小,则猜不中主模型的选择。

5.2 采样参数不一致,接受率当场崩盘

这是我在真实项目里踩得最深的一个坑。某次我用一个现成推理库接草稿模型,默认配置下小模型采样温度是 0.8,主模型是 0.6,跑出来加速比只有 1.2 倍,一度以为是库本身没生效。后来把两边的采样参数调成完全一致,同样的模型组合立刻到了 2.5 倍。

给一个排查技巧:先用贪婪解码(temperature=0)跑一遍。如果贪婪模式下匹配率正常,而采样模式匹配率骤降,问题基本出在采样参数不一致;如果贪婪模式匹配率就很低,那问题多半出在词表、模型选型或任务本身。这个二分法可以帮你快速定位是“模型问题”还是“配置问题”。

5.3 流式输出和显存占用的取舍

接力不是免费的。两个模型同时驻留显存,显存占用会明显上升。如果总显存吃紧,草稿模型可以用低比特量化版,我在实测里 int8 量化草稿模型和原版草稿模型的接受率差距通常在 1-2 个百分点以内,对端到端加速比影响很小。

还有一个容易被忽略的场景:流式输出。很多在线应用追求“第一个字越快越好”,而接力模式必须等小模型跑完候选、大模型验证完,才能吐出第一批 token。因此首 token 延迟通常不会改善,甚至可能略增。如果业务真的对首 token 极敏感,可以把 K 调到 3 甚至 2,或者只在生成长回复时启用接力;如果业务更在意总耗时和吞吐,接力就很划算。

5.4 代码、数学等低匹配率场景的应对

代码补全和数学推导里,token 的分布和自然语言很不一样,大模型的选择有时相当“独断”,小模型很难猜。遇到这类任务,我的做法是:

  • 把 K 主动降到 4 左右,减少白跑草稿的成本;
  • 换一个训练数据里包含更多代码或数学的草稿模型;
  • 如果线上统计下来匹配率长期低于 0.4,我会干脆关掉接力,让大模型单跑,免得小模型白白耗电。

下面是我在几个场景看到的典型匹配率范围,只作参考,不同模型组合之间差异很大:

场景类型典型匹配率我的建议
开放闲聊/摘要0.70-0.85放心开接力
知识问答/通用0.60-0.75正常使用
代码补全0.50-0.70建议开启,K 适中
数学/符号推导0.30-0.50谨慎,K 调小甚至关掉

6. 接力的几种变体:从 token 级到请求级

6.1 请求级接力:难问题才上大模型

token 级接力最适合生成长文本。如果你的场景是短问答、分类、抽取这类请求,其实可以做更粗粒度的接力——请求级级联。

流程是:小模型先完整生成整个答案,同时算一个置信度指标,比如 token 平均概率、多次采样结果的一致性;置信度超过阈值就直接返回小模型答案,低于阈值才把请求交给大模型。这样连“验证往复”都省了,简单问题完全由小模型消化,大模型只处理真正难的问题。

这个思路适合大量轻量请求的场景,比如客服问答、知识库检索后的摘要。收益可能比 token 级接力更直接,因为它把大模型从大多数请求里彻底解放了。但缺点是需要一个可靠的置信度判断,否则简单问题也可能被小模型答错却没有拦截。

6.2 不用外部小模型:让大模型自带候选头

还有一种方案不依赖外部草稿模型:在大模型原有输出层之上,再训练一组“候选解码头”。推理时解码步骤同时产生多个不同的候选 token,再让主模型整体验证。这个方案的好处是省掉了额外的草稿模型加载、词表对齐和采样参数同步,缺点是候选头需要额外微调,工程量不小。

从实践看,如果团队目标是快速拿到收益,外部小模型的路线最省事;如果对延迟、显存有极致要求,又愿意投入定制开发,内部候选头的路子更干净。

6.3 动态 K 值和分层草稿,是我最看好的方向

目前多数实现的 K 是固定的,但实际生成过程中匹配率一直在波动:句子开头、转折处匹配率低,顺承平淡处匹配率高。固定 K 要么在低匹配率段浪费验证成本,要么在高匹配率段没有充分榨取草稿价值。

一个更聪明的做法是让 K 自适应:在线统计最近若干轮的接受率,接受率高就调大下一轮草稿长度,接受率低就缩短。更进一步还可以做分层草稿——准备两个大小不同的草稿模型,简单区域让最小模型跑,难度上来后再换稍大的草稿接力。这类方案实现复杂,但在超长文本生成、内容续写这类场景里,边际收益非常可观。

最后聊一点我自己的体会。如果你刚接触大小模型接力,别一上来就钻理论,先用一个和主模型同家族、同词表的小模型,把现成推理库的 assisted decoding 打开,K 设成 6 左右,跑一批线上真实请求,把匹配率和端到端加速比打出来。只要能稳定看到 1.5 倍以上,就说明路数对了,之后再慢慢调 K、调采样一致性、试动态草稿。

我踩过最大的坑是太早追求大 K 值,结果显存涨了一倍,加速比反而没动。后来稳定的配置很简单:草稿模型够用就好,K 值够用就好,别贪。这个调优思路可以复用到你手头任何一个大模型推理服务上,先跑通,再抠细节,收益自然就出来了。

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

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

立即咨询