FlashAttention-2的核心设计原理
FlashAttention-2是FlashAttention的升级版本,通过减少GPU上的非矩阵乘法运算量并优化并行策略,将注意力机制的计算效率提升到接近理论峰值。传统注意力计算需要O(n²)的显存空间存储完整的注意力矩阵,FlashAttention-2采用分块计算和在线softmax技术,将显存复杂度降至O(n),同时减少HBM高带宽存储器的读写次数。
FlashAttention-2相比第一版的主要改进包括:减少non-matmul运算占比从约50%降低至约25%,使计算更接近A100 GPU的理论峰值;在序列长度维度上进行并行,更好地利用GPU的SM资源;改善work partitioning,减少warp之间的同步和通信开销。大模型训练中注意力计算的显存瓶颈通过FlashAttention-2得到显著缓解。
FlashAttention-2与标准注意力的计算对比
标准注意力计算直接将Q、K、V矩阵相乘得到注意力权重矩阵,然后进行softmax归一化:
import torch
import torch.nn.functional as F
def standard_attention(q, k, v):
# q: [batch, heads, seq_len, d_k]
scores = torch.matmul(q, k.transpose(-2, -1)) / (q.size(-1) ** 0.5)
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, v)
return output
# 标准注意力需要实例化完整 [seq_len x seq_len] 注意力矩阵
# seq_len=8192, d_k=64, batch=2, heads=32 时约16GB显存
FlashAttention-2通过分块计算避免实例化完整注意力矩阵:
from flash_attn import flash_attn_func
def flash_attention_2(q, k, v):
# q, k, v: [batch, seq_len, heads, d_k]
output = flash_attn_func(q, k, v, causal=False)
return output
# FlashAttention-2 显存复杂度 O(seq_len) 而非 O(seq_len^2)
# 同样参数下显存仅需约0.5GB
FlashAttention-2在PyTorch中的安装与使用
FlashAttention-2的安装需要CUDA工具链支持,建议在NVIDIA A100/H100或RTX 30/40系列GPU上使用:
# 安装FlashAttention-2
pip install flash-attn --no-build-isolation
# 验证安装
import torch
from flash_attn import flash_attn_func, flash_attn_varlen_func
# 基础用法
batch_size = 4
seq_len = 4096
num_heads = 32
head_dim = 128
q = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
k = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
v = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
# 因果注意力用于自回归生成
output = flash_attn_func(q, k, v, causal=True)
print(f"Output shape: {output.shape}") # [4, 4096, 32, 128]
变长序列处理与flash_attn_varlen_func
在训练大语言模型时,一个batch中的序列长度往往不同。FlashAttention-2提供了flash_attn_varlen_func来高效处理变长序列,避免padding浪费计算资源:
from flash_attn import flash_attn_varlen_func
# batch中3条序列,长度分别为128, 256, 512
total_len = 128 + 256 + 512
q = torch.randn(total_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
k = torch.randn(total_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
v = torch.randn(total_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
# cu_seqlens标记每条序列的起始位置累积和
cu_seqlens = torch.tensor([0, 128, 384, 896], dtype=torch.int32, device='cuda')
output = flash_attn_varlen_func(
q, k, v,
cu_seqlens_q=cu_seqlens,
cu_seqlens_k=cu_seqlens,
max_seqlen_q=512,
max_seqlen_k=512,
causal=True
)
print(f"Output shape: {output.shape}") # [896, 32, 128]
在Hugging Face Transformers中集成FlashAttention-2
Hugging Face Transformers从4.33版本开始原生支持FlashAttention-2,只需在加载模型时指定attn_implementation参数:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
attn_implementation="flash_attention_2"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
inputs = tokenizer("FlashAttention-2加速训练", return_tensors="pt").to('cuda')
outputs = model(**inputs)
print(f"Logits shape: {outputs.logits.shape}")
性能基准测试对比
在不同序列长度下对比标准注意力和FlashAttention-2的显存占用与计算速度:
import torch
import time
from flash_attn import flash_attn_func
def benchmark(seq_len, batch_size=2, num_heads=32, head_dim=128):
q = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
k = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
v = torch.randn(batch_size, seq_len, num_heads, head_dim, device='cuda', dtype=torch.float16)
# 标准注意力
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
start = time.time()
for _ in range(100):
scores = torch.matmul(q.transpose(1,2), k.transpose(1,2).transpose(-2,-1)) / (head_dim**0.5)
attn = torch.softmax(scores, dim=-1)
out_std = torch.matmul(attn, v.transpose(1,2)).transpose(1,2)
torch.cuda.synchronize()
std_time = (time.time() - start) / 100
std_mem = torch.cuda.max_memory_allocated() / 1024**3
# FlashAttention-2
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
start = time.time()
for _ in range(100):
out_flash = flash_attn_func(q, k, v, causal=True)
torch.cuda.synchronize()
flash_time = (time.time() - start) / 100
flash_mem = torch.cuda.max_memory_allocated() / 1024**3
print(f"seq_len={seq_len}:")
print(f" Standard: {std_time*1000:.2f}ms, {std_mem:.2f}GB")
print(f" Flash: {flash_time*1000:.2f}ms, {flash_mem:.2f}GB")
print(f" Speedup: {std_time/flash_time:.2f}x, Memory saving: {std_mem/flash_mem:.2f}x")
for sl in [2048, 4096, 8192, 16384]:
benchmark(sl)
在A100 80GB GPU上的典型测试结果:序列长度16384时,FlashAttention-2相比标准注意力实现约2.3倍速度提升和约20倍显存节省。序列长度越长,FlashAttention-2的优势越明显。
FlashAttention-2反向传播与多GPU训练
FlashAttention-2在反向传播阶段采用recomputation策略,不保存中间注意力矩阵,而是在反向传播时重新计算。虽然增加了计算量,但大幅减少了显存占用,使更大的batch size和更长序列成为可能:
# FlashAttention-2反向传播自动使用recomputation
q.requires_grad_(True)
k.requires_grad_(True)
v.requires_grad_(True)
output = flash_attn_func(q, k, v, causal=True)
loss = output.sum()
loss.backward()
print(f"q grad: {q.grad.shape}")
print(f"k grad: {k.grad.shape}")
print(f"v grad: {v.grad.shape}")
在多GPU训练场景中,FlashAttention-2与张量并行配合使用时需要注意输入张量的layout,FlashAttention-2期望输入格式为[batch, seq_len, heads, head_dim]:
import torch.distributed as dist
def tensor_parallel_flash_attn(q, k, v, rank, world_size):
local_heads = num_heads // world_size
q_local = q[:, :, rank*local_heads:(rank+1)*local_heads, :].contiguous()
k_local = k[:, :, rank*local_heads:(rank+1)*local_heads, :].contiguous()
v_local = v[:, :, rank*local_heads:(rank+1)*local_heads, :].contiguous()
output_local = flash_attn_func(q_local, k_local, v_local, causal=True)
output_list = [torch.zeros_like(output_local) for _ in range(world_size)]
dist.all_gather(output_list, output_local)
return torch.cat(output_list, dim=2)
FlashAttention-2已成为训练大型语言模型的标准组件,LLaMA、Falcon等主流开源模型均已集成支持。在实际工程中,通过pip安装flash-attn包并在模型加载时指定attn_implementation参数即可获得显著的训练速度提升和显存节省,无需修改模型架构代码。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/da-mo-xing-flashattention2-zhu-yi-li-ji-zhi-you-hua-yu-gpu/