RDNA & WMMA
Companion to CDNA & MFMA. This page
covers the other AMD compute family: RDNA (consumer, pro and APU), the
WMMA matrix path that arrived with RDNA3, and how hipfire
0.4.1 uses it on the two release cards, Strix Halo (gfx1151)
and the Radeon AI PRO R9700 (gfx1201).
The consumer-silicon realities: wave32, GDDR6 or shared system RAM, 16×16×16 matrix tiles via WMMA, and a dispatch story that spans RDNA1 (no matrix unit at all) to RDNA4 (a new gfx12 WMMA layout on the same 16×16×16 tile).
01 The RDNA family at a glance
hipfire targets the whole RDNA family with one Rust
binary; feature predicates over the matrix path are the load-bearing
piece of its dispatch layer.
| example card | LLVM target | VGPRs / SIMD | Infinity Cache | matrix path |
|---|---|---|---|---|
| RX 5700 XT (RDNA1) | gfx1010 | 1024* | — | packed fp16 (no packed DOT, no WMMA) |
| RX 6900 XT (RDNA2) | gfx1030 | 1024 | 128 MiB | v_dot4 / v_dot2 (packed DOT, no WMMA) |
| RX 7900 XTX (RDNA3) | gfx1100 | 1536 | 96 MiB | WMMA (16×16×16, half16) |
| RX 7800 XT / RX 7600 | gfx1101 / gfx1102 | 1536 / 1024 | 64 / 32 MiB | WMMA (16×16×16, half16) |
| Radeon 8060S, Strix Halo (RDNA3.5) | gfx1151 | 1536 | 32 MiB | WMMA (16×16×16, half16) |
| Radeon AI PRO R9700 / RX 9070 XT (RDNA4) | gfx1201 | 1536 | 64 MiB | WMMA (16×16×16, half8 gfx12 layout) |
From AMD's ROCm GPU hardware specification tables (VGPR file per WGP
÷ 4 SIMDs ÷ 128 bytes per wave32 VGPR). *RDNA1 value from
the table's gfx1012 entry, the same generation. Every RDNA
part listed has 128 KiB of LDS per WGP; a single workgroup can use up to
64 KiB.
Two structural breaks worth memorizing:
- RDNA1/2 → RDNA3 is where the matrix unit
appears. Before
gfx1100, the fastest f16-mac path isv_dot2_f32_f16(a 2-wide packed-FMA) orv_dot4_i32_i8(dp4a). Aftergfx1100, you havev_wmma_*with a 16×16 accumulator per wave. - RDNA3 → RDNA4 keeps the wave32 WMMA shape and
the 16×16×16 tile, but changes the layout: A/B packing
halves from
half16_ttohalf8_tper lane, the builtin gains a_w32_gfx12suffix, and the C-map moves. LDS stays ≤64 KB per workgroup (≤128 KB per WGP) on both gens. Most kernels still need their own.gfx12.hipvariant — for the incompatible builtin/layout, not a wider tile.
02 CDNA vs RDNA — the divergence summary
| CDNA — Instinct MFMA path, data center | RDNA — Radeon / Ryzen AI WMMA path, consumer | |
|---|---|---|
| Wavefront | 64 threads | 32 threads |
| Matrix unit | MFMA (v_mfma_*) — 16×16×16 fp16, 32×32×8 bf16, FP8 on CDNA 3 | WMMA (v_wmma_*) — 16×16×16 fp16 on RDNA3 and RDNA4; gfx12 halves A/B packing to half8 with a _w32_gfx12 builtin |
| Memory | HBM (192 GB HBM3 on MI300X; 288 GB HBM3E on MI355X) | GDDR6 (8–32 GB) or shared LPDDR5X system memory (Strix Halo) |
| Cache | Large L2 per XCD; 256 MiB Infinity Cache on MI300X / MI355X | Small L2 + Infinity Cache (96 MiB on 7900 XTX; 64 MiB on R9700; 32 MiB on Strix Halo) |
| Graphics | None (no rasterizer, no RT, no display) | Full graphics + RT pipeline |
| Transistor budget | More CUs, bigger matrix engines, more HBM controllers | Display engines, rasterizer, RT cores compete with compute |
| Typical port | gfx908 / gfx90a / gfx942 / gfx950 | gfx1010 → gfx1201 |
Why this matters for code: the same .hip
file compiles for either family — but a CDNA kernel ported to
RDNA hits three landmines: wavefront width (64 → 32 halves the
lanes seen by __shfl_*), matrix intrinsic (v_mfma_*
doesn't exist on RDNA — you must call v_wmma_*),
and memory-tier sizing (96 MB Infinity Cache on the 7900 XTX changes your tiling
math vs HBM-direct).
03 WMMA — one instruction, one 16×16 tile
The WMMA family is RDNA3+'s answer to CDNA's MFMA. Same shape —
a fused D = A × B + C over a 16×16 tile
— but reachable from wave32 (half the lanes) and from a
fundamentally consumer-silicon caches.
The canonical RDNA3 builtin (used in hipfire's gemm_f16_wmma.hip):
// WMMA layout (gfx11 RDNA3): __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a, b, c)
// computes D = A x B + C over one 16x16 tile (ISA-defined lane layout).
//
// A is half16_t (16 fp16 values per lane)
// B is half16_t
// C / D are float8_t (8 fp32 accumulators per lane on wave32)
// (gfx12 RDNA4 keeps 16x16x16 but packs A/B as half8_t per lane, uses a
// _w32_gfx12 builtin, and remaps C — see “What gfx12 WMMA changes” below.)
typedef _Float16 __attribute__((ext_vector_type(16))) half16_t;
typedef float __attribute__((ext_vector_type(8))) float8_t;
__launch_bounds__(32, 2)
extern "C" __global__ void gemm_f16_wmma(
const _Float16* __restrict__ W, const float* __restrict__ X,
float* __restrict__ Y, int M, int K, int N)
{
// Grid: [ceil(M/16), ceil(N/16)], Block: [32]
float8_t acc = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
for (int k0 = 0; k0 < K; k0 += 16) {
half16_t a_reg, b_reg;
// ...load 16x16 tiles into a_reg and b_reg...
acc = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a_reg, b_reg, acc);
}
// ...store acc back to Y...
} - tile shape
- M=16, N=16, K=16
- inputs · output
- fp16 × fp16 → fp32 accumulator
- per issue
- 4096 FMA (8192 FLOPs) · 16-element dot product per output cell
- theoretical peak
- ~123 TFLOPs · 7900 XTX (gfx1100) theoretical model, not measured
- notes
- Training-class precision. Default WMMA path on RDNA3.
What WMMA gives you (RDNA3, gfx1100 / 1151):
-
One dense
v_wmma_f32_16x16x16_f16issue performs 16×16×16 = 4096 FMA (8192 FLOPs) over the tile. Throughput is a separate model: GPUOpen lists 512 FLOPS/clk/CU fp16 dense on the 7900 XTX, i.e. ≈ 123 TFLOPs theoretical at boost — a nameplate ceiling, not a measured hipfire number. The packed-dot2 path sits on a much lower model roof, qualitatively far below WMMA. -
Wave32 native —
_w32suffix on the builtin._w64WMMA builtins also exist on gfx11, but hipfire's primary path issues wave32. -
The accumulator (
float8_ton wave32,float16_ton wave64) lives in the VGPR file next to the A/B fragments and any staging, so WMMA GEMMs are register-hungry. A real 0.4.1 example: the gfx1201 HC-down tile uses 178 VGPRs per lane with no scratch. § 06 shows what that does to occupancy.
What gfx12 WMMA (RDNA4, gfx1200+) changes:
-
Same 16×16×16 tile, new layout: A/B pack as
half8_t(8 fp16 per lane; K splits by lane group), the builtin becomes__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12, and the C-map moves. Most existing gfx11 kernels need their own.gfx12.hipoverride for the incompatible builtin/layout — not a wider tile. RDNA4 WMMA also adds FP8 inputs (v_wmma_f32_16x16x16_fp8_fp8) and a 4-bit integer form with twice the K per issue (v_wmma_i32_16x16x32_iu4), which 0.4.1 issues for Flash-Next's expert GEMMs on the R9700 (§ 07). - gfx1100, gfx1151 and gfx1201 all have 1536 VGPRs per SIMD (see § 01 and § 06), so the gfx12 change is layout and data types, not register capacity.
04 The roofline — three matrix paths, one chip
Because RDNA3 ships both v_wmma_* and
v_dot2_f32_f16 (and the scalar fallback), it has
three different compute ceilings on the same chip.
Which one bounds you is decided by your kernel's matrix path, not by
the GPU's nameplate TFLOPs.
- Decode GEMV (batch=1) lives far left on the AI axis. Even with WMMA available, you can't feed a 16×16 matrix unit from a length-1 batch — you fall back to dot2/scalar and end up memory-bound on GDDR or system RAM. Decode is a memory-bandwidth problem on RDNA for the same reason it is on CDNA.
- Batched prefill (B=8) sits mid-AI (AI=32, still memory-bound in this model at 0.960×32≈31 TF). The WMMA kernel path is selected but the memory slope still binds.
- Wide prefill (B=32+) is where WMMA wins. At AI≥128 the intensity crosses the ridge (0.960×128≈123) and the WMMA roof is the only ceiling left.
This is the regime split that 0.4.1's routing follows (§ 07): batch size and arithmetic intensity decide which roof binds, not the card's nameplate alone. Nothing on this chart is a measured hipfire throughput.
05 Why CDNA “throws away” graphics and RDNA doesn't
The mirror image of the CDNA page's transistor-budget section:
- Rasterizers, ROPs, display engines, video encode/decode.
- Ray-tracing accelerators (since RDNA2).
- Mesh shaders, primitive shaders.
- Smaller, latency-optimized L0 / L1.
- Fewer CUs (96 on 7900 XTX vs 304 on MI300X — about 3×).
- No HBM (24 GB GDDR6 vs 192 GB HBM3 — about 8×).
- No FP8 / FP6 / FP4 MFMA — RDNA3 WMMA is fp16 / bf16 / i8 / i4 only; RDNA4 WMMA adds FP8 mixes.
- No XCD-level multi-die packaging — RDNA3 chiplets are MCDs (memory) not compute.
The result for an LLM workload: fewer CUs and much less memory bandwidth than an Instinct part, but the same kind of 16×16 matrix tile per wave, on a card that sits in a workstation or a laptop-class APU. Decode is bandwidth-bound on both; what RDNA changes is how much bandwidth and memory you have to work with.
06 The wave32 occupancy table
WMMA GEMMs are register-hungry: the 8-float accumulator alone costs 8 VGPRs per lane, and A/B fragments, rings and staging come on top. The count has to fit the wave32 occupancy curve — drag the slider:
VGPR-capacity model for gfx1100 and
gfx1201: 1536 VGPRs per SIMD, wave32 allocation
granule 24, cap 16 waves/SIMD —
min(16, floor(1536 / (ceil(vgprs/24) × 24))).
LDS, thread/workgroup, and barrier limits are explicitly excluded.
full lookup table
| VGPRs / thread (.vgpr_count) | gfx1100 waves / SIMD 1536 pool · granule 24 · cap 16 | gfx1201 waves / SIMD 1536 pool · granule 24 · cap 16 |
|---|---|---|
| ≤ 96 | 16 | 16 |
| 97–120 | 12 | 12 |
| 121–144 | 10 | 10 |
| 145–168 | 9 | 9 |
| 169–192 | 8 | 8 |
| 193–216 | 7 | 7 |
| 217–240 | 6 | 6 |
| 241–256 | 6 | 6 |
A worked 0.4.1 example and two rules of thumb:
- The gfx1201 HC-down tile (
qwen4_wmma_lds_160_80_32_80_k32_p_s4, five 32×80 waves) uses 178 VGPRs: by the model above, 8 waves per SIMD. The bigger lever was LDS: the kernel it replaced needed 24,576 B per workgroup, which capped a WGP at five workgroups; the new tile needs 15,360 B, so eight fit in a WGP's 128 KiB. Same accumulation order, byte-identical output. - High theoretical occupancy + low VALUBusy = memory-bound. More occupancy won't help; you need more in-flight memory transactions per wave (interleave, splits, prefetch).
.private_segment_fixed_size: 0is the reliable “no spills” indicator from the AMDGPU note section — thevgpr_spill_countfield is sometimes elided by the toolchain when zero.
Reading those numbers out of a compiled .hsaco (from the gfx-kernel-metadata skill):
ARCH=gfx1100
# 1. Unbundle the offload container into a real ELF
/opt/rocm/llvm/bin/clang-offload-bundler --type=o --unbundle \
--input=kernel.hsaco --output=/tmp/kernel.elf \
--targets=hipv4-amdgcn-amd-amdhsa--$ARCH
# 2. Read AMDGPU notes
/opt/rocm/llvm/bin/llvm-readelf --notes /tmp/kernel.elf
# → .vgpr_count, .group_segment_fixed_size, .private_segment_fixed_size,
# .wavefront_size: 32 (RDNA) / 64 (CDNA) 07 How hipfire 0.4.1 uses WMMA
The § 04 regime split shows up directly in 0.4.1's routing: decode and few-row verify stay on GEMV-style kernels, and wide prefill chunks go to WMMA. For Flash-Next on the two release cards the line is drawn at 512 rows per prefill chunk; the chunk itself is 8,192 rows on Strix Halo and 4,096 on the R9700.
| Flash-Next prefill route | arch | WMMA form | admitted when | exactness |
|---|---|---|---|---|
| Symmetric IU4 MoE experts with A4 activations | gfx1151gfx1201 | v_wmma_i32_16x16x16_iu4v_wmma_i32_16x16x32_iu4 | chunks ≥ 512 rows, every expert header on the symmetric grid | KLD-gated |
| Gathered QSA attention (F16 K, block-transposed V) | gfx1151, gfx1201 | F16 → F32 WMMA | chunks ≥ 512 rows not taken by the dense full-window route | KLD-gated |
| MQ6 trunk projections (X staged in LDS) | gfx1201, gfx1151 | F16 → F32 WMMA, ascending-K chain | gfx1201: chunks ≥ 512 rows; gfx1151: per-shape tile table | byte-exact |
| HC GEMMs and BF16 projections | gfx1201 | gfx12 F16 WMMA | prefill | KLD-gated |
The IU4 route is the most RDNA-specific of these: once the weights sit on a symmetric 4-bit grid and the activations are quantized to 4 bits, an expert GEMM is an integer WMMA with one scale at the end (MagnumQuant § 04). On gfx12 the IU4 instruction takes K = 32 per issue instead of 16. Those expert kernels, the gathered QSA attention and the MQ6 trunk GEMMs are built with PeaceMaker.
Outside Flash-Next, the dense HFQ4 Q/K/V GEMM on RDNA3 has one more WMMA arm: from 128 rows, an MMQ path quantizes activations to Q8_1 and runs integer WMMA, ahead of the F16 WMMA kernel (§ 09).
What it buys you
08 Memory hierarchy — RDNA edition
Same layered story as CDNA, with consumer-silicon capacities. For decode the tier that matters is the bottom one: every active weight is read once per token, so the rate at which weights can be streamed sets the decode ceiling.
| tier | scope | RX 7900 XTX | R9700 | Strix Halo (8060S) |
|---|---|---|---|---|
| VGPRs | per SIMD | 192 KiB | 192 KiB | 192 KiB |
| LDS | per WGP | 128 KiB | 128 KiB | 128 KiB |
| L0 vector cache | per CU | 32 KiB | 32 KiB | 32 KiB |
| L2 | device | 6 MiB | 8 MiB | 2 MiB |
| Infinity Cache | device | 96 MiB | 64 MiB | 32 MiB |
| DRAM | device | 24 GB GDDR6 960 GB/s | 32 GB GDDR6 640 GB/s | shared LPDDR5X 256 GB/s |
Cache and register capacities from AMD's ROCm GPU specification tables; DRAM size and peak bandwidth from AMD's product pages.
The standard rule still holds: every step down the ladder is much slower, so a kernel that touches the same byte twice should get the second touch from registers or LDS, not DRAM. That is what the PM MQ6 twin on the PeaceMaker page does: it stages activations in LDS and keeps B fragments in an 8-deep register ring. A large MoE like Flash-Next (a 125 GB weights file) does not fit in any cache, or even in a discrete card's VRAM: every prefill chunk re-streams the routed experts, and on the R9700 part of the experts live in host memory.
09 Dispatch — fast paths first, baseline last
rdna-compute's Gpu methods are the
kernel-selection hot path in hipfire: GEMM, GEMV, attention, norm and
MoE calls route through them. The shape is always the same:
arch-feature predicates from arch_caps, fast paths first,
baseline last. Here is the real prefill (more than one row) cascade of
the dense HFQ4 Q/K/V GEMM in 0.4.1:
// gemm_qkv_hfq4g256, prefill (rows > 1), trimmed from crates/rdna-compute/src/gemm.rs.
// The CDNA3 rocBLAS and gfx906 wave64 arms that come first are covered on the CDNA page.
if self.arch_caps.should_use_mmq(rows) { // gfx11: rows >= 128 (gfx906: >= 8)
return mmq_set_prequant(...); // Q8_1 activations, integer WMMA
}
if self.arch_caps.has_hfq4_mmq() && m % 128 == 0 { // RDNA1.1 / RDNA2 (sdot4)
return self.gemm_qkv_hfq4g256_mmq(...);
}
if self.arch_caps.has_wmma_w32_gfx12() { // RDNA4: gfx1200 / 1201
return self.gemm_qkv_hfq4g256_wmma_gfx12(...);
}
if self.arch_caps.has_wmma_w32() { // RDNA3 / 3.5
return self.gemm_qkv_hfq4g256_wmma(...);
}
if self.arch_caps.has_dot2_f32_f16() { // gfx1011/1012/1030-1032
return self.gemm_qkv_hfq4g256_dot2(...);
}
self.gemm_qkv_hfq4g256_fp16(...) // gfx1010/1013: v_pk_fma_f16 Two rules from hipfire's architecture doc:
- Predicates are arch-feature checks, not inline
arch.starts_with(...)chains. New silicon = update one predicate, not 40 call sites. - No unreachable branches. When a new arch absorbs a
check that was matched by an older
|| starts_with("gfxN")clause, drop the redundant clause in the same diff.
The per-arch kernel-file naming convention follows the dispatch:
kernels/src/gemm_qkv_hfq4g256_wmma.hip # default WMMA (RDNA3)
kernels/src/gemm_qkv_hfq4g256_wmma.gfx12.hip # gfx12xx override (gfx12 WMMA layout)
kernels/src/gemv_hfq4g256.gfx1030.v4.hip # chip-specific versioned
kernels/src/gemv_hfq6g256_residual_wave64.hip # wave64 (CDNA fall-back)
Which source a module uses is decided in Rust (kernels.rs
includes the .gfx12.hip override where one exists) and
recorded in an exact source/flags registry;
scripts/compile-kernels.sh builds the per-arch kernel packs
from that registry, and the runtime verifies each installed object's
SHA-256 before loading it.
10 Summary — what to remember
- RDNA wave width is 32, not 64.
__shfl_*, occupancy math and the_w32WMMA builtins all key off this. - WMMA is the RDNA3+ matrix unit:
v_wmma_*over 16×16×16 tiles on RDNA3 / 3.5 (half16 A/B) and RDNA4 (half8 gfx12 layout, plus FP8 inputs and a K = 32 IU4 form).gfx1010has no packed DOT and no matrix unit; RDNA1.1 / RDNA2 addv_dot4_i32_i8/v_dot2_f32_f16. - 0.4.1 draws the WMMA line at 512-row prefill chunks for Flash-Next: IU4 experts with A4 activations, gathered F16 QSA attention, MQ6 trunk projections and gfx1201 HC GEMMs. Decode and verify stay on GEMV-style kernels.
- Dispatch is feature-gated (
should_use_mmq,has_hfq4_mmq,has_wmma_w32_gfx12,has_wmma_w32,has_dot2_f32_f16inarch_caps.rs), not arch-string-matched. - WMMA kernels are register- and LDS-hungry. gfx1100, gfx1151 and gfx1201 all have 1536 VGPRs per SIMD; the 0.4.1 gfx1201 HC-down tile (178 VGPRs, 15,360 B LDS) is a worked example of trading tile shape for occupancy.
- Infinity Cache sits between L2 and DRAM: 96 MiB on the 7900 XTX, 64 MiB on the R9700, 32 MiB on Strix Halo.
11 Primary sources
- AMD GPUOpen — WMMA on RDNA3: 16×16-only tiles,
…_16x16x16_…intrinsics, 512 FLOPS/clk/CU fp16 dense. - AMD GPUOpen — Using matrix core on RDNA4: 16×16 WMMA with gfx12 packing and
_w32_gfx12builtins. - AMD GPUOpen — Occupancy explained: 7900 XTX VGPR pool, wave slots, allocation granularity.
- AMD — ROCm GPU hardware specifications: CU counts, LDS, L2, Infinity Cache and VGPR file sizes in § 01 and § 08.
- AMD — Radeon RX 7900 XTX, Radeon AI PRO R9700, Ryzen AI Max+ 395: memory size and bandwidth.
- hipfire v0.4.1 —
crates/rdna-compute/src/arch_caps.rsandcrates/rdna-compute/src/gemm.rs: predicates and the dispatch cascade. - hipfire v0.4.1 — CHANGELOG (Flash-Next WMMA routes, the HC-down tile) and the release notes (all measured figures on this page).