Batching
Batching
Decode 阶段每步只有一个 token 做计算,GPU 算力大量空置。Batching 的核心思路:把多个请求打包,每步同时处理多个请求各自的新 token,让一次权重搬运服务多个请求。
Static Batching
最直接的版本:一批请求同时进来,一起跑到所有请求都完成再接新的。
问题是 Head-of-Line Blocking:短请求完成后必须等批次里最长的那个,期间 GPU 槽位在空转。
Continuous Batching
解法:不以"请求"为调度单位,改以每一步 decode 为单位(iteration-level scheduling)。每步结束后检查:有完成的请求就移出,有等待的新请求就插入。下一步的 batch 是动态组成的。
来自 2022 年 Orca 论文,vLLM 把它做进了系统。
Selective Batching:不同算子区别对待
Continuous Batching 带来新问题:batch 里的请求序列长度不同,矩阵维度对不上。用 padding 对齐会在短序列位置做无用计算。
关键认知:所有请求步调一致,同一时刻都在同一层的同一个算子里。区别只是这个算子能不能 batch:
第 N 层,同一时刻:
RMSNorm → 所有请求新 token 拼成 [B, D],一次算完 ✅
Attention → 每个请求单独算 ❌
FFN → 再拼回 [B, D],一次算完 ✅
为什么 non-attention 可以 batch:FFN、RMSNorm 每个 token 的输出只依赖自己那行向量,和其他 token 无关。拼成大矩阵做 matmul,每行独立计算,结果和拆开算完全一样,没有信息污染。
为什么 attention 不能跨请求 batch:attention 的输出是整个序列 V 的加权平均,每个 token 的输出依赖序列里所有其他 token。不同请求的 KV cache 长度不同(3、9、5),强行合并会让请求 A 的 Q 错误地 attend 到请求 B 的历史,计算结果污染。
warning: 容易误解的地方 不是"某些 token 在做 attention、某些在做 FFN"。所有请求同一时刻经历同一个算子,只是 attention 这个算子无法跨请求合并。 另外,attention 并非完全不能 batch——同一请求内的 prefill(多个 token 同时处理)完全可以 batch,不能 batch 的是 decode 阶段不同请求之间的 attention(各自 KV cache 长度不同)。
效果对比(A100 80GB,LLaMA-2 7B):
| 策略 | GPU 利用率 | 提升 |
|---|---|---|
| 无 Batching | <10% | 基线 |
| Static Batching (size=8) | ~50% | — |
| Continuous Batching (size=8) | >90% | 吞吐约 10-20x |
Prefill-Decode 干扰
Continuous Batching 引入了新问题:新请求进来要先做 Prefill,但其他槽位里的请求还在 Decode。Prefill 是大矩阵运算,会占满 GPU 算力,导致其他请求的 Decode 被阻塞,用户感受到卡顿。
这是 PD分离 的动机。
实验数据
并发扫描(RTX 4090,Qwen2.5-7B-AWQ,短 prompt):
| 并发 | SGLang tok/s | vLLM tok/s |
|---|---|---|
| 1 | 31.2 | 19.0 |
| 8 | 239.4 | 146.2 |
| 32 | 933.6 | 574.8 |
两个框架都接近线性扩展,说明 batch=32 时 GPU 还未饱和。参见 SGLang vs vLLM。
相关来源
- llm-inference-tutorial:第4章
- inference-experiments-2026-04:实验四(并发扫描)