光标在输入框里闪了两秒,回答才一个字一个字地冒出来。用户不会去分辨这两秒里模型跑了多少矩阵乘法,他只会判断这个产品卡不卡。推理优化要处理的,正是这段被反复放大的体感。
先分清等待发生在哪一段
一次生成通常被切成两段。前半段是预填充,把用户输入的整段提示词一次性读进去,算出缓存;后半段是逐字解码,每输出一个 token 都要重新跑一遍前向。两段的时间特征完全不同:预填充消耗算力、可以并行,解码则被显存带宽卡住,算力常常闲着。
不少优化做了却没效果,是因为没分清时间花在哪一段。如果输入只有一句话、输出却有两千字,瓶颈几乎全在解码;如果是一份几十页的材料要总结,预填充才是大头。这两件事的解法并不通用,把针对解码的手段搬到长输入场景,往往看不出变化。
批处理是一种交换,并非免费午餐
把多个请求拼成一批送进 GPU,是提升吞吐的常见做法。但同一批里的请求要互相等,谁也不能先走。于是整机吞吐上去了,单个用户的首字延迟反而变长。线上系统通常要在两者之间划一条线:交互式产品更在意那一秒,离线任务更在意每小时能处理多少。同一条推理优化策略,放在两个场景里会得出相反的结论。
压缩与投机,边界在哪里
量化把权重从高位宽降到低位宽,减少需要搬运的数据量,解码阶段受益明显;投机解码则让一个小模型先草拟几个 token,再由大模型一次校验。听起来都划算,但代价写在别处:量化到一定程度,某些需要精算的任务会开始出错,而且错得不明显;投机解码的收益取决于草稿的命中率,命中率一低,多出来的计算反而拖慢整体。
这些手段都有适用区间,越界之后,省下的时间和丢掉的质量开始不成比例。所以做推理优化时,我更愿意先固定一套能接受的评估集,再看指标怎么动,而不是先挑一个看起来漂亮的方法,再回头找场景。
长上下文带来的新账
上下文变长之后,KV 缓存占用的显存迅速膨胀,能同时服务的请求数就被压下去。于是有了各种缓存淘汰、分页管理、前缀复用的做法。这里有个容易被忽略的点:共享前缀的命中率取决于真实流量的重复程度,如果每次请求都带着不同的材料,复用带来的收益会明显小于预期。
- 先量清楚预填充和解码各占多少时间,再决定往哪边使劲。
- 把首字延迟和吞吐分开看,别用同一个指标衡量两类产品。
- 任何压缩手段都要配一套回归评估,确认质量退化仍在可接受范围内。
写到这里,想说的其实很简单:推理优化很少是某个单点技术的胜利,更多是一连串取舍的叠加——你要快给谁看,愿意为此牺牲什么。把这个问题回答清楚,剩下的才是工程。