深夜看接口监控,最容易产生一种错觉:GPU 利用率不算高,吞吐却上不去,用户侧的等待一会儿长一会儿短。于是有人提议换卡、加机器,或者把模型换小。先别急。很多回答变慢的时刻,卡的算力并没有闲着,真正拖住节奏的是数据在内存里来回搬。
大模型推理大致分两段:预填充要一次性处理输入,解码则逐个 token 往外吐。到了解码阶段,计算量未必大,但对显存带宽和缓存命中很敏感。权重读一遍、KV 缓存读一遍,算得再快,也要等数据到位。所谓推理优化,第一步往往不是“算得更快”,而是先确认等待发生在哪:排队、分词、显存搬运、批处理调度,还是网络回传。把它们混在一起谈,优化很容易变成换硬件的冲动。
批处理的两面
动态批处理能把多条请求拼在一起,提高单位时间内的产出,这一点不假。但批一大,队尾请求的等待也会变长;流式输出又让用户对首字延迟格外敏感。于是一个常见的取舍出现了:吞吐好看,体验未必好。连续批处理、分块预填充这类做法,本质上是在调度层面做折中,让新请求不必等整批结束。它们不是免费午餐,状态管理、内存碎片和超时策略都会跟着变复杂。
我倾向于把批处理看成交通调度,而不是简单调大一个参数。高峰期该合并,低峰期该让单车先走;对首字敏感的场景,甚至要主动限制批的等待窗口。推理优化在这里更像产品决策:你愿意让少数人多等一会儿,换取整体更平稳,还是反过来?没有统一答案,只有是否说清楚。
缓存省下的,不一定是净收益
KV 缓存和前缀缓存常被寄予厚望。相同系统提示、相同文档前缀,确实可以少算一些。但缓存占的是显存,命中率低时,它反而挤压了可用空间,让批处理更难排布。清理策略过于积极,命中率下降;过于保守,内存碎片又会积累。更隐蔽的是,缓存让延迟分布变得不均匀:命中时很快,未命中时突然变慢,监控上的平均值反而看不出问题。
所以做推理优化时,我会先看分位数和请求结构,而不是只看平均吞吐。缓存该不该开、开多大、按什么键失效,需要和业务输入分布一起考虑。没有一种缓存策略能同时讨好所有请求。
把预算花在用户能感知的地方
延迟不是一个数字,而是一段被用户感知的时间。首字出现得快,后面逐字输出稳定,即使总时长略长,体感也可能更顺;反过来,前面卡住几秒,后面再快也难挽回。推理优化如果只盯着端到端均值,很容易把资源花错地方。给不同请求设不同的等待预算,允许低优先级请求排队或降级,可能比全局提速更实际。
当然,边界也要说清楚:模型能力、上下文长度和输出长度都会改变等待结构,离线评测好看,不代表线上稳定。优化之前先问一句,等待是谁造成的;优化之后再看一眼,等待被搬到了哪里。能回答这两个问题,参数调整才有方向。