注意力计算的性能瓶颈

标准注意力计算的核心瓶颈不在于计算量,而在于显存访问。注意力矩阵 QK^T 的形状为 [n, n],对于 n=8192 的序列,仅这个矩阵就需要256MB显存(FP32)。GPU的计算速度远超显存带宽——A100的计算能力为312 TFLOPS,而HBM带宽仅2TB/s。

这就是经典的"内存墙"问题:注意力计算大部分时间不是在算,而是在等数据搬运。

Flash Attention V1:分块计算的突破

Tri Dao在2022年提出的Flash Attention V1核心思想是:通过分块计算避免实例化完整的注意力矩阵

在线Softmax

Flash Attention的关键创新是在线Softmax算法。标准Softmax需要先遍历一次数据求最大值,再遍历一次计算指数和归一化。在线Softmax通过维护一个运行最大值和运行和,在单次遍历中完成Softmax计算:

def online_softmax(blocks):
    """在线Softmax:逐块更新"""
    m = float('-inf')  # 运行最大值
    l = 0.0            # 运行和
    result = None
    
    for block in blocks:
        block_max = block.max()
        # 更新全局最大值
        new_m = max(m, block_max)
        # 修正之前的累积值
        alpha = math.exp(m - new_m)
        beta = math.exp(block_max - new_m)
        
        l = alpha * l + beta * block.sum()
        
        if result is None:
            result = beta * block
        else:
            result = alpha * result + beta * block
        m = new_m
    
    return result / l

分块策略

将Q、K、V分别切分为块,每次只加载一小块到SRAM(GPU片上高速缓存)中计算。外层循环遍历K/V块,内层循环遍历Q块:

for each K_j, V_j block:
    load K_j, V_j to SRAM
    for each Q_i block:
        load Q_i to SRAM
        compute S = Q_i @ K_j^T
        update online softmax statistics
        compute partial O = P @ V_j
        write O_i back to HBM

这种策略使得HBM访问量从O(n²)降低到O(n²d/M),其中M是SRAM大小。对于典型配置,这相当于减少了10-20倍的显存访问。

Flash Attention V2:效率的进一步提升

V2在V1基础上做了几个关键优化:

减少非矩阵乘法运算

GPU上矩阵乘法(GEMM)的效率远高于其他运算(如rescale、softmax)。V2重新组织了计算顺序,将rescale操作推迟到最后,减少了中间的rescale次数。

并行化改进

V1主要沿序列维度并行化,V2增加了批次和头维度的并行化,使得在长序列场景下能更好地利用GPU的并行能力。

前向和后向的分配优化

V2重新分配了前向和后向传播中的工作负载,减少了线程块之间的同步开销。

Flash Attention V3:FP8与异步流水线

2024年发布的Flash Attention V3针对H100 GPU的FP8张量核心和异步执行特性进行了深度优化。

FP8支持

H100的FP8张量核心提供接近2倍于FP16的吞吐量。但FP8的动态范围有限,直接用FP8计算注意力会导致精度问题。V3的创新在于分块量化

def flash_attention_v3_fp8(Q, K, V):
    """FP8 Flash Attention"""
    for each Q_i, K_j, V_j block:
        # 为每个块计算独立的缩放因子
        q_scale = Q_i.abs().max()
        k_scale = K_j.abs().max()
        
        # 量化到FP8
        Q_fp8 = (Q_i / q_scale).to(fp8_e4m3)
        K_fp8 = (K_j / k_scale).to(fp8_e4m3)
        
        # FP8矩阵乘法(利用Tensor Core)
        S = Q_fp8 @ K_fp8.T  # FP8 GEMM
        S = S * (q_scale * k_scale)  # 反量化
        
        # Softmax在FP32下计算
        P = softmax(S / sqrt(d))
        
        # P @ V也用FP8
        p_scale = P.abs().max()
        P_fp8 = (P / p_scale).to(fp8_e4m3)
        V_fp8 = (V_j / v_scale).to(fp8_e4m3)
        
        O = P_fp8 @ V_fp8  # FP8 GEMM
        O = O * (p_scale * v_scale)  # 反量化

关键洞察是:QK^T和PV是注意力中计算量最大的两个GEMM,用FP8计算它们可以获得最大收益;而Softmax的计算量较小,在FP32下计算可以保证数值稳定性。

异步流水线(Warpgroup级异步)

H100引入了TMA(Tensor Memory Accelerator)和warpgroup级别的异步执行。V3利用这些特性实现了生产者-消费者流水线

  • 生产者warpgroup:负责从HBM加载数据到共享内存
  • 消费者warpgroup:负责从共享内存读取数据并计算

两者通过异步屏障同步,实现了计算和访存的完全重叠:

时间线:
生产者: [加载K0,V0] [加载K1,V1] [加载K2,V2] ...
消费者:            [计算Q@K0]   [计算Q@K1]   [计算Q@K2] ...
                    ↑ 计算和加载重叠

后传优化

V3的后向传播也进行了深度优化:

  • 使用了更高效的梯度计算策略
  • 减少了中间结果的存储
  • 利用了H100的异步内存拷贝特性

性能对比

在A100 GPU上(FP16):

版本前向 TFLOPS后向 TFLOPS理论峰值利用率
标准注意力3530~25%
Flash V1195165~60%
Flash V2235205~72%

在H100 GPU上:

版本精度前向 TFLOPS后向 TFLOPS理论峰值利用率
Flash V2FP16350300~45%
Flash V3FP16600520~77%
Flash V3FP81100950~72% (FP8峰值)

V3在FP8下达到了约1.5-2倍于V2的吞吐量,这在实际应用中意味着训练时间的显著缩短。

实践中的考量

变长序列

实际应用中,batch内序列长度不一。Flash Attention通过varlen接口处理变长序列——将所有序列拼接为一维,用cu_seqlens记录每条序列的边界。这避免了padding造成的计算浪费。

滑动窗口注意力

Flash Attention可以与滑动窗口注意力结合——只在局部窗口内计算注意力。这在长上下文场景下进一步降低了计算复杂度。

与RoPE的融合

现代Flash Attention实现将RoPE的计算融合到注意力kernel中,避免了额外的显存读写。这种"融合RoPE"在实际推理中可带来5-10%的端到端加速。

量化注意力的兼容

Flash Attention V3的FP8分块量化策略可以自然地与KV Cache量化结合——KV Cache以FP8存储,在注意力计算中直接使用FP8格式的K/V。

结语

Flash Attention从V1到V3的演进,展示了一个优秀算法设计如何逐步榨干硬件性能。V3的FP8支持和异步流水线使得注意力计算接近理论峰值,但仍有改进空间——如FP4量化、更细粒度的流水线、以及与MoE路由的融合。随着GPU架构的持续演进,Flash Attention的下一个版本将继续推动注意力计算的效率边界。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。