TL;DR:在一台 NVIDIA Vera Rubin NVL72 机架上,vLLM 以如下性能服务 DeepSeek-V4.1-Flash:1.7Mtokens/s 每 GPU,同时每个用户获得387tokens/s,58×GB300 NVL72,并达到2.52M的峰值吞吐量,以及1,810tokens/s 的单用户性能。这些提升来自基于 Rubin 新特性的构建:为其分离式内存和 FP4 张量核心调优的内核、面向低延迟的专用解码内核,以及通过 NVLink 共享的机架级 KV 缓存和 Engram 表。

这是 DeepSeek-V4.1-Flash 系列的第三篇文章。第一篇文章涵盖了 day-0 启动和内核融合。第二篇通过 P/D 分离和 Wide-EP 将模型扩展到 GB300 NVL72 机架。本文把同一套栈迁移到 Vera Rubin NVL72,我们上个月介绍过。

Rubin 开辟了新的优化途径。我们先介绍塑造了我们工作的硬件事实,然后依次介绍四组优化:

  • 面向 DEP 预填充和解码的 Rubin 优化内核。1我们为 Rubin 重做了 MoE、注意力、GEMM、mHC 和 indexer 内核:受带宽约束的数据留在每个 SM 自己的内存域中,SM107 更宽的 MMA 指令每次发射完成更多工作,内核在等待之前就开始加载及其依赖项。DEP2 预填充步骤约快 28%,DEP 解码步骤约快 25%。
  • 面向最低延迟的专用 TP 解码路径。我们执行跨算子融合、将计算与通信重叠并进行调优,然后将模型以 TP8 分布到两个托盘上,在批大小为 1 时达到 1,810 tokens/s(P90)。
  • Dynamo 在机架级内存池上的工作负载感知路由。Rubin 的速度带来高得多的并发度,因此我们池化机架的内存:Mooncake KV 池从 DRAM 扩展到空闲 HBM,Engram 表分片到各 GPU HBM 上。即使在非常高的并发度下,前缀命中率仍保持在 96.5–97%,仅靠路由就能在各 rank 间实现负载均衡。

性能概览

图 1:SemiAnalysis AgentX 在 NVIDIA Vera Rubin NVL72(vLLM、P/D 分离、Wide-EP)与 GB300 NVL72 上的对比。吞吐量计入缓存输入、未缓存输入和输出 token(GB300 来源)。

我们使用以下基准测量性能:SemiAnalysis AgentX作为代表性智能体服务负载的基准测试,与我们之前的文章保持一致。图 1 在两个标签页中分别比较了两种机架的原始吞吐量(每 GPU 每秒总 token 数,包括输入、缓存和输出)和功率归一化吞吐量。

我们如何测量

我们使用与我们的上一篇 GB300 文章相同的方法。对于内核和模型前向分析,vllm bench forward通过真实路径、真实权重和真实 AgentX token 对精确的 prefill 和 decode 批次进行计时。我们在持续数秒的循环中对 prefill 内核进行 A/B 测试,并在结果不同时报告持续数值。execute_modelpath, with real weights and real AgentX tokens. We A/B prefill kernels in loops that run for several seconds and report sustained numbers where they differ.

透过 DeepSeek-V4.1-Flash 看 Rubin

Vera Rubin NVL72 的三个特性驱动了下面的大部分决策。当某项优化是 Rubin 特有的,我们会说明它依赖其中哪一个。

  • 每个 GPU 有两个内存域。SM 读取自己的域比读取交错内存快得多,而从另一个域读取则更慢。正如Rubin 预览所示,这对带宽受限的数据很重要:MoE 权重、LM head、MegaMoE 的 combine 缓冲区以及 mHC 残差流。
  • SM107 增加了更宽的块缩放 MMA 指令。我们在 MegaMoE 和低延迟路由 GEMM 中使用了新的 K64 混合精度(FP8×FP4)MMA,每条指令的工作量翻倍:MegaMoE 在每 rank 8K+ token 时吞吐量提升 8–11%,低批量大小的路由 GEMM 运行速度快 12%。在 indexer 中,新的 K128 FP4 MMA 配合六 token 查询块,使 decode MQA-logits 内核快 15–22%。
  • 72 个 GPU 共享一个 NVLink 域。TP8 可以跨两个计算托盘,几乎不增加 all-reduce 成本。Prefill 实例可以高效读取彼此的 HBM,因此大表不再需要复制到每个实例。

图 2:在一个 SM 的共享内存中,路由专家 GEMM 的权重阶段,分别使用 SM100 MXFP8×MXFP4 MMA 和 SM107 在打包 FP4 上的 K64 MXFP8×MXFP4 MMA。

深入优化

内核

我们保留了 GB300 的三种引擎形态:DEP2 prefill、Wide-EP decode(DEP16 或 DEP32),以及用于最低延迟的 TP decode 路径(下一节)。我们还保留了 DeepSeek 的内核集(MegaMoE、MegaAttn、Mega-mHC、MegaGate、DeepSelect),并为 SM107 重建了其中大部分。

DEP prefill:每步时间减少约 28%

在每 rank 32K token 的情况下,prefill 步骤主要被专家 GEMM 占据;其余大部分开销来自将残差和注意力的工作集通过 HBM 流式传输。因此这一轮的工作集中在 GEMM 周边的 kernel 以及 Rubin 的两个内存域上:Mega-mHC 让残差保持在 die-local,MegaAttn 让其张量核心持续发射而不是等待加载,MegaGate 只对仍有可能进入前 6 的专家打分。三者合计将 32K token 的 DEP2 prefill 步骤缩短了 28%。

图 3:prefill kernel 的三项改动:Mega-mHC 的 die-local 残差、MegaAttn 的流水线以及 MegaGate 的候选过滤器,以及它们合计带来的步骤收益。

Prefill 优化细节
  • MegaMoE:按最繁忙的 rank 来确定 tile 大小。vLLM 在 eager prefill 中不会对 DP 批次做填充,因此在 86% 的真实 DEP2 prefill 步骤中,一个空闲 rank 按自身极少的 token 数为 MegaMoE 划分 tile,而其专家却在服务繁忙 rank 的 token。现在 tile 大小取本地与 DP 平均 token 数中的较大者。在 40 步回放中 MegaMoE 时间下降 55%,在空闲 rank 旁的单请求 TTFT 从 8K 到 32K token 下降 13–39%。
  • Mega-mHC:将残差流放入 die-local 内存。在 prefill 规模下,Mega-mHC 受 HBM 带宽限制,每个 token 需流式传输约 100 KB 的四路残差。现在残差存放在一个跨两个内存域条带化的池中,Mega-mHC 为每个域维护一个任务计数器,因此每个 SM 先排空自己所属的域再去帮助另一个域。这使得大网格受指令缓存限制,因此它们现在循环执行一份 Post 主体副本,而不是四份。在 32K token 时 Mega-mHC 每次调用从 354 µs 降至 317 µs;5K+ token 的 DEP2 步骤快了 0.6–1.3%。
  • 在 SM107 的 K64 块缩放 MMA 上进行 query 上投影。MegaAttn 的 [tokens × 1,280] × [1,280 × 32,768] query GEMM 从 cuDNN 的 SM100 kernel 迁移到 FlashInfer 的 CuTe DSL GEMM,运行在 SM107 的 K64 MMA 上:在 4K 到 32K 行范围内快 13–18%,在 16K–32K 时使 DEP2 步骤缩短 1.1–1.7%。
  • MegaAttn:让张量核心持续获得数据供给。在 16K–32K tokens 每 rank 的情况下,稀疏注意力受限于发射和加载延迟。现在一个空闲 warp 会发射 SV,因此 QK 不再在其后等待;KV 生产者会在等待槽位之前加载下一个块的 top-k 索引;集群启动控制 warp 会将下一个作业的索引行预取到 L2;下一个作业的查询会在当前作业的最后一个 QK 之前加载。每个 TMEM lane 组现在会完整地为自己的键打分,因此 softmax 不再通过共享内存交换部分和。该内核在 16K–32K tokens 时快 15%,在 4K 时快 23%,DEP2 步骤提升 0.9–2.1%。
  • Mega-mHC:一个带票据的任务环。在长提示下,固定的任务步幅使 RMSNorm 工作线程远远落后于 Post,导致它们对 Post 输出的重读未命中 L2。现在大型网格从原子票据计数器领取任务(32K 时提升 18%)。Norm 任务还会预先发出所有加载,下一个任务的系数提前到达,并且两个 Post warpgroup 共享一份代码副本以节省指令缓存:在 4K 到 32K tokens 范围内,每次调用又提升 13–22%。
  • MegaGate:每个 token 只读取一次,只对竞争者打分。对于长提示,一对 CTA 从同一个共享内存阶段计算一个 token 块的全部 384 个专家(M=256 和 M=128 UMMA),而不是按每 128 个专家组重读 token。路由器只对仍有可能进入前 6 的专家计算精确的 sqrt-softplus:对第六大分数的上界和该函数的斜率给出每个专家的阈值,384 个专家中约有 7 个通过。该内核从 4K tokens 起快 3–13%(32K 时从 77 µs 降至 68 µs)。

DEP 解码:每步时间减少约 25%

与 GB300 一样,解码受限于 MoE。与我们首个 Rubin 构建(该构建已具备 die-local 权重)相比,Wide-EP 调度变更和 MQA logits 变更使这些优化将 DEP32 解码步骤的目标前向(每 rank 20 个请求 × 6 个 tokens,DSpark k=5,在单个 4-GPU 节点上模拟)减少了约 25%。

图 4:Wide-EP 解码变更中的四项:die-local 专家权重、每个调度 token 一次 NVLink 往返、等待数据的 combine,以及跨 SM107 集群的 split-K 投影。

解码优化细节
  • Die-local MegaMoE 权重。Decode MoE 受限于权重读取,而一个 SM 流式访问自身内存域的速度远快于交错内存。vLLM 按输出列将专家权重拆分到各内存域,MegaMoE 只给每个域的 SM 分配本地 tile。DEP2 decode 前向在 batch size 32 时快了 10.1%,在 168 时快了 5.5%。
  • Wide-EP dispatch:每个 token 一次 NVLink 往返。在 DEP8 decode 下,NVLink 仅以峰值的 39% 运行。现在每个拉取的 token 会在任何本地存储之前发出所有远程加载,只需支付一次往返而非两到三次。warp 通过一次 fence 和一次选出的 release 来发布拉取,专家计数以不返回的red原子操作发出,因此第一个 SM 的 392 次远程更新不再串行化。MegaMoE 在 DEP8 下快了 17%(192 → 160 µs)。
  • MegaMoE:一个等待数据而非屏障的 combine。combine 缓冲区在每次启动时初始填充一个任何输出都无法取用的哨兵值,因此 CTA 在自身 GEMM 任务完成后立即进行 combine,只重新读取仍持有哨兵值的 16 字节组,然后将其恢复。跨 rank 的尾部屏障在本次启动结束时发出信号,并在下次启动开始时等待。当本地专家较少时,所有第一次 GEMM 任务都会在任何第二次 GEMM 任务之前被认领,从而保持权重持续流式传输。MegaMoE 在 decode 规模下快了 3.5–10.8%。
  • SM107 集群上的 Split-K GEMM。每个 rank 只有几十行时,注意力输入和输出投影启动的 CTA 太少,无法以全带宽流式传输权重。Split-K GEMM 将 K 分散到 SM107 集群上,并通过分布式共享内存归约部分和;当某个 CTA 的对等切片超过 28 KB 时,则从寄存器经 L2 归约。这些 GEMM 快了 10–38%,decode 前向在每个 rank ≤16 个请求时快了 3.1%,在 17–24 个请求时快了 1.9%。
  • MQA logits:六 token tile 与 SM107 的 K=128 MMA。DSpark 验证每个请求向 indexer 发送 5–6 个 query 行。该 kernel 现在将它们打包成一个 192 宽的 tile,分布在两个 warpgroup 上,而非两个 4-token tile 分布在三个上;每个 128 维 head 作为一次 SM107 K=128 MMA 发出,而非两次 K=64,并以立即偏移量存储,无需按拆分进行 64 位地址运算:在每个请求 5–6 个 token 时快了 15–22%。
  • Split Mega-mHC:关键路径上只保留下一层所需的内容。下一层需要 Mega-mHC 的 Post、route collapse 和 RMSNorm,但不需要它的 next mixes。现在一个小内核只做这三件事,而 mixes 在侧流上运行,并在下一个边界处汇合。它以合并的 16 字节块移动每条路由,每个 CTA 最多处理三个 token,因此在 212 个 SM 上一波可容纳 636 个 token。在每 rank 20–30 个请求时,解码前向快了 2.0–4.8%;合并重写使内核在 18–192 个 token 时再快 30–58%。
  • MegaAttn 解码:更深的流水线。解码注意力每个 CTA 运行一两个 token,是一条由索引加载、KV 收集和解包,再到逐块 QK、softmax 和 SV 组成的延迟链。现在有三块索引加载和四批收集保持在进行中,空闲的 query warpgroup 在每个 SM 只有一个任务时解包 KV,内核在自身依赖等待结束后立即触发依赖项,将输出投影的序言与注意力重叠。该内核在 6–424 行时快了 7–10%。

低延迟:带 TP 的专用解码路径

为实现最低延迟,我们运行一条专用解码路径。我们从 GB300 文章中移植了它,为 Rubin 做了大量重建,并将其以 TP8 分布在两个计算托盘上。在并发为 1 的真实 AgentX 会话中,固定 DSpark 接受度为每步 3.51 个 token,它在 P50 下从每用户 1,117 tokens/s 提升到 1,844 tokens/s。除了第一个专家 GEMM,一步就是一条接近其延迟下限的小内核链。

图 5:专用 TP8 解码路径的一步:引擎分布在两个计算托盘上,以及逐层内核链及其边界已融合。

低延迟优化细节
  • 跨两个托盘、基于 NVL72 的 TP8。每个 rank 持有每个专家的 288 列切片和 8 个注意力头。跨托盘 all-reduce 仅比托盘内 all-reduce 多 0.3 µs,因此 TP8 几乎免费地将每 GPU 权重读取减半。采用路由 GEMM 和稀疏 MLA 的原生 TP8 形状后,一步快了 35%。
  • SM107 上的 Packed-FP4 权重阶段。第一个专家 GEMM 受限于每个 SM 在飞行中保留的权重字节数。UMMA 描述符只能寻址 256 KB 的共享内存,在 SM100 的单字节 FP4 布局下可容纳 7 个 stage。SM107 的 K64 MMA 读取打包的 FP4,因此同样的空间可容纳 11 个 stage(图 2),每步节省 57 µs。
  • 片内本地 LM head。LM head 读取其权重的副本,该副本拆分在两个内存域中。每个 SM 只流式读取自己那一半,接近域内本地上限。
  • all-reduce 与 mHC 合并为一次。两个 all-reduce 都从各自的 GEMM epilogue 直接发布到带 NVLink 多播存储的 Lamport 邮箱。接收内核对各 rank 求和,应用 mHC Post,折叠四条路由并计算 RMSNorm;在 MoE 边界处,它还会在 grid 等待之前运行 router GEMM(每步约 −30 µs)。下一层的 mHC pre-norm 在旁路流上运行,拆分为 64 路(5.8 → 3.7 µs),路由 GEMM 为它留出 Rubin 212 个 SM 中的 16 个(−4 至 −10 µs)。
  • 稀疏 MLA。在 TP8 下,每个 rank 运行一个 8 头稀疏 MLA 内核。当前一个内核仍在运行时,它收集除本步新增行之外的所有 KV 行,每个 PV 块在其 MMA 完成后立即将部分结果发送给所有者(−16 至 −21 µs)。Query RoPE 和 KV-cache 插入被融合进 attention 周围的内核中,这些内核也会提前启动其 prologue(再 −36 µs)。
  • 共享专家放入路由 GEMM 内部。在 80% 的层中,共享专家是 MoE 子层的尾部。在少于 16 行时,它现在运行在路由 GEMM 内部,每步节省 220 µs。
  • 提前启动下一个内核。all-reduce 终结器在其自身 grid 等待之后立即启动其依赖项(−16 µs),而 indexer 的 MQA logits 在 grid 同步之前加载 KV(−24 µs)。
  • Engram 在该层的流上进行 all-gather。在 TP8 下,NCCL all-gather 在 Engram 的关键路径上增加 22 µs。融合的入口内核和在该层自身流上的 Lamport all-gather 每步节省约 40 µs。

Dynamo 工作负载感知路由 + 机架级共享内存池

Rubin 计算速度快,因此一个机架能维持比 GB300 高得多的并发量,而这些智能体会话的活跃前缀也超出了任何单 rank 缓存的容量。在 GB300 上,我们把前缀放在每个 rank 的 CPU 层,并把每个会话固定到某个 rank;在 Rubin 的并发水平下,这一层太小,而固定会话又导致一些 rank 空闲、另一些排队。我们把内存跨整个机架池化以推到极限:KV 放在 DRAM 和空闲 HBM 中,通过 RDMA 加载,Engram 表放在 HBM 中,通过 NVL72 的 NVLink 读取。即使在非常高的并发下,前缀命中率仍保持在 96.5–97%,接近理论命中率。由于没有会话绑定到某个 rank,仅靠负载感知路由就能让各 rank 保持均衡。

图 6:每个 prefill rank 都通过机架的 DRAM 和空闲 HBM(RDMA)从一个 Mooncake 池加载 KV,并从对等节点的 HBM(NVLink)读取 Engram 表,因此 Dynamo 路由器可以把每个请求发送到负载最低的 rank。

Mooncake 池。一Mooncake 存储跨每个 prefill 和 decode 托盘的 DRAM,取代了每个 rank 的 CPU 层,因此任何 rank 都能通过 RDMA 加载任何前缀,被迁移的请求只需一次 KV 加载,而不是重新计算。我们优化了 prefill 调度器等待该池的方式:查找在进程外异步运行,已在本地 HBM 命中的请求跳过查找但仍刷新其租约,KV 加载在查找返回后立即开始,而不是等到下一步。因等待查找而未充分填充的 prefill 步骤从占步骤时间的 6–14% 降至 0.5% 以下,因等待 KV 加载而未充分填充的步骤从 13.5% 降至 1%。

HBM 池。DeepSeek-V4.1-Flash 很小,其每 token 的 KV 缓存也很小,因此服务一个请求只用到 prefill 和 decode GPU 的 HBM 的一小部分。一旦 Mooncake 持有这些前缀,其余部分就会闲置。现在每个 prefill rank 只保留刚好够一个最大长度请求使用的 KV 缓存,并把其余 KV 预算作为 HBM 段交给 Mooncake,其大小自动确定,从而把池从 DRAM 扩展到每个 prefill GPU 的空闲 HBM;这些段通过同一条 RDMA 路径读取。HBM 池仍处于实验阶段。

Engram 池。两个角色现在都把 Engram 的两张 94.4 GiB 哈希 n-gram 表放在 HBM 中。Decode 已经把它们拆分到其 DP rank 上(vllm#54371),但每个 DEP2 预填充实例都在主机内存中保留了自己的副本,每个托盘约 378 GiB。在 NVL72 上,预填充引擎现在组成一个对等组来拆分这些表,18 块 GPU 上每块 10.5 GiB,其余部分通过 NVL72 的 NVLink 从对等节点的 HBM 读取,而不是 RDMA(vllm#66552,图 7)。查找比从主机内存读取更快,而释放出的主机内存可容纳约 1,500 个更多会话的活跃前缀。

图 7:每个实例的 Engram 表在主机 DRAM 中(之前)与拆分到对等组 HBM 上(之后)的对比。

工作负载感知路由。当所有前缀都位于共享池中时,会话不再需要固定在一个 rank 上,这消除了会话亲和性导致的 DP 不均衡。Dynamo 将每个请求放置在未缓存工作量最少的(worker, DP rank)上,这仅是配置变更。在并发 4,800 的三个 DEP2 实例上,池加路由使服务请求数增加 68%,TTFT P50 从 29.8 s 降至 4.2 s,命中率从 91.8% 提升至 96.1%(图 8)。在 12 × DEP2 模拟中,某个 rank 空闲而另一个 rank 排队的时间占比从 10.3% 降至 1.3%,TTFT P90 从 0.98 s 降至 0.38 s。

图 8:并发 4,800 时三个 DEP2 预填充实例上的请求数/秒、TTFT P50 和命中率。

致谢

我们感谢DeepSeek开源 DeepSeek-V4.1-Flash 及其内核,以及NVIDIA提供 Vera Rubin NVL72 的早期访问权限。我们特别感谢来自 NVIDIA 的 Hanjie Qiu、Lisa Wang、Jie Hao、Yaoyao Ding、Pei Li、Xin Li 和 Pen Chung Li 在性能优化上的紧密合作以及对vLLM和Dynamo的持续支持。我们也感谢Mooncake团队的 KV 缓存存储,以及SemiAnalysis开发 AgentX 基准测试。

这项工作由Inferact领导。

脚注

  1. DEP2、DEP16 和 DEP32 指在 2、16 和 32 块 GPU 上的数据并行注意力加专家并行。↩