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× 是我们在 第一篇文章 中的单节点结果的 ,并且在 DeepSeek 的非高峰时段价格下成本为每 GPU 小时 38.96美元。专用解码路径可为单个用户达到 826 tokens/s 826 tokens/s 8 到 256 各批次大小范围内 6% 在 8–256 的批量大小范围内,并且其 prefill 与 DeepInfer 公布的曲线保持一致。
高效率来自对 vLLM 已有组件的刻意工程优化,构建在 DeepSeek 开源的内核之上。本文将介绍我们的性能研究,逐项解析各项优化,并分享我们学到的心得:
- 通用引擎可以匹敌模型专用引擎
- 优化之前先理解模型
- 小型 DEP 实例最适合 prefill1
- Wide-EP 解码配合 EPLB 扩展良好
- CPU 开销在 NVL72 规模下变得至关重要
- 专用解码路径解锁低延迟
性能概览
图 1:NVIDIA GB300 NVL72 上的 SemiAnalysis AgentX:采用 P/D 分离和 Wide-EP 的 vLLM(金色)相比我们从单节点聚合的结果,进一步实现了 7.9× 的吞吐量提升,相对于 第一篇文章中的聚合单节点结果进一步提升 7.9 倍吞吐量。吞吐量计入缓存输入、未缓存输入和输出 token(来源).
与 DeepInfer 对比
DeepInfer 是 DeepSeek 自己的 DeepSeek-V4.1-Flash 推理引擎,本周作为一个离线引擎发布,并公布了 prefill 和 decode 曲线。它是该模型最相关的参照对象,因此在深入介绍我们的优化之前,我们先展示 vLLM 与其相比的表现。
解码: 在 DeepSeek 的解码配置(131K 上下文,nextn=3)下,GB200 上的 vLLM 在批次大小 8 到 256 的范围内吞吐量与 DeepInfer 公布结果的差距在 6% 以内(图 2)。
图2:131K上下文、nextn=3 时,每块 GPU 的解码输出吞吐量随 batch size 的变化。GB200 上的 vLLM 与 DeepInfer 公布的结果对比,数据读取自其 README 中的图表(来源).
Prefill(预填充)。 DeepInfer 公布的预填充曲线使用 EP4DP4,配置 32 个冗余专家和 140-token 有界回放,且运行硬件未在图表中说明,我们从图表中读取其数值。我们的运行在 GB300 上使用 DEP2,无冗余专家,采用 128-token 回放和我们自己的提示词。由于设置不同,我们不将两条曲线之间的差距解读为引擎对引擎的加速。图3展示的是,vLLM 的 DEP2 预填充在其公布的整个范围内与 DeepSeek 自己的引擎表现相当。
图3:预填充吞吐量随新 token 数的变化。GB300 上的 vLLM DEP2 与 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%。
下表显示了目标模型在 GB300 上、DEP32、每个 rank 16 个请求、每个请求 6 个 token(一个 token 加五个 DSpark 草稿 token)、且所有请求上下文长度相同的情况下的前向耗时。从 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 缓存。更小的 world size 大大简化了 DP/EP 负载均衡。
- KV 容量很少限制批大小。 KV 缓存可以轻松放入 HBM,使每个操作能够接近其计算或内存带宽极限,而无需绕开 KV 容量的限制。这也使我们的扫描保持一致且低噪声,没有抢占或 KV 缓存压力。
- Wide-EP 对解码很有效。 MegaMoE 解码受限于权重读取,而 Wide-EP 直接削减了这一占主导地位的 MoE 开销。
- 解码 DP 负载均衡更简单。 该模型的解码步耗时随上下文长度变化很小,因此 DP 负载均衡可以简化为按每个 rank 的请求数取最小负载。
- 合成工作负载对解码仍具有代表性。 使用平均输入和输出长度,无需重放完整的 agentic 轨迹就能很好地估计性能。
我们的测量方法
在不使方法论过度复杂化的前提下准确测量性能并非易事。我们将其拆分为两个问题:一个 prefill 或 decode 步骤的成本应该是多少,以及 DP 和 EP 不均衡带来多少额外开销?
- Prefill 和 decode 步耗时。
vllm bench forward(#61339) 通过实际的execute_model路径跨 DP 和 EP rank 测量精确 prefill 或 decode 批次的模型前向时间。它只测量 GPU 时间,并提供干净的轨迹。此外,vLLM 的DecodeBenchConnector测量给定并发下的引擎级端到端解码性能。仅 GPU 时间与服务步耗时之间的差距揭示了意外的开销。 - Prefill DP 不均衡:mirror 与 lag 重放。 Agentic 轨迹在每次运行之间都有所不同,因此 DP 不均衡很难隔离。因此我们在每个 DP rank 上重放完全相同的请求。在 mirror 运行中,所有 rank 同时收到它们的请求。在 lag 运行中,rank r 接收到相同的请求 r × 5 秒后,因此各 rank 的步调不同步,但完成的总工作量相同。两次运行之间的差异就是 DP 不均衡的代价。
- 解读 EP 不均衡:真实路由 vs. 均匀路由。 对于每个 MoE 层,我们将 balancedness 定义为平均 rank 负载除以最大 rank 负载,按步骤以及按 EPLB 的窗口计算。作为均衡参考,我们运行相同的解码批次,但令牌以均匀随机方式路由。该参考仍存在采样噪声:在 DEP32、每 GPU 72 个请求的情况下,均匀路由对目标模型(top-6 路由)达到约 0.96 的 balancedness,对草稿模型(top-3)达到 0.94。真实路由与均匀路由之间的差距是专家均衡最多能挽回的部分,而 EPLB 运行显示了它实际挽回了多少差距。
Prefill:DEP2 效果最佳
如上所述,较小的 DEP 尺寸对 prefill 很有吸引力。在并发为 256 的 AgentX 仅 prefill 运行中,DEP4 仅达到 DEP2 每 GPU 吞吐量的 83%(53.3K 对 64.4K tokens/s)。关闭前缀缓存的镜像和滞后重放指向 DP 不均衡:当各 rank 步调不同步时,DEP4 损失了 13.6% 的吞吐量。2 这使 DEP2 成为我们简单默认的选择。
| 每实例 prefill 吞吐量(tokens/s) | mirror(均衡) | lag(不均衡) | 损失 |
|---|---|---|---|
| DEP2 | 138.7K | 130.5K | 5.9% |
| DEP4 | 267.6K | 231.1K | 13.6% |
隐藏在追踪背后的优化
我们的 第一篇文章 涵盖了一系列广泛的预填充优化。更干净的追踪帮助我们识别出剩余的瓶颈,尤其是那些随上下文长度增长而扩大的瓶颈。例如,在两块GB300 GPU上,8K token的预填充在无先前上下文时耗时 87.9 ms,而在有512K缓存上下文时耗时 在缓存了512K上下文的情况下为189.4毫秒。我们又解决了三个额外的瓶颈:
将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缺失。将这些表迁移到主机NUMA cuMem分配后,可以使用2 MB GPU页面,使预填充吞吐量再提升 在32K新token时提升2.8%.
预填充索引器现在使用DeepSelect对候选块进行排序,与解码路径保持一致。其收益随着被排序的键数量增加而增长,使预填充吞吐量再提升 在32K新token时提升15.6% ,在64K时提升15.2%。
综合起来,这些改动在128K缓存前缀的基础上,使4K–64K新token范围内的 新token预填充吞吐量提升35.6–43.9% 即在128K缓存前缀之上新增4K–64K个token。在32K时,合计增益为 42.5%;在64K时,吞吐量从 71.2K提升至102.5K tokens/s/GPU(+43.9%).
图4:在DEP2上经过128K缓存命中后,逐项添加第20层重放、Engram 2 MB页面和DeepSelect top-k后的每GPU新token预填充吞吐量。
解码:使用EPLB扩展Wide-EP并均衡专家负载
在解码侧,带EPLB的Wide-EP是一种成熟做法,在 DeepSeek的推理系统概述. vLLM 已经支持两者一段时间了(参见我们的 大规模服务文章),我们发现它们仍然工作良好。本节重点介绍我们的新发现和经验教训。
Wide-EP 的扩展
我们测量了从 DEP4 到 DEP32 的解码扩展,每个 GPU 64 个请求,上下文为 128K(启用 EPLB;见下一节)。随着每个 rank 持有的专家更少,目标 MegaMoE 时间从 13.4 毫秒降至 6.0 毫秒,而其余工作保持在约 10 毫秒左右。因此,每 GPU 吞吐量的提升逐渐递减。在 DEP32 时,草稿模型的通信开销也开始抵消更宽并行带来的收益。
图 5:在 NVIDIA GB200 上,每个 GPU 64 个请求、128K 上下文时,从 DEP4 到 DEP32 每个 rank 的解码步时间,以及每 GPU 输出吞吐量。
EPLB
我们让 EPLB 每 3,000 步根据前 1,000 步的负载重新排列专家。EPLB 将目标模型的均衡度从 0.35 提升到 0.94,草稿模型从 0.21 提升到 0.89,分别恢复了与均匀路由差距的约 97% 和 93%。
图 6:在 GB300 NVL72 上解码,分别为关闭 EPLB、仅目标模型启用 EPLB、以及目标模型和 DSpark 草稿模型都启用 EPLB。标签页显示每步的均衡度、DEP32 下每步的时间分解以及吞吐量(三次运行的平均值;柱状图显示范围)。
两项改动使其成为可能。 #62009 引入了基于 NVLink 的 symm_mem 后端,可在不停止解码的情况下一秒内重新平衡整个 DEP32,将 EPLB 的副本映射和负载记录融合进 MegaMoE 内核(每步节省约 0.2 毫秒),并从解码循环中移除了主机同步。 #61395 支持异构目标模型和草稿模型的 EPLB。DSpark 草稿模型有 128 个路由专家,采用 top-3 路由,而目标模型有 384 个专家,采用 top-6 路由,因此 EPLB 为每个模型分别跟踪专家放置和负载,并对两者进行重新平衡。
克服快速大规模解码中的 CPU 开销
Wide-EP 解码在每一层同步所有 rank,因此一个慢的 rank 会阻塞整个实例。异步调度在小规模下能很好地隐藏 CPU 开销,但更多的 rank 会在两个方面使这变得更难:GPU 步变得更短,留给 CPU 工作的余量更少,并且某个 rank 落后的可能性更大。
在实践中,我们观察到未优化的 DEP32 比 DEP16 运行得更慢(每步 17.57 毫秒对比 13.84 毫秒),尽管其 GPU 执行快了 1.1 毫秒。下表列出了我们发现的所有原因,按影响排序。由于这些影响来自不同的设置,每行都给出了各自的数据。
显示原因、修复措施及其影响| 原因 | 修复措施 | PR | 影响 | 设置 |
|---|---|---|---|---|
| KV 读取在解码工作线程上同步提交 | 使用 UCX scatter-gather 的 NIXL 推送;打包的 KV 行作为一个 NIXL 区域发送 | 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 解码 |
| 准入突发:单步最多准入 140 个请求 | 可选的每步 4 个新 KV 加载上限 | #61725 | 每 GPU 460K → 516K tokens/s;每用户 P90 从 109 → 153 tokens/s | GB300,7P1D,并发 4,800,另加两项其他 CPU 修复 |
| 随提示长度增长的准入工作 | 提示作为单个 int32 缓冲区;跳过滑动窗口块表的空块;分块提示复制内核 | #59908, #62702, #61724, #62698 | 855K 提示:每次准入的 CPU 时间从 38.8 → 6.0 ms | 引擎微基准测试 |
低延迟:针对小批量的专用解码路径
我们还将低延迟内核支持扩展到了 DeepSeek-V4.1-Flash(#61868 和 #61869),延续了我们在 上一篇文章中的工作。在真实的 AgentX 会话中,专用解码路径在四块 B300 GPU 上为单个用户达到 826 P90 tokens/s,比标准解码路径(575)高 44%。
图 7:在四块 B300 GPU 上以 TP4 运行 SemiAnalysis AgentX,并发为 1、8 和 16,切换前后对比 InferenceX #4332 切换到专用解码路径。
致谢
我们想感谢 DeepSeek 开源并分享 DeepSeek-V4.1-Flash 及其优化内核(这为本工作提供了基础),以及发布 DeepInfer 及其性能结果(这为我们提供了强有力的学习和比较参照)。我们还感谢 SemiAnalysis 提供 AgentX 基准测试平台以及所有帮助。
我们特别感谢 裴力、邱汉杰、Lisa Wang、姚瑶丁、Rohit Nagraj 和 Xin Li 来自 英伟达 感谢他们在性能优化方面的密切合作、在 vLLM 和 Dynamo 上的持续支持,以及对 AgentX 提交工作的协助。我们还要感谢 Canlin Guo 来自 vLLM 社区 感谢他做出的宝贵预填充优化。
这项工作由 Inferact.