[Kernel][Perf] Add RDNA3 INT8 WMMA GEMM, 1.23-2.80x over torch - #1073
Open
vlluvia wants to merge 3 commits into
Open
[Kernel][Perf] Add RDNA3 INT8 WMMA GEMM, 1.23-2.80x over torch#1073vlluvia wants to merge 3 commits into
vlluvia wants to merge 3 commits into
Conversation
coderfeli
reviewed
Aug 27, 2026
| row = wave_m * (reg_m * WMMA_M) + WMMA_M * rm_val + lane16 | ||
| return _v16_load(buf_offset + _lds_elem(BLOCK_M, ROW_STRIDE_A, row, col)) | ||
|
|
||
| def _barrier(): |
Collaborator
There was a problem hiding this comment.
why inline asm here? we have swait hint. has_side_effects=True, could cause data hazard bug
Contributor
Author
There was a problem hiding this comment.
My mistake. Fixed by using rocdl.s_waitcnt(lgkmcnt=0) and gpu.barrier(). Thanks for catching it.
vlluvia
force-pushed
the
rdna3-int8-wmma-gemm
branch
from
August 27, 2026 12:25
a5d23f8 to
e5faff3
Compare
Implement an INT8 16x16x16 WMMA GEMM for gfx11, with i32/f32/bf16/f16 output and an optional fused per-row/per-column dequantisation epilogue. Tiles are chosen without a search by pick_tile, which rejects tiles taller than M and then takes the widest one whose grid still covers the device. Split-K accumulates atomically into the i32 output so shapes with a small M and a long K can fill a grid their output tiles alone cannot. On gfx1100 this sustains 90-93% of the WMMA issue ceiling on compute-bound shapes and is a median 1.39x over hipBLASLt, which is what torch._int_mm dispatches to. Co-authored-by: Cursor <cursoragent@cursor.com>
vlluvia
force-pushed
the
rdna3-int8-wmma-gemm
branch
from
August 27, 2026 12:31
e5faff3 to
2782595
Compare
Contributor
Author
|
/rerun-ci |
coderfeli
reviewed
Aug 28, 2026
| @@ -0,0 +1,358 @@ | |||
| #!/usr/bin/env python3 | |||
Contributor
Author
There was a problem hiding this comment.
I’ll remove these unnecessary scripts.
Keep the PR focused on the kernel, autotuner, and correctness coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an INT8 WMMA GEMM for RDNA3 (gfx11), built on
v_wmma_i32_16x16x16_iu8. Supportsi32,f32,bf16andf16output, plus an optional per-row/per-column dequantisation fused into the epilogue.On gfx1100 it sustains 90–93% of the hardware WMMA issue ceiling on compute-bound shapes. It delivers 1.23–2.00× over
torch._int_mmon 15 of 17 tested shapes, with up to 4.33× on skinny-M workloads, and a median 1.36× over Triton 3.8.Motivation
torch._int_mmalways routes to hipBLASLt regardless oftorch.backends.cuda.preferred_blas_library, which costs 25–75% against rocBLAS depending on the shape. rocBLAS is not a drop-in either: its INT8 throughput swings up to 3.0× with the operand layout. This gives FlyDSL a native INT8 GEMM that consumes the K-major layout quantised inference already produces, with no repacking and no tuning run.Changes
i32,f32,bf16andf16output, with optional fused row/column dequantisation.torch._int_mmand best rocBLAS, and 44 correctness tests.All files are new, nothing existing is touched, and no new third-party dependency is added.
Two things are worth calling out. Split-K lets several workgroups share one output tile and accumulate into it atomically, so a small M with a long K can still fill the device; integer adds stay exact under any order, so the result is bit-identical to the unsplit kernel, and a scaled epilogue is excluded because it would need the full sum first.
pick_tilerejects tiles taller than M — a 64-row tile on a 32-row problem makes the WMMA multiply 32 rows of padding — then takes the widest remaining tile whose grid still puts two workgroups on every processor, counting split-K because it multiplies the same grid.Performance
gfx1100, 96 CU, i32 output, median of five CUDA Graph rounds after 60 warmups, every result checked bit-exact against
torch._int_mmbefore timing. The table reports only the fastest rocBLAS result for each shape. Triton uses nativetl.dot(int8, int8) -> int32, the sameA[M,K]andB_T[N,K]inputs without repacking, and the fastest of 10 autotuned tile configurations.The ceiling is 102.5 TOP/s: RDNA3 retires one 16×16×16 INT8 WMMA per 32 wave32 cycles, and under a saturating GEMM the card holds 2085 MHz at 241 W without throttling.
Median 1.39× over
torch._int_mm, 1.36× over Triton, and 1.00× against best rocBLAS. Triton leads at 128³ and 256³ and is within measurement noise at 384³; FlyDSL wins the other 14 shapes, by up to 2.59×. Against rocBLAS, FlyDSL wins the skinny-M shapes by up to 1.52× because split-K and a matched tile fill a grid rocBLAS leaves short; rocBLAS wins the small squares and stays 2–4% ahead on 6144³ and 1024×8192×8192.The percentages below 90% are not tuning left on the table. Efficiency tracks how much work each workgroup has to amortise its prologue and epilogue over, and converges from below: 81.6% at 2048³, 90.4% at 4096³, 91.8% at 6144³, 92.7% at 8192³.
Nor is the config space: 234 configurations were built and timed at 4096³ and the default
pick_tilealready returns is the fastest of them. Register block, K depth and wave grid span 2.3% over 60 configs; swizzle group, LDS layout and stagger span 0.3% over 160, which is the informative one, since a memory-bound kernel would respond to them and this one does not. What remains is inside the main loop — one unrolled iteration is 64v_wmma_i32_16x16x16_iu8against 48ds_load_b128and 45s_waitcnt, at 146 VGPRs with no spills — and closing it means a deeper software pipeline in codegen rather than a tuning knob.Testing
tests/kernels/test_rdna3_int8_gemm.py, all passing on gfx1100scripts/bench_rdna3_int8_gemm.py --rocblas --torch-int-mm --checkBreaking Changes
None.