← 返回日报
🌐 机器翻译 · DeepSeek · HF Blog

Unlocking asynchronicity in continuous batching


以下是您要求的英文文章的中文翻译,已按照保留原文结构、技术术语保留、代码块不翻译等要求处理。


解锁连续批处理中的异步性

回到文章 解锁连续批处理中的异步性 发布于 2026 年 5 月 14 日 在 GitHub 上更新 点赞 10 +4 Rémi Ouazan Reboul ror 关注 Pedro Cuenca pcuenq 关注 Aritra Roy Gosthipaty ariG23498 关注

同步批处理 创建并发 什么是 CUDA 流? 默认流与非默认流 回到连续批处理 强制同步 什么是 CUDA 事件? 在连续批处理中使用事件 填补真空 竞态条件 结转 完整的异步循环 它真的有效吗? 结论

TL;DR: 我们解释了如何将 CPU 和 GPU 的工作负载分离,从而为推理带来巨大的性能提升。这是关于高效 LLM 推理系列文章的第二篇。第一篇从基本原理出发介绍了连续批处理,其中引入了一些我们在此基础之上构建的概念:KV 缓存、FlashAttention、注意力掩码等。

一台 H200 在 Inference Endpoints 上每小时大约花费 5 美元。按小时算不贵,但如果用上一整天,你就要支付 120 美元。既然如此,你肯定希望 GPU 被充分利用。我们已经看到,连续批处理通过调度紧密打包的批次来提高 GPU 利用率,从而避免在填充上浪费算力。但还有第二种浪费来源是连续批处理没有解决的:默认情况下,它是同步的。这意味着 CPU 和 GPU 轮流工作:当 GPU 计算时,CPU 等待;而当 CPU 准备下一个批次时,GPU 等待。在一个每秒运行数百步的循环中,这些空闲间隙会累积起来,正如我们将要展示的,它们可能占到总运行时间的近四分之一。

为了确保 GPU 100% 的时间都在忙于计算,我们需要消除这些间隙。为此,我们可以使用异步批处理:我们将把 CPU 的批次准备与 GPU 的批次计算分离开来,这样两者可以并行运行,从而让 GPU 始终保持高效工作 🔥

同步批处理

这就是朴素的同步批处理的工作方式:

当 CPU 准备一个新批次时,它会选择要包含哪些请求,更新 KV 缓存表,驱逐在前几轮中完成的请求,并接纳新请求以填补释放的空间。完成后,它将准备好的输入传输到 GPU。GPU 执行前向传播并对每个请求进行采样(即选择)一个新 token。结果返回给 CPU,这样 CPU 就知道每个请求刚刚生成了哪个 token,然后整个循环再次重复。

注意右侧的红色标注:GPU 完成计算后,它会进入空闲状态。下一个批次必须等到 CPU 完成其更新步骤(对输出 token 进行采样、更新请求状态、重新调度批次)后才能开始。这就是同步批处理的核心低效之处:CPU 和 GPU 轮流工作。当 GPU 计算时,CPU 空闲;当 CPU 更新时,GPU 空闲。在任何情况下,它们都不会同时做有用的事情。

对于单次前向传播来说,这似乎是一个很小的代价,但在一个每秒运行数百步的连续批处理循环中,这些空闲间隙会累积成真正的吞吐量损失。为了说明这一点,我们分析了使用 8B 模型、批次大小为 32、生成 8K token 时 CPU 和 GPU 上的时间消耗:

如果你想生成类似的图表,可以在连续批处理代码中添加检测,以转储 CPU 和 GPU 的活动时间跨度,并使用这个脚本。

时间线在绿色(GPU 活跃,CPU 空闲)和红色(CPU 活跃,GPU 空闲)之间交替:两者从未重叠。总生成时间为 300.6 秒,其中 24.0% 的时间 GPU 处于空闲状态,等待 CPU 完成。从 GPU 的角度来看,近四分之一的生成时间被浪费了。这是悲观的观点。乐观的观点是,如果我们能完全消除 CPU 开销,生成时间将从 300 秒下降到 228 秒(免费获得 24% 的加速!)。这不需要任何新的内核或模型更改,只需要对硬件进行仔细协调。

从根本上说,这个想法很简单:我们需要弄清楚如何在批次 N 进行计算的同时,为批次 N+1 准备批次。但这个简单的想法隐藏着一些技术难点:

通过回答这些问题,我们将从头构建异步批处理。我们按照相同的步骤在 transformers 库中将其实现为连续批处理的一部分。欢迎查看代码并进行比较!

创建并发

我们的最终目标是实现 CPU 和 GPU 操作的并发执行。我们需要一种对操作进行分类的方法,以便让机器知道哪些操作可以并发运行。我们可以使用 CUDA 流来实现这一点。

什么是 CUDA 流?

要理解 CUDA 如何对其操作进行排序,我们需要谈谈 CUDA 流。流是一个有序的 GPU 操作队列(内核启动、内存拷贝、同步屏障),这些操作按照提交的顺序执行。每个 GPU 操作总是在某个流中被调度。同一流中的操作是顺序执行的:GPU 不会在前一个操作完成之前开始下一个操作。不同流中的操作彼此独立,可以并发运行。

为了说明这一点,如果你在 3 个不同的流中启动 3 个操作,执行过程如下所示:

所有三个操作同时开始。这是一个简化的描述:实际上每个 GPU 操作最终都由 CPU 发起,而发起过程需要少量时间:找到合适的内核、发出调用、将命令从 CPU 传输到 GPU 等。这被称为 CPU 启动开销,一个更现实的图表如下所示:

操作仍然是并发的,但它们的开始时间因每次 CPU 启动的开销而错开。我们将继续展示这些 CPU 启动事件。


(注:原文在“We will keep showing these CPU launch events t”处截断,翻译至此结束。若您有后续内容需要继续翻译,请提供完整文本。)

📖 阅读原文 →