推理批尺寸动态调节的核心答案不是“调一个固定值”,而是让引擎在每次迭代中根据显存水位、请求队列长度和请求类型自动决定batch size,优先保证吞吐稳定而不被某一个大请求堵死。
大模型上线之后,很多人还抱着玩传统深度学习的习惯,把batch size当成一个超参,调完就固定不动,但推理场景的输入长度是动态的,请求到达时间是随机的,显存占用更是像过山车,固定batch size要么导致GPU闲置,要么直接触发OOM重启服务,动态调节不是优化手段,而是大模型推理服务活下去的前提。
为什么推理批尺寸需要动态调节
先看一个真实场景,你的服务早上十点迎来流量高峰,队列里混着一篇万字长文的总结请求、二十来个短对话、还有一些多轮上下文很深的Agent调用,固定batch size设为16时,这批请求的总token数可能直接突破显存极限;设为4时,短请求的吞吐又被白白浪费,无论怎么设,都是在两种失败之间选一种。
问题的根源在于,推理服务里的每个“样本”根本不是等价的,一个生成512个token的长请求,占用的GPU内存可能是短请求的几十倍,传统训练里batch size均匀切分显存的思路,在推理场景里完全不成立,行业共识认为,把batch size从静态超参变成运行时变量,是推理引擎必须跨过的一道坎。
固定batch size有三个绕不过去的痛点:
- 波峰波谷吞噬缓冲能力:流量低峰时小batch浪费算力,高峰时大batch直接超时
- 长短请求互相拖累:一个超长请求卡住整批,后面的短请求全部排队等待
- 显存碎片无法回收:固定batch次数的显存预留,和实际请求的token级占用完全脱节
这三个痛点叠加起来,就是你的GPU利用率看似很高,实际上排队延迟一直在涨,长尾时延惨不忍睹。
推理引擎动态批处理与固定batch size的核心区别
动态批处理的核心思路叫连续批处理(Continuous Batching),传统动态批处理在每一步结束时统一换一批请求,连续批处理则把一个序列的生成拆成独立状态,任何一个sequence生成完毕,立刻从等待队列里拉一个新请求补位,同一时刻,GPU里既有刚进来的prefill请求,也有已经生成到一半的decode请求,还有已经结束等输出的尾部请求。
如果你在排查自己的批尺寸策略,首先要区分这三个概念:
- max_num_seqs:引擎允许同时处理的最大序列数,相当于batch size的上限
- max_num_batched_tokens:单次迭代允许处理的token总量,这里是显存的真正边界
- gpu_memory_utilization:为KV Cache预留的显存比例,预留不足会限制batch容量
固定batch和动态批处理之间,差距体现在四个维度上:
| 对比维度 | 固定batch size | 连续批处理动态调节 |
|---|---|---|
| 调度粒度 | 每步整批换入换出 | 每步逐序列增量补位 |
| 显存利用率 | 按最大请求预留,浪费严重 | 按实际token数动态分配KVCache |
| 短请求延迟 | 被长请求阻塞,尾延迟极高 | 短请求可提前退出,不被拖累 |
| 波峰应对能力 | 只能超卖或拒绝 | 通过水位感知自动压缩/扩容 |
动态batch size怎么设置,生产环境三步实操
这一步直接给可落地的路径,以vLLM为例(这类框架统称推理引擎),动态batch size不用你写调度算法,但需要配好让调度器自由发挥的边界条件。
第一步:设置显存预算。
显存预算是动态调节的物理边界,模型权重占一部分,剩下的全部留给KV Cache和激活值,建议先按gpu_memory_utilization设置为0.85左右起步,留出激活值和推理框架自身开销的余量。
第二步:放开batch上限,而不是设置成一个保守值。
max_num_seqs不要设成很小的值,这会限制调度器的补位能力,参考经验是:设置到接近理论显存上限的最大值,比如64甚至128,让引擎根据实际token量来柔性伸缩,如果框架的调度器设计合理,batch size会在每次迭代自动波动。
第三步:开启连续批处理和分块预填充(Chunked Prefill)支持。
分块预填写的是将长请求的prefill阶段切分到多个迭代中执行,避免前序长请求独占算力导致动态batch失效,很多框架默认开启,如果被关闭了需要手动打开,目标是让prefill和decode在同一迭代里交错进行。
操作完成后,观察GPU利用率曲线理想形态是稳定在中高水位,而不是间歇性飙到满格后掉到零。
在线推理批尺寸动态调节的显存水位策略
生产环境的麻烦在于,连续批处理虽然能动态补位,但如果光补不控,某个请求瞬间吃满KVCache,后续请求全被卡在preemption,这里需要一个显存水位预警机制。
常见策略分三层:
硬上限硬顶
KVCache用满时,新请求直接进排队队列,不参与本轮调度,简单可靠,但排队延迟会随波峰上升。
软水位预警
当KVCache占用达到预留容量的70%左右时,调度器开始主动检票对已经生成的序列做preemption优先级排序,优先保短请求、保交互性请求。
弹性压缩
检测到批量过大且无法再压缩时,通过降低生成长度上限、合并相同前缀请求等方式换取batch继续运行,这与模型的max_tokens参数联动实现。
实践中的一个关键技巧是,不要把max_num_seqs设得过紧如果上限只有8,引擎完全没有调节空间;设上限32后,调度器自然会把大请求和小请求交错排开,想要感知实时水位,可以用 vllm serve 暴露的metrics端点拉取 gpu_cache_usage_perc 指标,这个字段代表KV Cache占用百分比。
场景化调节建议,batch size多大合适
你优化的目标决定了动态调节的方向,大模型推理batch size多大合适,这个问题的答案取决于你的服务类型:
线上交互场景
像实时客服、聊天助手这类应用,首要守住延迟,抑制请求吞吐的野性,采用低水位高阈值策略:把软水位预警设低一些(60%左右),KVCache上限不超过总显存的80%,保证吞吐波动时仍有缓冲余量,宁可让batch size小一点,也要控制在60毫秒内返回TTFT。
离线批处理场景
一次性处理大批量离线数据时,也不需要绝对动态,把intra-request并发调低、extra-request并发调高,max_num_batched_tokens直接拉满,让引擎把短请求打包,这个场景下时延完全不是瓶颈。
GPU显存不足怎么调batch size
显存不够的时候,第一反应不该是把batch调小,而是先检查显存分配是否合理:
- 降低
gpu_memory_utilization到0.7,给激活值留足空间,降低OOM触发KV Cache驱逐的概率 - 启用KV Cache的量化压缩(如FP8存储),在同等物理显存下提高有效batch容量
- 让框架开启swap到CPU内存的选项,但注意这会带来两到三倍的显存速度退化,只设单次swap量上限
这类场景下不要把动态调节的期望值调成极限吞吐,优先保证持续稳定运行。
动态批处理的三个常见误区
batch越大越好。 动态调节的目标是贴合当前负载,而不是追求数值上限,batch大时,生成阶段的吞吐反而因为内存带宽争抢而下降,KVCache被吃满后,preemption频率大增,整体响应速度反而变慢。
把max_num_seqs当成batch size设置。 它只是上限,真正的batch size是每轮迭代实际调度的序列数,把上限调小,相当于给调度器戴了手脚,动态调节的灵活性直接被阉割。
忽略preemption开销。 动态批处理触发preemption时,被抢占的请求重新计算prefill的部分需要额外时间,与其让引擎反复抢占,不如通过软水位提前限制调度,preemption应作为兜底手段而非主动手段,业内专家指出,生产环境上一旦抢占率超过一定比例,吞吐不升反降,这是动态调节过度激进的一个典型信号。
把batch size从固定值交给调度器,不是放手不管,而是设定边界、监控水位、让引擎在边界内自主决策,动态调节的最终形态是系统根据实时请求特征自行匹配最优batch,你只需要做一件事,设定一个合理的调节区间和保护机制。
推理批尺寸动态调节常见问题解答
大模型推理batch size多大合适?
没有定值,标准是看两个指标:单次迭代的token总量是否接近KVCache预留上限,以及TTFT和TPOT是否在服务SLA内,如果你的短请求TTFT持续超过阈值,说明batch过密;如果GPU利用率长期低于40%,说明batch过疏,动态调节的意义就在于让batch不是固定数,而是在上下限之间自动游走。
动态batch size会不会增加单请求的生成延迟?
在连续批处理机制下,单请求的decode迭代本身并不变慢,动态调节影响的主要是排队延迟请求等待被调度的时长,显存水位预留合理时,动态batch会缩短而不是加长等待时间,因为引擎在每轮迭代腾出空位立即补入新请求,前提是水位和上限设置正确,设置不当才是延迟升高的主要原因。
连续批处理触发preemption时动态调节会失效吗?
不会失效,preemption是动态批处理在显存耗尽时的保护机制,被抢占的请求会重新排队,之后仍可重新参与调度,这个过程的代价是重新计算被踢出部分的前向传播,有一定的时间开销,通过软水位低值预警,让调度器在还没满的时候就开始排队,可以大幅减少这种开销发生的频率。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/623983.html





