hipfire
/learn · silicon · RDNA / WMMA

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.

interactive · matrix-path generations · what each arch adds
matrix unit arrives gfx12 layout + half8 pack RDNA1 gfx1010 scalar RDNA2 gfx1030 scalar dp4a (i8) RDNA3 gfx1100 scalar dp4a dot2 fp16 v_wmma_* 16×16 tile RDNA3.5 gfx1151 scalar dp4a dot2 fp16 v_wmma_* APU / iGPU RDNA4 gfx1201 scalar dp4a dot2 fp16 v_wmma_* 16×16 · half8 pack
RDNA3 (gfx1100) First gen with the WMMA matrix unit. 16×16×16 fp16 tile per issue, 4096 FMA (8192 FLOPs); ~123 TFLOPs theoretical peak on the 7900 XTX (model, not measured).
example card LLVM target VGPRs / SIMD Infinity Cache matrix path
RX 5700 XT (RDNA1)gfx10101024*—packed fp16 (no packed DOT, no WMMA)
RX 6900 XT (RDNA2)gfx10301024128 MiBv_dot4 / v_dot2 (packed DOT, no WMMA)
RX 7900 XTX (RDNA3)gfx1100153696 MiBWMMA (16×16×16, half16)
RX 7800 XT / RX 7600gfx1101 / gfx11021536 / 102464 / 32 MiBWMMA (16×16×16, half16)
Radeon 8060S, Strix Halo (RDNA3.5)gfx1151153632 MiBWMMA (16×16×16, half16)
Radeon AI PRO R9700 / RX 9070 XT (RDNA4)gfx1201153664 MiBWMMA (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:

  1. RDNA1/2 → RDNA3 is where the matrix unit appears. Before gfx1100, the fastest f16-mac path is v_dot2_f32_f16 (a 2-wide packed-FMA) or v_dot4_i32_i8 (dp4a). After gfx1100, you have v_wmma_* with a 16×16 accumulator per wave.
  2. RDNA3 → RDNA4 keeps the wave32 WMMA shape and the 16×16×16 tile, but changes the layout: A/B packing halves from half16_t to half8_t per lane, the builtin gains a _w32_gfx12 suffix, 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.hip variant — 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
Wavefront64 threads32 threads
Matrix unitMFMA (v_mfma_*) — 16×16×16 fp16, 32×32×8 bf16, FP8 on CDNA 3WMMA (v_wmma_*) — 16×16×16 fp16 on RDNA3 and RDNA4; gfx12 halves A/B packing to half8 with a _w32_gfx12 builtin
MemoryHBM (192 GB HBM3 on MI300X; 288 GB HBM3E on MI355X)GDDR6 (8–32 GB) or shared LPDDR5X system memory (Strix Halo)
CacheLarge L2 per XCD; 256 MiB Infinity Cache on MI300X / MI355XSmall L2 + Infinity Cache (96 MiB on 7900 XTX; 64 MiB on R9700; 32 MiB on Strix Halo)
GraphicsNone (no rasterizer, no RT, no display)Full graphics + RT pipeline
Transistor budgetMore CUs, bigger matrix engines, more HBM controllersDisplay engines, rasterizer, RT cores compete with compute
Typical portgfx908 / gfx90a / gfx942 / gfx950gfx1010 → 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...
}
interactive · v_wmma_f32_NxNxN_f16 · one issue, one tile
A · fp16 · 16×16
×
B · fp16 · 16×16
+
C · fp32 · accumulator
=
D · fp32 · result
wave32 lanes contributing:
1 issue · 4096 FMA (8192 FLOPs) per issue — ~123 TFLOPs theoretical peak, 7900 XTX (model)
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_f16 issue 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 — _w32 suffix on the builtin. _w64 WMMA builtins also exist on gfx11, but hipfire's primary path issues wave32.
  • The accumulator (float8_t on wave32, float16_t on 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.hip override 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.

interactive · roofline · 7900 XTX · illustrative theoretical model (nameplate roofs, not measured) · AI = 4·B pedagogical, 0.960 TB/s memory slope · drag the batch-size slider
arithmetic intensity (FLOPs / byte) → performance (TFLOPs/s) → GDDR6 roof · 0.960 TB/s dot2 roof · 41 TF (model) WMMA roof · 123 TF (model) B=1 · decode GEMV
B=1
arithmetic intensity ~4.0 FLOPs/byte
currently bound by GDDR bandwidth
model ceiling (not measured) ~4 TFLOPs/s
  • 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:

RDNA spends transistors on ( + )
  • Rasterizers, ROPs, display engines, video encode/decode.
  • Ray-tracing accelerators (since RDNA2).
  • Mesh shaders, primitive shaders.
  • Smaller, latency-optimized L0 / L1.
What it gives up vs CDNA ( − )
  • 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.

interactive · wave32 occupancy explorer
178
gfx1100 · 7900 XTX 1536 VGPRs / SIMD · granule 24
0481216
16 waves / SIMD
gfx1201 · R9700 1536 VGPRs / SIMD · granule 24
0481216
16 waves / SIMD
High VGPR use (~178 VGPRs/thread, alloc 192): 8/16 waves/SIMD — VGPR-capacity bound only; LDS, thread/workgroup, and barrier limits 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 1616
97–120 1212
121–1441010
145–1689 9
169–1928 8
193–2167 7
217–2406 6
241–2566 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: 0 is the reliable “no spills” indicator from the AMDGPU note section — the vgpr_spill_count field 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 routearchWMMA formadmitted whenexactness
Symmetric IU4 MoE experts with A4 activations gfx1151
gfx1201
v_wmma_i32_16x16x16_iu4
v_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

synthetic pp8192 prefill · 0.4.1 release defaults · medians of three fresh processes
2,072.9tok/s
Flash-Next, Strix Halo (gfx1151)
range 2,072.7–2,076.9
3,817tok/s
Flash-Next, R9700 (gfx1201)
range 3,801.6–3,819.9
5,166.1tok/s
Qwen3.8 27B MQ4, R9700
8K-token prompt; AR decode 41.06 tok/s on the same GPU
Compare decode on the same cards: Flash-Next greedy AR runs at 33.78 tok/s on Strix Halo and 34.92 tok/s on the R9700. A wide prefill chunk reuses every weight it reads across hundreds of rows, which is the regime where WMMA throughput, not bandwidth, sets the pace. The R9700 Flash-Next cells were measured on an earlier build of the 0.4.1 release cycle; the 27B cells are on the release build.

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.

tierscopeRX 7900 XTXR9700Strix Halo (8060S)
VGPRsper SIMD192 KiB192 KiB192 KiB
LDSper WGP128 KiB128 KiB128 KiB
L0 vector cacheper CU32 KiB32 KiB32 KiB
L2device6 MiB8 MiB2 MiB
Infinity Cachedevice96 MiB64 MiB32 MiB
DRAMdevice24 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:

interactive · dispatch tree · click an arch to see which kernel branch wins
gemm_qkv_hfq4g256(...)
should_use_mmq(rows) → MMQ: Q8_1 activations, integer WMMA gfx11 from 128 rows
has_hfq4_mmq → wave32 MMQ (packed int8 dot) gfx1011 / 1012 / 1030–1032
has_wmma_w32_gfx12 → F16 WMMA (gfx12 layout) gfx1200 / 1201
has_wmma_w32 → F16 WMMA (RDNA3) gfx1100–1103 / 1150–1152
has_dot2_f32_f16 → dot2 path RDNA1.1 / RDNA2 when MMQ can't take the shape
(default) → packed fp16 baseline gfx1010 / 1013 (no packed DOT)
gfx1100 takes the F16 WMMA (RDNA3) path below 128 rows; from 128 rows the MMQ arm (integer WMMA on Q8_1 activations) wins first.
// 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:

  1. Predicates are arch-feature checks, not inline arch.starts_with(...) chains. New silicon = update one predicate, not 40 call sites.
  2. 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 _w32 WMMA 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). gfx1010 has no packed DOT and no matrix unit; RDNA1.1 / RDNA2 add v_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_f16 in arch_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