LLM 中的 Continuous Batching

---
title: "LLM 中的 Continuous Batching"
author: "Amit Shekhar"
source_url: "https://outcomeschool.com/blog/continuous-batching-in-llms"
published_at: "2026-05-11T00:00:00.000Z"
fetched_at: "2026-07-01T23:14:00Z"
updated_at: "2026-07-02T01:45:05Z"
language: "zh"
review_status: "draft"
---

# LLM 中的 Continuous Batching

![Continuous Batching in LLMs](https://outcomeschool.com/static/images/blog/continuous-batching-in-llms.png)

在这篇文章里,我们会学习 Continuous Batching。这是一种让 LLM 服务器在生成的每一步都让 GPU 保持忙碌,从而同时处理更多用户的技术。

我们会讲这些内容:

- 大图景
- 快速回顾:LLM 如何生成 token
- 为什么 batching 对 LLM 很重要
- 旧做法:Static Batching
- Static Batching 的问题
- 什么是 Continuous Batching?
- 拼车类比
- Continuous Batching 如何一步步工作
- 一个数字例子
- 真实数字与加速效果
- Continuous Batching 的好处
- 几点重要说明
- 快速总结

我是 **Amit Shekhar**,[Outcome School](https://outcomeschool.com) 创始人。我教过并辅导过很多开发者,他们靠自己的努力拿到了高薪技术岗位;我也帮助过许多科技公司解决各自独特的问题,并创建过很多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。

我在 Outcome School 教 [AI and Machine Learning](https://outcomeschool.com/program/ai-and-machine-learning)。

我们开始吧。

## 大图景

在进入细节之前,我们先理解大图景。

当很多用户同时向 LLM 发送请求时,服务器必须处理所有这些请求。服务器运行在 GPU 上,而 GPU 很擅长并行做很多小任务。为了更好地利用 GPU,服务器会把请求合并成一个 **batch**,一起处理。

旧的 batching 方式叫 **Static Batching**,它会让 GPU 等待。新的方式叫 **Continuous Batching**,它会让 GPU 在每一步都保持忙碌。

简单说:

**Continuous Batching = 不等整个 batch 全部完成,而是在任意旧请求完成的瞬间,就把新请求加入 batch。**

就这么一个想法,可以让 LLM 服务器用同样的硬件服务更多用户。

## 快速回顾:LLM 如何生成 token

要理解 Continuous Batching,我们必须先理解 LLM 如何生成回复。

token 是一小段文本。它可以是一个词、词的一部分,或者单个字符。

LLM 生成文本是**一次一个 token**。为了生成下一个 token,它会查看目前为止的所有 token,并预测最可能的下一个 token。然后把这个 token 加到文本里,再预测下一个。这个过程会持续到模型决定停止。

推理有两个阶段:

- **Prefill 阶段:** 模型一次性读取整个 prompt,并在一次前向传播(forward pass)中一起处理所有输入 token。在这次前向传播里,它会为每个 prompt token 计算 Key 和 Value,并把它们存进 [KV Cache](https://outcomeschool.com/blog/kv-cache-in-llms)。prefill 结束时,模型会产生第一个输出 token。每个请求只会在开始时发生一次 prefill。
- **Decode 阶段:** 模型一次生成一个 token。每个新 token 都是一步。一个 200 token 的回复,就意味着 200 个 decode step。

对我们来说,关键点是:生成一个回复不是一个大任务,而是几百个小步骤,每一步生成一个 token。

因为回复是每一步产生一个 token,服务器可以在每个 token 准备好的一刻就把它发送给用户,而不是等整段回复全部完成。我们有一篇文章介绍 [Token Streaming 如何工作](https://outcomeschool.com/blog/how-does-token-streaming-work),解释了这一点。

为了让 decode 更快,服务器会为每个请求保存一个叫 **[KV Cache](https://outcomeschool.com/blog/kv-cache-in-llms)** 的东西。KV Cache 保存目前为止已经看过的所有 token 的 Key 和 Value,所以模型不必在每一步重新计算它们。batch 中的每个请求都有自己的 KV Cache。如果想深入了解,可以读我们关于 [KV Cache](https://outcomeschool.com/blog/kv-cache-in-llms) 的详细文章。

如果想深入学习 LLM 内部机制、KV Cache 和 Tokenization,可以看看 Outcome School 的 [AI and Machine Learning Program](https://outcomeschool.com/program/ai-and-machine-learning)。

## 为什么 batching 对 LLM 很重要

LLM 运行在 GPU 上。GPU 很擅长同时对多份数据做同一种工作。如果我们只给 GPU 一个请求处理,它的大部分能力都会闲置。

为了让 GPU 保持忙碌,服务器会同时处理多个请求。这个请求组就叫一个 **batch**。

假设 8 个用户同时发送请求。服务器不会一个接一个地跑,而是把这 8 个请求作为一个 batch 一起跑。GPU 为这 8 个请求做完工作,大致只需要处理 1 个请求的时间。

这就是 batching 重要的原因:它让单个 GPU 可以同时服务多个用户,而且不会让每个用户等待太久,不会比自己独占 GPU 时慢很多。

但这里有个问题。我们怎么组成 batch?什么时候开始?什么时候结束?这就是 Static Batching 和 Continuous Batching 的区别所在。

## 旧做法:Static Batching

在 Static Batching 中,服务器会收集一组固定请求,把它们一起运行,并等它们全部完成之后,才开始新的 batch。

我们看看它怎么工作:

- 服务器收集比如 4 个请求组成一个 batch。
- 它一起运行这 4 个请求的 prefill 阶段。
- 然后运行 decode 阶段。每个 decode step 都会为这 4 个请求各产生一个新 token。
- 它会持续 decode,直到 batch 中**每个**请求都完成。
- 只有这 4 个请求全部完成以后,服务器才会开始新的 batch。

这种方式简单,也容易实现。但它有一个严重问题。

## Static Batching 的问题

问题在于,不同请求完成的时间差异很大。

一个用户可能问“2 + 2 等于多少?”,回复只有几个 token。另一个用户可能问“写一篇关于气候变化的详细文章。”,回复有 1,000 个 token。

如果这两个请求在同一个 batch 里,短请求可能在 5 个 decode step 后就完成了。但 batch 还要继续运行几百步,因为长请求还没结束。

短请求占用的那个 slot 会怎么样?**它就空在那里。** GPU 仍然会为这个空 slot 做工作,但这份工作不会产生任何有用结果。它被浪费了。

看到问题了吗?

在 batch 生命周期的大部分时间里,只有少数 slot 在做有用工作。剩下的 slot 都是空的,在等最慢的请求完成。GPU 看起来很忙,但大部分工作都浪费了。

而那些中途到来的新用户,必须等当前整个 batch 全部完成,才能开始处理。

于是,Continuous Batching 就派上用场了。

## 什么是 Continuous Batching?

**Continuous Batching** 是一种运行 batch 的方式:服务器不会等待整个 batch 完成。一旦 batch 中有任意请求完成,服务器就立刻用队列里等待的新请求替换它。

batch 永远不会闲置。每个 slot 都始终在为某个请求做有用工作。

不要再把 batch 想成“一组一起开始、一起结束的请求”,而要把它想成“一组永远有人坐着的 slot”。

核心想法很简单:

**Static Batching 在请求级别工作。Continuous Batching 在 token 级别工作。**

在 Static Batching 中,工作单位是“一个完整请求”。batch 在所有请求开始时开始,在所有请求结束时结束。

在 Continuous Batching 中,工作单位是“当前 batch 中这些请求的一次 decode step”。每一个 decode step 之后,服务器都会检查:有没有请求完成?如果有,就把它换出去。有没有新请求到达?如果有,就把它放进空 slot。

## 拼车类比

我们用一个现实世界的类比来理解。

想象一辆有 4 个座位的拼车。车沿着一条长路线行驶,乘客到站后一个个下车。

**Static Batching 像这样:** 一开始 4 个乘客上车。车会在每一站等到所有 4 个乘客都到达目的地。即使某个乘客 5 分钟后就到了,他的座位在剩下的行程里也一直空着。路边等待的新乘客不能上车,必须等整趟行程结束、车回来接下一组 4 个人。

**Continuous Batching 像这样:** 一开始 4 个乘客上车。只要有任何一个乘客到站下车,路边等待的新乘客就立刻坐进这个空座。车里始终有 4 个乘客。没有座位会空很久。

哪辆车一天能服务更多乘客?显然是第二辆。座位是昂贵资源,我们要让每个座位始终坐满。

座位就是 GPU slot。乘客就是请求。乘客坐车的时间,就是这个请求会生成的 token 数量。Continuous Batching 会让每个 GPU slot 在每一步都保持满载。

## Continuous Batching 如何一步步工作

我们一步步看支持 Continuous Batching 的服务器如何处理请求。

**第 1 步:** 服务器在 batch 中有固定数量的 slot。假设是 4 个 slot。

**第 2 步:** 请求进入队列。只要 batch 里有空 slot,服务器就从队列里取出下一个请求,放进这个 slot。服务器会为新请求运行 prefill 阶段,准备它的状态。

**第 3 步:** 服务器为当前 batch 中的所有请求运行一个 decode step。每个请求都会得到一个新 token。

**第 4 步:** decode step 之后,服务器检查每个请求。如果模型产生 stop token,或者达到最大长度,请求就算完成。服务器会把完成的请求从 slot 中移除,并把结果发回给用户。

**第 5 步:** 服务器查看队列。如果有等待中的请求,它就进入刚空出来的 slot。服务器为它运行 prefill。

**第 6 步:** 服务器运行下一个 decode step。然后从第 4 步重复。

整个流程如下:

```text
                +-----------+
                |   Queue   |  <- new requests arrive here
                +-----------+
                      |
                      | pull when a slot is free
                      v
        +-------------------------------+
        |   Batch (4 slots)             |
        |   [Slot 1] [Slot 2]           |
        |   [Slot 3] [Slot 4]           |
        +-------------------------------+
                      |
                      v
        +-------------------------------+
        |   Decode step                 |
        |   (one new token per slot)    |
        +-------------------------------+
                      |
                      v
        +-------------------------------+
        |   Check each slot:            |
        |   - finished? remove and send |
        |   - free?     pull from queue |
        +-------------------------------+
                      |
                      +---> loop back to Decode step
```

这个循环会一直运行。每一个 decode step,服务器都会检查完成的请求和新到达的请求。batch 始终是满的。GPU 始终在做有用工作。

**给你一个小提示**

无论你在哪个技术领域工作,都应该熟悉这些主题:

- LLM
- RAG
- MCP
- Agent
- Fine-tuning
- Quantization

我们把它们整理进了一个视频:

[AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization](https://www.youtube.com/watch?v=lnfWvX66FUk)

不用现在停下来去看——先收藏,等有时间再看。未来的你会感谢现在的你。

现在,我们回到主题。

## 一个数字例子

我们用真实数字来把这件事放到具体场景里看。

假设服务器有 4 个 slot。来了 4 个请求:

- Request A:需要 100 个 decode step
- Request B:需要 20 个 decode step
- Request C:需要 50 个 decode step
- Request D:需要 200 个 decode step

新请求 E 在 batch 刚开始后到达。E 需要 30 个 decode step。

**使用 Static Batching:**

batch 从 A、B、C、D 开始。E 在队列里等待。下面是每一步 slot 的样子(`[.]` 表示空 slot,正在做浪费的工作):

```text
            Slot 1   Slot 2   Slot 3   Slot 4    Queue
Step   0:    [A]      [B]      [C]      [D]      [E]
Step  20:    [A]      [.]      [C]      [D]      [E]    <- B done, slot sits empty
Step  50:    [A]      [.]      [.]      [D]      [E]    <- C done, two slots empty
Step 100:    [.]      [.]      [.]      [D]      [E]    <- A done, three slots wasted
Step 200:    [.]      [.]      [.]      [.]      [E]    <- D done, batch ends
```

从第 20 步开始,GPU 大部分时间都在空 slot 上做浪费的工作。E 这段时间一直在队列里等待。直到第 200 步之后,E 才终于拿到 slot 开始处理。

**使用 Continuous Batching:**

batch 从 A、B、C、D 开始。E 在队列里等待。

```text
            Slot 1   Slot 2   Slot 3   Slot 4    Queue
Step   0:    [A]      [B]      [C]      [D]      [E]
Step  20:    [A]      [E]      [C]      [D]      [ ]    <- B done, E slots in immediately
Step  50:    [A]      [.]      [.]      [D]      [ ]    <- E and C both finish
Step 100:    [.]      [.]      [.]      [D]      [ ]    <- A done
Step 200:    [.]      [.]      [.]      [.]      [ ]    <- D done
```

E 在第 50 步完成,而不是等到第 200 步才开始。这里 slot 会空下来,只是因为队列里已经没有请求了。在有稳定流量的真实服务器里,每个 slot 在每一步都会保持满载,GPU 会持续做有用工作。

## 真实数字与加速效果

我们再用真实数字来算清楚能省多少。

假设:

- GPU 上每个 decode step 需要 50 ms
- batch size 是 4 个 slot
- 使用上面例子里的同 5 个请求(A=100、B=20、C=50、D=200、E=30 token)

**不使用 Continuous Batching(Static):**

```text
Round 1: A, B, C, D in batch. Batch runs until all done = 200 steps.
Time: 200 x 50 ms = 10,000 ms = 10.0 s

Round 2: E (alone in next batch). Runs 30 steps.
Time: 30 x 50 ms = 1,500 ms = 1.5 s

Total time to finish all 5 requests: 11.5 s
Total tokens generated: 100 + 20 + 50 + 200 + 30 = 400 tokens
```

**使用 Continuous Batching:**

```text
All 5 requests finish in one continuous run.
E slots in at step 20 and finishes at step 50.
D, the longest, finishes at step 200.

Total time to finish all 5 requests: 200 x 50 ms = 10,000 ms = 10.0 s
Total tokens generated: 400 tokens
```

现在比较用户实际感受到的东西:每个请求的响应时间。

```text
                  Static Batching   Continuous Batching
Request A (100):       5.0 s              5.0 s
Request B  (20):       1.0 s              1.0 s
Request C  (50):       2.5 s              2.5 s
Request D (200):      10.0 s             10.0 s
Request E  (30):      11.5 s              2.5 s     <- 4.6x faster
Average:               6.0 s              4.2 s
```

用户 E 原本要等前一个 batch 结束 10 秒后才能开始。使用 Continuous Batching 后,E 会立刻进入空 slot,并快 4.6 倍完成。

**现在,我们看看它对现实工作负载的影响。**

假设有 100 个请求到达服务器:

- 50 个短请求(每个 20 token)
- 50 个长请求(每个 200 token)
- 总共要生成的 token 数:50 x 20 + 50 x 200 = 11,000 token

这里的关键指标是**吞吐量(throughput)**,也就是服务器每秒生成的 token 数。吞吐量越高,同样硬件上能服务的用户越多。计算如下:

```text
Without Continuous Batching:
  Each batch of 4 has on average 2 short + 2 long requests.
  The batch runs until the longest finishes = 200 steps.
  Number of batches: 100 / 4 = 25 batches
  Time per batch: 200 x 50 ms = 10 s
  Total time: 25 x 10 = 250 s
  Throughput: 11,000 tokens / 250 s = 44 tokens/sec

With Continuous Batching:
  Slots stay full because the queue keeps feeding new requests.
  Each step produces 1 token per slot = 4 tokens per step.
  Steps needed: 11,000 / 4 = 2,750 steps
  Total time: 2,750 x 50 ms = 137,500 ms = 137.5 s
  Throughput: 11,000 tokens / 137.5 s = 80 tokens/sec

Speedup: 80 / 44 = 1.8x
Time saved: 250 - 137.5 = 112.5 s (45% less time for the same work)
```

下面是同一组对比放到时间线上:

```text
Time (seconds):  0     50    100   150   200   250

Without:         |==========================================|  250 s
                 (25 batches of 4, each running 200 steps)

With:            |=======================|                     137.5 s
                 (continuous, 4 slots always full)

                                         <--- 112.5 s saved --->
```

服务器用 137.5 秒完成了同样的工作,而不是 250 秒。硬件不变,完成的工作几乎翻倍。这就是 Continuous Batching 的妙处。

在实践中,对多数真实工作负载来说,加速通常在 **2 倍到 4 倍** 之间。具体数字取决于:

- 请求长度差异有多大(差异越大,收益越大)
- 进入流量有多稳定(流量越稳定,收益越大)
- 模型和硬件

来自 `vLLM` 等系统的公开基准测试显示,Continuous Batching 结合 Paged Attention 后,相比朴素服务方式可以提供超过 20 倍的吞吐量;在同一块 GPU 上,相比 Static Batching 也能高 2 到 4 倍。

如果想端到端深入学习 vLLM、Paged Attention 和推理优化,我们有一套完整课程——可以看看 Outcome School 的 [AI and Machine Learning Program](https://outcomeschool.com/program/ai-and-machine-learning)。

## Continuous Batching 的好处

Continuous Batching 带来三个主要好处:

- **更高吞吐量:** throughput 是服务器每秒生成的 token 数。因为 GPU 始终在做有用工作,所以每秒能生成更多 token。在真实系统里,相比 Static Batching,吞吐量往往能提升数倍。
- **更低等待时间:** 新请求不必等待整个 batch 完成。只要有座位空出来,它就能立刻进入。这会让用户感觉服务器更快。
- **更好的硬件利用率:** GPU 是 LLM 服务器里最昂贵的部分。Continuous Batching 让它始终忙于有用工作,所以同一块 GPU 能发挥更大价值。

这就是为什么几乎所有现代 LLM 服务系统,比如 [`vLLM`](https://outcomeschool.com/blog/how-does-vllm-work)、[`SGLang`](https://outcomeschool.com/blog/how-does-sglang-work)、`TensorRT-LLM` 等,都会使用 Continuous Batching。

## 几点重要说明

- Continuous Batching 和 [Paged Attention](https://outcomeschool.com/blog/paged-attention-in-llms) 配合得很好。Paged Attention 解决 KV Cache 的内存浪费问题,Continuous Batching 解决静态 batch 的计算浪费问题。两者结合,正是单个 GPU 能同时服务很多用户的原因。我们有一篇详细介绍 [vLLM 如何工作](https://outcomeschool.com/blog/how-does-vllm-work)的文章,解释了服务系统如何把这两者组合起来。
- Continuous Batching 不会改变模型本身。它不会改变注意力机制的工作方式,不会改变 token 的生成方式,也不会改变模型输出。它只改变服务器如何在 GPU 上调度请求。每个用户得到的输出,和单独运行这个请求时一样。
- batch size 不是无限的。GPU 内存有限,batch 中每个请求都要为自己的 KV Cache 使用内存。服务器会选择一个能放进内存的 batch size(批大小)。如果 batch 已满,新请求就会在队列里等待,直到有 slot 空出来。
- 例子里的数字为了说明清楚做了一些简化。当新请求加入 continuous batch 时,会有一小段 prefill 成本,我们把它当作 0;而 Static Batching 中 2 个短请求 + 2 个长请求的组合,只是一个平均情况,真实 batch 会围绕它波动。但定性规律不变:当请求长度差异很大时,Continuous Batching 通常能带来数倍收益。

## 快速总结

我们回顾一下学到的内容:

- **LLM 一次生成一个 token。** 一个回复是几百个小 decode step,不是一个大任务。
- **Batching 很重要**,因为 GPU 最擅长同时对多份数据做同一种工作。
- **Static Batching** 会把一组固定请求一起运行,并等待最慢的请求完成。提前完成的 slot 会空着,浪费 GPU 工作。
- **Continuous Batching** 会在任何旧请求完成的一刻换入新请求。batch 始终是满的。
- **拼车类比:** Static Batching 是一辆等所有乘客都到站才结束行程的车。Continuous Batching 是一辆只要有座位空出就接新乘客的车。
- **token 级调度:** Continuous Batching 在 token 级别工作,而不是请求级别。每个 decode step,服务器都会检查完成和新到达的请求。
- **好处:** 更高吞吐量、更低等待时间,以及更好的 GPU 利用率。
- **和 [Paged Attention](https://outcomeschool.com/blog/paged-attention-in-llms) 配合很好:** 它们一起解决了 LLM 服务中的内存浪费和计算浪费问题。

这就是 Continuous Batching 如何让 GPU 保持忙碌,并让单台服务器同时处理更多用户。

准备 AI 工程面试可以看这里:[AI Engineering Interview Questions](https://github.com/amitshekhariitbhu/ai-engineering-interview-questions)

这次就到这里。

谢谢

**Amit Shekhar**
[Outcome School](https://outcomeschool.com) 创始人