夜里十一点,测试同事发来一句:“同一个问题,白天问很快,现在要等好几秒。”屏幕上的服务没有报错,模型也在正常吐字。问题不在“会不会答”,而在“答得过来吗”。这类时刻,推理优化才从论文里的词,变成机房里要有人负责的活。
慢,不一定因为模型大
很多人把延迟归因于参数规模,于是想到换小模型。可线上请求并不按教科书到达:有的短,有的长;有的并发高,有的只是零星试探。请求排队、显存读写、批处理策略、上下文长度,都会改变用户等待的时间。把模型换小,可能让单次计算轻一些,却未必解决排队;把机器加多,也可能只是把空闲算力摊薄。
所以我更愿意把推理优化看成三笔账:算力账、内存账、体验账。算力决定单位时间能算多少,内存决定能同时装下多少上下文,体验决定请求该如何被调度。三笔账互相牵制,没有单独一笔能长期占上风。
批处理与缓存的取舍
批处理是常见手段:把多个请求拼在一起送进计算单元,提高吞吐。听起来很顺,但用户并不喜欢“等别人一起走”。批越大,单次计算效率可能越高,排队时间却可能变长。于是工程上常要做动态批处理,按请求长度和等待时间调整组合。这里没有一劳永逸的参数,只有对业务节奏的持续观察。
缓存则更像经验复用。相同或相似的前缀、系统提示、多轮对话里的历史状态,如果能留在显存中,就不必每次都从头算。KV 缓存、前缀缓存这类做法,解决的是重复劳动。但缓存会占内存,也会带来失效和一致性问题。省下来的时间,可能被另一处等待抵消。
量化不是免费午餐
量化降低数值精度,减少显存占用和计算开销,常被当作加速的快捷按钮。它确实有用,尤其当显存是瓶颈时。但精度下降会改变输出分布,某些任务对细微差异敏感,压缩后输出可能稳定,也可能漂移。上线前需要按任务做小范围对照,而不是只看吞吐数字。
同样,投机解码、算子融合、图优化这些方法,各有适用边界。它们解决的是不同层面的浪费:有的是减少串行等待,有的是减少内存搬运,有的是让计算图更紧凑。把它们混成一个“加速包”,容易在真实流量前露出短板。
先定义用户能感知的慢
我见过一种常见误区:只看服务端平均耗时,忽略首字延迟和完整回答时间。对聊天类产品,用户先感知的是“多久开始回”,然后才是“多久说完”。如果首字很快但后续卡顿,体验依旧破碎;如果整体不慢但排队很久,用户可能早已离开。推理优化要先定义哪一段慢值得改,再决定动批处理、缓存还是模型结构。
另一个边界是成本。延迟降低往往要换来更多显存、更复杂的调度或更频繁的监控。不是所有业务都需要把响应压到极短。有人愿意多等一会儿换取完整回答,也有人只要快速确认。把体验目标写清楚,才有可执行的调参依据。
留一点余量
回到那个深夜问题,排查下来发现是批处理窗口在低峰期没有及时收缩,请求被不必要地攒在一起。调整策略后,延迟回落,模型本身没换。这个结论并不戏剧,却接近推理优化的日常:它很少靠一个神奇开关,更多是对流量、内存和等待时间的反复校准。
如果要说判断,我会把推理优化放在产品与系统的交界处。它需要懂模型,也需要承认用户的时间不是免费资源。先把“慢”拆开,再谈加速,往往能少走弯路。