跳到主要内容

持续批处理:LLM 服务中提升 GPU 利用率的最关键技术

· 阅读需 14 分钟
Tian Pan
Software Engineer

生产环境中大多数 LLM 推理基础设施的故障并不是模型故障——而是调度故障。团队部署了一个高性能模型,进行了压力测试,却发现用户在等待的同时,昂贵的 GPU 时间仅以 35% 的利用率在消耗。罪魁祸首几乎总是静态批处理(Static batching):这是从传统深度学习中继承下来的默认设置,但根本不符合语言模型生成文本的方式。

持续批处理(Continuous batching)——也称为迭代级调度(Iteration-level scheduling)或飞行中批处理(In-flight batching)——是解决这一问题的核心机制。它不是一个微调旋钮,而是对推理循环运行方式的架构性改变。在使用相同硬件的情况下,使用该技术的系统与不使用的系统相比,吞吐量可能相差 4–8 倍。

要理解其中的原因,首先需要了解原始方法到底出了什么问题。

为什么静态批处理会浪费你的 GPU

传统的批处理会收集一组请求,将它们作为一个整体由模型处理,直到每个序列都生成完毕,然后再进入下一个批次。这在图像分类中运行良好,因为每个输入都会产生一个固定大小的输出。但对于语言生成来说,这简直是一场灾难。

问题在于输出长度的差异。一个聊天请求可能在 15 个 token 内完成,而同一批次中的代码生成请求可能需要 800 个 token。在短请求完成后的 785 次迭代中,为其分配的 GPU 显存和计算槽位都处于闲置状态(即填充 padding),而整个批次必须等待最长的序列执行完毕。你支付了全部的吞吐成本,但利用率曲线显示 GPU 占用率仅为 30–60%。

动态批处理(Dynamic batching)通过在时间窗口内(例如 50ms)对请求进行分组来减少准入延迟,从而改善了这一状况,但批次级调度的问题依然存在:一旦窗口关闭,该批次将作为一个整体运行,直到最后一个序列完成。

持续批处理通过将调度决策从 请求 粒度转移到 迭代 粒度解决了这个问题。调度器在模型的每次前向传播时运行一次,而不是每个请求运行一次。当一个序列发出终止符(EOS)并结束时,其显存槽位会 立即 被释放。下一个等待中的请求会在 下一次迭代开始前 插入到批次中。没有请求需要等待另一个请求完成——批次构成在每个解码步骤都会发生变化。

吞吐量的提升非常显著。在大规模场景下引入迭代级调度的 ORCA 论文(OSDI 2022)表明,在同等延迟目标下,其吞吐量比 FasterTransformer 提高了 36.9 倍。Anyscale 的真实基准测试显示,与原生的 HuggingFace Transformers 推理相比,性能提升了 8 倍。结合基于 PagedAttention 的 KV 缓存管理,vLLM 的初始版本吞吐量比 HuggingFace Transformers 高出 24 倍,比 HuggingFace TGI 高出 3.5 倍。

调度器是如何运作的

在每次前向传播中,持续批处理调度器会执行一个简短的循环:

  1. 扫描运行中的批次,查找已完成的序列(已发出 EOS)
  2. 释放它们的 KV 缓存块
  3. 从队列中提取等待中的请求——数量取决于显存和批次大小限制
  4. 将所有活动序列连接成一个复合批次
  5. 运行一次模型前向传播;每个序列产生其下一个 token
  6. 重复上述过程

连接(Concatenation)步骤是使其在结构上与众不同的原因。静态批处理要求序列填充到相同长度,因为批处理矩阵运算需要统一的张量形状。而持续批处理则构造一个带有注意力掩码(Attention masks)的“超级序列”,防止任何请求关注到其他请求的 token。没有填充,没有浪费的计算——每一个 GPU FLOP 都在处理真实的 token。

这种连接形式自然地与 FlashAttention 的变长算子(Variable-length kernel)变体相集成,尽管长度不同,它仍能在单个 GPU 算子调用中处理所有序列。结果是,即使批次中混合了处于不同进度的短序列和长序列,GPU 占用率依然很高。

PagedAttention:内存管理层

持续批处理解决了 何时 调度请求。PagedAttention(vLLM, SOSP 2023)则解决了 何处 存储它们的 KV 缓存。

在 PagedAttention 出现之前,LLM 推理框架会为每个请求预分配一块连续的 GPU 显存,大小取决于其 最大可能输出长度。这由于碎片化导致了 60–80% 的显存浪费:过度保留的槽位、对齐间隙,以及大多数序列并不会用完其最大分配空间的事实。

PagedAttention 将操作系统的虚拟内存分页模型应用于 KV 缓存管理。KV 缓存被划分为固定大小的块(vLLM 默认每块 16 个 token),根据序列生成 token 的需求动态分配,而不是预先分配。一个块表将每个序列的逻辑块映射到物理 GPU 显存位置——这些块不需要连续。显存浪费降至 4% 以下(每个序列仅浪费最后一个未填满的块)。

第二个好处是:物理块可以通过写时复制(Copy-on-write)语义在序列之间共享。束搜索(Beam search)的分支、并行采样以及共享相同系统提示词(System prompt)的请求,在发生分歧之前都可以引用相同的物理 KV 块。对于束搜索,这可以减少高达 55% 的开销,并使吞吐量比非共享分配提高 2.2 倍。

SGLang 通过 RadixAttention 进一步扩展了这一能力:这是一种基数树(Radix tree)数据结构,可以在 不同请求 之间维护 KV 缓存,实现自动前缀重用。共享系统提示词、少样本示例或 RAG 上下文的请求可以重用彼此已缓存的 KV 块,而无需重新计算。在具有大量前缀共享的工作负载中,这可以使推理速度提高 5 倍。

权衡曲线:连续批处理何时有效,何时无效

连续批处理 (Continuous Batching) 的收益与输出长度的方差成正比。在每个请求都精确生成 50 个 Token 的工作负载中,相比静态批处理的优势几乎为零——因为没有需要消除的空闲填充 (Idle padding)。在输出范围从 5 到 1000 个 Token 的混合聊天工作负载中,性能提升可达 4–8 倍。

工作负载的适配情况如下:

  • 聊天、智能体 (Agents)、交互式助手:高方差,大量并发用户,长短响应混合。连续批处理在此类场景中具有极强的优势——这正是它设计的初衷。
  • 变动 QPS 下的在线 API:序列会被立即接纳,而不是等待批处理填满,这在中等负载下显著降低了首词延迟 (TTFT)。
  • 具有共享前缀的 RAG 流水线:RadixAttention (SGLang) 或前缀缓存 (vLLM) 通过跨请求的 KV 重用复合提升收益。
  • 具有同质输出的离线批处理推理:静态批处理在此场景中具有竞争力,且通常更简单。当序列长度预先已知且统一时,连续批处理的调度开销几乎没有收益。
  • 极低 QPS(每秒个位数请求):所有方法的表现都差不多;连续批处理的调度开销比利用率收益更重要。
加载中…
References:Let's stay in touch and Follow me for more thoughts and updates