Flash Attention是LLM推理加速的核心技术之一,理解了它就知道为什么大模型推理越来越快。把学习笔记整理一下。

先搞清楚原理

Flash Attention的核心思想是通过分块计算减少GPU显存访问。传统Attention需要把完整的注意力矩阵存到HBM,而Flash Attention在SRAM中分块计算,避免频繁读写HBM。看下面这段代码:

import torch
import torch.nn.functional as F
import math

# 传统Attention
def standard_attention(Q, K, V):
    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(K.size(-1))
    attn = F.softmax(scores, dim=-1)
    return torch.matmul(attn, V)

# Flash Attention (PyTorch 2.0+)
batch, heads, seq_len, dim = 2, 8, 4096, 64
Q = torch.randn(batch, heads, seq_len, dim, device='cuda')
K = torch.randn(batch, heads, seq_len, dim, device='cuda')
V = torch.randn(batch, heads, seq_len, dim, device='cuda')

output = F.scaled_dot_product_attention(Q, K, V)
print(f'输出形状: {output.shape}')

实际怎么用

实际项目中直接用PyTorch 2.0+的SDPA接口,底层自动启用Flash Attention:

import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model=512, n_heads=8):
        super().__init__()
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, x):
        batch, seq_len, _ = x.shape
        Q = self.W_q(x).view(batch, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = self.W_k(x).view(batch, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        V = self.W_v(x).view(batch, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        attn = F.scaled_dot_product_attention(Q, K, V, is_causal=True)
        attn = attn.transpose(1, 2).contiguous().view(batch, seq_len, -1)
        return self.W_o(attn)

# KV Cache加速
class KVCache:
    def __init__(self, batch, heads, max_len, d_k):
        self.k = torch.zeros(batch, heads, max_len, d_k, device='cuda')
        self.v = torch.zeros(batch, heads, max_len, d_k, device='cuda')
        self.pos = 0
    def update(self, k, v):
        n = k.size(2)
        self.k[:,:,self.pos:self.pos+n] = k
        self.v[:,:,self.pos:self.pos+n] = v
        self.pos += n
        return self.k[:,:,:self.pos], self.v[:,:,:self.pos]

进阶一点的用法

用torch.compile进一步优化:

import torch
import torch.nn.functional as F

@torch.compile(mode='max-autotune')
def compiled_attention(Q, K, V):
    return F.scaled_dot_product_attention(Q, K, V, is_causal=True)

# 性能对比
import time
Q = torch.randn(2, 32, 8192, 128, device='cuda')
K = torch.randn(2, 32, 8192, 128, device='cuda')
V = torch.randn(2, 32, 8192, 128, device='cuda')

# warmup
for _ in range(3): compiled_attention(Q, K, V)
torch.cuda.synchronize()

start = time.time()
for _ in range(10): compiled_attention(Q, K, V)
torch.cuda.synchronize()
print(f'Flash Attention: {(time.time()-start)/10*1000:.1f}ms')

容易踩的坑

第一个是PyTorch版本太低不支持SDPA,需要2.0+。第二个是GPU架构不支持Flash Attention,需要Ampere及以上。第三个是mask和is_causal不能同时指定。

写在最后

Flash Attention的核心是分块计算减少显存访问。实际开发中直接用PyTorch SDPA接口,配合torch.compile和KV Cache可以最大化推理性能。

常见问题解答

Flash Attention能快多少?

相比标准Attention快2-4倍,显存占用减少5-20倍。长序列(4K+)提升更明显。

哪些GPU支持Flash Attention?

需要Ampere架构及以上:A100、A10、RTX 3090/4090等。V100不支持。

怎么确认Flash Attention已启用?

用torch.backends.cuda.sdp_kernel()上下文管理器强制指定,或检查日志中的kernel选择信息。

Flash Attention v1和v2区别?

v2优化了并行度,减少了非矩阵运算,比v1快约2倍。PyTorch 2.1+默认使用v2。