调查研究-220 Batching 才是 GPU Serving 的第一性原理

Batching 才是 GPU Serving 的第一性原理

如果只用一句话解释 GPU Serving 的核心,那就是:把零散请求合成 GPU 喜欢的大任务。这个动作就是 batching。

TL;DR

  • 场景:自研 LLM Serving 框架,或在 vLLM / SGLang / TGI / TensorRT-LLM 之间选型调参,遇到吞吐上不去、TTFT 抖动、长 prompt 阻塞解码等问题。
  • 结论:Batching 不是"可选优化",而是 GPU Serving 的地基。LLM 因为 decode 是逐 token 循环,传统静态 batch 会被长输出拖死;continuous batching 把调度粒度从"整批请求"推进到"每个生成 step",请求可以动态进出。
  • 产出:三层次 batching 框架(请求级 / token 级 / workload 级)+ padding/prefill/decode 约束关系 + 调参顺序清单 + 8 项 batching 常见错误速查。

版本矩阵

机制 / 动作 状态 说明
dynamic batching(短窗口凑批) ✅ 已验证 vLLM / TGI / SGLang / TensorRT-LLM 默认开启
continuous batching(每 step 重组) ✅ 已验证 HF Transformers 官方文档 + vLLM blog 2025-09-05
in-flight batching(TensorRT-LLM 术语) ✅ 已验证 NVIDIA TensorRT-LLM 文档明确,核心思想与 continuous batching 一致
paged KV Cache / PagedAttention ✅ 已验证 vLLM 论文 arXiv:2309.06180;2025--2026 仍是 KV 管理事实标准
chunked prefill(长 prompt 切块) ✅ 已验证 vLLM / SGLang 当前主推;防长 prefill 拖垮 decode 节奏
prefill / decode 分离(disaggregated) ✅ 已验证 vLLM 官方文档 disagg_prefill 章节;2024--2025 落地
按 token budget 调度(区分 prefill/decode 预算) ✅ 已验证 vLLM max_num_batched_tokens、SGLang 同类参数
prefix caching(重复上下文复用) ✅ 已验证 vLLM / SGLang / TGI 均原生支持
静态 batch(等齐再跑)作为在线 LLM 主方案 ❌ 反模式 文章第二节已分析 4 类失败模式
只用 GPU Util 调 batching 参数 ⚠️ 反模式 必须同时看 TTFT / TPOT / P95 / KV usage / waiting queue

如果只用一句话解释 GPU Serving 的核心,那就是:把零散请求合成 GPU 喜欢的大任务。这个动作就是 batching。

很多推理优化看起来很高级,比如 continuous batching、in-flight batching、chunked prefill、speculative decoding、prefix cache reuse、paged KV cache,但它们背后都有一个共同目标:让 GPU 尽量不要孤零零地处理一个小请求。

GPU Serving 和普通 Web 服务最大的差异,不在接口形式,而在执行形状。Web 服务里,一个请求可以对应一个线程、一个协程、一个异步任务。GPU 推理里,如果每个请求都单独跑,硬件往往吃不满。真正的服务能力来自调度器:它持续把随机到达、长度不一、生命周期不一致的请求,组织成 GPU 能高效执行的批次。

所以,batching 不是一个"可选优化",而是 GPU Serving 的地基。

一、为什么 batching 能提升吞吐

GPU 的强项是并行。一次矩阵乘法中,如果 batch size 太小,GPU 上大量计算单元会空闲。扩大 batch 后,同一个模型权重可以被更多输入共享访问,矩阵计算规模更大,kernel 执行效率更高,单位 token 的成本下降。

比如一个请求单独跑,GPU 可能只能达到很低的利用率;十几个请求合起来,利用率会明显提升。这里的提升不是因为模型变小了,也不是因为算法突然更聪明,而是因为计算形状更适合 GPU。

batching 的收益主要来自三点。

第一,摊薄固定开销。kernel launch、调度、框架运行时开销可以被多个请求共享。请求越碎,固定开销越显眼;batch 越稳定,这部分开销越容易被摊平。

第二,提高矩阵计算效率。更大的矩阵维度通常能让 GEMM 更接近硬件峰值。GPU 不怕大规模规整计算,怕的是每次只喂一点点工作。

第三,减少空转。请求之间互相填补执行空隙,让 GPU 持续有活干。Serving 系统不是只要"能跑",而是要让昂贵的 GPU 少等、少空、少碎。

但 batching 不是越大越好。batch 越大,等待凑批的时间越长,显存占用越高,单个请求可能排队更久。Serving 的难点就在这里:batching 是吞吐的朋友,也是延迟的敌人。

二、静态 batch:最简单,也最不适合在线 LLM

静态 batch 的方式很朴素:等一批请求到齐,合成一个 batch,跑完后一起返回。

这在离线任务里很好用。比如有 100 万条文本要做 embedding,可以直接设定 batch size 128 或 512,持续喂给 GPU。图像分类、离线转写、批量重排、批量 embedding,都可以接受更大的 batch 和更长的等待。

但在线 LLM 生成里,静态 batch 很容易出问题。

第一,请求到达时间不一致。为了凑够 batch,需要让早到的请求等待。等待越久,TTFT 越差。

第二,输入长度不一致。短 prompt 要被 padding 到长 prompt 的长度,造成计算浪费。一个 100 token 的请求和一个 2000 token 的请求硬塞在一起,短请求会被长请求的形状拖着跑。

第三,输出长度不一致。短输出请求早就结束了,但如果 batch 的生命周期绑定在一起,就会被长输出请求拖住。

第四,流式输出场景更复杂。用户希望 token 一边生成一边返回,而不是等整批完成。

因此静态 batch 更适合离线推理、embedding、图像分类、语音批量转写,不适合作为高并发在线 LLM Serving 的核心方案。

动态 batch 是在线推理的第一步。它不是等待固定数量请求,而是在一个很短的时间窗口内收集请求。例如服务端设置 max_wait_ms = 5。第一个请求到达后,不是立刻执行,而是等待最多 5 毫秒,看看有没有其他请求一起进来。时间到或者 batch 满了,就立即执行。

动态 batch 的关键参数有几个:

参数 作用 调得过大的风险
max_batch_size 一次最多合多少请求 显存压力高,短请求排队久
max_wait_ms 最长等待多久 TTFT 变差,交互感下降
max_tokens 一次 batch 允许的 token 总量 长 prompt 把批次撑爆
priority 不同请求的排队顺序 策略不清会制造饥饿和不公平

动态 batch 是 GPU Serving 的基本功,但对于 LLM 生成仍然不够,因为 LLM 的 decode 是逐 token 循环。

三、为什么 LLM 需要 continuous batching

普通动态 batch 的问题是:一旦一批请求开始执行,后来的请求要等这批执行完才能加入。

这对"一次输入一次输出"的模型可以接受。但 LLM 生成不是一次性完成。一个 batch 可能要 decode 几百轮。假如 batch 里有一个请求生成 1000 个 token,后面的请求一直等它结束,延迟会很差。

continuous batching 的思路是:不要把 batch 看成固定生命周期,而是在每一个 decode step 重新组织 batch。

某个请求生成结束,就从 running batch 中移除。新的请求如果已经完成 prefill 或者可以被调度,就立刻加入下一轮 decode。这样 GPU 上的 batch 是流动的,而不是固定的。

Hugging Face Transformers 文档对 continuous batching 的描述是:在每个生成步骤动态重排 batch,请求结束后新请求可以立即加入。这也是现代 LLM Serving 框架的核心能力。

continuous batching 解决了在线 LLM 的两个痛点。

第一,避免短请求被长请求拖死。短请求结束后释放位置,新请求马上补进来。

第二,提高 GPU 利用率。decode 循环中不断有请求进出,GPU 更容易保持饱和。

不同框架里术语略有差异。vLLM 常说 continuous batching。TensorRT-LLM 文档中常见 in-flight batching。它们的核心思想类似:正在执行的一批请求不是封闭的,系统可以在生成过程中动态加入新请求、移除完成请求。

这和传统 batch 最大的区别是调度粒度。传统 batch 的调度粒度是"整个请求"或者"整批请求";continuous batching 的调度粒度是"生成 step"。

调度粒度变细之后,系统可以更灵活,但 scheduler 的复杂度也大幅提升。每一轮都要判断:哪些请求可运行?KV Cache 是否够?哪些请求要被抢占?是否插入 prefill?是否让 decode 优先?是否限制长输出请求?

因此 continuous batching 不是简单的队列合并,而是一个持续运行的调度系统。

四、Batching 的三个层次

LLM Serving 里可以把 batching 分成三个层次。

第一层是请求级 batching。多个请求一起进入模型执行。它解决的是 GPU 利用率问题。很多自研系统一开始只做到这一层:把请求聚合起来,凑够 batch,再做一次 forward。

第二层是 token 级 batching。decode 阶段每个 step 重新组织正在生成的请求。它解决的是输出长度不一致问题。短请求结束就退出,长请求继续生成,新请求可以补位。

第三层是 workload 级 batching。系统根据 prefill、decode、长 prompt、短 prompt、优先级、租户、SLO,把不同类型工作混合调度。它解决的是生产系统的稳定性问题。

这三个层次可以放在一张表里看:

层次 调度对象 解决的问题 常见机制
请求级 一批请求 GPU 利用率低 dynamic batch、token budget
token 级 每个 decode step 长短输出互相拖累 continuous batching、in-flight batching
workload 级 prefill / decode / 租户 / SLO 尾延迟和稳定性 priority、preemption、chunked prefill、disaggregation

很多系统看起来能跑,但很快遇到长尾延迟和显存问题,原因往往是只做到了第一层。成熟框架通常至少做到了第二层,并开始在第三层上做更多策略。

五、padding 浪费:batching 的隐藏成本

batching 的副作用是 padding。

如果一个 batch 里有三个 prompt,长度分别是 100、500、2000。普通实现可能要把它们 pad 到 2000。这样前两个请求的大量位置是无效计算。

输出阶段也类似。一个请求输出 20 个 token,另一个输出 1000 个 token。如果 batch 生命周期固定,短请求结束后留下空位,会浪费后续 step。

现代 LLM Serving 要尽量减少 padding 浪费。常见做法包括:

  • 按长度分桶;
  • 按 token budget 调度;
  • continuous batching;
  • paged KV cache;
  • chunked prefill。

长度分桶适合离线任务。在线服务中,请求是随机到达的,不能无限等待同长度请求,所以通常要在等待时间和长度匹配之间折中。

这里还要区分 prefill batch 和 decode batch。很多人说 batch size,但没有说明是哪一段。

Prefill 阶段一次处理整个 prompt,工作量和输入 token 数强相关。一个请求的 prompt 可能有 200 token,也可能有 20K token。因此 prefill 更适合用 token budget 来调度,而不是只看请求数。

Decode 阶段每个请求每轮通常只生成一个 token。它的 batch size 更接近"当前并发生成中的请求数"。Decode 的瓶颈常常是权重读取和 KV Cache 访问。

所以调度器通常需要区分两类预算:

text 复制代码
prefill token budget    = 本轮最多处理多少输入 token
decode sequence budget  = 本轮最多处理多少条正在生成的序列

如果不区分,长 prompt 请求可能把整个 batch 撑爆,导致短请求 TTFT 变差;或者 decode 请求太多,导致 TPOT 变差。

六、Chunked prefill:避免长 prompt 阻塞系统

长 prompt 是线上 LLM Serving 的常见杀手。RAG、Agent、长文档分析、多轮对话都会制造长 prompt。

如果一个 30K token 的 prefill 一次性进入 GPU,它会占用较长时间,期间 decode 请求可能被阻塞。用户表现上就是:已有流式输出突然卡顿,或者新请求首 token 很慢。

chunked prefill 的思路是把长 prompt 切成多个 chunk 分批处理。调度器可以在 chunk 之间插入 decode step,避免长 prefill 独占 GPU。

它牺牲了一点实现复杂度,换来更稳定的交互体验。对于语音对话、在线聊天、Agent 工具调用,这个能力很重要。

vLLM 文档也把 chunked prefill、continuous batching、prefix caching、PagedAttention 放在高吞吐 serving 的关键能力中。进一步的 prefill / decode disaggregation,则把这两类工作负载拆到不同资源池上,减少互相干扰。是否要上到 disaggregation,不取决于名字高级不高级,而取决于你的尾延迟、KV Cache 压力、请求长度分布和运维复杂度是否已经需要它。

对于大多数团队,合理顺序通常是:

  1. 先确认动态 batching 和 token budget 做对;
  2. 再看 continuous batching 是否稳定;
  3. 再处理 KV Cache 管理和抢占策略;
  4. 最后评估 chunked prefill 或 prefill/decode 分离。

不要一上来就堆术语。先把请求分布和关键指标看清楚。

七、Batching 参数怎么调

没有通用最优值,只有业务最优值。

如果是离线批处理,目标是最大吞吐,可以扩大 max_batch_sizemax_num_batched_tokens,允许更长等待。这里用户不在屏幕前等首 token,系统可以更激进地把 GPU 喂满。

如果是在线聊天,目标是低 TTFT 和稳定 TPOT,等待窗口要小,decode 优先级要高,长 prompt 要 chunked prefill。这里的体验不是平均 tokens/s,而是用户是否能尽快看到第一个 token,后续流式是否稳定。

如果是语音对话,TTFT 更敏感。首 token 慢会直接影响语音首字延迟。此时不能只追求 tokens/s,而要控制 P95 TTFT 和 P95 TPOT。

如果是 Agent 系统,一次用户请求可能触发多次 LLM 调用。单次调用延迟会被链式放大,因此尾延迟比平均吞吐更重要。

压测时至少要同时观察:

  • 输入长度分布;
  • 输出长度分布;
  • 并发数;
  • TTFT;
  • TPOT;
  • P95 / P99;
  • GPU util;
  • KV cache usage;
  • waiting queue length;
  • running requests;
  • preemption 次数;
  • 每类请求的 SLO 违约率。

只看 GPU util 很危险。GPU util 高,可能是健康,也可能是长 prompt 把系统堵住了。只看 tokens/s 也不够,tokens/s 高但 TTFT 爆炸,在线体验仍然很差。

八、核心结论

Batching 是 GPU Serving 的第一性原理。没有 batching,GPU 很难在在线请求中发挥成本优势。

但 LLM 的 batching 不是简单把请求拼起来。因为 LLM 是自回归生成,请求会在 decode 阶段持续占用系统,且输出长度不确定。传统静态 batch 会被长请求拖死,普通动态 batch 也不够。

continuous batching 把调度粒度从"整批请求"推进到"每个生成 step",让请求可以动态加入和退出。这是 vLLM、TGI、TensorRT-LLM 等现代 LLM Serving 框架的共同核心。

真正的工程目标不是 batch 越大越好,而是在吞吐、TTFT、TPOT、显存、尾延迟之间找到业务可接受的平衡点。

可以把这篇文章浓缩成一句工程判断:

不会 batching 的 Serving,只是在用昂贵 GPU 跑小任务;只会放大 batch 的 Serving,又会把用户体验压进队列里。

好的 GPU Serving,不是把所有请求塞进一个更大的 batch,而是在每一轮调度里,知道什么该等、什么该跑、什么该拆、什么该抢占。

参考资料


错误速查卡

症状 根因 定位 修复
TTFT 普遍偏高,新请求都要等 凑批窗口设太大(max_wait_ms 过大)或 batch 已满,新请求只能等下一轮 看 queue time、waiting requests、max_wait_ms 配置 缩短 max_wait_ms;调小 max_batch_size;评估 token budget
P95 偶发爆掉,长 prompt 进来后整批都被拖住 长 prompt 一次性 prefill 把 GPU 占满,期间 decode 被阻塞 看 P99 命中样本的 prompt 长度、prefill step 时长 开 chunked prefill;按 token budget 调度
tokens/s 看着不错,但用户感觉流式卡顿 静态 batch 或普通动态 batch,长输出拖住短输出 看每请求的 TPOT 分布、decode 步间隔方差 上 continuous batching / in-flight batching
GPU util 接近 100%,但并发提不上去 显存被 KV Cache / 长上下文占满,新请求被 KV 预算拒掉 单看 KV cache usage 与 max_num_seqs 配置 开 paged KV(PagedAttention);设 KV 预留水位
平均延迟还行,尾延迟严重抖动 scheduler 没做优先级,长请求抢资源、短请求被反复抢占 看 preemption 次数、被抢占请求的分布 上优先级 + 抢占策略;长 prompt 单独队列
padding 浪费严重,长 prompt 进来后短请求陪着算 没用长度分桶或 token budget,batch 内形状严重不齐 看 prompt 长度分布、batch 内 max/p50 比例 按 token budget 调度;按长度分桶(离线场景)
调大 max_batch_size 后吞吐反而下降 等待窗口同步放大,TTFT 变差导致部分请求超时重试 看 TTFT 是否随 max_batch_size 单调上升 max_batch_size 与 max_wait_ms 联合调,先固定一个调另一个
服务看着正常,但 LLM 调用的 P99 在 Agent 链路里被放大 只调了平均吞吐,尾延迟没单独保护 在 Agent 链路里给单次 LLM 调用记录 TTFT/TPOT P95/P99 把 P95/P99 写进调度策略;SLO 违约请求降级或单独池

作者:武子康的个人博客

相关推荐
dreamread1 小时前
2026盲派排法八字排盘软件怎么选:看排法口径、案例记录和学习边界
人工智能·软件工具·传统文化
happyprince1 小时前
2026年07月07日全球AI前沿动态
人工智能·ai
Zldaisy3d1 小时前
3D打印换热器及散热器白皮书-第四版
人工智能
媒介发稿小能手1 小时前
权威+垂直+全域媒体资源:选对GEO发稿平台,抢占AI搜索高地
大数据·人工智能·媒体
AI 大模型学习不踩坑1 小时前
Harness 架构原理与工程实践详解
java·人工智能·神经网络·机器学习·计算机视觉·架构
GitCode官方2 小时前
openJiuwen 总 Star 突破 3.3 万,与 AtomGit 携手构筑 AI Agent 的开源“生态飞轮”
人工智能·开源
Medeaselab2 小时前
生理学实验之神经放电模块的应用
人工智能·功能测试·需求分析
武子康2 小时前
调查研究-219 GPU 为什么能快,也为什么容易被大量小请求拖慢?
人工智能·llm·gpu
Drgfd2 小时前
多模态大模型技术深耕:从模态融合到跨域迁移,重构AI理解与生成能力
人工智能·深度学习·计算机视觉