Continuous Batching:从 static 到 iteration-level scheduling
本文通过可视化工具,讲解 static batching 为什么浪费 GPU,以及 continuous batching 的优化方案。可视化工具提供逐 iteration 对比。
Static batching 的问题
LLM 生成是自回归的:每个 iteration(一次 forward)为批内每个请求生成一个 token。同一批请求的输出长度差别很大。
static batching 把一批请求绑在一起跑。先完成的请求不能退出,槽位空转(下图的 斜纹格,即空泡),直到整批里最慢的请求完成;中途到达的新请求即使眼前有空槽, 也只能排队。
Static batching:整批进,整批出场景「日常流量」:请求陆续到达,输出长度中等且各不相同,最贴近日常在线流量
排队中 0已完成 0/10
t = 0/27
图里有两种空泡:请求已完成但槽位被整批锁住(悬停可以看到是哪个请求); 组批时没凑满、整批期间一直空着的槽。这分别对应着 static batching 的两个痛点:
- 当有某个 request 比较长,其他的 request 即使完成也需要等待;
- 组 batch 的时候可能凑不满,但中途不能加入新的 request,必须等待整批完成。
这两个痛点都会导致 GPU 利用率下降。
Iteration-level scheduling
ORCA 提出把调度粒度从一整批细化到一个 iteration,SGLang 等主流引擎都采用了 这个做法:每步结束,完成的请求立即释放槽位,队首请求下一步就能进来。 同样的请求流:
Continuous batching:iteration 级调度场景「日常流量」:请求陆续到达,输出长度中等且各不相同,最贴近日常在线流量
排队中 0已完成 0/10
t = 0/19
只要队列里还有请求,槽位就不会空着(work-conserving)。
对比
同一批请求,同一条时间轴。切换场景,看差距什么时候最大:
static vs continuous:同一批请求场景「长尾输出」:大多数请求很短,少数请求特别长,真实 LLM 流量的典型形态
Static batching排队 0 · 完成 0/10 · 空泡 0 格利用率0%
Continuous batching排队 0 · 完成 0/10 · 空泡 0 格利用率0%
t = 0/35
- 等长不稳定到达:有一定差距,即使所有 request 都等长,在不稳定到达的 情况下,static batching 每次组 batch 组不满,也会导致 GPU 资源浪费。
- 长尾输出:差距最大。static 每一批都被批内最长的请求拖住(head-of-line blocking),而真实 LLM 流量恰好是长尾的。
- 等长突发:严格等长、同时到达时,两者完全打平,这是 static 唯一不吃亏的 情况。它说明 static 的浪费全部来自长度差异和到达时间差异,而线上流量 恰恰两者都有,因此在现实使用中 continuous batching 的优化效果比实验室 理想结果更加好。
补充说明
- 为什么可以中途换请求?decode 时每个请求只依赖自己的 KV cache,batch 维度上互不相干。
- 显存成为新瓶颈:准入不再受批次限制之后,并发上限取决于 KV cache 占用的 显存,这是 PagedAttention 要解决的问题。
- prefill 会抢占 decode:新请求进来要先跑 prefill(图中浅色格,文中简化成 一步 iteration,真实系统里比一步 iteration 重得多),会拖慢同批其他请求 的出词速度。chunked prefill 和 PD 分离都是针对这个问题。
图 ↔ 真实系统对照
点选图里的元素,看它对应真实系统里的什么
一列一次 forward(一个 iteration)
延伸阅读
可视化教学由 Yichi Zhang 写作。