qwen3-4b-blockdist / eval /scripts /flex_attn_test.py
hxia7's picture
Upload folder using huggingface_hub
fedf514 verified
Raw
History Blame Contribute Delete
4.07 kB
"""Operator-level validation of block attention under FlexAttention.
1) CORRECTNESS: flex block-attention output == reference SDPA-with-additive-block-mask (small T).
2) STABILITY: flex is deterministic across repeated runs.
3) SCALE: flex runs at long T (O(T) memory) where the eager T x T mask would need terabytes.
This validates the kernel/mask logic your serving stack (vLLM) needs, independent of the HF model.
"""
from __future__ import annotations
import argparse, torch
import torch.nn.functional as F
from torch.nn.attention.flex_attention import create_block_mask, flex_attention
NEG = torch.finfo(torch.float32).min
def make_block_ids(T, n_blocks, dev):
"""token 0..s = system(-1); then n_blocks equal blocks; last quarter = query(-2)."""
ids = torch.empty(T, dtype=torch.long, device=dev)
s = max(1, T // 20)
q = T - max(1, T // 4)
ids[:s] = -1
ids[q:] = -2
body = q - s
per = max(1, body // n_blocks)
for b in range(n_blocks):
ids[s + b*per : s + (b+1)*per] = b
ids[s + n_blocks*per : q] = n_blocks - 1
return ids
def block_mask_mod(bids):
def mm(b, h, qi, ki):
causal = ki <= qi
bq = bids[qi]; bk = bids[ki]
return causal & ((bk == -1) | (bq == bk) | (bq == -2))
return mm
def ref_sdpa(q, k, v, bids):
T = q.shape[-2]; dev = q.device
causal = torch.tril(torch.ones(T, T, dtype=torch.bool, device=dev))
bq = bids.view(T, 1); bk = bids.view(1, T)
allowed = (((bk == -1) | (bq == bk) | (bq == -2)) & causal).view(1, 1, T, T) # boolean mask (no NaN)
return F.scaled_dot_product_attention(q, k, v, attn_mask=allowed)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--H", type=int, default=8); ap.add_argument("--D", type=int, default=128)
ap.add_argument("--T", type=int, default=1024); ap.add_argument("--blocks", type=int, default=8)
ap.add_argument("--big-T", type=int, default=16384)
args = ap.parse_args()
dev = "cuda"; torch.manual_seed(0)
# (1) correctness at moderate T
T = args.T
q = torch.randn(1, args.H, T, args.D, device=dev, dtype=torch.bfloat16)
k = torch.randn(1, args.H, T, args.D, device=dev, dtype=torch.bfloat16)
v = torch.randn(1, args.H, T, args.D, device=dev, dtype=torch.bfloat16)
bids = make_block_ids(T, args.blocks, dev)
bm = create_block_mask(block_mask_mod(bids), B=None, H=None, Q_LEN=T, KV_LEN=T, device=dev)
flex = torch.compile(flex_attention)
o_flex = flex(q, k, v, block_mask=bm)
o_ref = ref_sdpa(q, k, v, bids)
d = (o_flex.float() - o_ref.float()).abs()
print(f"(1) CORRECTNESS T={T} H={args.H} D={args.D} blocks={args.blocks}")
print(f" max|Δ|={d.max():.4f} mean|Δ|={d.mean():.6f} (bf16 noise floor ~1e-2) -> "
f"{'MATCH' if d.max()<0.05 else 'MISMATCH'}")
# (2) determinism
o2 = flex(q, k, v, block_mask=bm)
print(f"(2) STABILITY flex run twice: max|Δ|={(o_flex.float()-o2.float()).abs().max():.6f} "
f"-> {'deterministic' if (o_flex.float()-o2.float()).abs().max()<1e-3 else 'nondeterministic'}")
# (3) scale — flex at big T (eager T x T mask would be H*T*T*2 bytes)
bt = args.big_T
qb = torch.randn(1, args.H, bt, args.D, device=dev, dtype=torch.bfloat16)
kb = torch.randn(1, args.H, bt, args.D, device=dev, dtype=torch.bfloat16)
vb = torch.randn(1, args.H, bt, args.D, device=dev, dtype=torch.bfloat16)
bidsb = make_block_ids(bt, args.blocks * 8, dev)
bmb = create_block_mask(block_mask_mod(bidsb), B=None, H=None, Q_LEN=bt, KV_LEN=bt, device=dev)
torch.cuda.reset_peak_memory_stats()
ob = flex(qb, kb, vb, block_mask=bmb); torch.cuda.synchronize()
peak = torch.cuda.max_memory_allocated() / 1e9
eager_mask_gb = args.H * bt * bt * 2 / 1e9
print(f"(3) SCALE flex at T={bt}: OK, peak mem {peak:.1f} GB, out finite={bool(torch.isfinite(ob).all())}")
print(f" (an eager [1,{args.H},{bt},{bt}] score/mask alone would be ~{eager_mask_gb:.0f} GB -> impossible)")
if __name__ == "__main__":
main()