|
| 1 | +"""Standalone stage1-only perf comparison: asm sparse decode kernel vs ATOM's |
| 2 | +gfx1250 gluon `pa_decode_sparse` stage1 (bf16). |
| 3 | +
|
| 4 | +- asm stage1 = the `..._sparse` .co kernel (fp8, its native dtype), timed alone. |
| 5 | +- pa stage1 = aiter `pa_decode_sparse(..., skip_reduce=True)` with kv_splits>1, |
| 6 | + which launches ONLY the split (stage1) gluon kernel (no reduce). |
| 7 | +
|
| 8 | +Only stage1 GPU device-time is compared. NOT bit-exact / not memory-fair: pa is |
| 9 | +bf16 while asm is fp8, and D=512 here (MLA QK is really 576-wide, so pa QK is |
| 10 | +~11% under-counted). Perf only. |
| 11 | +
|
| 12 | +Usage: |
| 13 | + ENABLE_CK=0 python op_tests/_stage1_bench_pa.py # default sweep |
| 14 | + ENABLE_CK=0 python op_tests/_stage1_bench_pa.py 128 2048 4 # one combo |
| 15 | +""" |
| 16 | +import sys |
| 17 | +sys.path.insert(0, "op_tests") |
| 18 | +sys.path.insert(0, "/home/amd/feifei/ATOM") |
| 19 | + |
| 20 | +import torch |
| 21 | +import test_mla_v4_kargpreld as T |
| 22 | +import aiter, aiter.mla |
| 23 | +from aiter import dtypes |
| 24 | +from aiter.test_common import run_perftest |
| 25 | +from aiter.ops.triton.attention.pa_decode_sparse import pa_decode_sparse |
| 26 | + |
| 27 | +Q = 1 |
| 28 | +SINK = True |
| 29 | + |
| 30 | +# Mirror test_mla_v4_kargpreld.py sweep grids + active kernel variants. |
| 31 | +_GQA_LIST = [64, 128] |
| 32 | +_CTX_LENS = [256, 512, 1024] |
| 33 | +_BATCH_SIZES = [64] |
| 34 | +_SPLITS = [1] |
| 35 | + |
| 36 | +# Perf iteration counts for run_perftest (mirrors test_mla_v4_kargpreld.py's |
| 37 | +# _PERF usage). Kept as module constants so both bench fns share them. |
| 38 | +_PERF = {"num_iters": 50, "num_warmup": 2} |
| 39 | + |
| 40 | + |
| 41 | +def bench_asm_stage1(batch, ctx, split, gqa): |
| 42 | + inp = T._build_bf16_inputs(batch=batch, kv_seq_lens=ctx, q_seq_logical=Q, |
| 43 | + seed=0, gqa_ratio=gqa, attn_sink=SINK) |
| 44 | + sm = 1.0 / (T._QUANT_D ** 0.5) |
| 45 | + qp, qr = T._native_to_2buff_for_asm(inp["q_bf16"]) |
| 46 | + kvp, kvr = T._native_to_2buff_for_asm(inp["kv_bf16"]) |
| 47 | + total_q = inp["q_bf16"].size(0) |
| 48 | + ns = inp["qo_indptr"].size(0) - 1 |
| 49 | + nh = T.NUM_KV_HEADS * gqa |
| 50 | + dev = "cuda" |
| 51 | + ob = torch.empty((total_q, gqa, T.V_HEAD_DIM), dtype=dtypes.bf16, device=dev) |
| 52 | + sidx = torch.tensor([i * split for i in range(ns + 1)], dtype=torch.int32, device=dev) |
| 53 | + lb = torch.empty((total_q, split, nh, T.V_HEAD_DIM), dtype=dtypes.fp32, device=dev) |
| 54 | + eb = torch.empty((total_q, split, nh, 1), dtype=dtypes.fp32, device=dev) |
| 55 | + kw = dict( |
| 56 | + q=qp, qrope=qr.contiguous(), kv_buffer=kvp, kvrope=kvr.contiguous(), output=ob, |
| 57 | + qo_indptr=inp["qo_indptr"], kv_indptr=inp["kv_indptr"], |
| 58 | + kv_page_indices=inp["kv_page_indices"], kv_last_page_lens=inp["kv_last_page_lens"], |
| 59 | + split_indptr=sidx, max_seqlen_q=inp["max_seqlen_q"], sink=inp["sink"], |
| 60 | + sm_scale=sm, out_16_nosplit=0, num_kv_splits=split, logits=lb, attn_lse=eb, |
| 61 | + ) |
| 62 | + # asm mla_decode_fwd_v4_nm launches stage1 (sparse) + stage2 (merge). Match |
| 63 | + # test_mla_v4_kargpreld.py's stage1_us: profile and keep ONLY the stage1 |
| 64 | + # "sparse" kernel's device time (drop stage2), so this column lines up with |
| 65 | + # the kargpreld summary table. |
| 66 | + s1_us, _s2_us = T._profile_stage_times( |
| 67 | + lambda: aiter.mla.mla_decode_fwd_v4_nm(**kw), |
| 68 | + iters=_PERF["num_iters"], |
| 69 | + warmup=_PERF["num_warmup"], |
| 70 | + ) |
| 71 | + return s1_us |
| 72 | + |
| 73 | + |
| 74 | +def bench_pa_stage1(batch, ctx, split, gqa): |
| 75 | + dev = "cuda" |
| 76 | + D = T.V_HEAD_DIM # 512 |
| 77 | + Tn = batch |
| 78 | + H = gqa |
| 79 | + q = torch.randn((Tn, H, D), dtype=torch.bfloat16, device=dev) |
| 80 | + total_pages = batch * ctx |
| 81 | + unified_kv = torch.randn((total_pages, D), dtype=torch.bfloat16, device=dev) * 0.1 |
| 82 | + kv_indices = torch.arange(total_pages, dtype=torch.int32, device=dev) |
| 83 | + kv_indptr = torch.arange(0, (Tn + 1) * ctx, ctx, dtype=torch.int32, device=dev) |
| 84 | + attn_sink = torch.randn((H,), dtype=torch.float32, device=dev) |
| 85 | + sm = 1.0 / (D ** 0.5) |
| 86 | + |
| 87 | + # skip_reduce=True + kv_splits>1 => only the stage1 (split) kernel runs. |
| 88 | + _, us = run_perftest( |
| 89 | + pa_decode_sparse, |
| 90 | + q, unified_kv, kv_indices, kv_indptr, attn_sink, sm, |
| 91 | + kv_splits=split, has_invalid=False, skip_reduce=True, |
| 92 | + num_iters=_PERF["num_iters"], |
| 93 | + num_warmup=_PERF["num_warmup"], |
| 94 | + ) |
| 95 | + return us |
| 96 | + |
| 97 | + |
| 98 | +import itertools |
| 99 | + |
| 100 | +print(f"{'gqa':>4} {'batch':>6} {'ctx':>6} {'split':>6} | {'asm_stage1_us':>14} {'pa_stage1_us':>13} {'pa/asm':>8}") |
| 101 | +print("-" * 74) |
| 102 | +# gqa outermost so rows mirror the kargpreld summary-table grouping. |
| 103 | +for gqa, batch, ctx, split in itertools.product(_GQA_LIST, _BATCH_SIZES, _CTX_LENS, _SPLITS): |
| 104 | + asm_us = bench_asm_stage1(batch, ctx, split, gqa) |
| 105 | + pa_us = bench_pa_stage1(batch, ctx, split, gqa) |
| 106 | + ratio = pa_us / asm_us if asm_us > 0 else float("nan") |
| 107 | + print(f"{gqa:>4} {batch:>6} {ctx:>6} {split:>6} | {asm_us:>14.2f} {pa_us:>13.2f} {ratio:>7.2f}x") |
0 commit comments