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:实验四(并发扫描)