大模型推理投机解码与首字延迟优化落地

日期:2026-08-24

一、项目背景 我们一个代码补全场景对首字延迟特别敏感,用户在编辑器里敲到一半,模型要等好久才吐第一个字,很多人等不住就切去别的方式或者干脆手敲。我们之前主要靠堆 KV 缓存命中率来缓解,命中上去了但首字还是卡在几百毫秒,因为解码本身是逐 token 自回归,长一点就要等。老板给的指标是首字延迟要压到一百毫秒量级,纯靠加缓存做不到。我们当时研究了一圈加速手段,最后把投机解码搬进来试,思路是用一个小模型先草拟一串候选,再用大模型并行验证,能接受多少就一次吐多少。

草稿模型的部署也讲究,我们让它和目标模型共用同一张卡的空闲算力,不单独占卡,否则为了省延迟反而多出一张卡的成本,账算下来不划算。

选型时我们对比过几类加速手段,包括模型蒸馏和缓存复用,最后选投机解码是因为它对业务无感,调用方完全不用改代码,接入成本最低,这也是能快速落地的关键。

二、落地场景 第一块是草稿模型选型,我们拿业务日志里的高频 prompt 做评测,挑一个比目标模型小两到三档的草稿模型,太大反而拖慢。第二块是候选生成,草稿模型一次产出若干 token 的候选序列。第三块是目标模型并行验证,把候选一次性喂进去,算每个位置接受还是拒绝,接受的连续前缀直接输出。第四块是接受率监控,线上实时看接受率,掉到阈值以下自动降级回普通解码。第五块是长度自适应,简单请求多草稿几步,复杂请求少草稿,平衡加速比和额外开销。

验证阶段还有个数值稳定问题,草稿模型输出概率和目标准度不一致时,接受判定要用目标准确的 softmax 而非草稿的,我们一开始混用导致接受率虚高。

三、关键技术挑战与解决思路 草稿模型选不对是最容易翻车的点。我们第一版图省事用了只小一档的草稿模型,结果接受率只有三成左右,并行验证的开销比省下的还多,P99 反而涨了。换成小两档之后接受率回到七成上下,才真正转正。第二个难点是长度自适应,固定草稿步数在复杂请求上会浪费算力,我们按草稿模型自身置信度动态收尾。第三个难点是和连续批处理的兼容,投机解码要嵌进批处理调度里,不能因为验证一个请求把整批卡住,我们改了调度器让验证走旁路。

资源账也算清了,投机解码多花的那点草稿算力,靠首字延迟下降带来的留存提升完全覆盖,我们内部评估过,单这一项对补全功能日活的正向贡献就很可观。

部署层面还有个坑是多草稿步数下的显存占用,步数开太大草稿序列占显存,反而挤占目标模型的批处理空间,我们用压测定了一个和并发量联动的上限,高峰期自动收步数保吞吐,平峰放开提延迟,这个联动比固定值更贴合真实负载,也是上线前灰度时调出来的经验。

四、效果数据 上线后首字延迟从之前的约三百二十毫秒降到约一百一十毫秒,降幅接近六成,用户因等待切走的比例明显下降。草稿接受率稳定在七成左右,吞吐相比纯大模型自回归基本持平略升,没有为了延迟牺牲产量。长尾的复杂请求靠长度自适应保住,没有因为投机而变慢。用户补全采纳率提升约一成,主要就是等得住的人多了。

监控上我们给接受率加了趋势告警,一旦连续下滑就自动回退普通解码并通知我们,相当于给加速上了一道保险,不用担心某天模型分布变了反而变慢。

五、可复用经验总结 投机解码不是开个开关就快,草稿模型小两到三档、接受率七成左右才是甜区,我们当初小一档那版纯属帮倒忙。它和连续批处理要磨合,调度器不改好会卡整批,这点容易被忽略。另外别指望它救所有场景,长尾复杂请求要靠长度自适应兜底,否则省了前面亏了后面。我后来觉得,延迟优化这事,先量清楚瓶颈在解码还是在网络,再选武器,别一上来就堆花活。

加速技术选型别只看论文里的数字,业务分布和硬件占用都得算进去,我们这次是把草稿模型的卡成本和延迟收益放一起看,才敢全量推。

这套加速如今已经跑了一段时间,稳定性比我们预想的好,回头看当初那次小一档的试错反而省了后来的大麻烦。

结语 现在我们把它接进了更多对首字敏感的场景,比如对话式检索,下一步在看草稿模型的业务自适应微调。

案例片段(已脱敏): 接受率监控片段(伪码):acc := acceptedTokens / draftedTokens if acc < 0.4 {    mode = PlainDecoding          // 接受率过低降级    log.Warn("spec_decode low acc", "acc", acc) } metrics.Report("spec.acc", acc)   // 实时上报灰度初期我们盯着这条曲线,小一档草稿模型时 acc 长期在 0.3 附近,P99 涨了约十八毫秒;切到小两档后 acc 回到 0.68 到 0.74 区间,首字延迟才真正下来。