Skip to content

[gfx1250] Add MXFP4 GEMM support to FlyDSL backend - #786

Open
aris134 wants to merge 5 commits into
devfrom
amartin/flydsl-mxfp4-gfx1250
Open

aris134 wants to merge 5 commits into
devfrom
amartin/flydsl-mxfp4-gfx1250

Conversation

@aris134

@aris134 aris134 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds MXFP4 dense GEMM support for gfx1250 as part of the FlyDSL GEMM backend for training. Preliminary support does not have activation/bias, which is intended for a follow-up PR.

The GEMM backend requires plain scales and data, and performs scale shuffles and swizzles internally. The scales are shuffled into iteration-major order. The operand matrices are pre-swizzled at the scale-block level before the GEMM to work with TDM loads.

Fixes AMD-ROCm-Internal/frameworks-internal#394

Pipeline structure

All operands stream through LDS with no K-dependent allocation; total LDS use is a constant 144 KB.

  • Data tiles — 3-stage pipeline. A and B each get NUM_STAGES = 3 LDS pages (BLOCK_M×16 words per page). Each K-loop iteration issues one TDM load into page j % 3 while the MMA consumes an earlier page, giving ~2 iterations of load/compute overlap. In steady state a K-tile is loaded by 4 waves (TDM_GROUP_WAVES): 2 split A's 256 rows into halves (TDM_WAVES), 2 split B's.
  • Scales — 2-stage, grouped pipeline. SCALE_STAGES = 2 LDS pages each hold SCALE_GROUP = 8 K-tiles of scales (16 KB per operand). The prologue loads groups 0–1; thereafter, every 8th iteration the tile-load also streams the next scale group into page g % 2, sharing that iteration's wait slot. On those scale-refill iterations the data load switches from 2×half-tile to 1×full-tile to free two waves for the scale groups.
  • Registers — double-buffered. frag_A/B/sA/sB are 2-deep; tile k+1 is read into set k%2 while gemm_step(k) runs, interleaving next-tile LDS reads with current WMMAs.
  • Epilogue — 2-pass C store. The output tile is written in two C_HALF_ROWS = 128-row halves, staged register→LDS→global (TDM) through the LDS region reused from the K-loop, serialized so half 1 reuses half 0's buffer.

Performance

https://github.com/AMD-ROCm-Internal/frameworks-internal/issues/394#issuecomment-6525965976

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Please list the changes introduced in this PR:

  • Change A
  • Change B

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@aris134 aris134 self-assigned this Oct 30, 2026
@aris134 aris134 added the ci-level 1 CI test level 1 label Oct 30, 2026
@aris134 aris134 changed the title Add gfx1250 MXFP4 GEMM support to FlyDSL backend [gfx1250] Add MXFP4 GEMM support to FlyDSL backend Nov 2, 2026

@alextmagro alextmagro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Aristotle, working on a review of the main loop now but a few integration comments to start.

Comment thread tests/pytorch/test_gemm_backends.py Outdated
return (K, M), (N, K)
if layout == "NT": # A not transposed, B transposed
return (K, M), (K, N)
if layout == "TT": # both transposed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need TT for anything? My understanding is that it is more efficient to swap the operands and do B@A and run a TT GEMM as an NN, then you get a row-major C instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. No, I don't actually think we need TT for anything here, so I dropped it in 126f1dd

Comment thread tests/pytorch/test_gemm_backends.py Outdated
def create_mxfp4_tensors(M, K, N, layout):
"""Create MXFP4 inputs and references dequantized from the buffers ``layout`` consumes.

MXFP4 rowwise and columnwise data are quantized separately (columnwise is the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment trim

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 126f1dd

buffer the GEMM reads: rowwise for a transposed operand, columnwise otherwise.
"""
# the MXFP4 quantizer takes BF16 input
A_bf16, B_bf16 = create_regular_tensors(M, K, N, layout, dtype=torch.bfloat16)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we limited to bf16 input? I think supporting fp32 and fp16 is important as well, especially since the test outputs all 3.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the bf16 requirement is a hard-coded constraint.

"MXFP4 quantization only supports BF16 input (got ", to_string(input.dtype()), ")");

Extending support would probably fit better with a follow up PR perhaps?



def _select_mxfp4_storage_for_layout(A, transa, B, transb):
"""Select TE MXFP4 data and scales for a layout.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment trim

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 126f1dd

B_words = as_words(B, N, k_words)

# physical chunks of this lane's logical chunks khalf and khalf + 2 (same for all its rows)
swz = (l16 // 4) % SWZ_CHUNKS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

116 is ambiguous here, can we use a const var name?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it is "L"16, that is a bit hard to read.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're right, this isn't obvious, so I renamed it to lane_mod16 in 126f1dd

@aris134
aris134 requested a review from alextmagro November 3, 2026 20:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-level 1 CI test level 1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants