跳到主内容
@wquguru
精选90PyTorch 博客(RSS)技巧与观点

PyTorch发布FlashAttention-4 MXFP8实现

Low Precision Flash Attention 4: End-to-End Block-Scaled Attention for Blackwell

原文
发到 X
推荐理由

面向底层算子开发者的硬核工程实践,详细拆解了Blackwell架构下低精度Attention的TMEM分配与PTX优化细节,可直接指导高性能Kernel开发。

TL;DR

TL;DR

We extend FlashAttention-4 [1] with MXFP8 forward and backward, reaching 2.85 PF/s forward and 2 PF/s backward on LLM shapes. On our internal shapes, FA4 MX8 reaches 2.54 PF/s forward and 1.58 PF/s backward, delivering up to 1.6× and 1.52× gains over BF16. We fuse quantization into the surrounding producers and develop an end-to-end zero-gather jagged module in which most activations and compute remain in FP8. This module is used internally at Meta for GEM training [2]. To our knowledge, this is one of the first SoTA implementations of MXFP8 FA4 forward and backward being used in production training workloads. We have open sourced the code in https://github.com/facebookresearch/ads_model_kernel_library/tree/main/lp_fa4

我们在 FlashAttention-4 [1] 的基础上扩展了 MXFP8 的前向和反向传播,在 LLM 形状下实现了 2.85 PF/s 的前向速度和 2 PF/s 的反向速度。在我们的内部形状上,FA4 MX8 达到了 2.54 PF/s 的前向速度和 1.58 PF/s 的反向速度,相比 BF16 分别提升了最高 1.6 倍和 1.52 倍。我们将量化融合到周围的 producer 中,并开发了一个端到端的零 gather jagged 模块,其中大部分激活值和计算保持在 FP8 格式。该模块已在 Meta 内部用于 GEM 训练 [2]。据我们所知,这是首批将 MXFP8 FA4 前向和反向传播投入生产训练工作负载的 SOTA 实现之一。我们已将代码开源至 https://github.com/facebookresearch/ads_model_kernel_library/tree/main/lp_fa4

1. Introduction

1. 引言

Blackwell’s tensor cores introduce block-scaled MMA instructions (tcgen05.mma.block_scale) that operate natively on microscaling formats — MXFP8, MXFP6, MXFP4 & NVFP4 — delivering 2-4x the throughput of BF16 MMA [4,5]. However, exploiting this in real training workloads requires more than just swapping the attention kernel’s data type. Scale factors must be managed in the already-saturated TMEM, quantization along the GEMM K-dimension must be handled for every operand (including online-computed intermediates like P and dS), and the overhead of converting between precisions must be overlapped so MMA can continue to execute at full speed.

Blackwell 的张量核心引入了块缩放 MMA 指令(tcgen05.mma.block_scale),这些指令原生支持微缩放格式——MXFP8、MXFP6、MXFP4 和 NVFP4——其吞吐量是 BF16 MMA 的 2-4 倍 [4,5]。然而,要在实际训练工作负载中利用这一点,仅仅替换注意力内核的数据类型是不够的。必须在已经饱和的 TMEM 中管理缩放因子,必须为每个操作数(包括在线计算的中间变量如 P 和 dS)处理沿 GEMM K 维度的量化,并且必须重叠精度转换的开销,以便 MMA 能够以全速继续执行。

In this work, we extend the FA4 attention kernel with end-to-end MXFP8 support for both forward and backward passes, and integrate it into a cross-attention module for Ads training with fused producer and output epilogues. The key contributions are: (1) TMEM allocation strategies that fit scale factors into fully-utilized 512-column TMEM with minimal new barriers, (2) online transpose invariant square block scale quantization of dS using Blackwell’s redux.sync.max.abs.f32 warp-wide reduction, (3) fused RMSNorm+Quantize and GEMM+Quantize kernels that eliminate quantization overhead by producing FP8 output with dual scale factor layouts in a single pass, and (4) a zero-gather jagged module where FP8 data stays at unpadded positions and only the much smaller scale factors are scattered, padded, and permuted to tensor-core friendly 128-aligned addresses for TMA.

在这项工作中,我们为 FA4 注意力内核扩展了对前向和后向传递的端到端 MXFP8 支持,并将其集成到一个带有融合 producer 和输出 epilogues 的广告训练交叉注意力模块中。主要贡献包括:(1) TMEM 分配策略,将缩放因子放入完全利用的 512 列 TMEM 中,同时引入最少的新的 barrier;(2) 使用 Blackwell 的 redux.sync.max.abs.f32 warp 级归约对 dS 进行在线转置不变方形块缩放量化;(3) 融合 RMSNorm+Quantize 和 GEMM+Quantize 内核,通过在单次传递中生成具有双缩放因子布局的 FP8 输出来消除量化开销;(4) 一个零 gather jagged 模块,其中 FP8 数据保留在未填充位置,而只有更小得多的缩放因子被分散、填充并置换为适合张量核心的 128 对齐地址以用于 TMA。

2. Implementation Details

2. 实现细节

2.1 Attention Forward

2.1 注意力前向传播

Attention forward consists of the following primary operations:

注意力前向传播包含以下主要操作:

S = Q @ K.T

S = Q @ K.T

P = Softmax(S)

P = Softmax(S)

O = P @ V

O = P @ V

To enable blockscaled MMA, we follow existing CuTe DSL examples from Quack GEMM kernels and CUTLASS C++ examples [5,6]. We use TMA loads to fetch scale factors from GMEM to SMEM, and copy SFs from SMEM to TMEM before triggering the UMMA. The primary challenge here is TMEM contention, which is explained in later.

为了启用分块缩放 MMA,我们遵循 Quack GEMM 内核和 CUTLASS C++ 示例中现有的 CuTe DSL 示例 [5,6]。我们使用 TMA 加载将比例因子从 GMEM 获取到 SMEM,并在触发 UMMA 之前将 SF 从 SMEM 复制到 TMEM。这里的主要挑战是 TMEM 争用,将在后文解释。

Currently, in the softmax warp, softmax computation happens in FP32, and then the results are converted to BF16 before the PV multiplication. We convert P to MXFP8 while also computing the scales. We deep dive into the PTX optimizations done to achieve this efficiently later.

目前,在 softmax warp 中,softmax 计算以 FP32 进行,然后在 PV 乘法之前将结果转换为 BF16。我们在计算比例因子的同时将 P 转换为 MXFP8。稍后我们将深入探讨为此实现高效性所进行的 PTX 优化。

One subtle thing to note is that for P.V blockscaled MMA to work, the scales need to be computed along the MMA K-dim. For Q and K, this is the embedding dimension (D) of attention, but for V, the scales and quantization need to be computed along the sequence dimension (N).

需要注意的一个微妙之处是,为了使 P.V 分块缩放 MMA 正常工作,比例因子需要沿 MMA K 维度进行计算。对于 Q 和 K,这是注意力机制的嵌入维度 (D),但对于 V,比例因子和量化需要沿序列维度 (N) 进行计算。

2.1.1 TMEM Allocation and barrier synchronization

2.1.1 TMEM 分配与屏障同步

Blackwell architecture has a fixed TMEM size of 512 column, which is completely utilized for MMA operands and accumulators in existing Blackwell FA kernels. This makes it challenging to add block scaled MMA, since scales also need to be in TMEM.

Blackwell 架构具有固定的 512 列 TMEM 大小,这在现有的 Blackwell FA 内核中被完全用于 MMA 操作数和累加器。这使得添加分块缩放 MMA 变得具有挑战性,因为比例因子也需要位于 TMEM 中。

FA4 forward uses a ping-pong computation between two Q tiles. We load two Q tiles, Q0 & Q1 of size [128, 128], and loop over K/V tiles (N dimension). The order of GEMMs is:

FA4 前向传播在两个 Q 图块之间使用乒乓计算。我们加载两个大小为 [128, 128] 的 Q 图块 Q0 和 Q1,并循环遍历 K/V 图块(N 维度)。GEMM 的顺序为:

GEMM Prologue S0 = Q0 @ K0 S1 = Q1 @ K0 Mainloop (for n in 0 .. N-1) O0 = P0_n * V_n S0 = Q0 * K_{n+1} O1 = P1_n * V_n S1 = Q1 * K_{n+1} Epilogue O0 = P0_N * V_N O1 = P1_N * V_N

GEMM 序言阶段 S0 = Q0 @ K0 S1 = Q1 @ K0 主循环 (for n in 0 .. N-1) O0 = P0_n * V_n S0 = Q0 * K_{n+1} O1 = P1_n * V_n S1 = Q1 * K_{n+1} 尾声阶段 O0 = P0_N * V_N O1 = P1_N * V_N

This is the TMEM Alloc:As you can see, TMEM is fully utilized with no spare room for SFs. Note that we cannot overlap input SFs with the accumulator TMEM. To solve this problem, we overlap the SF’s in the following way:

以下是 TMEM 分配:正如你所见,TMEM 被完全利用,没有多余空间用于 SF。请注意,我们不能将输入 SF 与累加器 TMEM 重叠。为了解决这个问题,我们以以下方式重叠 SF:

  • For the prologue S(i) GEMMs, we can use O(i) for S(i) SFs, as O(i) hasn’t started yet
  • SFs for O(i) can live overlapped with S(i) – this is the same strategy used by regular FA which overlaps P(i) with S(i). Thus, there already exists a barrier which ensures that S(i) TMEM is consumed, before we copy O(i) SFs. We just need to choose a region in S(i) distinct from P(i). Note that for FP8, P(i) is 32 columns, vs S(i) being F32 is 128 columns.
  • SFs for S(i) live overlap with S(1-i). This requires an additional barrier between the MMA and Softmax warps, since MMA is executed asynchronously, it is possible that there is a write-write conflict between S(1-i) accumulator and S(i) SFs. Hence, we add a barrier in MMA warp which waits before copying S(i) SFs, and arrives when S(1-i)’s accumulator is read by the softmax warp. Note that this barrier doesn’t usually add any extra cost, since there is an O(i) GEMM to overlap the TMEM->reg reads that need to precede the barrier. Typically, the GEMM takes longer than TMEM->reg read.
  • 对于序言阶段的 S(i) GEMM,我们可以使用 O(i) 来存储 S(i) 的 SF,因为 O(i) 尚未开始计算
  • O(i) 的 SF 可以与 S(i) 重叠——这与常规 FA 将 P(i) 与 S(i) 重叠的策略相同。因此,已经存在一个屏障,确保在我们复制 O(i) 的 SF 之前消耗掉 S(i) 的 TMEM。我们只需要在 S(i) 中选择一个与 P(i) 不同的区域。注意,对于 FP8,P(i) 占 32 列,而 S(i) 为 F32 时占 128 列。
  • S(i) 的 SFs 与 S(1-i) 存在重叠。这需要在 MMA 和 Softmax warp 之间增加一个额外的屏障,因为 MMA 是异步执行的,S(1-i) 累加器与 S(i) 的 SFs 之间可能存在写-写冲突。因此,我们在 MMA warp 中添加了一个屏障,该屏障在复制 S(i) SFs 之前等待,并在 softmax warp 读取 S(1-i) 的累加器时到达。请注意,这个屏障通常不会增加额外开销,因为有一个 O(i) GEMM 可以重叠必须在屏障之前进行的 TMEM->reg 读取操作。通常,GEMM 的执行时间比 TMEM->reg 读取更长。

Concretely, the GEMM execution order with SF placement is shown below:

具体而言,带有 SF 放置的 GEMM 执行顺序如下所示:

GEMMSF TMEM Region
Prologue
S0 = Q0 @ K0O0 (free, not started)
S1 = Q1 @ K0O1 (free, not started)
Mainloop (for n in 0 .. N-1)
O0 = P0_n * V_nS0 (S consumed, P distinct)
S0 = Q0 * K_{n+1}S1 (new barrier!)
O1 = P1_n * V_nO0 (existing barrier)
S1 = Q1 * K_{n+1}S0 (new barrier!)
Epilogue
O0 = P0_N * V_N
O1 = P1_N * V_N
GEMMSF TMEM 区域
序言阶段
S0 = Q0 @ K0O0 (空闲,未启动)
S1 = Q1 @ K0O1 (空闲,未启动)
主循环 (for n in 0 .. N-1)
O0 = P0_n * V_nS0 (S 被消耗,P 不同)
S0 = Q0 * K_{n+1}S1 (新屏障!)
O1 = P1_n * V_nO0 (现有屏障)
S1 = Q1 * K_{n+1}S0 (新屏障!)
尾声阶段
O0 = P0_N * V_N
O1 = P1_N * V_N

2.1.2 Improved unroll-KV

2.1.2 改进的 unroll-KV

As we move to lower precision, MMA throughput increases (2x for MXFP8, 4x for MXFP4 vs BF16), the softmax warp’s SFU-bound computation stays the same. This shifts the bottleneck: softmax bubbles that were hidden behind slow BF16 MMA now become exposed.

随着我们转向更低精度,MMA 吞吐量增加(MXFP8 为 BF16 的 2 倍,MXFP4 为 BF16 的 4 倍),softmax warp 的 SFU 绑定计算保持不变。这改变了瓶颈:原本隐藏在缓慢的 BF16 MMA 背后的 softmax 气泡现在暴露出来了。

In the persistent kernel, the tile boundary is particularly problematic. Without unroll-KV, the last two GEMMs of a tile are both PV, followed by both QK of the next tile. Softmax for the next tile’s Q0 can’t start until QK0 completes — but QK0 can’t start until all of tile(n)’s PV GEMMs finish. This creates a bubble at every tile boundary. Unroll-KV (inspired by GDPA) [3] interleaves the last PV of the current tile with the first QK of the next tile:

在持久化内核中,图块边界尤其成问题。如果没有 unroll-KV,一个图块的最后两次 GEMM 都是 PV,接着是下一个图块的 QK。下一个图块 Q0 的 Softmax 无法在 QK0 完成之前启动——但 QK0 必须等到 tile(n) 的所有 PV GEMM 完成后才能启动。这在每个图块边界处都会产生气泡。Unroll-KV(受 GDPA [3] 启发)将当前图块的最后一个 PV 与下一个图块的第一个 QK 交错排列:

Now softmax for Q0 triggers one GEMM earlier, hiding the tile boundary latency behind the PV pipeline.

现在,Q0 的 softmax 触发早了一个 GEMM,从而将图块边界延迟隐藏在 PV 流水线后面。

However, enabling this for BF16 caused a regression: the correction warp (which processes both stages sequentially) was delayed because PV1 was pushed later, which cascaded through the softmax_corr_empty barrier into the next softmax. We fixed this by moving the barrier wait to after the row-sum computation (which has no dependency on the barrier), decoupling the correction pipeline from the critical path.

然而,在 BF16 上启用此功能导致了性能回退:校正 warp(按顺序处理两个阶段)被延迟了,因为 PV1 被推后,这通过 softmax_corr_empty 屏障级联到了下一个 softmax。我们通过将屏障等待移动到行求和计算之后来解决这个问题(该行求和计算不依赖于屏障),从而将校正流水线与关键路径解耦。

2.1.3 Optimized Online MXFP8 conversion

2.1.3 优化的在线 MXFP8 转换

Since we want to do both QK and PV GEMMs using block-scaled attention, this necessitates an online conversion of the post-softmax output (P) from FP32 to MXFP8, instead of BF16. Applying blockscaling is a 3-step process. For a block x consisting of 32 elements:

由于我们希望使用块缩放注意力机制同时执行 QK 和 PV GEMM,这需要将后 softmax 输出(P)从 FP32 在线转换为 MXFP8,而不是 BF16。应用块缩放是一个三步过程。对于一个由 32 个元素组成的块 x:

Here, a is the amax for the block, and sigma is the scaling factor. In order to do this optimally in Blackwell, we make use of 3-instruction max, and fmul2 instructions (exposed in CuteDSL nvvm) for step (1) and (3) – which happen for every element. For computing the scale factor (Step 2) – we use an optimized PTX sequence which avoids log2 and division by extracting FP32 exponent and mantissa bits.

其中,a 是该块的 amax,sigma 是缩放因子。为了在 Blackwell 架构中最优地实现这一点,我们在步骤 (1) 和 (3)(每个元素都会发生)中使用了 3-instruction max 和 fmul2 指令(在 CuteDSL nvvm 中暴露)。为了计算缩放因子(步骤 2),我们使用了一个优化的 PTX 序列,通过提取 FP32 的指数和尾数位来避免 log2 和除法运算。

Additionally, we notice that computation of softmax already involves calculating row maxes for the 128 elements before the exponentiation. Since exp is a monotonically increasing function, we can re-use the max computed for softmax, thus preventing additional max operations while adding 1 exp operation per 32 elements. We find this approach to yield better performance.

此外,我们注意到 softmax 的计算已经涉及在指数运算之前计算 128 个元素的行最大值。由于 exp 是一个单调递增函数,我们可以重用为 softmax 计算得到的最大值,从而在不增加额外最大运算的情况下,每 32 个元素仅增加 1 次 exp 运算。我们发现这种方法能带来更好的性能。

更进一步:量化金融体系

看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力

进入量化体系 →

相似阅读

关联信息,但可能不是同一事件