返回 文章 build CMS 文章

ThunderKittens 移植 NVIDIA Vera Rubin NVL72:NVFP4 GEMM 突破 22 PFLOPS

ThunderKittens 团队详解如何利用 Vera Rubin 新 ISA 特性,将 NVFP4 GEMM 从 42% roofline 提升至 22+ PFLOPS。

ThunderKittensNVIDIA Vera RubinNVFP4GEMM
成长分 / 100 76 综合收获、行动、留存与影响

ThunderKittens 移植 NVIDIA Vera Rubin NVL72:NVFP4 GEMM 突破 22 PFLOPS
为什么值得读了解 NVIDIA Vera Rubin 相比 Blackwell 在张量核心、张量内存、共享内存和收集器等方面的关键 ISA 变化。

学习如何通过 2x1 分块、深化流水线、B 侧收集器和提前释放 A 等优化手段,将 GEMM 性能推向硬件极限。

关键洞察
  1. Vera Rubin 的张量核心 K 步长从 32 字节翻倍至 64 字节,但仅开启宽编码不足以提升性能,需配合减少操作数流量和深化流水线。
  2. 张量内存从 512 列增至 576 列,额外 32 KiB 可通过 .exclusive 限定符访问,用于存储缩放因子,支持 2x1 分块格式。
  3. 共享内存从 228 KiB 增至 328 KiB,允许构建更深的流水线,提前暂存更多分块,隐藏数据传输延迟。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:14518

Together 的内核团队最近拿到了 NVIDIA Vera Rubin NVL72 平台的使用权限。过去几天,我们深入研究了新的 ISA,并用微基准测试对这颗芯片做了各种探查。新特性很多,也很有意思!我们已经在 ThunderKittens 中补充了一些功能,可用于在 Vera Rubin 上编写 NVFP4 和 FP8 GEMM,也能帮助其他小猫们去探索星辰。

插图:三只卡通机器人小猫在“Vera Rubin Observatory”前,带着望远镜和发光的地球仪,上方是一个猫形星座。
天文学家小猫。好可爱!

在深入介绍 Vera Rubin 带来了什么之前,我们先快速回顾一下 Blackwell GPU 上的 GEMM。

起点:NVIDIA HGX B200 上的 GEMM

NVIDIA Blackwell 架构的第五代张量核心从根本上改变了 GEMM 的编程模型。NVIDIA Hopper 架构的 wgmma 指令由一个 warpgroup 集体发射,而 Blackwell 的 tcgen05 指令由单个线程发射,因此只需一个小的生产者 warp 就能驱动张量核心。累加器也从寄存器移到了张量内存(Tensor Memory),操作数直接从共享内存读取,使得单个 MMA 可以跨越两个 SM 上的两个 CTA。

为了在 Blackwell 上达到有竞争力的性能,我们的 GEMM:

  • 启动线程块集群(threadblock cluster),使每对 CTA 可以通过 TMA 多播共享操作数,把来自 HBM 的内存流量减半。
  • 在集群内对 warp 做分工:加载器通过 TMA 把 A 和 B 搬入共享内存,单个 MMA warp 驱动张量核心,消费者 warpgroup 则把算好的累加器从张量内存送往 HBM。
  • 以持久化方式运行:一个分块的输入正在流入时,上一个分块的输出仍在排出。

通过这些努力,我们得到了如下结果。

柱状图“HGX B200 上的 NVFP4 GEMM”:ThunderKittens、cuBLASLt 和 CuTeDSL 在不同矩阵尺寸下的 TFLOPS。

柱状图“HGX B200 上的 FP8 (E4M3) GEMM”:ThunderKittens、cuBLASLt 和 CuTeDSL 在不同矩阵尺寸下的 TFLOPS。

想进一步了解这些内核及其优化,请阅读我们之前的 Together 博客文章,或查看 ThunderKittens 2.0 的发布!

由于 Rubin 保留了 Blackwell 的编程模型,我们原有的 GEMM 仍然可以运行。不过,直接把它们放到 Vera Rubin 上跑,我们发现 NVFP4 和 FP8 内核只达到了屋顶线(roofline)的约 42.1% 和 44.4%,还有很大的优化空间!

柱状图“Vera Rubin 上的 TK B200 NVFP4 和 FP8 GEMM”:ThunderKittens 在不同矩阵尺寸下的 TFLOPS,NVFP4 对比 FP8。

本文余下内容分为两部分。首先,我们介绍 Rubin 上对 GEMM 重要的新特性,以及如何在 ThunderKittens 中使用它们。然后,我们把这些特性逐步整合进现有的 Blackwell NVFP4 内核,使其性能超过 22 PFLOPS,与 cuBLAS 和 CuTE DSL 不相上下。

我们发现的核心问题是:Rubin 让张量核心消费操作数的速度提高了一倍,但我们旧的 Blackwell 内核喂数据的速度跟不上。要达到算力上限,我们需要用分块(tile)从已经在片上的数据中榨取更多复用。

NVIDIA Vera Rubin 平台有哪些新变化?

对比厂商规格,从 Blackwell 到 Vera Rubin 有如下改进。

NVIDIA HGX B200 NVIDIA Vera Rubin NVL72
NVFP4 张量核心 9 PFLOPS / GPU 35 PFLOPS / GPU
FP8 张量核心 4.5 PFLOPS / GPU 17.5 PFLOPS / GPU
FP16 / BF16 张量核心 2.25 PFLOPS / GPU 4 PFLOPS / GPU
内存带宽 8 TB/s / GPU 22 TB/s / GPU
SM 数量 148 / GPU 224 / GPU
峰值功耗 1000W / GPU 2300W / GPU

就编写高性能 GEMM 而言,我们特别关注以下几项特性。

1. 张量核心的 K 翻倍

先回顾一下,tcgen05.mma 在 MxNxK 的分块上计算 C = A@B + C,每一步沿 K 方向消费固定字节数。在 Blackwell 上,这一步是 32 字节,而在 Vera Rubin 上可以增加到 64 字节。MMA 本身耗费的周期数不变,因此 K 翻倍让我们能在同样的指令窗口内塞进两倍的工作量。

示意图:对比 Blackwell 的 32 字节 K 步长与 Vera Rubin 的 64 字节 K 步长,每条指令的操作数分块翻倍。

在 ThunderKittens 中,我们通过给现有 mma 操作新增一个模板参数来表达这一点。

mma_ABt    (...);   // Blackwell Default: 32-byte K step
mma_ABt<64>(...);   // Vera Rubin: 64-byte K step

2. 张量内存增长到 576 列

Blackwell 引入了张量内存的概念,这是一块 128 行(lane)x 512 列 x 32 位的空间,张量核心可以直接读写。在 Vera Rubin 上,这块空间增加到 576 列,多出 32 KiB 张量内存可供使用。

请注意,这些额外的列只能通过 .exclusive 限定符访问。这是 PTX 9.4 新增的特性,用于确保一个 SM 上只有一份有效的张量内存分配。非独占分配仍然上限为 512,且必须是 2 的幂。

在 ThunderKittens 中,用户可以通过张量内存分配器的一个模板参数来请求这一点,表明该分配是独占的。

template<int _nblocks_per_sm, int _ncta, bool _managed = true, bool _exclusive = false&gt
struct tensor_allocator { .... }

tensor_allocator<1, C::CLUSTER_SIZE, false> tm; // Blackwell default: 512 columns
tensor_allocator<1, C::CLUSTER_SIZE, false, true>  tm; // Rubin: Up to 576 columns

3. 共享内存增加到 328 KiB

Hopper 和 Blackwell 提供 228 KiB 共享内存,而 Vera Rubin 引入了超大共享内存模式,可以动态增加到 328 KiB。这是主机端的设置,可以像下面这样调用。

CUfunction function = nullptr;
cudaGetFuncBySymbol(&function,reinterpret_cast<const void*>(kernel));
cuFuncSetAttribute(function, CU_FUNC_ATTRIBUTE_SHARED_MEMORY_MODE,
                   CU_SHARED_MEMORY_MODE_ALLOW_OVERSIZED_SHARED_MEMORY);

4. B 侧收集器

Blackwell 引入了收集器缓冲区(collector buffer)的概念,这是一个小型的 MMA 暂存缓冲区,可以锁存一个 A 分块,让下一条指令从这里取数,而不是从共享内存取。Vera Rubin 通过 .collector::b::* 把这一功能也扩展到了 B 分块。

示意图:两个 MMA 共用一个收集器缓冲区,B 只取一次(FILL)并被复用(LASTUSE),无需再次取数。

为了利用这一点,我们给每个 MMA 的操作数标注四种标签之一,用来描述它对收集器缓冲区的操作。

  • “FILL” 从共享内存读取操作数并将其锁存
  • “USE” 从缓冲区读取
  • “LASTUSE” 从缓冲区读取并释放它
  • “DISCARD” 为默认值,跳过锁存

这些标签是复用的许可限定符,而不是保证。也就是说,即使被允许复用,张量核心仍然可能重新加载矩阵。

既然任一操作数都可以驻留在收集器缓冲区中,我们就可以尝试新的模式。例如在 2x2 块上,两侧都收集,可以让 4 个 MMA 的操作数读取次数从 8 次降到只有 5 次。

示意图:2x2 MMA 块使用两侧收集器锁存,使 512 个端口周期与 512 个数学运算周期保持平衡。

在 ThunderKittens 中,我们可以通过下面的方式暴露这一功能。

mma2_ABt_chunk<64, false, false, collector::FILL,collector::DISCARD>(C[0][0], a0, b0, ...);
mma2_ABt_chunk<64, false, false, collector::LASTUSE, collector::FILL   >(C[0][1], a0, b1, ...);
mma2_ABt_chunk<64, false, false, collector::FILL,    collector::LASTUSE>(C[1][1], a1, b1, ...);
mma2_ABt_chunk<64, false, false, collector::LASTUSE, collector::DISCARD>(C[1][0], a1, b0, ...);

5. A 的提前释放

tcgen05.commit 会在其处理的 MMA 完成后到达(arrive)一个 mbarrier,通知生产者某个阶段槽位可以复用了。PTX 9.4 引入了 tcgen05.commit.sync_restrict::shared::read::mma::a,这条新指令允许我们为 A 分块把这一信号提前。我们不必等 MMA 完成,而是在 MMA 刚把 A 操作数从共享内存读完时就触发屏障,从而通知 TMA 加载器开始存入下一阶段的数据。

时序图:展示 A 提前释放屏障在 MMA 读完 A 之后、MMA 完全退役之前触发。

ThunderKittens 引入了一种新的提交(commit)类型,供用户表达这一点。

tensor_commit<2> (inputs_finished[stage], mask); // arrives when MMA retires
tensor_aread_commit<2>(A_finished[slot], mask); // arrives when MMA finishes reading A

构建 GEMM

现在我们有了新特性,也有了 Blackwell GEMM。接下来的部分将把这些特性逐步整合进现有的 Blackwell 内核,并解释迁移到 Vera Rubin 时为什么需要它们。

加宽指令:

最直观的瓶颈在于,我们仍在使用 Blackwell 的 32 字节 K 步长。在 Vera Rubin 上,这种编码的 ISA 上限约为 16.8 PFLOPs,而我们的 NVFP4 Blackwell GEMM 开箱即用就已达到 14.7 PFLOPs(上限的 88%)。要进一步提升,必须把 MMA 处理的 K 字节数翻倍。

图表:32 字节 K 步长的内核达到 14,741 TFLOPS,为其 16.8 PFLOPS 上限的 88%,而 64 字节的上限为 35 PFLOPS。

然而,我们只是为现有的 Blackwell 内核打开更宽的编码,就发现性能仅有小幅提升,而不是预期的 2 倍。更宽的 MMA 让张量核心消费操作数的速度翻倍,却无助于提高我们供给操作数的速度。要让 K 翻倍真正有用,我们需要从两方面入手,让核心保持忙碌:搬运更少的字节,以及加深流水线,让这些取数操作相互重叠。

读取更少的字节:

为了少取字节,我们在同一对 CTA 上沿 M 维度叠放第二个输出分块。由于两个累加器只在 M 上不同,它们可以共享同一块 B。我们最初的 Blackwell NVFP4 内核使用 1x1 分块格式,这意味着要覆盖 M512xN256 的输出,需要两个配对作业,各自独立传输一份 B。改用 2x1 格式后,我们只需取一次 B 就能覆盖同样的输出,从而减少了操作数流量。

示意图:2x1 分块布局,两个 M 分块(A0、A1)共享一块 B,生成 512x256 的累加器区域。

这种 2x1 分块格式在我们的 NVFP4 Blackwell 内核中不易实现,因为张量内存上限为 256 KiB。两个 M256xN256 的累加器就已占满 512 列,这意味着分块缩放(block-scaled)MMA 没有空间存放 A 和 B 的缩放因子。程序员虽然可以绕过这一限制,让后处理(epilogue)warp 只加载累加器的一部分列,再发出 MMA 空闲信号,使下一个 K 分块的 MMA 得以开始,但这会引入一小段无法隐藏的延迟。幸运的是,借助 Vera Rubin 多出的 64 列,我们无需再折腾这套把戏,就能存放缩放因子。

张量内存列示意图:两个累加器加上 A/B 缩放因子共用 Rubin 576 列中的 560 列,而 Blackwell 只有 512 列。

加深流水线:

改变分块格式减少了操作数流量,但并不能缩短每次取数的耗时。下一个挑战是让张量核心持续不断地获得数据。Vera Rubin 更大的共享内存让我们可以构建更深的流水线,提前暂存更多分块,给数据传输留出更多完成时间。我们对 NVFP4 和 FP8 的 16k 方阵 GEMM 扫描了环形缓冲区深度,结果如下。

NVFP4 16k 方阵 GEMM:

# Smem 分块阶段数 所需共享内存 实测 TFLOPS
3 202 KiB 17,054
4 258 KiB 20,595
5 314 KiB 22,239

FP8 (E4M3) 16k 方阵 GEMM:

配置 所需共享内存 实测 TFLOPS
4 209 KiB 10,895
5 257 KiB 11,995
6 305 KiB 11,288

虽然最大的收益出现在最后这一档,但我们注意到,这些收益依赖于前面的优化。下面是分别独立扫描 K 步长、共享内存流水线和分块策略的结果。

折线图“NVFP4 16,384 方阵 GEMM”:三种内核配置(2x1/1x1,64 字节/32 字节)下 TFLOPS 随每个 CTA 共享内存的变化。

最后的调优:

为了把内核进一步推向极致,我们还调整了最后几个旋钮

  1. 内核配置调优:我们针对不同的工作负载进一步调优内核,以获得最佳性能。在 CTA 配对尺寸方面,我们发现 Blackwell 内核最初的 1x1 分块格式在较小的方阵工作负载上性能最好。对于较大的形状,我们采用 2x1 的 CTA 配对分块,并将集群大小调为 2、4 或 8 个 CTA。此外,对所有形状,我们都会重排分块光栅化顺序,以改善内存局部性和性能。
  2. B 侧收集器:由于采用 2x1 分块格式,我们可以使用 B 侧收集器。在一个 MMA 上指定 “FILL”,在下一个 MMA 上指定 “LASTUSE”,就能把 B 的读取从两次减少到只有一次。我们实测这带来了约 1-3% 的提升。
  3. 利用 sync_restrict::shared::read::mma::a:对于更大的 64k 和 128k 方阵 NVFP4 GEMM,我们观察到 A 的提前释放分别带来了 13.5% 和 22.1% 的加速。我们发现,这条指令在较大尺寸下很有用:此时 A 分块要与其他资源争夺驻留空间,导致其行在两次复用之间被逐出,迫使加载器等待它们。有了这条指令,我们就能获得 A 提前复用的收益。在较小尺寸下,A 分块始终不会离开 L2,也就是说读取本来就足够快,不需要提前释放 A。为了使用这条指令,我们修改了传统的环形顺序逻辑。在普通 GEMM 中,A 和 B 分块属于同一个环,并在单次提交下步调一致地运行。但要让 A 的提前释放生效,我们需要把两种分块解耦,使 A 的加载可以独立进行。A 的提前释放要求 A 的槽位在更早的信号上被释放,所以我们给 A 配备了独立的环,以及独立的到达/完成屏障对。
  4. L2 逐出提示:我们给 A 操作数标记 EVICT_LAST,以促使它们驻留在 L2 中,供后续复用它们的作业使用。这里的复用收益来自作业之间,而不是集群内部,带来了零点几个百分点的提升。

结果:

柱状图“Vera Rubin 上的 NVFP4 GEMM”:ThunderKittens、cuBLASLt 和 CuTeDSL 在不同矩阵尺寸下的最终 TFLOPS。

柱状图“Vera Rubin 上的 FP8 GEMM”:ThunderKittens、cuBLASLt 和 CuTeDSL 在不同矩阵尺寸下的最终 TFLOPS。

请注意,以上所有测量都是在合格样品(Qualification Sample,QS)GPU 上使用 NVIDIA CUDA 13.4 得出的。我们预计,随着 Vera Rubin 软件的陆续发布,所有基线的性能都将继续提升。

结语:

希望其中一些内容对你有帮助,我们也很期待大家很快上手尝试。从 LUT GEMM、硬件原生的 megakernel,到新的引擎优化,还有很多有趣的内容可以分享。敬请期待!

Together AI 的内核与性能团队正在招聘!如果你想进一步了解这些内核,或者想与我们一起开发下一批更新,请联系 Simran 或 Dan!