
TL;DR:在一台 NVIDIA GB300 NVL72 机架上,采用 P/D 分离和 Wide-EP 的 vLLM 在 SemiAnalysis AgentX 上服务 DeepSeek-V4.1-Flash,达到每 GPU 954K tokens/s每用户 P90 为 196 tokens/s,7.9×我们单节点结果相比,来自第一篇博文以及每 GPU-小时 $38.96按 DeepSeek 的错峰价格计算。专用解码路径可达826 tokens/s单用户。与 DeepInfer(DeepSeek 自研引擎)相比,vLLM 的解码吞吐在6%以内,覆盖 batch size 8–256,其预填充也贴合 DeepInfer 公布的曲线。
这种效率来自对 vLLM 已有组件的刻意工程优化,构建在 DeepSeek 开源的 kernel 之上。本文详述我们的性能研究,拆解每一项优化,并分享我们得到的经验:
- 通用引擎可以匹敌专用引擎
- 优化之前先理解模型
- 小型 DEP 实例最适合预填充1
- Wide-EP 解码借助 EPLB 扩展良好
- 在 NVL72 规模下 CPU 开销变得关键
- 专用解码路径解锁低延迟
性能概览
图 1:NVIDIA GB300 NVL72 上的 SemiAnalysis AgentX:采用 P/D 分离和 Wide-EP 的 vLLM(金色)相比我们单节点聚合结果进一步实现了 7.9× 的吞吐提升,来自第一篇博文。吞吐统计缓存输入、未缓存输入和输出 token(来源)。
与 DeepInfer 对比
DeepInfer是 DeepSeek 为 DeepSeek-V4.1-Flash 自研的推理引擎,本周作为离线引擎发布,并公布了预填充和解码曲线。它是该模型最相关的参照,因此在深入我们的优化之前,我们先展示 vLLM 相对它的位置。
解码。在 DeepSeek 的解码配置下(131K 上下文,nextn=3),GB200 上的 vLLM 在 batch size 8 到 256 范围内达到 DeepInfer 公布结果的 6% 以内(图 2)。
图 2:131K 上下文、nextn=3 下每 GPU 解码输出吞吐 vs. batch size。GB200 上的 vLLM vs. DeepInfer 公布结果,读取自其 README 中的图表(来源)。
预填充。DeepInfer 公布的 prefill 曲线使用 EP4DP4、32 个冗余专家和 140 token 的有界重放,硬件条件图表中未说明,我们是从图表上读取其数值的。我们的运行在 GB300 上使用 DEP2,没有冗余专家,采用 128 token 的重放和我们自己的提示词。由于配置不同,我们不把两条曲线之间的差距解读为引擎之间的加速比。图 3 表明的是,vLLM 的 DEP2 prefill 在他们公布的范围内与 DeepSeek 自家引擎的表现相当。
图 3:Prefill 吞吐量 vs. 新 token 数。GB300 上的 vLLM DEP2 vs. DeepInfer 公布的曲线(EP4DP4、32 个冗余专家、140 token 重放、硬件未说明),从其 README 中的图表读取(来源)。
两个引擎表现接近,是因为它们在两个方向上都有共同的渊源。DeepInfer 最初构建于 vLLM 之上,而 vLLM 反过来也持续集成了 DeepSeek 库中的内核,包括 MegaAttn、MegaMoE 和 Mega-mHC,并围绕这些内核对其余技术栈进行了大量优化。DeepInfer 在离线模式下只针对单一模型;同一个 vLLM 构建还能以在线方式服务该模型,支持 P/D 分离和跨整个 NVL72 机架的 Wide-EP,这正是本文其余部分所衡量的内容。后续章节将描述这项工作。
深入底层:速度从何而来
先测量模型
在调优之前,我们先量化了 DeepSeek-V4.1-Flash 的高效架构设计:
- 较小的 KV 缓存占用。得益于跨层 KV 共享和 FP4 压缩,全局 KV 缓存每 token 仅占 890 字节(参见DeepSeek-V4.1-Flash 技术报告)。
- 轻量级注意力。第 2、8 和 14 层使用 2× 压缩进行全上下文索引。在有界重放下,第 20 层为所有 token 写入全局 KV,但仅对最后 128 个 token 运行索引和注意力;第 21–39 层只处理这些 token。
- 即使在长上下文中,解码也受 MoE 制约。见下方图 5。在 128K 上下文、每 rank 64 个请求的 DEP4 解码下,MoE 占目标步骤的 62%,而注意力和索引器合计仅占 13%。
下表展示了目标模型在 DEP32 下的前向时间,每个 rank 16 个请求,每个请求 6 个 token(一个 token 加五个 DSpark 草稿 token),且所有请求的上下文长度相同,测试在 GB300 上进行。从 128K 到 855K,上下文长度增加 6.7 倍,目标模型解码延迟仅增加 0.6 ms(+7%)。
| 每个请求的上下文 | 64K | 128K | 256K | 512K | 855K |
|---|---|---|---|---|---|
| 目标前向(ms) | 8.86 | 8.93 | 9.03 | 9.25 | 9.53 |
| 对比 128K | −0.8% | – | +1.1% | +3.6% | +6.7% |
这些特性塑造了我们后续的优化策略:
- Prefill 应以较小的 DEP 规模运行。即使在 B300 或 GB300 上的 DEP2 实例,也能同时容纳模型权重和许多并发会话的 KV cache。更小的 world size 极大简化了 DP/EP 负载均衡。
- KV 容量很少限制 batch size。KV cache 可以轻松放入 HBM,使我们能够将每个操作推向其计算或内存带宽极限,而不必围绕 KV 容量做取舍。这也让我们的扫描保持一致且低噪声,没有抢占或 KV cache 压力。
- Wide-EP 对解码有效。MegaMoE 解码受限于权重读取,而 Wide-EP 直接削减了这一占主导地位的 MoE 开销。
- 解码 DP 负载均衡更简单。该模型的解码步时间随上下文长度变化很小,因此 DP 负载均衡可以简化为按每个 rank 的请求数做最小负载分配。
- 合成工作负载对解码仍具代表性。使用平均输入和输出长度可以很好地估计性能,而无需重放完整的智能体轨迹。
我们如何测量
在不使方法过于复杂的前提下准确测量性能并非易事。我们将其拆分为两个问题:一次 prefill 或 decode 步应该花费多少,以及 DP 和 EP 不均衡带来多少额外开销?
- Prefill 和 decode 步时间。
vllm bench forward(#61339)测量精确的 prefill 或 decode 批次通过实际execute_model路径跨 DP 和 EP rank 的模型前向时间。这仅测量 GPU 时间,并给出干净的 trace。此外,vLLM 的DecodeBenchConnector测量给定并发下引擎级端到端解码性能。仅 GPU 时间与服务步时间之间的差距揭示了意外开销。 - Prefill DP 不均衡:镜像重放 vs. 滞后重放。智能体轨迹每次运行都会变化,因此 DP 不均衡很难被单独隔离出来。为此,我们在每个 DP rank 上重放相同的请求。在一次镜像运行中,所有 rank 同时收到各自的请求。在一次滞后运行中,rankr收到相同的请求,但时间r× 5 s 之后,因此各 rank 步调不一致,但完成的总工作量相同。两次运行之间的差异就是 DP 不均衡的代价。
- 解码 EP 不均衡:真实路由与均匀路由对比。对每个 MoE 层,我们将均衡度定义为每个 rank 的平均负载除以最大负载,按步统计并在 EPLB 的窗口内统计。作为均衡参照,我们用均匀随机路由的 token 运行相同的解码批次。该参照仍存在采样噪声:在 DEP32、每 GPU 72 个请求时,均匀路由对目标模型(top-6 路由)达到约 0.96 的均衡度,对草稿模型(top-3)达到 0.94。真实路由与均匀路由之间的差距是专家均衡最多能挽回的部分,而 EPLB 运行则显示了它实际挽回了其中多少。
预填充:DEP2 效果最佳
如上所述,较小的 DEP 规模对预填充很有吸引力。在一次并发数为 256 的 AgentX 纯预填充运行中,DEP4 的每 GPU 吞吐仅为 DEP2 的 83%(53.3K 对 64.4K tokens/s)。关闭前缀缓存后的镜像与滞后重放指向 DP 不均衡:当各 rank 步调不一致时,DEP4 损失了 13.6% 的吞吐。2这使 DEP2 成为我们简单的默认选择。
| 每实例预填充吞吐(tokens/s) | 镜像(均衡) | 滞后(不均衡) | 损失 |
|---|---|---|---|
| DEP2 | 138.7K | 130.5K | 5.9% |
| DEP4 | 267.6K | 231.1K | 13.6% |
轨迹背后隐藏的优化
我们的第一篇博文涵盖了一系列广泛的预填充优化。更清晰的轨迹帮助我们找出了剩余的瓶颈,尤其是那些随上下文长度增长的瓶颈。例如,在两块 GB300 GPU 上,8K token 的预填充在无先前上下文时耗时87.9 ms,但在有 512K 缓存上下文时耗时 189.4 ms。我们解决了另外三个瓶颈:
将 SWA 有界重放扩展到第 20 层(#59894):在 32K 新 token 时提升 19.9%SWA 有界重放已将第 21–39 层限制为仅处理最后 128 个 token。此前,第 20 层仍会对所有新 token 执行索引器计算。现在它会为所有新 token 写入 KV,但仅在 128 token 的重放批次上运行其余计算。这将预填充吞吐量提升了在 32K 新 token 时提升 19.9%,且增益从 4K 时的 10.1% 递增至 64K 时的 23.1%。
为共享的 Engram 主机表使用 2 MB 页(#62359):在 32K 时再提升 2.8%Engram 会从跨 DP rank 共享的主机表中执行随机读取。此前的/dev/shm映射使用 64 KB GPU 页,导致频繁的 GPU TLB 未命中。将表迁移到 host-NUMA cuMem 分配后可启用 2 MB GPU 页,从而将预填充吞吐量再提升在 32K 新 token 时提升 2.8%。
预填充索引器现在使用 DeepSelect 对候选块进行排序,与解码路径保持一致。其收益随参与排序的键数量增长而增加,从而将预填充吞吐量再提升在 32K 新 token 时提升 15.6%,在 64K 时提升 15.2%。
这些改动共同将新 token 预填充吞吐量提升了 35.6–43.9%在 128K 缓存前缀之上,覆盖 4K–64K 新 token。在 32K 时,综合增益为42.5%;在 64K 时,吞吐量从71.2K 提升至 102.5K tokens/s/GPU(+43.9%)。
图 4:在 128K 缓存命中后,DEP2 上每 GPU 的新 token 预填充吞吐量,依次叠加第 20 层重放、Engram 2 MB 页和 DeepSelect top-k。
解码:扩展 Wide-EP 并用 EPLB 平衡专家
在解码侧,Wide-EP 配合 EPLB 已是成熟实践,详见DeepSeek 的推理系统概览。vLLM 已支持这两者一段时间(参见我们的大规模服务博文),并且我们发现它们仍然运行良好。本节聚焦于我们的新发现和经验教训。
Wide-EP 的扩展
我们在 128K 上下文、每 GPU 64 个请求的条件下,测量了 DEP4 到 DEP32 的解码扩展性(启用 EPLB;见下一节)。随着每个 rank 持有的专家数减少,目标 MegaMoE 时间从 13.4 ms 降至 6.0 ms,而剩余工作仍保持在 10 ms 左右。因此,每 GPU 吞吐量的增益逐渐减弱。在 DEP32 时,draft 的通信开销也开始抵消更宽并行带来的收益。
图 5:在 NVIDIA GB200 上,128K 上下文、每 GPU 64 个请求时,DEP4 到 DEP32 每个 rank 的解码步时间,以及每 GPU 的输出吞吐量。
EPLB
我们让 EPLB 每 3,000 步根据前 1,000 步的负载重新排列专家。EPLB 将 target 的均衡度从 0.35 提升到 0.94,将 draft 的均衡度从 0.21 提升到 0.89,分别弥补了与均匀路由之间差距的约 97% 和 93%。
图 6:在 GB300 NVL72 上关闭 EPLB、仅对 target 模型启用 EPLB,以及对 target 和 DSpark draft 都启用 EPLB 时的解码表现。标签页分别显示每步均衡度、DEP32 下的每步时间分解,以及吞吐量(三次运行的平均值;条形表示范围)。
两项改动使这成为可能。#62009引入了一个基于 NVLink 的symm_mem后端,可在不停止解码的情况下于 1 秒内重新均衡整个 DEP32,将 EPLB 的副本映射和负载记录融合进 MegaMoE 内核(每步节省约 0.2 ms),并从解码循环中移除了主机同步。#61395支持 target 与 draft 模型异构场景下的 EPLB。DSpark draft 有 128 个路由专家、top-3 路由,而 target 有 384 个专家、top-6 路由,因此 EPLB 分别跟踪每个模型的专家放置和负载,并对两者都进行重新均衡。
克服快速大规模解码中的 CPU 开销
宽 EP 解码在每一层都会同步所有 rank,因此一个慢 rank 就会拖住整个实例。异步调度在小规模下能很好地隐藏 CPU 开销,但 rank 越多,这一点就越难做到,原因有二:GPU 步变得更短,留给 CPU 工作的余量更少;并且某个 rank 落后的可能性也更大。
在实践中,我们观察到未优化的 DEP32 比 DEP16 运行得更慢(每步 17.57 ms 对 13.84 ms),尽管其 GPU 执行快了 1.1 ms。下表列出了我们找到的每一个原因,按影响排序。这些影响来自不同的配置,因此每一行给出各自的数据。
展示原因、修复及其影响| 原因 | 修复 | PR | 影响 | 配置 |
|---|---|---|---|---|
| KV 读取在 decode worker 线程上同步提交 | 使用 UCX scatter-gather 的 NIXL push;打包的 KV 行作为单个 NIXL region 发送 | NIXL push 连接器,#61121 | 每 GPU 141K → 440K tokens/s;每用户 P90 ~40 → 159 tokens/s | GB300,8P1D,并发 3,072 |
| Gloo all-reduce 延迟随 rank 数量线性增长 | 递归倍增 CPU all-reduce | #61767,基于#60267 | DP 同步每步 10.8 → 1.5 ms;每步 16.10 → 13.15 ms | DEP32 decode |
| 准入突发:单步最多准入 140 个请求 | 每步最多 4 个新 KV 加载的可选上限 | #61725 | 每 GPU 460K → 516K tokens/s;每用户 P90 109 → 153 tokens/s | GB300,7P1D,并发 4,800,另有两项其他 CPU 修复 |
| 随 prompt 长度增长的准入工作 | 将 prompt 作为单个 int32 缓冲区;跳过滑动窗口块表的空块;分块 prompt 拷贝 kernel | #59908,#62702,#61724,#62698 | 855K prompt:每次准入的 CPU 时间 38.8 → 6.0 ms | 引擎微基准测试 |
低延迟:面向小批量的专用 decode 路径
我们还将低延迟 kernel 支持扩展到了 DeepSeek-V4.1-Flash(#61868和#61869),延续了我们上一篇博文中的工作。在真实的 AgentX 会话中,专用 decode 路径在四块 B300 GPU 上为单个用户达到 826 P90 tokens/s,比标准 decode 路径(575)高 44%。
图 7:SemiAnalysis AgentX 在四块 B300 GPU 上使用 TP4,并发为 1、8 和 16,切换前后对比InferenceX #4332切换到了专用 decode 路径。
致谢
我们感谢DeepSeek开源并分享 DeepSeek-V4.1-Flash 及其优化 kernel,为本工作提供了基础,并发布 DeepInfer 及其性能结果,为我们提供了学习和对比的有力参考。我们也感谢SemiAnalysis提供 AgentX 基准测试平台以及所有帮助。
我们尤其感谢Pei Li、Hanjie Qiu、Lisa Wang、Yaoyao Ding、Rohit Nagraj 和 Xin Li来自NVIDIA感谢他们在性能优化方面的密切合作、对 vLLM 和 Dynamo 的持续支持,以及在 AgentX 提交方面的协助。我们还要感谢Canlin Guo来自vLLM 社区感谢他宝贵的 prefill 优化。
本工作由Inferact主导。