Skip to content

fx.gemm uses more VGPR than raw MFMA, and fails to compile with BufferCopy* atoms for fp8 M=64/N=16/K=128 #821

Description

@fsx950223

Summary

While optimizing kernels/pa_decode_tile.py's QK matmul (raw mfma_f32_16x16x32_fp8_fp8 intrinsics, M=64/N=16/K=128 fp8), I compared it against the equivalent computation expressed via the high-level fx.gemm + make_tiled_mma + make_tiled_copy_A/B/C API. Two issues showed up:

  1. fx.gemm uses more VGPR than the equivalent raw MFMA sequence for the same M/N/K/dtype (confirmed, modest but real: +2 VGPR / +8% in an isolated microbenchmark).
  2. fx.gemm's fragment/copy path fails to compile at all when paired with fx.rocdl.BufferCopy{32,64,128}b (the AMD buffer-descriptor copy atoms recommended in the docs for global memory access) for this fp8 M=64/N=16/K=128 shape — only fx.UniversalCopy32b (plain-pointer copy) worked.

Repro

Attached: gemm_vgpr_compare.py (self-contained, no other repo files needed beyond flydsl/torch).

FLYDSL_RUNTIME_ENABLE_CACHE=0 python3 gemm_vgpr_compare.py raw    # raw MFMA version
FLYDSL_RUNTIME_ENABLE_CACHE=0 python3 gemm_vgpr_compare.py gemm  # fx.gemm version

Both kernels do the same M=64,N=16,K=128 fp8 mfma_f32_16x16x32_fp8_fp8 GEMM with a 4-warp (NWARP,1,1) tiled_mma split (matching pa_decode_tile.py's own QK tiling).

Issue 1 — VGPR overhead

.amdhsa_next_free_vgpr from the dumped final ISA (FLYDSL_DUMP_IR=1):

Version VGPR
raw MFMA intrinsics 24
fx.gemm (with fx.UniversalCopy32b, since BufferCopy* doesn't compile — see below) 26

Reproducible across repeated runs. The gap is small in this toy kernel (no other loop-carried state), but in the real pa_decode_tile.py context, VGPR is the kernel's binding occupancy constraint (waves/SIMD = 512 // vgpr), so even a modest per-operand overhead compounds across a kernel with many such GEMM calls and loop-carried fragments — worth knowing about when choosing between the two APIs for register-pressure-sensitive kernels.

Issue 2 — BufferCopy* + fx.gemm fragments fail to compile

Swapping fx.UniversalCopy32b() for fx.rocdl.BufferCopy128b() in the gemm variant (same script, same shapes) hits a C++ assertion:

python3: include/flydsl/Dialect/Fly/Utils/LayoutUtils.h:791: std::pair<_FIter, _FIter> mlir::fly::detail::complementImpl(...): Assertion `(R == 1 || filteredStride.isStatic()) && "stride must be static for complement"' failed.

Switching to fx.rocdl.BufferCopy64b() (matching the fragment's 8-element/lane granularity) avoids the assertion but hits an MLIR legalization failure instead:

error: failed to legalize operation 'fly.copy_atom_call_ssa' that was explicitly marked illegal:
  %80 = "fly.copy_atom_call_ssa"(%55, %78) <{operandSegmentSizes = array<i32: 1, 1, 0, 0>}> :
    (!fly.copy_atom<!fly_rocdl.cdna3.buffer_copy<64>, 8>, !fly.memref<f8E4M3FNUZ, global, 8:1>) -> vector<8xi8>

fx.rocdl.BufferCopy32b() produces the same class of legalization failure (just with vector<4xi8> / buffer_copy<32>).

Only fx.UniversalCopy32b() (plain llvm.load-based copy, no buffer-resource descriptor) compiles successfully for this fp8 shape. Given the docs (docs/cute_layout_algebra_guide.md §6.2) present BufferCopy* as the recommended AMD-specific path for efficient global memory access, and examples/03-tiledMma.py's own working example uses BufferCopy32b successfully for fp32 — this looks like a gap specific to fp8 element types (1-byte) combined with make_tiled_copy_A/B + thr_mma.make_fragment_A/B for this particular MNK/warp-tiling combination, rather than BufferCopy* being broadly broken.

Environment

  • gfx942 (MI300X-class)
  • Repro'd via FLYDSL_DUMP_IR=1 FLYDSL_RUNTIME_ENABLE_CACHE=0

Context

Found while working on pa_decode_tile.py (a hand-tuned reference PA-decode kernel that intentionally uses raw MFMA intrinsics instead of fx.gemm for its QK/PV matmuls, for exactly this kind of register-pressure reason — this issue is filed to check whether the raw-intrinsic-over-fx.gemm choice there is also masking an underlying fx.gemm/BufferCopy compile gap worth fixing independently).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions