@pallavishekhar_: LLM中的连续批处理 阅读:https://outcomeschool.com/blog/continuous-batching-in-llms…

X AI KOLs Timeline 新闻

摘要

一篇介绍连续批处理的博客文章,该技术通过动态地将新请求添加到已完成请求的批次中,持续保持GPU忙碌并减少空闲时间,从而提高LLM服务吞吐量。

LLM中的连续批处理 阅读原文:https://outcomeschool.com/blog/continuous-batching-in-llms…
查看原文
查看缓存全文

缓存时间: 2026/06/30 15:45

大语言模型中的连续批处理

来源:https://outcomeschool.com/blog/continuous-batching-in-llms 大语言模型中的连续批处理

在本博客中,我们将学习连续批处理(Continuous Batching)——一种让 LLM 服务器在生成过程的每一步都保持 GPU 忙碌,从而能够同时处理更多用户的技术。

我们将涵盖以下内容:

  • 全局概览
  • 快速回顾:LLM 如何生成 Token
  • 为什么批处理对 LLM 很重要
  • 旧方法:静态批处理
  • 静态批处理的问题
  • 什么是连续批处理?
  • 拼车类比
  • 连续批处理分步工作原理
  • 数值示例
  • 实际数据与加速效果
  • 连续批处理的好处
  • 几点重要说明
  • 快速总结

我是 Amit Shekhar,Outcome School (https://outcomeschool.com/) 的创始人。我曾教导和指导过许多开发者,帮助他们获得了高薪技术岗位,协助多家科技公司解决了独特问题,并创建了许多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。

我在 Outcome School 教授 人工智能与机器学习 (https://outcomeschool.com/program/ai-and-machine-learning)。

让我们开始吧。

全局概览

在深入细节之前,我们先理解整体图景。

当许多用户同时向 LLM 发送请求时,服务器必须处理所有请求。服务器运行在 GPU 上,而 GPU 非常擅长并行执行许多小任务。为了充分利用 GPU,服务器将请求分组为批 (batch) 并同时处理它们。

老的批处理方法称为静态批处理 (Static Batching),它会让 GPU 等待。新方法称为连续批处理 (Continuous Batching),它能在每一步都保持 GPU 忙碌。

简单来说:

连续批处理 = 一旦某个旧请求完成,立即将新请求加入批次,而不是等待整个批次完成。

这个简单的想法使得 LLM 服务器能用相同的硬件服务更多用户。

快速回顾:LLM 如何生成 Token

要理解连续批处理,我们必须先了解 LLM 如何生成响应。

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

LLM 一次生成一个 token。为了生成下一个 token,它会查看到目前为止的所有 token,并预测最可能的下一个 token。然后将该 token 添加到文本中,再预测下一个,直到模型决定停止。

推理分为两个阶段:

  • 预填充阶段 (Prefill phase): 模型一次读取整个提示,并在单个前向传播中处理所有输入 token。在此过程中,它会计算每个提示 token 的键和值,并将其存储在 KV 缓存 (https://outcomeschool.com/blog/kv-cache-in-llms) 中。预填充结束时,模型产生第一个输出 token。每个请求仅发生一次,在开始时。
  • 解码阶段 (Decode phase): 模型一次生成一个 token。每个新 token 是一步。200 个 token 的响应意味着 200 个解码步骤。

对我们来说关键点是:生成响应不是一个大任务,而是数百个小步骤,每一步产生一个 token。

由于响应是逐步生成的,服务器可以在每个 token 准备好时立即将其发送给用户,而无需等待整个回复完成。我们有一篇关于 Token 流式传输如何工作 (https://outcomeschool.com/blog/how-does-token-streaming-work) 的博客,解释了其原理。

为了加快解码速度,服务器为每个请求存储一个 KV 缓存 (https://outcomeschool.com/blog/kv-cache-in-llms)。KV 缓存保存了到目前为止所有已见 token 的键和值,这样模型就不必在每一步都重新计算它们。批次中的每个请求都有自己的 KV 缓存。如果想深入了解,可以阅读我们关于 KV 缓存的详细博客 (https://outcomeschool.com/blog/kv-cache-in-llms)。

要深入学习 LLM 内部原理、KV 缓存和分词,请查看我们在 Outcome School 的人工智能与机器学习课程 (https://outcomeschool.com/program/ai-and-machine-learning)。

为什么批处理对 LLM 很重要

LLM 运行在 GPU 上。GPU 非常擅长在大量数据上同时执行相同类型的计算。如果只给 GPU 一个请求处理,大部分算力会闲置。

为了让 GPU 保持忙碌,服务器会一起处理多个请求。这个组称为批 (batch)

假设 8 个用户同时发送请求。服务器不会逐一处理,而是将 8 个请求作为一个批次一起运行。GPU 处理所有 8 个请求所需的时间大致与处理 1 个请求相同。

这就是批处理的重要性:它让单个 GPU 能同时服务多个用户,而不会让每个用户的等待时间比单独处理长太多。

但这里有个问题。我们如何组成批次?何时开始?何时结束?这就是静态批处理和连续批处理的区别所在。

旧方法:静态批处理

在静态批处理中,服务器收集一组固定的请求,一起运行,并等待所有请求完成后再开始新的批次。

让我们看看它的工作方式:

  • 服务器将(例如)4 个请求收集到一个批次中。
  • 同时为所有 4 个请求运行预填充阶段。
  • 然后运行解码阶段。每个解码步骤为 4 个请求各产生一个新 token。
  • 一直解码直到批次中的每个请求都完成。
  • 仅在所有 4 个请求完成后,服务器才启动新批次。

这种方法简单且易于实现,但它有一个严重问题。

静态批处理的问题

问题是不同请求完成时间差异很大。

一位用户可能问“2+2 等于几?”只有几个 token 的响应。另一位用户可能要求“写一篇关于气候变化的详细论文。”响应有 1000 个 token。

如果两者都在同一个批次中,短请求在 5 个解码步骤后就完成。但批次会继续运行数百步,因为长请求仍在进行。

短请求使用的槽位会怎样?它就空在那里。 GPU 仍然会为那个空槽位干活,但产生的都是无用功。这是一种浪费。

看到问题了吗?

在批次的大部分生命周期中,只有少数槽位在做有用功。其余都是空的,在等待最慢的请求完成。GPU 很忙,但大部分工作被浪费了。

而中途到达的新用户必须等待整个当前批次结束后才能开始。

所以,连续批处理来拯救了。

什么是连续批处理?

连续批处理是一种运行批次的方式,服务器不会等待整个批次结束。一旦批次中的某个请求完成,服务器立即用队列中等待的新请求替换它。

批次永远不会空闲。每个槽位总是在为某个请求做有用功。

我们不再将批次视为“一起开始和结束的一组请求”,而是将其视为“一组始终有人占据的槽位”。

关键思想很简单:

静态批处理在请求级别工作。连续批处理在 token 级别工作。

在静态批处理中,工作单元是“一个完整的请求”。批次在所有请求开始时开始,在所有请求结束时结束。

在连续批处理中,工作单元是“当前批次中某个人的一个解码步骤”。每完成一个解码步骤,服务器就会检查:有人完成了吗?如果有,就换出它们。有新请求到达吗?如果有,就塞入它们。

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

想象一辆有 4 个座位的共享出租车。出租车沿着一条长路线行驶,并在乘客到达各自目的地时逐个放下他们。

静态批处理是这样的: 一开始 4 位乘客上车。出租车在每个站点等待,直到所有 4 位乘客都到达目的地。即使其中一位乘客在 5 分钟后到达目的地,他的座位在剩下的路程中也会空着。在路上等待的新乘客无法上车,直到整个行程结束,出租车返回去接下一组 4 位乘客。

连续批处理是这样的: 一开始 4 位乘客上车。只要任何乘客到达目的地并下车,在路上等待的新乘客就会立即占据那个空座位。出租车始终有 4 位乘客。没有一个座位会长时间空着。

哪辆出租车一天内服务的乘客更多?显然是第二辆。座位是昂贵的资源,我们要始终保持每个座位满员。

座位是 GPU 槽位。乘客是请求。乘客在出租车上的时间是该请求生成的 token 数。连续批处理在每一步都保持每个 GPU 槽位满负荷。

连续批处理分步工作原理

让我们一步步看服务器如何使用连续批处理处理请求。

第 1 步: 服务器在批次中有固定数量的槽位。假设 4 个槽位。

第 2 步: 请求到达队列。只要批次中有空闲槽位,服务器就从队列中取出下一个请求并将其放入该槽位。服务器为新请求运行预填充阶段以准备其状态。

第 3 步: 服务器为当前批次中的所有请求运行一个解码步骤。每个请求获得一个新 token。

第 4 步: 解码步骤后,服务器检查每个请求。当模型产生停止 token 或达到最大长度时,请求完成。如果请求完成,服务器将其从槽位中移除,并将结果发送回用户。

第 5 步: 服务器查看队列。如果有等待的请求,它就会进入现在空闲的槽位。服务器为其运行预填充。

第 6 步: 服务器运行下一个解码步骤。从第 4 步重复。

整个流程如下所示:

+-----------+
                |   队列    |  <- 新请求到达这里
                +-----------+
                      |
                      | 当有空闲槽位时拉取
                      v
        +-------------------------------+
        |   批次 (4 个槽位)             |
        |   [槽位1] [槽位2]           |
        |   [槽位3] [槽位4]           |
        +-------------------------------+
                      |
                      v
        +-------------------------------+
        |   解码步骤                    |
        |   (每个槽位一个新 token)     |
        +-------------------------------+
                      |
                      v
        +-------------------------------+
        |   检查每个槽位:              |
        |   - 完成?移除并发送        |
        |   - 空闲?从队列拉取        |
        +-------------------------------+
                      |
                      +---> 循环回到解码步骤

这个循环持续运行。每单个解码步骤,服务器都会检查完成情况和新的到达。批次始终是满的。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 个槽位。四个请求到达:

  • 请求 A:需要 100 个解码步骤
  • 请求 B:需要 20 个解码步骤
  • 请求 C:需要 50 个解码步骤
  • 请求 D:需要 200 个解码步骤

一个新请求 E 在批次开始后不久到达。E 需要 30 个解码步骤。

使用静态批处理:

批次以 A、B、C、D 开始。E 在队列中等待。以下是每一步的槽位情况([.] 表示空槽位,做无用功):

槽位1   槽位2   槽位3   槽位4    队列
第 0 步:  [A]    [B]    [C]    [D]    [E]
第 20 步: [A]    [.]    [C]    [D]    [E]    <- B 完成,槽位空着
第 50 步: [A]    [.]    [.]    [D]    [E]    <- C 完成,两个槽位空着
第 100 步:[.]    [.]    [.]    [D]    [E]    <- A 完成,三个槽位浪费
第 200 步:[.]    [.]    [.]    [.]    [E]    <- D 完成,批次结束

从第 20 步开始,GPU 大部分时间在空槽位上做无用功。E 在这段时间一直排队。直到第 200 步之后,E 才终于得到一个槽位开始。

使用连续批处理:

批次以 A、B、C、D 开始。E 在队列中等待。

槽位1   槽位2   槽位3   槽位4    队列
第 0 步:  [A]    [B]    [C]    [D]    [E]
第 20 步: [A]    [E]    [C]    [D]    [ ]    <- B 完成,E 立即入槽
第 50 步: [A]    [.]    [.]    [D]    [ ]    <- E 和 C 都完成
第 100 步:[.]    [.]    [.]    [D]    [ ]    <- A 完成
第 200 步:[.]    [.]    [.]    [.]    [ ]    <- D 完成

E 在第 50 步完成,而不是等到第 200 步才开始。这里槽位空着只是因为队列中没有请求了。在实际流量稳定的服务器中,每个槽位在每一步都保持满负荷,GPU 持续做有用功。

实际数据与加速效果

让我们用实际数字来直观感受一下节省了多少。

假设:

  • 每个解码步骤在 GPU 上耗时 50 ms
  • 批次大小为 4 个槽位
  • 使用上面示例中的相同 5 个请求(A=100, B=20, C=50, D=200, E=30 个 token)

没有连续批处理(静态):

第 1 轮:A、B、C、D 在一个批次中。批次运行直到全部完成 = 200 步。
时间:200 x 50 ms = 10,000 ms = 10.0 秒

第 2 轮:E(单独在下一个批次)。运行 30 步。
时间:30 x 50 ms = 1,500 ms = 1.5 秒

完成所有 5 个请求的总时间:11.5 秒
生成的 token 总数:100 + 20 + 50 + 200 + 30 = 400 token

使用连续批处理:

所有 5 个请求在一次连续运行中完成。
E 在第 20 步入槽,在第 50 步完成。
最长的 D 在第 200 步完成。

完成所有 5 个请求的总时间:200 x 50 ms = 10,000 ms = 10.0 秒
生成的 token 总数:400 token

现在让我们比较每个用户实际感受到的响应时间。

静态批处理    连续批处理
请求 A (100):    5.0 秒           5.0 秒
请求 B  (20):    1.0 秒           1.0 秒
请求 C  (50):    2.5 秒           2.5 秒
请求 D (200):   10.0 秒          10.0 秒
请求 E  (30):   11.5 秒           2.5 秒     <- 快 4.6 倍
平均:          6.0 秒           4.2 秒

用户 E 等待了 10 秒钟,直到之前的批次结束才得以开始。而使用连续批处理,E 立即入槽,完成速度提高了 4.6 倍。

现在,看看在真实工作负载下的影响。

假设服务器收到 100 个请求:

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

关键指标是吞吐量 (throughput)——服务器每秒产生的 token 数。更高的吞吐量意味着在相同硬件上服务更多用户。计算结果如下:

没有连续批处理:
  每个 4 个请求的批次平均有 2 个短 + 2 个长请求。
  批次运行直到最长的完成 = 200 步。
  批次数量:100 / 4 = 25 个批次
  每批时间:200 x 50 ms = 10 秒
  总时间:25 x 10 = 250 秒
  吞吐量:11,000 token / 250 秒 = 44 token/秒

有连续批处理:
  槽位保持满负荷,因为队列不断提供新请求。
  每一步每个槽位产生 1 个 token =

相似文章

在连续批处理中实现异步性

Hugging Face Blog

本文解释了如何为LLM推理实现异步连续批处理,将CPU批处理准备与GPU计算重叠,以最大化利用率并减少空闲时间。

基于阈值的LLM推理独占批处理

arXiv cs.AI

本文分析了混合批处理与独占批处理在LLM推理中的权衡,表明最优选择取决于GPU内存带宽。提出了一种基于阈值的混合调度器,可在两种方法间动态切换,在带宽受限的GPU上实现高达41.9%的吞吐量提升。