ITADN

[FA4] [head_dim=256] dV is incorrect when using non-contiguous q, k, v from a single qkv projection

#2541Openvladrfl 创建于 2026-05-05
V
vladrflcommented
Hey team, I am trying to use the recent release https://github.com/Dao-AILab/flash-attention/releases/tag/fa4-v4.0.0.beta11 that adds experimental support for head_dim=256 and hitting issues with the dV tensor being corrupted when q, k, v are non contiguous. This does not happen with head_dim=64 or 128. I am also using `torch.use_deterministic_algorithms(True)` to force nans instead of garbage data. ``` device NVIDIA B300 SXM6 AC head_dim=64 random contiguous: ok (128/128 tokens finite) three separate Linears: ok (128/128 tokens finite) fused QKV Linear, sliced views: ok (128/128 tokens finite) head_dim=128 random contiguous: ok (128/128 tokens finite) three separate Linears: ok (128/128 tokens finite) fused QKV Linear, sliced views: ok (128/128 tokens finite) head_dim=256 random contiguous: ok (128/128 tokens finite) three separate Linears: ok (128/128 tokens finite) fused QKV Linear, sliced views: NaN 32/128 tokens finite, finite_idx[:8]=[0, 4, 8, 12, 16, 20, 24, 28], nan_idx[:8]=[1, 2, 3, 5, 6, 7, 9, 10] ``` To reproduce: ```python import os os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") import torch import torch.nn as nn from flash_attn.cute.interface import flash_attn_varlen_func def _pkg_version(name): import importlib.metadata as md try: return md.version(name) except md.PackageNotFoundError: return "?" print(f"torch {torch.__version__} (cuda {torch.version.cuda})") print(f"flash_attn_4 {_pkg_version('flash_attn_4')}") print(f"cutlass_dsl {_pkg_version('nvidia_cutlass_dsl')}") print(f"device {torch.cuda.get_device_name(0)}") print() # to force nans torch.use_deterministic_algorithms(True, warn_only=False) device = torch.device("cuda:0") dtype = torch.bfloat16 n_tokens = 128 n_q_heads, n_kv_heads = 4, 2 seg_lens = [68, 60] # cu_seqlens=[0, 68, 128] cu_seqlens = torch.tensor( [0, *torch.cumsum(torch.tensor(seg_lens), 0).tolist()], dtype=torch.int32, device=device, ) max_seqlen = max(seg_lens) def fa4_backward_dv(q, k, v, head_dim): captured = {} v.register_hook(lambda g: captured.__setitem__("dv", g.detach().cpu())) out = flash_attn_varlen_func( q, k, v, cu_seqlens_q=cu_seqlens, cu_seqlens_k=cu_seqlens, max_seqlen_q=max_seqlen, max_seqlen_k=max_seqlen, softmax_scale=head_dim ** -0.5, causal=True, window_size=(-1, -1), num_splits=0, deterministic=False, ) if isinstance(out, (list, tuple)): out = out[0] out.sum().backward() return captured["dv"] def random_contig(d): q = torch.randn(n_tokens, n_q_heads, d, dtype=dtype, device=device, requires_grad=True) k = torch.randn(n_tokens, n_kv_heads, d, dtype=dtype, device=device, requires_grad=True) v = torch.randn(n_tokens, n_kv_heads, d, dtype=dtype, device=device, requires_grad=True) return fa4_backward_dv(q, k, v, d) def three_linears(d): hidden = n_q_heads * d x = torch.randn(n_tokens, hidden, dtype=dtype, device=device, requires_grad=True) proj_q = nn.Linear(hidden, n_q_heads * d, bias=False).to(device=device, dtype=dtype) proj_k = nn.Linear(hidden, n_kv_heads * d, bias=False).to(device=device, dtype=dtype) proj_v = nn.Linear(hidden, n_kv_heads * d, bias=False).to(device=device, dtype=dtype) q = proj_q(x).reshape(n_tokens, n_q_heads, d) k = proj_k(x).reshape(n_tokens, n_kv_heads, d) v = proj_v(x).reshape(n_tokens, n_kv_heads, d) return fa4_backward_dv(q, k, v, d) def fused_qkv(d): hidden = n_q_heads * d x = torch.randn(n_tokens, hidden, dtype=dtype, device=device, requires_grad=True) proj_qkv = nn.Linear( hidden, (n_q_heads + 2 * n_kv_heads) * d, bias=False, ).to(device=device, dtype=dtype) qkv = proj_qkv(x) q_end = n_q_heads * d k_end = q_end + n_kv_heads * d q = qkv[:, :q_end].reshape(n_tokens, n_q_heads, d) k = qkv[:, q_end:k_end].reshape(n_tokens, n_kv_heads, d) v = qkv[:, k_end:].reshape(n_tokens, n_kv_heads, d) return fa4_backward_dv(q, k, v, d) def summarize(label, dv): finite = torch.isfinite(dv).flatten(1).all(dim=-1) bad = (~finite).nonzero().flatten().tolist() if not bad: print(f" {label}: ok ({dv.shape[0]}/{dv.shape[0]} tokens finite)") return good = finite.nonzero().flatten().tolist() print( f" {label}: NaN {len(good)}/{dv.shape[0]} tokens finite, " f"finite_idx[:8]={good[:8]}, nan_idx[:8]={bad[:8]}" ) for d in (64, 128, 256): print(f"head_dim={d}") for fn, label in ( (random_contig, "random contiguous"), (three_linears, "three separate Linears"), (fused_qkv, "fused QKV Linear, sliced views"), ): torch.manual_seed(0) summarize(label, fn(d)) ``` The interesting pattern is that finite gradients can be seen only at positions that are %4==0: 0, 4, 8, 12, 16, 20, 24, 28 etc.
0 条评论