D1: Parameterize HEAD_DIM in FmhaKernel (64→512)

- Promote HEAD_DIM from module constant to constructor parameter
- FmhaKernel(head_dim=64, s_k=128, ...) — default 64 for regression
- All references to HEAD_DIM replaced with self.head_dim
- PV MMA tiler, V layout, softmax corr_tiles all parameterized
- TMEM budget warning when num_tmem_alloc_cols > 512
- New test: test_fmha_v3_stage_d1.py tests hd=64 (regression) and hd=512
- Stage C test preserved as-is for reference
This commit is contained in:
2026-05-23 03:19:52 +00:00
parent 3be9d6ed8c
commit ea9264a469
2 changed files with 477 additions and 2 deletions

View File

@@ -0,0 +1,78 @@
"""
FMHA v3 Stage D1: Parameterized HEAD_DIM (64 → 512).
Tests the FmhaKernel class from dsv4.kernels.attention.fmha with variable head_dim.
- HEAD_DIM=64: regression test (must match Stage C results)
- HEAD_DIM=512: DSV4 production config (TMEM budget is the key risk)
"""
import torch, math, sys
import cutlass.cute as cute
import cutlass.torch as ct
import cuda.bindings.driver as cuda
from dsv4.kernels.attention.fmha import FmhaKernel
def test_head_dim(hd, n_kv):
"""Test FMHA kernel at given head_dim and KV length."""
m = 128 # M tile is always 128
torch.manual_seed(42)
q = torch.randn(m, hd, 1, dtype=torch.bfloat16, device='cuda')
k = torch.randn(n_kv, hd, 1, dtype=torch.bfloat16, device='cuda')
v = torch.randn(n_kv, hd, dtype=torch.bfloat16, device='cuda')
v_kernel = v.unsqueeze(-1)
c = torch.zeros(m, hd, 1, dtype=torch.bfloat16, device='cuda')
# FP32 reference
qf = q[:, :, 0].float()
kf = k[:, :, 0].float()
scale = 1.0 / math.sqrt(hd)
attn = qf @ kf.T * scale
attn = torch.softmax(attn, dim=-1)
ref = attn @ v.float()
mQ = ct.from_dlpack(q).mark_layout_dynamic(leading_dim=ct.get_leading_dim(q))
mK = ct.from_dlpack(k).mark_layout_dynamic(leading_dim=ct.get_leading_dim(k))
mV = ct.from_dlpack(v_kernel).mark_layout_dynamic(leading_dim=ct.get_leading_dim(v_kernel))
mC = ct.from_dlpack(c).mark_layout_dynamic(leading_dim=ct.get_leading_dim(c))
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
kernel = FmhaKernel(head_dim=hd, s_k=n_kv)
print(f'hd={hd}, n={n_kv}: Compiling...', flush=True)
compiled = cute.compile(kernel, mQ, mK, mV, mC, stream)
compiled(mQ, mK, mV, mC, stream)
torch.cuda.synchronize()
out = c[:, :, 0].float()
cos = torch.nn.functional.cosine_similarity(
out.flatten().unsqueeze(0), ref.flatten().unsqueeze(0)
).item()
max_abs = (out - ref).abs().max().item()
status = "PASS" if cos >= 0.97 else "FAIL"
print(f'hd={hd}, n={n_kv}: cos {cos:.6f} max_abs {max_abs:.4f} {status}')
if cos < 0.97:
print(f' out[0,:4]={out[0,:4].tolist()}')
print(f' ref[0,:4]={ref[0,:4].tolist()}')
return cos
def test():
print("=== Stage D1: Parameterized HEAD_DIM ===\n")
# Regression: hd=64 must match Stage C results (cos ~0.973)
print("--- Regression: HEAD_DIM=64 ---")
cos64_128 = test_head_dim(64, 128)
cos64_256 = test_head_dim(64, 256)
# DSV4 production: hd=512
print("\n--- Production: HEAD_DIM=512 ---")
cos512_128 = test_head_dim(512, 128)
# Summary
print("\n=== Summary ===")
print(f"hd=64, n=128: cos={cos64_128:.6f} {'PASS' if cos64_128 >= 0.97 else 'FAIL'}")
print(f"hd=64, n=256: cos={cos64_256:.6f} {'PASS' if cos64_256 >= 0.97 else 'FAIL'}")
print(f"hd=512, n=128: cos={cos512_128:.6f} {'PASS' if cos512_128 >= 0.97 else 'FAIL'}")
if __name__ == '__main__':
test()