深夜调接口,盯着进度条一格一格往前挪。问题往往不是模型不会答,而是它答得太慢。推理优化常被当作工程侧的降本手段,实际动手才发现,它首先是一门关于“等待”的学问:等在哪一段、等多久、谁在等。
慢在哪里:两段截然不同的时间
一次生成大致分成两段。预填充把整段输入一次算完,吃的是算力;解码一个词一个词往外吐,吃的是显存带宽。两者瓶颈不同,手法就不能混着用。把预填充的批开大,吞吐会上来,可解码阶段要照顾批里最慢的那条序列,其余请求只能空转。很多“优化后更卡”的现场,根源就在这里。
缓存与猜测:把重复劳动省掉
KV 缓存是常见的起点——算过的注意力键值不必重算,代价是显存占用随上下文长度增长。于是有了分页管理、前缀复用这类做法,把长上下文里反复出现的开销摊薄。它们不改变模型能力,只改变同一份计算被做了几遍。
投机解码走另一条路:用一个小模型先草拟若干词,大模型一次并行校验。草稿被接受得多,收益就明显;碰上术语密集、句式别扭的文本,草稿频频被打回,收益随之缩水。所以这类方法值不值得上,取决于流量里“好猜”的比例,而不是论文里的理想条件。
量化:精度换空间的边界
把权重和激活压到更低比特,显存和带宽压力都下降,这是推理优化里最直接的一招。但压缩不是免费的:长链推理、数值敏感的任务,误差会沿着步骤累积,前面偏一点,后面越走越远。工程上常见的处理是分层对待——敏感部分保留较高精度,其余放开。这条边界只能靠自己的评测集去划,别人跑通的配置,换一份输入未必迁得过来。
被忽略的另一半:调度与产品
- 排队策略决定的是尾延迟,而不只是平均值。批处理窗口开大一点,整体吞吐好看,单个用户的等待也可能变长。
- 流式输出让首字时间变得可感知。用户对“慢”的容忍度,很大程度上取决于是否看得见进展。
- 缓存命中、超时重试、高峰期降级到更小的模型,这些产品侧的选择,往往比调一个参数更影响体感。
优化到哪算够
先想清楚哪条指标真正影响使用——首字延迟、单次调用成本,还是高峰期的稳定——再决定动哪里。反过来,把所有手段一股脑叠上去,系统会变得难以解释,出了故障也不好定位是哪一层在捣鬼。留一点余量,通常比压到极限更耐用。
所以推理优化的终点不是一个数字,而是一份预算:你愿意为用户多等的那几百毫秒付出多少机器,又愿意为省下的机器牺牲多少可控性。这个问题没有通用答案,只有各自业务里能接受的那一档。