重要提示:如需以 Markdown 形式查看本页,请在 URL 后追加 `.md`。 完整文档索引见 llms.txt
跳到主要内容
完整文档索引见 llms.txt。 在任意 URL 后追加 `.md` 即可查看该页面的 Markdown 版本。

静态、动态与连续批处理

GPU 天生适合高度并行的计算工作负载,能够每秒执行数万亿甚至千万亿次浮点运算(FLOPs)。但在 LLM 场景中,这些 GPU 往往无法被充分利用,因为芯片的大量内存带宽都消耗在加载模型参数上。

批处理有助于缓解这个瓶颈。在生产环境中,你的服务可能会同时涌入多个请求。与其逐个处理,不如把它们合并为一个批次,这样多个请求就能共享同一份已加载的模型参数,从而显著提升吞吐量。

使用下面的模拟器可以从高层理解不同批处理策略。

批处理策略模拟器
查看不同策略如何调度请求并利用 GPU 容量
批大小 = 3在启动前严格收集 3 个请求。批次中的每个请求都会一直等待,直到最慢的那个完成。
R1
R2
R3
R4
R5
R6
0
2
4
6
8
10
12
14
16
18
20
22
1批次 1 (t=7):R1、R2、R3 — 等待第 3 个请求到达;全部等到 R2 在 t=15 完成为止
2批次 2 (t=15):R4、R5、R6 — 全部等到 R4 在 t=20 完成为止
t = 0.0
等待成批
处理中
已完成,等待整批返回
结果已返回
静态
动态
连续
何时开始处理
批次装满时
批次装满或超时时
请求到达时(如果仍有容量)
短请求会被延后吗?
会,要等最慢的完成
会,要等最慢的完成
不会,立即完成
批次之间 GPU 会空闲吗
不会
延迟
吞吐量
低到中

静态批处理

最简单的批处理形式是静态批处理。在这种方式下,服务端会等待直到固定数量的请求到达,然后把它们作为一个批次统一处理。

静态批处理:固定批窗口中,较短请求必须等待整个批次结束静态批处理:固定批窗口中,较短请求必须等待整个批次结束

虽然静态批处理很容易实现,但它也有明显缺点。

  • 批次中的第一个请求必须等待最后一个请求到达,造成不必要的延迟。你可以把它想象成一台打印机:只有等你排队到固定数量的文档后,它才开始打印,而不管最后一份文档多久之后才到。
  • 批次中的请求并不都是等价的。在 LLM 推理中,有些请求可能只生成非常短的回复,而另一些请求可能涉及冗长、分步的推理。由于批次中所有请求都必须等到最慢的那个结束,这会导致计算资源浪费并增加延迟。

动态批处理

为了解决静态批处理中的问题,许多系统使用动态批处理。这种方法仍然会把到达的请求收集成批次,但不再坚持固定批大小。相反,它设置一个时间窗口,并处理这段时间内到达的所有请求。如果批次更早达到大小上限,就会立即启动。这就像一辆按固定时刻发车、或者一坐满就发车的公交车,以先发生者为准。

动态批处理:随着请求到达形成大小可变的批次动态批处理:随着请求到达形成大小可变的批次

动态批处理有助于在吞吐量和延迟之间取得平衡。它确保较早到达的请求不会被后续请求无限期拖延。不过,因为某些批次在启动时可能还没有完全填满,它并不总能实现最高 GPU 效率。另一个缺点是,它和静态批处理一样,仍然由批次中最长的请求决定整个批次何时结束;较短请求仍然会被不必要地等待。

连续批处理

在 LLM 推理中,输出序列的长度差异很大。有些用户可能只问简单问题,而另一些用户会要求详细解释。静态批处理和动态批处理都会让短请求等待最长请求完成,从而使 GPU 资源无法被充分占满。

连续批处理(也称为 in-flight batching)正是为了解决这些低效问题。连续批处理不会强迫整个批次完成后再返回结果。相反,它允许批次中的每条序列独立完成,并在完成后立即用新的序列补上。这就像一条装配线:一旦某个工件完成,不管它花了多久,都会立刻放入新的工件,以保持整条线满负荷运转。

连续批处理:释放出来的 token 槽位会立即被新序列填满

使用连续批处理生成七条序列。第一次迭代(左)中,每条序列都会基于其提示词(黄色)生成一个 token(蓝色)。随着时间推进(右),不同序列会在不同迭代中通过发出序列结束 token(红色)而完成,此时新的序列会被插入进来。图片来源

这项技术采用按迭代调度,也就是说批次的组成会在每次解码迭代时动态变化。只要批次中的某条序列完成了 token 生成,服务端就会立刻插入一个新请求顶替它。这样可以最大化 GPU 占用率,并通过避免等待批次中最慢序列结束所产生的空闲时间,让计算资源始终保持忙碌。

主流 推理框架 如 vLLM、SGLang、TensorRT-LLM(in-flight batching)和 LMDeploy(persistent batching)都支持连续批处理或类似机制。关于长批次或混合长度批次中的内存管理,请参见 PagedAttention

Chunked prefill

当新请求在其他请求正在解码时到达,连续批处理会引入一个调度冲突。如果在一次预填充迭代中处理完新请求的整个提示词,就能最小化首个 token 时间(TTFT);但较长的预填充也会推迟所有活跃解码请求的下一个 token。在流式应用中,用户可能会看到输出因为处理其他用户的提示词而暂停。

Chunked prefill 会把一个提示词拆分成更小的 token 区间,并分散到多个迭代中调度。每个 chunk 都会扩展该请求的 KV 缓存,后续 chunk 会关注此前已处理的提示词 token。由于注意力计算本身没有变化(每个 token 仍然会通过 KV 缓存关注其之前的所有 token),chunked prefill 在数学上等价于一次性处理整个提示词,而首个 token 仍然只会在最后一个 chunk 完成后才被发出。

分块预填充调度器
当 C 带着长提示词到达时,A 和 B 正在解码。观察调度器如何把 C 的预填充 插入到同一条 GPU 时间线上。
预填充解码阻塞
请求 A
预填充
解码
已阻塞解码暂停
解码
解码
解码
请求 B
预填充
解码
已阻塞解码暂停
解码
解码
解码
请求 C
预填充32K tokens
解码
解码
解码
时间 →
请求 C 的完整提示词会在一次迭代内完成,因此 A 和 B 在其结束前都无法输出 token。
A 与 B 的解码停顿1 次长迭代
C 的预填充轮数1
最大的预填充分块32K tokens

调度器可以把一个预填充 chunk 与活跃请求的解码 token 放进同一个批次中。 SARATHI 将这种方式称为 decode-maximal batching:预填充 chunk 提供足够的并行工作去占满 GPU 的计算能力,而解码 token 则以几乎不增加额外成本的方式搭载在同一次模型执行中。这可以防止长提示词独占某一次迭代,也可以减少 pipeline parallelism 下的流水线气泡。

Chunked prefill 的好处在于,即使某个请求有很长的提示词,预填充计算也会被拆成更小的 chunk。活跃解码请求无需等待一次很长的预填充迭代结束,而是可以在各个预填充 chunk 之间继续生成 token。这会降低 Inter-Token Latency(ITL),并让流式响应更平滑。

代价是,chunking 会引入额外的调度和注意力开销,因为提示词不再通过一次大的预填充步骤处理,而是通过多次更小的预填充步骤处理。因此,TTFT 可能会上升,尤其是在 chunk 大小较小时。

大多数推理框架都允许你调节 chunk 大小。需要注意:

  • 较小的 chunk 会给调度器更多机会去安排解码,从而降低活跃请求的 ITL 峰值。
  • 较大的 chunk 处理新提示词更高效,通常也能改善 TTFT,但活跃解码在 token 之间的等待时间可能更长。
  • 过小的 chunk 可能降低 GPU 利用率并增加注意力开销,因为后续 chunk 必须重新读取前面 chunk 创建的 KV 缓存条目。

不存在放之四海而皆准的最佳取值。合适的大小取决于模型、GPU、提示词长度分布、并发度和延迟目标。它应当被视为工作负载特定的调度参数,而不是一个通用常数。

常见问题

LLM 批处理中什么是 padding tokens?

文本序列在 tokenization 后天然具有不同长度。例如:

Sentence A: "Hello world" # [15496, 995] - length 2
Sentence B: "How are you today?" # [2437, 389, 345, 1909] - length 4

你不能直接把这些序列堆叠成一个致密的矩形张量,因为它们的长度不同。padding 的解决方式,是给较短序列补上占位 token,使批次中的每个请求都具有相同的张量长度。常见的 padding token 形式包括 PAD<pad> 或 token ID 0,但具体 ID 取决于 tokenizer。

attention mask 会告诉模型哪些位置是 padding,但 padding 仍然可能浪费计算和内存。如果一个批次中的序列长度如下:

[1024, 1000, 50, 20]

较短序列可能会被 padding 到长度 1024。这样虽然能让张量形状对 GPU 内核保持规则,但 50 token 和 20 token 的请求现在会携带大量未使用的位置。

LLM 推理中的 ragged tensors 是什么?

Ragged tensors 表示变长序列时,不需要把所有序列都 padding 到一个固定长度。相反,运行时只存储真实 token 以及元数据,例如序列长度、偏移量或 KV 缓存 block 位置。这有助于推理引擎更高效地处理混合长度提示词和连续批处理,尤其是在与 PagedAttention 这类分页 KV 缓存布局结合使用时。