diff --git a/csrc/tests/gqa_decode_test.cu b/csrc/tests/gqa_decode_test.cu index a81caf0..0dfb910 100644 --- a/csrc/tests/gqa_decode_test.cu +++ b/csrc/tests/gqa_decode_test.cu @@ -10,6 +10,9 @@ nvcc -I csrc -arch=sm_89 -O3 \ #include #include #include "../kernels/gqa_decode_attn.cuh" +#ifndef ASTRAI_NO_MMA +#include "../kernels/gqa_decode_attn_mma.cuh" +#endif static double now_ms() { struct timeval tv; @@ -17,6 +20,80 @@ static double now_ms() { return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; } +// Split-K scratch (torch-free): the production launcher allocates these from +// torch; here we pass pre-allocated device buffers so the bench loop doesn't +// pay a cudaMalloc per iteration. Size for the maximum split count (32). +struct DecodeScratch { + float* o_part = nullptr; + float* ml_part = nullptr; +}; + +// Launch the production decode path (tensor-core head-packing MMA on sm_80+, +// scalar fallback otherwise), mirroring dispatch_decode() in gqa_decode_attn.cu. +#ifndef ASTRAI_NO_MMA +static bool decode_use_mma(const GQAParams& p) { + int G = p.q_head / p.kv_head; + return !p.use_mask && G > 1 && G <= 16; +} + +static int decode_num_splits(const GQAParams& p, int tiles_total) { + int sm_count = 0; + cudaDeviceGetAttribute(&sm_count, cudaDevAttrMultiProcessorCount, 0); + int base_blocks = p.kv_head * p.batch; + int desired = 2 * (sm_count > 0 ? sm_count : 64); + int n = (desired + base_blocks - 1) / base_blocks; + return max(1, min(n, min(tiles_total, 32))); +} + +template +static void launch_mma_decode(GQAParams& p, DecodeScratch& sc) { + constexpr int BR = 16, LD = HEAD_DIM; + int smem = (2 * BC * LD + BR * LD) * (int)sizeof(bf16); + int tiles_total = (p.kv_len + BC - 1) / BC; + int num_splits = decode_num_splits(p, tiles_total); + + if (num_splits <= 1) { + cudaFuncSetAttribute(gqa_decode_attn_mma_kernel, + cudaFuncAttributeMaxDynamicSharedMemorySize, smem); + gqa_decode_attn_mma_kernel + <<>>(p); + return; + } + cudaFuncSetAttribute(gqa_decode_attn_mma_splitk_kernel, + cudaFuncAttributeMaxDynamicSharedMemorySize, smem); + gqa_decode_attn_mma_splitk_kernel + <<>>( + p, sc.o_part, sc.ml_part, num_splits); + gqa_decode_combine_kernel<<>>( + sc.o_part, sc.ml_part, p.o, num_splits, p.head_dim); +} +#endif + +static void launch_scalar_decode(const GQAParams& p) { + int gs = p.q_head / p.kv_head; + size_t smem = DC_CHUNK * p.head_dim * sizeof(bf16); + gqa_decode_attn_kernel<<>>(p); +} + +template +static void dispatch_decode_t(GQAParams& p, DecodeScratch& sc) { +#ifndef ASTRAI_NO_MMA + if (decode_use_mma(p)) { launch_mma_decode(p, sc); return; } +#endif + (void)sc; + launch_scalar_decode(p); +} + +static void dispatch_decode(GQAParams& p, DecodeScratch& sc) { + switch (p.head_dim) { + case 32: dispatch_decode_t<32>(p, sc); break; + case 64: dispatch_decode_t<64>(p, sc); break; + case 128: dispatch_decode_t<128>(p, sc); break; + case 256: dispatch_decode_t<256>(p, sc); break; + default: printf("bench: unsupported D=%d\n", p.head_dim); + } +} + static void cpu_decode(const float* Q, const float* K, const float* V, const bool* mask, float* O, int B, int Hq, int Hk, int seq_len, int D) { @@ -54,6 +131,84 @@ static bf16 f2bf(float x) { return __float2bfloat16(x); } static float bf2f(bf16 x) { return __bfloat162float(x); } static float randf() { return (float)rand() / (float)RAND_MAX - 0.5f; } +// Warmed-up, CUDA-event timed sweep over the production decode MMA path. +// Decode (q_len==1) is memory-bound: the two matmuls are GEMV-shaped, so we +// report both effective K/V read bandwidth and the (small) attention FLOP/s. +// FLOP/s = 2 matmuls (q@K^T, P@V), each 2*B*Hq*kv*D flops. +// Bytes = K + V read = 2 * B*Hk*kv*D * sizeof(bf16). +static void bench() { + const int cfgs[][5] = { + {1, 32, 4, 512, 128}, // B,Hq,Hk,seq,D + {1, 32, 4, 1024, 128}, + {1, 32, 4, 2048, 128}, + {1, 32, 4, 4096, 128}, + {16, 32, 4, 2048, 128}, + {32, 32, 4, 1024, 128}, + }; + int n = sizeof(cfgs)/sizeof(cfgs[0]); + const int WARMUP = 10, ITERS = 100; + printf("\n===== DECODE BENCH (warmup=%d iters=%d) =====\n", WARMUP, ITERS); + printf("%-46s | %10s | %10s | %10s\n", + "config", "latency", "bandwidth", "throughput"); + printf("---------------------------------------------------------------" + "----------------------------\n"); + + for (int ci = 0; ci < n; ci++) { + int B=cfgs[ci][0], Hq=cfgs[ci][1], Hk=cfgs[ci][2]; + int sl=cfgs[ci][3], D=cfgs[ci][4]; + size_t nQ=(size_t)B*Hq*D, nKV=(size_t)B*Hk*sl*D; + + bf16 *dQ,*dK,*dV,*dO,*tmp; + cudaMalloc(&dQ,nQ*2); cudaMalloc(&dK,nKV*2); + cudaMalloc(&dV,nKV*2); cudaMalloc(&dO,nQ*2); + size_t big = nQ>nKV?nQ:nKV; tmp=new bf16[big]; + for (size_t i=0;i>>(p); + dispatch_decode(p, sc); cudaDeviceSynchronize(); double kms=now_ms()-t0; cudaError_t err=cudaGetLastError(); @@ -123,8 +277,10 @@ int main() { printf("kernel: %.3f ms max_err: %.6e\n\n",kms,max_err); cudaFree(dQ);cudaFree(dK);cudaFree(dV);cudaFree(dO);cudaFree(dMask); + cudaFree(sc.o_part);cudaFree(sc.ml_part); delete[]hQ;delete[]hK;delete[]hV;delete[]hMask;delete[]hOut;delete[]ref;delete[]tmp; } printf("All tests passed!\n"); + bench(); return 0; } diff --git a/csrc/tests/gqa_prefill_test.cu b/csrc/tests/gqa_prefill_test.cu index 3acebec..6d2a753 100644 --- a/csrc/tests/gqa_prefill_test.cu +++ b/csrc/tests/gqa_prefill_test.cu @@ -10,6 +10,9 @@ nvcc -I csrc -arch=sm_89 -O3 \ #include #include #include "../kernels/gqa_prefill_attn.cuh" +#ifndef ASTRAI_NO_MMA +#include "../kernels/gqa_prefill_attn_mma.cuh" +#endif static double now_ms() { struct timeval tv; @@ -17,6 +20,32 @@ static double now_ms() { return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; } +// Launch the production prefill path (tensor-core MMA on sm_80+, else the +// scalar fallback), mirroring dispatch_prefill() in gqa_prefill_attn.cu. +template +static void launch_prefill(GQAParams& p) { +#ifndef ASTRAI_NO_MMA + constexpr int WARPS = 4, BC = 16, BR = 16; + constexpr int MIN_BLOCKS = (HEAD_DIM <= 64) ? 6 : (HEAD_DIM <= 128) ? 3 : 2; + dim3 grid((p.q_len + BR * WARPS - 1) / (BR * WARPS), p.q_head, p.batch); + dim3 block(WARPS * 32, 1, 1); + gqa_prefill_attn_mma_kernel<<>>(p); +#else + constexpr int G = 8, ROWS = 32, P_BC = 32; + dim3 grid((p.q_len + ROWS - 1) / ROWS, p.q_head, p.batch); + dim3 block(G, ROWS, 1); + gqa_prefill_attn_kernel_t<<>>(p); +#endif +} + +static void dispatch_prefill(GQAParams& p) { + switch (p.head_dim) { + case 64: launch_prefill<64>(p); break; + case 128: launch_prefill<128>(p); break; + default: printf("bench: unsupported D=%d\n", p.head_dim); + } +} + static void cpu_attention(const float* Q, const float* K, const float* V, float* O, int B, int Hq, int Hk, int q_len, int kv_len, int D, int is_causal, int causal_off) { @@ -56,6 +85,78 @@ static __nv_bfloat16 f2bf(float x) { return __float2bfloat16(x); } static float bf2f(__nv_bfloat16 x) { return __bfloat162float(x); } static float randf() { return (float)rand() / (float)RAND_MAX - 0.5f; } +// Warmed-up, CUDA-event timed throughput sweep over the production MMA path. +// Reports per-call latency and effective tensor-core TFLOP/s (2 matmuls: +// QK^T and P@V, each 2*B*Hq*ql*kl*D flops; halved for causal). +static void bench() { + const int cfgs[][7] = { + {1,32,4,512,512,128,0}, + {1,32,4,1024,1024,128,0}, + {1,32,4,2048,2048,128,0}, + {1,32,4,2048,2048,128,1}, + {4,32,4,2048,2048,128,1}, + {1,32,4,4096,4096,128,1}, + }; + int n = sizeof(cfgs)/sizeof(cfgs[0]); + const int WARMUP = 10, ITERS = 50; + printf("\n===== PREFILL BENCH (warmup=%d iters=%d) =====\n", WARMUP, ITERS); + printf("%-46s | %10s | %10s | %10s\n", + "config", "latency", "bandwidth", "throughput"); + printf("---------------------------------------------------------------" + "----------------------------\n"); + + for (int ci = 0; ci < n; ci++) { + int B=cfgs[ci][0], Hq=cfgs[ci][1], Hk=cfgs[ci][2]; + int ql=cfgs[ci][3], kl=cfgs[ci][4], D=cfgs[ci][5], causal=cfgs[ci][6]; + size_t nQ=(size_t)B*Hq*ql*D, nKV=(size_t)B*Hk*kl*D; + + bf16 *dQ,*dK,*dV,*dO,*tmp; + cudaMalloc(&dQ,nQ*2); cudaMalloc(&dK,nKV*2); + cudaMalloc(&dV,nKV*2); cudaMalloc(&dO,nQ*2); + size_t big = nQ>nKV?nQ:nKV; tmp=new bf16[big]; + for (size_t i=0;i<<>>(p); break; - case 128: gqa_prefill_attn_kernel_t<128,G,ROWS,P_BC><<>>(p); break; - default: printf("unsupported D=%d\n",D); return 1; - } + dispatch_prefill(p); cudaDeviceSynchronize(); double kms=now_ms()-t0; cudaError_t err=cudaGetLastError(); @@ -128,5 +219,6 @@ int main() { delete[]hQ;delete[]hK;delete[]hV;delete[]hOut;delete[]ref;delete[]tmp; } printf("All tests passed!\n"); + bench(); return 0; }