Repository navigation
feat(rocke): add gfx950 scaled MFMA GEMM examples, 32x32x64 tile - #13450
Conversation
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
There was a problem hiding this comment.
🟡 Changes recommended
Dispatch pins are ignored, malformed spec fields are normalized, and Python/C++ behavior diverges for long kernel names.
5 open findings
Lowering wrapper discards diagnostics and reports unknown error · New Native spec fields normalize malformed values instead of rejecting them · New Selector ignores algorithm and spec_id pins · New Whitespace and case normalization accepts malformed spec values · New Python and C++ builders disagree on generated name length limits · New
What changed in this PR
Adds native gfx950 32x32x64 scaled-MFMA GEMM support across Python/C++, dispatch, examples, documentation, and numerical/parity testing.
Changes:
- Adds all 25 ordered FP8/BF8/FP6/BF6/FP4 combinations with E8M0 scaling.
- Adds LLVM23/HIP lowering, dispatch/binding APIs, and native bindings.
- Adds examples, reference fixtures, goldens, parity tests, and GPU numerical coverage.
Paths below are relative to dnn-providers/hip-kernel-provider/rocke/platform/.
| File | Description |
|---|---|
tests/instances/test_gfx950_mxfp8_numeric.py |
Adds FP8/BF8 GPU tests. |
tests/instances/test_gfx950_mxfp6_numeric.py |
Adds FP6/BF6 and code-space tests. |
tests/instances/test_gfx950_mxfp4_numeric.py |
Adds FP4 GPU tests. |
tests/instances/test_gfx950_mxfp_examples.py |
Tests examples, fixtures, and dispatch. |
tests/instances/test_gfx950_mixed_scaled_gemm_numeric.py |
Tests mixed-format pairs and K64. |
tests/instances/parity/gfx950_block_scaled_gemm_emit.py |
Adds Python parity emitter. |
tests/instances/parity/gfx950_block_scaled_gemm_emit.c |
Adds native parity emitter. |
tests/instances/gfx950_scaled_numeric.py |
Adds GPU and ISA validation helpers. |
tests/instances/conftest.py |
Exposes the gfx950 numerical fixture. |
tests/core/test_mma_catalog_lookup.cpp |
Verifies native catalog layouts. |
tests/core/test_gfx950_scaled_mfma.py |
Tests contracts, parity, and rejection. |
tests/core/gfx950_scaled_mfma_llvm23.json |
Records LLVM23 golden hashes. |
python/rocke/instances/SUPPORT_MATRIX.md |
Documents gfx950 scaled GEMM support. |
python/rocke/instances/gfx950/block_scaled_gemm.py |
Implements the Python GEMM builder. |
python/rocke/instances/gfx1250/block_scaled_gemm.py |
Reorders imports. |
python/rocke/examples/gfx950/gemm/README.md |
Documents examples and storage contracts. |
python/rocke/examples/gfx950/gemm/mxfp8_gemm.py |
Adds the F8 example. |
python/rocke/examples/gfx950/gemm/mxfp6_gemm.py |
Adds the F6 example. |
python/rocke/examples/gfx950/gemm/mxfp4_gemm.py |
Adds the F4 example. |
python/rocke/examples/gfx950/gemm/mixed_scaled_gemm.py |
Adds the mixed-format example. |
python/rocke/examples/gfx950/gemm/FP6.md |
Documents FP6 storage. |
python/rocke/examples/gfx950/gemm/FP4_SCALE.md |
Documents FP4 and scale storage. |
python/rocke/examples/gfx950/gemm/block_scaled_gemm_verify.py |
Adds numerical execution and verification. |
python/rocke/examples/gfx950/gemm/_scaled_gemm_example.py |
Adds shared example CLI handling. |
python/rocke/examples/gfx950/gemm/__init__.py |
Initializes the example package. |
python/rocke/examples/gfx1250/gemm/mixed_scaled_gemm.py |
Reorders imports. |
python/rocke/examples/gfx1250/gemm/block_scaled_gemm_verify.py |
Uses shared reference utilities. |
python/rocke/examples/common/scaled_gemm_reference.py |
Centralizes low-bit reference fixtures. |
python/rocke/dispatch/gemm/block_scaled.py |
Adds selection, sweep, and binding APIs. |
python/rocke/dispatch/gemm/__init__.py |
Exports the new dispatch surface. |
python/rocke/core/verify.py |
Verifies scaled-MFMA contracts. |
python/rocke/core/lower_llvm.py |
Adds LLVM23 intrinsic lowering. |
python/rocke/core/lower_hip.py |
Adds HIP builtin lowering. |
python/rocke/core/isa/backend.py |
Routes scaled-MFMA operations. |
python/rocke/core/ir.py |
Supports scaled-MFMA IR construction. |
python/rocke/core/arch/target.py |
Adds scaled-MFMA lane mappings. |
python/rocke/core/arch/mfma_scale.py |
Defines formats, packing, and contracts. |
python/rocke/core/arch/data/intrinsic_arch_domain.llvm23.json |
Updates LLVM23 intrinsic evidence. |
python/rocke/core/arch/data/arch_specs.json |
Registers all 25 gfx950 atoms. |
dsl_docs/instances/index.md |
Links the new instance documentation. |
dsl_docs/instances/gfx950_block_scaled_gemm.md |
Documents the public GEMM contract. |
cpp/instances/gfx950/block_scaled_gemm.cpp |
Implements the native GEMM builder. |
cpp/include/rocke/mfma_scale_internal.h |
Adds native scaled-MFMA helpers. |
cpp/include/rocke/instance_gfx950_block_scaled_gemm.h |
Declares the native instance API. |
cpp/core/verify.cpp |
Verifies native scaled-MFMA operations. |
cpp/core/lower_llvm/mma.cpp |
Lowers scaled MFMA to LLVM23. |
cpp/core/lower_llvm/data.cpp |
Registers the intrinsic declaration. |
cpp/core/lower_llvm/core.cpp |
Emits override-only declarations. |
cpp/core/lower_hip/lower_hip_mma.cpp |
Lowers scaled MFMA to HIP. |
cpp/core/ir/ir_tile.cpp |
Adds native IR contract handling. |
cpp/core/arch/data.cpp |
Adds native catalog entries and layouts. |
cpp/bindings/rocke_engine.cpp |
Exposes native Python bindings. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Assessed the Copilot comments against head The gfx950 scaled GEMM builder intentionally remains outside production dispatch. This PR provides native scaled-MFMA instruction support, mirrored Python/C++ builders and lowerers, and untuned correctness examples that invoke the builder directly. The missing dispatch route is expected within that scope; adding a production selector is not required for this PR. Copilot's new comment follows the unconditional wording of Rule 7, which should distinguish production-selection changes from explicitly scoped instruction-support examples. The remaining comments were also checked:
Fresh validation: 36 focused host tests passed, covering direct example builds, CLI case expansion, dtype handling, diagnostics and long native names. Separate raw native-input checks for A concise documentation clarification would be: "This builder intentionally remains outside production GEMM dispatch. The focused examples invoke it directly to demonstrate native instruction support and validate correctness." Rule 7 should allow this explicit scope, and Rule 4 should exempt documented dtype spelling normalization. No remaining actionable defect was established in these Copilot comments. |
cgmillette
left a comment
There was a problem hiding this comment.
PR #13450 — add gfx950 scaled MFMA GEMM examples, 32x32x64 tile (tenpercent, AICK-2061) — 🟢 ACCEPT: solid; one test-portability nit
| Prior item | Status |
|---|---|
| Copilot (10-10): lowering wrapper drops diagnostics | 🟢 FIXED — _ex path keeps the error; thread resolved |
Copilot: selector ignores algorithm/spec_id pins |
🟢 N/A — dispatcher module removed |
| Copilot: Python/C++ name-length mismatch (Rule 5) | 🟢 FIXED — arena-owned names; 255/256/512-byte tests |
| Copilot ×2: dtype whitespace/case normalized (Rule 4) | 🟢 Declined, justified — develop's shared normalize_dtype does the same (verified) |
| Copilot (10-12): unreachable from dispatch (Rule 7) | 🟢 Declined — examples-only scope, same as the gfx1250 builder |
Copilot (10-14): omitted block_k defaulted (Rule 4) |
🟢 Declined, justified — 32 is the only legal value; validator rejects others |
| Bots / CI | 🟢 Green at head (TheRock Win + Linux, superbuild); merge clean, no new failures (ran) |
| # | Sev | file:line | Finding |
|---|---|---|---|
| N1 | 🟡 | tests/instances/differential/test_gfx950_scaled_mfma_emit.py:44 |
Hashes raw CLI stdout: fails on Windows (CRLF, ran); differential/ not installed, so CI misses it |
Utility: 🟢 Positive — native gfx950 scaled MFMA, mirrored engines, HW-validated examples.
Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good
|
let's address differential enablement in a follow-up |
TheRock Submodule Bump ActivityNewest first
|
…12441) ISSUE ID : AICK-2273 # summary ## What this branch is about rocke's emitter had no way to know whether an intrinsic it declares actually exists on the architecture it is lowering for. An unknown `llvm.amdgcn.*` declare is not a parse error, not a verifier error, and not a `clang -S` error — it survives every check rocke ran and fails much later, in the backend, as `Cannot select`. Worse, the same name can be present at one LLVM flavor and absent at another, and present on one arch and absent on its neighbour. #12269 put the measurement on develop: a generator that probes the toolchain, an emitted-IR validity gate that compiles *and links* every corpus case, and three committed columns of answers (one per LLVM flavor). Nothing read them. This branch is their first consumer, plus the fixes that building a consumer forced: 1. **A reader and a warning lane** — the lowerer asks the table whenever a kernel demands an intrinsic, on both backends. 2. **Removal of the implicit `gfx950` target default** everywhere it had spread: both engines, the per-family bindings and the C wrappers, so every lowering names the target it is actually for. This is what lets the lane mean anything. 3. **A corrected table and sturdier gates**: reading the columns as a consumer showed that one of them was wrong, and that the generator and the corpus gates each measured less than they reported. Plus a long series of methodology fixes, all of which draw one line: > **A non-answer must never be recorded as a negative.** > "We could not ask" is not "the answer is no". The lane is warn-only. Making it an error is a separate change (AICK-2274). The commit list includes commits that #12269 already merged by squash; the diff against `develop` is what this PR changes. --- ## 1. The warning lane **Reader** `python/rocke/core/arch/domain.py` — validates the envelope and raises on a malformed or wrong-schema column rather than defaulting every field (which would have presented a corrupt artifact as a complete absence of data and switched the lane off in silence — the one failure the lane exists to prevent). A column that exists but cannot be read raises too; only a *missing* file means "no data". **What it reads** — `python/rocke/core/arch/data/intrinsic_arch_domain.<flavor>.json`, schema `rocke.intrinsic_arch_domain/v1`, **171 keys × 7 wired arches = 1197 cells per column**: | Flavor | `ok` | `name_absent` | `arch_absent` | no-data | |---|---|---|---|---| | llvm20 | 535 | 294 | 194 | 174 | | llvm22 | 669 | 147 | 340 | 41 | | llvm23 | 740 | 21 | 391 | 45 | One column per flavor because **a host can only measure its own flavor** — flavor is a runtime choice (`ROCKE_LLVM_FLAVOR`), so the generator can never produce all three in one run, and a gate must skip the flavors its host cannot measure rather than mark them failing. The lowerer's declaration table has 27 more keys than the columns: the 26 declarations develop added for data prefetch, workgroup clusters and TDM (#12857), and `raw.ptr.buffer.load.f16` (#12710), have not been measured yet. An unmeasured key is no data, so the lane stays silent for those intrinsics until the columns are regenerated. That is a measurement on each of the three toolchains, and it is left to a follow-up. **Status vocabulary** splits answers from non-answers: - Answers: `ok`, `name_absent`, `arch_absent` - No data: `target_unsupported`, `toolchain_crash`, `toolchain_timeout`, `probe_error` Only `arch_absent` is negative (`is_negative`). Everything else is silence. `name_absent` is silent on purpose: it is a flavor-axis fact that holds for every target, and the validity gate already reports a reachable one with the compiler's own diagnostic. **Warning lane** — `_Lowerer._need()` consults the table and emits `ArchDomainWarning`. The default backend is the C++ engine, which returns finished IR without the Python lowerer running, so the same check also runs over its emitted `declare` lines; the declare → key mapping is exact because both engines emit the table's declares verbatim. Controlled by `ROCKE_ARCH_DOMAIN`: `warn` (default) / `off`; any other value **raises**, because every way of getting this variable wrong is somebody trying to switch the lane off, and silently defaulting to `warn` hands them the opposite of what they asked for. The value is read in `lower_kernel_via_backend`, which every lowering passes through (`lower_kernel_to_llvm`, `compile_kernel` and the per-family wrappers), so it raises for every kernel, entry point and backend rather than only for a kernel that happens to demand an intrinsic. The LLVM flavor is likewise resolved once per lowering and handed to each stage. `error` mode is planned (AICK-2274). **Gate** `tools/check_arch_domain.py` — wired into `run_all.py`. It lowers the 194-case corpus at all three committed flavors (pure Python; it needs no toolchain, so it rules on every flavor on any host) and compares the warnings with `EXPECTED_WARNINGS`, which only ever shrinks. The list holds nine entries: three intrinsics (`ds.read.tr16.b64`, `mfma.f32.16x16x32.bf16`, `mfma.f32.16x16x32.f16`) on gfx942 at each flavor. All nine are one defect in the parity harness rather than in a kernel: it builds the gfx950 3D attention kernel for two gfx942 cases. The validity gate lists the same two cases as `KNOWN_BAD`. Fixing the harness is left to the change that makes the lane binding, which needs the list empty. Each entry names the corpus cases allowed to demand it, so the list cannot widen in silence: a case an entry does not name is unexpected even when its intrinsic is already allowlisted, and an entry whose case stops demanding it, or no longer exists, is stale. A case that did not run (`--only`, `--arch`, `--flavor`, or an instance that declines the flavor) is judged neither way. The comparison is a pure function with a test per rule. ## 2. No implicit target default Two commits, in order: first a pure refactor making all 158 implicit lowering call sites name their arch explicitly; then removal of the default itself from both engines. Omitting the target now raises. This is what lets the warning lane mean anything — a kernel lowered at a default arch is a kernel whose arch nobody chose. The default had also spread beyond the lowerer, and the same series removes it from those places: the per-family pybind entry points (one shared `require_arch` replaces their per-family "usual target" fallbacks), the Python wrappers in `core/backend.py` that mirrored them, and the scalar-attention C wrappers below. ## 3. A corrected table and sturdier gates - **Generator 4 → 6, and one column corrected.** The generator built its probes from rocke's hand-written declaration table, which records `immarg` only where an author happened to add it, and passed the unmarked operands as variables where the compiler demands constants. llvm22 and llvm23 reject that in the verifier, so the generator's rescue recorded the true answer; llvm20 has no such check, died at ISel with `Cannot select` — which is also what a genuine negative looks like — and recorded `arch_absent`. Probes are now built from the declare `opt` resolves, so the signature comes from the LLVM being measured. 33 llvm20 cells move: 27 `arch_absent` → `ok` and 6 `arch_absent` → `toolchain_crash` (a crash is no data, so the lane stays silent). llvm22 and llvm23 move no status. Two more ways to bank a non-answer as an answer are closed: a failed name probe (`opt` crashed or timed out) was recorded as `name_absent` for every arch, and a partial `--only`/`--arch` sweep could be filed as a whole column. - **One meaning of "this flavor refuses the instance".** The arch-domain gate counted four gfx1250 instances that refuse an older LLVM as lowering errors — eight failures on every run of a clean tree — while the validity gate already treated them as unmeasurable. Both now import one predicate from `tools/_flavor_gate.py`, and a test asserts they hold the same object. - **Provenance.** The validity gate no longer trusts an `opt` from another install: an older `opt` rejecting a construct its parser predates reads as "rocke emitted invalid IR" when the IR is fine, so that lane is skipped on a mismatch. The generator's identity check covers `opt` as well as `clang`. - **The arch-domain gate lowers through the native lowerer.** Through the dispatching entry point, any host with the C++ engine built would lower every case without consulting the table, harvest nothing, and call every allowlist entry stale. ## 4. What testing this turned up **The scalar-attention C wrappers.** The three public `_lower_to_llvm` wrappers in `attention_unified_entry.cpp` are the only lowering entries whose signature carries no target, and nothing in the tree called them. They passed `NULL`, which the lowerer now rejects, so all three had turned into `ROCKE_ERR_VALUE`. They now name `gfx950` explicitly — a fixed target, not a fallback, and the header says so; a caller who wants another target builds with the `_new` entry and lowers through `rocke_lower_kernel_to_llvm_ex`. **A crash in the C++ scalar reduce builder.** Testing the wrappers found that `rocke_build_unified_attention_reduce_scalar` died with SIGSEGV on every input: its loops read three tensor descriptors that nothing had built. It now builds them, in the order the Python builder does, and emits the kernel the Python builder emits (byte-identical). A new ctest lowers each wrapper and requires text identical to lowering the same kernel through the target-taking entry. **Bindings that ignored their target.** A sweep over every per-family binding that takes a `(spec, arch)` found six that discarded `arch` (layernorm2d, elementwise and the scalar attention family's serialize/verify entry points), two fused-MoE e2e entry points that hard-coded gfx950 into the spec they built, and a ninth that kept a pybind default. All now call `require_arch` first. The sweep is a test now: an extension-gated check reads every `(spec, arch)` binding off the module and requires each to reject an empty target, so a binding added later is held to the same rule. **Bindings that arrived with develop.** Three stream-K wgrad host-plan bindings (`conv_wgrad_is_valid_spec`, `conv_wgrad_streamk_default_pool` and `conv_wgrad_streamk_host`, from #13151) resolved their target with `arch_or_default`, the helper this branch removed, and declared `arch` with a gfx950 default. `rocke_engine.cpp` stopped compiling, so the pybind extension could not be built; CI builds no extension, so nothing turned red. They now take the target like every other binding, and the sweep covers the two that take a spec (pybind prints `arch: str = 'gfx950'` for a defaulted argument, which is why the sweep could not see a default before). One decision to look at: develop's stream-K tests pinned "without an arch both engines default to gfx950" for the host planner. The Python planning helpers keep their default; the C++ binding now refuses an empty target, and the test asserts that. The same thing happened a second time with the gfx950 block-scaled GEMM bindings that arrived with #13450 (`gfx950_scaled_serialize_ir`, `gfx950_scaled_verify` and `gfx950_scaled_lower_llvm`: four uses of `arch_or_default`), and `rocke_engine.cpp` again stopped compiling without anything turning red. They call `require_arch` first now. Because the extension-gated sweep cannot run where no extension is built, a second test reads `cpp/bindings` and fails on either way a binding guesses its target: the removed `arch_or_default` helper, or a gfx target as the default of an `arch` argument. It lists every offending line, and it fails on the previous merge without the fix. It reads the source, so it runs from a source checkout and skips in an installed layout. ## 5. Review Every review thread has a fix, one commit per concern: - **Tracker IDs out of code** (Rule 1): four places cited IDs; each now names the defect. The allowlist's reason string was also wrong about the defect — it blamed two declaration-table items, but the nine warnings come from the parity harness — and now says so. - **Trust boundaries** (Rule 4): a malformed or wrong-schema column, an unreadable column and an unknown `ROCKE_ARCH_DOMAIN` value each raise, instead of reading as "no data" or defaulting to `warn`. - **One flavor per lowering**: with no flavor given, the C++ path and the arch-domain post-pass each resolved their own, and compiler discovery is retryable, so the IR could be checked against the wrong flavor's column. It is now resolved once in the dispatcher, including for both engines under `ROCKE_BACKEND=both`. - **Tests lower at the arch they are about**: three substitutions of an explicit `arch=` had put the wrong arch in, and `test_legacy_equals_gfx950`, which compared a call with itself, is replaced by `test_omitting_the_target_raises`. - **`develop` merged four times** since the first review (111 commits, then 99, 29 and 14). Only the first conflicted textually (four binding translation units and `test_compiler_detection.py`). The trouble in all four was semantic: develop's new code was written against the default target this PR removes, so it called the lowerer, or a binding, without naming one. Each occurrence is fixed here: the compiler-detection tests (three of the calls sit inside a subprocess script string, and they had turned the gfx94X CI shards red) and a binding-selection probe; the flag-attribute test that turned the hipkernelprovider jobs red (twelve subtests), a library gate test that CI does not run, and the FP64 elementwise and reduce tests; and the stream-K and block-scaled GEMM bindings in section 4. A scan of the Python and C++ that the merges brought in finds no other call that names no target. - **Formatting after the merge**: this branch's parenthesized `with` statements make black treat `test_rocke.py` as Python 3.9+, so develop's three multi-line `with a, b, c:` blocks in that file now need reformatting. Each version of the file is black-clean alone; the merge is not, and the pre-commit job checks every file a PR changes. - **The allowlist names its cases** (see the gate in section 1): it was keyed by intrinsic, target and flavor alone, so a second corpus case demanding an allowlisted intrinsic left the gate green, and fixing one of the two cases that demand it could not make the entry stale. - **Wording**: the status vocabulary in `TESTING.md` lists `probe_error`, durations are out of the generator's comments, and the documentation examples name the target. --- ## Verification Run in the three toolchain images (one per LLVM flavor) against an export of the branch tip; no GPU. The pybind extension is built in each image and put on `PYTHONPATH`, so the tests that need it run (CI builds no extension and skips them). | Check | llvm20 | llvm22 | llvm23 | |---|---|---|---| | byte identity, every family | GREEN | GREEN | GREEN | | ctest | 20/20 | 20/20 | 20/20 | | emitted-IR validity (194 cases) | GREEN (2 documented `KNOWN_BAD`, 8 unvalidated) | GREEN (2 documented `KNOWN_BAD`, 4 unvalidated) | GREEN (2 documented `KNOWN_BAD`) | | arch-domain corpus gate (194 cases, 3 flavors) | GREEN (exactly the 9 documented warnings) | same | same | | platform pytest, default and under `ROCKE_BACKEND=both` | 2685 passed, 59 failed (both runs) | 2689 passed, 59 failed (both runs) | 2690 passed, 59 failed (both runs) | | library pytest | 2598 passed, 1 failed | 2599 passed, 1 failed | 2599 passed, 1 failed | Every failure is also a failure of `develop` itself, checked on an export of its tip (59 in the platform suite at every flavor, one in the library suite). The failing set is the same at the three flavors: - 56 tests import `ml_dtypes`, which none of the images has: six in `core/test_gfx1250_fp4_scaled_wmma.py` and fifty in `instances/test_gfx950_mxfp_examples.py` (new with #13450); - two tests in `core/test_arch_domain_artifact.py` (every declaration is covered; regenerating the host's column is a no-op) fail because of the 27 unmeasured declarations in section 1. That file is excluded from the installed test tree, so CI does not run it; - `tools/test_run_all.py::test_existing_engine_archive_is_reused_by_pytest` fails whenever pytest runs under `tests/run_all.py`, which exports the variable the test expects to be unset; - `library/tests/test_kda_hostpack.py::test_runner_geometry_without_gpu` builds a stub kernel without the `cluster_dims` attribute that #12857 made the manifest code read. The key #13450 added to the three columns (`mfma.scale.f32.32x32x64.f8f6f4.v8i32.v8i32`) was re-measured in each image with this branch's generator: every cell, status and evidence, matches the committed one. A note on the llvm20 run. At llvm20, `raw.ptr.buffer.load.async.lds` runs past the 60 s probe budget on six targets (the committed column records `toolchain_timeout` for them), and the generator's timeout kills the `clang` driver but not the `clang -cc1` it started, so each regeneration (including the regeneration test above) leaves twelve runaway compiler processes behind. In a two-CPU container they starve the rest of the suite: an earlier full run timed out `test_binding_compiler_selection.py::test_resolver_failure_releases_binding_builders` (valgrind, a fixed 120 s limit), which passes in about 15 s alone, on this branch and on `develop`. The numbers above are from a run that killed the leftover probes as they appeared. The leak is in the generator already on `develop`, not in this PR, and is left to a follow-up. Not run: a GPU, Windows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

ISSUE ID : AICK-2061
Add native gfx950 32x32x64 scaled MFMA support and a reusable packed RCR GEMM consumer. All 25 ordered FP8 E4M3, BF8 E5M2, FP6 E2M3, BF6 E3M2 and FP4 E2M1 pairs use independent E8M0 K32 scales, FP32 accumulation and BF16/FP16 output. Four focused F8, F6, F4 and mixed examples mirror the existing gfx1250 structure.
The Python and C++ catalog, lane maps, builders, type contracts, LLVM/HIP lowerers and native bindings are mirrored. F8 payloads interleave 16-value chunks; F6/F4 payloads use contiguous 32-value chunks. The overloaded nine-argument ABI uses eight padded i32 carriers and separate A/B scale VGPRs on LLVM20, LLVM22 and LLVM23. Other targets reject the new family. The spec-driven builder is used directly by four untuned correctness examples, following the gfx1250 structure. They demonstrate instruction support and make no performance claim. Storage/support documentation is included. The shared MMA reference explains catalog selection, IR emission and dispatcher responsibilities, and compares the existing 16x16x128 FP8 fused-MoE consumer with the direct 32x32x64 E8M0 GEMM family. Native lowering preserves diagnostic errors, and native kernel names use arena-owned storage without the previous 255-byte limit. Shared dtype normalization remains unchanged.
The finite numerical fixtures use independent A/B scales of 1/4, 1/2, 2 and 4 across rows, columns, K32 groups, K64 steps and output tiles. Signed and positive mantissas, one-sided controls, isolated groups and a NaN output sentinel are checked against an independent decoded reference. Host sensitivity tests prove that output rounding exposes omitted scaling, collapsed halves and shifted group/row/column ownership. These bounded tests do not establish arbitrary-input rounding, E8M0 NaN/extreme exponent behavior or output zero-sign semantics.
Test plan
developcompiler (8f497e0992fb7513f7f78a6f6b6f1056c375e961) and re-probed the new MFMA row using the recorded LLVM20/22 compilers. Each flavor artifact now differs fromdeveloponly by the new scaled-MFMA key and its seven measured target cells; all 1,176 existing cells, canonical names and toolchain provenance per flavor are unchanged. The full LLVM23 measurement reproduced every existing row. All 50 complete-kernel LLVM23 golden hashes were regenerated and remain byte-identical, with native serialized IR and LLVM agreement. Removed two unrelated gfx1250 import-order changes. Focused checks passed 516 tests / 26 subtests plus 25 artifact tests / 3,608 subtests; all 77 families / 1,091 configurations per flavor were GREEN on LLVM20/22/23, with output hashes and verdicts identical to the pre-cleanup head. Full PR pre-commit passed.developatfad26ec7660c8bad7b77f60eb601101adc5164ef: retained both gfx950 scaled-MFMA and gfx1250 prefetch/cluster declarations in matching Python/C++ order. Added the callable parity-emitter entrypoint required by the new differential batch runner, with a regression covering repeated configs and the terminal record on LLVM20/22/23. Fresh native build and 18/18 CTests passed; all 77 families / 1,091 configurations per flavor were GREEN on LLVM20, LLVM22 and LLVM23, including 50 scaled-MFMA configs per flavor and the extra gfx1250 LLVM23 lanes. Full PR pre-commit and PR diff whitespace checks passed.438a3e78): 354 passed and 3,617 subtests. This includes availability-artifact checks and legacy scaled-MFMA coverage. The original-head ROCm 7.14/LLVM23 host run had 1,319 passes and 83 dependency/hardware skips.438a3e78): all 25 ordered pairs × BF16/FP16 on each of AMD LLVM20, LLVM22 and LLVM23 (150 complete kernels), compiled and linked to gfx950 code objects; disassembly checked instruction, selectors, physical 8/6/4-word tuples and separate VGPR scale sources. LLVM20 and LLVM22 availability rows were measured with the exact compiler builds already recorded in those artifacts.--case all: 225 comparisons.The existing relative-path guard still reports the unchanged
cmake/InstallGpuReferences.cmakereference-bundle diagnostic; its emitted-IR validity stage passes. Numerical validation uses NumPy/ml_dtypes and HIP transport without torch. No performance claim is made.Fresh validation of these fixes is host/compiler validation. No fresh GPU numerical run was performed; LLVM20/LLVM22 numerical behavior remains unvalidated on hardware. The prior GPU evidence above belongs to head
9884ea51ad45d78bee5b3afd3397a33caf49a1b7.The final dispatcher removal changes no kernel builders, lowering, native bindings or numerical harnesses. Its fresh validation is host-only; the compiler/parity evidence above was collected before this removal.
Current cleanup validation is host, compiler-probe and Python/C++ emitted-IR validation; no fresh GPU numerical run was performed. The inherited availability-artifact coverage gap for 26 new gfx1250 prefetch/cluster declaration keys remains outside this PR: their missing rows were reproduced on unmodified
developatebcb34e73e5e2723aafc0fe893a997c25c2592a8. The full LLVM23 sweep was retained as validation evidence, while the committed cleanup preserves the base coverage and adds only the scaled-MFMA row. The focused artifact run excludes the full declaration-coverage and full committed-column regeneration checks that encounter that inherited gap; full LLVM20/22 sweeps were not repeated.