Skip to content

feat(rocke): add logical TF32 and native gfx942 atoms - #12612

Merged
tenpercent merged 28 commits into
developfrom
users/mpodkory/rocke/tf32-native
Oct 3, 2026
Merged

tenpercent merged 28 commits into
developfrom
users/mpodkory/rocke/tf32-native

Conversation

@tenpercent

@tenpercent tenpercent commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

ISSUE ID : AICK-1714

TF32 needs a logical identity independent of its storage carrier so catalog queries can select native XF32 instructions without changing ordinary FP32 selection. This adds tf32 (xf32 alias), represented as I32 words, and the gfx942 16x16x8 and 32x32x4 native atoms in both engines. Lowering bitcasts the carrier to the intrinsic's float-vector ABI.

Raw FP32 payload wrapping is separate from explicit round-to-nearest-even preparation. The reusable numerical example demonstrates that raw inputs are accepted on gfx942 and match truncation for the tested corpus; RNE-prepared inputs differ at rounding boundaries. For example, multiplying input bits 3f801001 by one produces 3f800000 through raw XF32 and 3f802000 after RNE preparation. I32 transport preserves the raw result.

This reuses the fragment packing/loading helpers now present in the develop base. It includes dtype/serialization/transport support, operand and target guards, a mirrored probe, documentation, and ten new LLVM golden records. Generic scalar and vector TF32 arithmetic is rejected; payload transport, bitcasts, and selection remain supported. Direct global vector loads accept 2, 3, 4, or 8 TF32 values; stores accept 1, 2, 4, or 8, using I32 alignment rules. Payload width and address alignment are independent. Both LLVM store lowerers honor the explicit alignment, and HIP uses an exact-byte copy for underaligned stores. Ordinary MMA atoms also reject TF32 result types. Existing golden records are unchanged. Production GEMM dispatch and cross-target emulation are outside this change.

Architecture and input preparation:

flowchart TD
    F["FP32 input payloads"] --> L["Existing fragment packing and loading helpers"]
    I["Prepared I32 input words"] --> L
    L --> W["Bitcast: preserve raw payload"]
    L -->|FP32 values| R["Optional FP32 to TF32 RNE recipe"]
    W --> T["Logical A/B: two TF32 values per lane"]
    R --> T
    Q["Catalog query: tf32, tf32, fp32"] --> A["gfx942 XF32 atom: 16x16x8 or 32x32x4"]
    T --> IR["Typed tile.mma IR, serialization and validation"]
    A --> IR
    C["FP32 accumulator C"] --> IR
    IR --> PY["Python lowering"]
    IR --> CPP["C++ lowering"]
    PY -->|A and B| S["I32 carrier: LLVM 2 x i32 / HIP i32x2"]
    CPP -->|A and B| S
    PY -->|C| ACC["FP32 accumulator vector"]
    CPP -->|C| ACC
    S --> ABI["A/B bitcast: LLVM 2 x float / HIP f32x2"]
    ABI --> M["Native gfx942 XF32 MFMA"]
    ACC --> M
    M --> D["FP32 result D"]
Loading

Logical TF32 drives catalog selection and operand validation. The I32 carrier preserves A/B payload bits; the float-vector ABI cast does not round them. C and D remain FP32. RNE preparation expands to existing integer IR operations, with no new serialized opcode. The two LLVM emitters are checked for byte identity.

Current-head gfx942 hardware validation (4f18490d8ce6, 2026-09-30):

  • Slurm job 68011208 completed with exit status 0:0 on a real gfx942. The Python source and freshly built C++ bindings came from commit 4f18490d8ce6314ef06014a5f951a38312caa411; the embedded source archive was verified before extraction (SHA-256 625c9e203ff7a35e6ac9eb5907345272a26434db05b7a29f5826f3bd5b9bd2a1).
  • Both engines × both native shapes (16x16x8, 32x32x4) × six variants passed with normal Python, then passed again with python -O: 24 + 24 successful variant runs, each with 2,156 batched atoms. Variants were raw FP32 payloads, I32 transport, GPU RNE preparation, host RNE preparation, host masking, and ordinary FP32 MFMA.
  • The unmodified numerical harness checked actual prepared A/B words, exact single-product boundaries (including subnormals and special values), dense inputs, cancellation, and nonzero accumulators against independent NumPy references. Existing tolerances were unchanged. Python/C++ builder and LLVM-lowering parity, cross-variant equality, and cross-engine output equality passed. Input bits 3f801001 still produced 3f800000 through raw/I32 transport and 3f802000 after RNE preparation.
  • Environment: ubuntu-24 + rocm/7.14.0, HIP runtime 7.14.60850, LLVM23, Python 3.12.3, NumPy 2.4.6, and freshly built native bindings using GCC 13.2.0. HIP, ROCr, and COMGR were loaded from the same ROCm module. Kernels were compiled from LLVM IR through COMGR and launched through HIP; PyTorch was not used.
  • All 48 generated code objects contained the expected MFMA instructions, with XF32 present for TF32 variants and absent for the ordinary FP32 control. Corresponding LLVM IR and code objects were byte-identical between the normal and optimized Python runs. Public TF32 export identity also passed on the compute node.
  • Two earlier attempts (68011174, 68011200) on another gfx942 node aborted with SIGBUS at hipStreamSynchronize before numerical comparison. Their first compiled object is byte-identical to the successful run's object. These attempts provide no passing numerical result, and the cause of that node's failure remains undiagnosed.

Public Python API validation (4f18490d8ce6): from rocke import TF32 and from rocke.core import TF32 both expose the canonical IR type and include it in __all__. All 152 focused TF32 tests and full PR-range pre-commit hooks passed. This export-only follow-up does not change generated kernels; current-head GPU validation is recorded above.

Numerical harness tightening (1c75ffdf3c01):

  • All eight parity/numerical checks now use explicit conditional exceptions, so validation remains active with python -O and PYTHONOPTIMIZE=1.
  • 178 focused host tests passed, including 27 subprocess cases covering success and all eight failure paths under normal Python, -O, and PYTHONOPTIMIZE=1. Injected failures verify nonzero exit status, results.json failure status, and diagnostic context. Compiler/GPU calls are mocked in these new tests; they establish validation-control behavior, not GPU numerical correctness.
  • Ruff and full PR-range pre-commit hooks passed. Kernel builders, emitters, goldens, and numerical tolerances are unchanged. The full all-family byte-identity gate was not rerun for this host-only change; current-head GPU validation is recorded above.

Validation after merging develop and tightening the API (a09dd3378f2b):

  • 151 focused host tests passed with freshly built native bindings. Coverage includes serialized TF32 vector arithmetic rejection, scalar/vector TF32 results on ordinary MMA atoms, positive payload transport, direct vector loads, and store alignment for TF32/I32/F32. Default, weaker, stronger, and absent legacy alignment attributes are covered; zero, negative, and non-power-of-two serialized store alignments are rejected consistently by LLVM and HIP, with Python/C++ LLVM parity for LLVM20/22/23.
  • All 14 native CTests passed, including native HIP rejection and aligned/underaligned store lowering. At the preceding tightening revision (75508e81e332), eight direct TF32 store kernels assembled as LLVM IR and compiled as HIP device code for gfx942. The four-element, four-byte-aligned HIP case still emits global_store_dwordx4; the alignment fix preserves the wide transfer in that tested case. The prior eight direct vector-load compilation checks also passed.
  • All 71 families / 816 sampled configurations were exercised in each LLVM20/22/23 byte-identity run using local system Python. TF32 passed in every flavor. The remaining gfx942_attention_tiled_3d config 3 mismatch is inherited from develop: it was reproduced on the exact base with identical per-engine output hashes (Python includes _fnuz in the kernel name; C++ does not).
  • Combined TF32, dtype, storage, serialization, and MMA host coverage at 75508e81e332: 358 passed, 39 skipped, and four failures. The four storage failures are inherited from develop and concern the removed ScalePacking.llvm_type property.
  • Final local architecture/API review closed the vector arithmetic, vector-load admission, store alignment and its validation, and ordinary-MMA result-type gaps using existing mirrored helpers. The canonical dtype reference distinguishes load/store widths and address alignment. Repository pre-commit hooks passed over the full PR range.

Earlier implementation validation (before the develop merge and this tightening):

  • Slurm gfx942, ROCm 7.14: both engines × both shapes × six variants passed, with 2,156 batched atoms per variant. Checked prepared input words, exact single-product boundaries (including subnormals and special values), dense inputs, cancellation, and nonzero accumulators against independent references. The 24 programs at that revision matched the hardware-tested artifacts and contained the expected instructions.
  • At that revision, all ten probe modes compiled through COMGR and HIP on LLVM20/22/23, and Python/C++ HIP emission matched byte-for-byte.
  • Windows validation was not available; LDS/loop transport has host lowering/parity coverage rather than a separate GPU LDS round-trip test.

Reproduce the numerical example with a matching ROCm compiler/runtime and built native engine on PYTHONPATH:

python -m rocke.examples.gfx942.tf32_numerics --backend both --shape all --output-dir results
python -O -m rocke.examples.gfx942.tf32_numerics --backend both --shape all --output-dir results-optimized

Report complete quant type aliases while preserving scalar quantization restrictions.
Keep empty descriptors valid while rejecting fragment loads before IR emission in both engines. Cover empty shapes and nonempty controls in Python and native tests.
Validate complete static fragment displacements before emitting i32 offsets in both engines. Resolve CTest readiness from registered commands and cover multi-config execution and failure propagation.
Size temporary load-pointer storage by addressable units so padded slots
cannot overrun the array. Preserve IR order and add mirrored transport
coverage for a layout requiring more loads than logical elements.

Simplify scale-width queries while retaining the shared packing adapter,
and clarify the contiguous-K matrix layout contract.
@tenpercent
tenpercent marked this pull request as ready for review September 28, 2026 20:38
@tenpercent
tenpercent requested review from a team as code owners September 28, 2026 20:38
Base automatically changed from users/mpodkory/rocke/packed-operand-common to develop September 29, 2026 22:44
@therock-pr-bot

therock-pr-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

therock-pr-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The numerical harness can report success without executing its validation checks when Python assertions are disabled.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

The new TF32 type is missing from the standard Python public re-export surfaces.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Re-export TF32 from rocke and rocke.core public APIs

dnn-providers/​hip-kernel-provider/​rocke/​platform/​python/​rocke/​core/​ir.py:54

The new logical type is not re-exported from either rocke.core or the top-level rocke package, unlike the existing public scalar types such as F32. As a result, the normal authoring import from rocke import TF32 (and from rocke.core import TF32) fails even though TF32 is now part of the public IR API. Please add TF32 to both packages' imports and __all__ lists.

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

It spans two compiler engines and hardware-specific numerical behavior that was not rerun after the final integration changes.

Review effort: Balanced
Findings: None

@cgmillette cgmillette 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.

PR #12612 — logical TF32 + native gfx942 XF32 atoms · Verdict: 🟢 merge-worthy (both engines, byte-identity
LLVM20/22/23, 2,156-atom GPU validation). 9 copilot threads resolved — not duplicated.

# Sev Location Net-new finding
1 🟡 raw transport vs cvt_f32_to_tf32 Raw FP32→TF32 truncates (HW drops low 13 mantissa bits); RNE is opt-in, so future GEMM dispatch wrapping raw FP32 is silently less accurate than framework "TF32"
2 🟡 PR validation GPU pass came from one gfx942 node; two others aborted undiagnosed SIGBUS at hipStreamSynchronize on a byte-identical object — note before broad reliance
V1 🟢 ir_arith.cpp cvt_f32_to_tf32 RNE recipe verified correct: bias 0x0FFF+odd, mask 0xFFFFE000, Inf passthrough, sNaN→qNaN with no NaN→Inf collapse, ±0/subnormals preserved
V2 🟢 tf32.py/tf32_internal.h TF32 guards now check results on XF32 and non-XF32 paths; generic arith on i32 carrier blocked via allowlist — symmetric both engines
3 🟢 regression status "Red" is only inherited failures (gfx942_attention_tiled_3d cfg3 + removed ScalePacking.llvm_type), not caused here

Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor-nit · 🟢 info/confirmed.

Active questions:
@tenpercent how are we going to address 1 & 2?

@tenpercent

Copy link
Copy Markdown
Contributor Author

@cgmillette
RNE vs truncation is a design decision. It needs a bit of further study what is the common framework convention and we may flip the default if needed. Both code paths are enabled so it looks like a matter of framework integration rather than compiler. I think it shouldn't be blocking

SIGBUS diagnostics need stress testing. I don't think this is blocking either

@tenpercent
tenpercent requested a review from a team October 2, 2026 16:36
@tenpercent
tenpercent enabled auto-merge (squash) October 2, 2026 18:40
@tenpercent
tenpercent merged commit e01aac5 into develop Oct 3, 2026
163 of 165 checks passed
@tenpercent
tenpercent deleted the users/mpodkory/rocke/tf32-native branch October 3, 2026 17:48
brockhargreaves-amd added a commit that referenced this pull request Oct 4, 2026
…port

The installed Linux test artifact cannot run two TF32 tests introduced
in #12612. Recipe replay attempts to configure a native source build
from the installed bin directory, which has no CMake source tree. The
gfx942 numerical test requests both backends and imports rocke_engine,
which is absent from the installed artifact.

Add direct pytest skip decorators to the ten recipe-replay cases and
the one gfx942 numerical case pending fixes to their artifact support.
Keep the remaining TF32 tests enabled and include the originating PR
in each skip reason. These temporary skips also apply to local runs.

Validation: targeted host tests passed (34 passed, 127 skipped), and
repository pre-commit checks passed. The gfx942 Enroot/Slurm run honors
all eleven skips and passes both SDPA suites; the broader platform
suite still has six separate FP4 alias test failures.
AviralGoelAMD added a commit that referenced this pull request Oct 4, 2026
Resolves conflicts with #12612 (logical TF32 + native gfx942 atoms), which
taught the plain HIP global_store_vN to honour `align` (memcpy fallback when
under-aligned) in both engines. The store now mirrors the load exactly:
under-aligned or non-power-of-two payloads take the memcpy path, and a
STREAMING hint there is refused (NotImplementedError / ROCKE_ERR_NOTIMPL),
since the HIP backend does not yet lower nontemporal through memcpy.
ir.py: global_store_vN docstring keeps develop's alignment text plus the
temporal_hint paragraph. Tests updated to the memcpy-path wording.
AviralGoelAMD added a commit that referenced this pull request Oct 5, 2026
The nontemporal_emit parity family (4 copy-kernel configs: streaming /
default loads and stores on gfx942 / gfx950) had cross-engine parity and
substring checks but no golden entry, so an identical regression in both
lowerers would go unnoticed. Record its Python reference shas under both
golden keys (`ll` and `ir_canonical`), as #12612 did for tf32_mma.

Recorded with the family GREEN in both modes (C++ == Python). Only the
`nontemporal` entries are added (12 lines, 0 changed): `--record-golden`
rewrites the whole key from the current run, and most other families do
not match the committed golden on an llvm20 host even on develop, so
re-recording everything would rewrite unrelated hashes. tf32_mma, the
latest blessed family, matches on the same host.
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

tenpercent added a commit that referenced this pull request Oct 8, 2026
The installed rocKE suite failed eleven TF32 cases introduced by #12612:
ten recipe cases attempted a native source build from the installed
`bin` directory, and the gfx942 numerical test requested `rocke_engine`,
which the artifact does not contain. #12718 temporarily skipped these
tests in every environment.

Restore the tests by installing the existing native recipe replay CLI
under `tests/portable_ir/`, which TheRock already captures in the test
artifact. Compare native CBOR replay with Python LLVM lowering for both
atom shapes, all preparation modes, and LLVM 20/22/23. Installed CTest
supplies a relocatable executable path through the existing
`ROCKE_REPLAY_CLI` setting for all native CLI replay tests. TF32 keeps
its module-local fixture and uses the same setting. This also enables
the four existing portable-IR CLI tests in installed artifacts. An unset
or missing replay executable skips the native lane, matching the
existing portable-IR tests; execution errors and IR mismatches remain
failures.

Run all 12 Python numerical variants against the independent NumPy
references when the native binding is absent. When the binding is
available, require all 24 variants, native builder/lowerer byte
identity, and backend numerical parity; a broken binding remains a
failure. The standalone numerical CLI retains its strict both-backend
default. Document general native replay rules in `TESTING.md`: use
prebuilt artifacts, skip absent CLIs, avoid implicit source builds,
isolate fixtures, and validate relocated installs.

Fixes #12718

ISSUE ID : AICK-2514

## Test plan

- [x] Reproduce all ten original recipe source-build failures in an
isolated installed layout, then pass all ten cases with matching newly
built artifacts.
- [x] Fresh native build and focused TF32 core/wrapper suite before
removing three wrapper tests: **155 passed**. Existing numerical
fault-injection coverage retained.
- [x] Native TF32, DOM decoder, and recipe VM CTests: **3 passed**.
- [x] TF32 core and numerical-validation source suite without binding:
**63 passed, 116 capability skips**.
- [x] Full relocated installed `rocke_pytest` with the shared replay
setting: **1,727 passed, 321 skipped, 1,632 subtests passed**.
- [x] Matching platform/library wheels installed with `--no-deps
--reinstall`; CTest discovery confirmed the selected entry and installed
import root.
- [x] Single-setting replay check: all 14 TF32/portable-IR CLI cases
pass in source, fresh installed, and relocated layouts; unset or missing
CLI gives 14 skips and a broken executable gives 14 failures. Provider
and standalone CTest paths verified.
- [x] Repository pre-commit hooks over the PR range and whitespace
checks.
- [ ] Real gfx942 numerical execution and native Windows validation:
deferred to CI; no new GPU result is claimed.

The local installed replay used the platform's provider-style
installation and generated provider CTest entries, rather than the
complete provider superbuild. Tools were Python 3.12.12, NumPy 2.5.3,
pytest 9.1.1, pytest-timeout 2.4.0, ml-dtypes 0.6.0, and ROCm 7.1 Clang
20; these differ from the original CI runtime. Kernel emitters and
numerical tolerances are unchanged.

An initial full run used `/usr/bin/llc` and failed four unrelated
debug-info object tests because that compiler does not recognize gfx950.
The reported passing run selects ROCm LLVM via `ROCKE_TEST_LLVM_BIN` and
`PATH`.
CongMa13 added a commit that referenced this pull request Oct 16, 2026
…#13588)

# feat(rocke): emulate logical TF32 MMA on gfx950 with three BF16 MFMAs

Branch: `users/congma13/xf32-bf16x3-emulation-v2`

ISSUE ID : AICK-2208

---

# Summary — For human reviewers

## The short version

gfx942 has a hardware matrix instruction that multiplies fp32 values at
reduced
precision (a 19-bit format; the instruction names call it XF32, and it
is commonly
called TF32). gfx950 does not. #12612 added a logical `tf32` type and
the two native gfx942
atoms, so a kernel can ask the arch catalog for "a TF32 matrix op" and
get an
answer on gfx942 — and nothing at all on gfx950.

This PR adds two gfx950 catalog atoms, `mfma_f32_16x16x32_xf32` and
`mfma_f32_32x32x16_xf32`, that answer the same query. Each one lowers to
three
BF16 matrix instructions over a split of the fp32 operands, in both the
Python
and the C++ engine, with byte-identical output. They take the **same
operand
type** as the native atoms, so one kernel body can reach either.

This is the whole gfx950 capability. Calling it from production
dispatch, and an
accuracy-driven option on gfx942, are separate changes (see "Reviewing
this").

## Why the operand type has to match the native atoms

The first version of this branch, and the obvious design, took ordinary
fp32
vectors. It would have worked, and nothing would have objected: the TF32
verifier from #12612 only rejects TF32-typed operands on atoms it does
not know,
so an fp32 vector slips straight through.

The cost only shows up when someone writes a kernel. Asked for
`(tf32, tf32, fp32)`, the catalog would hand back an atom that wants
`vec<tf32x2>` on gfx942 and one that wants `vec<f32x8>` on gfx950. The
one thing
emulation is for — write the kernel once, let the catalog pick native or
emulated — would not exist. Handing the gfx950 atom the TF32 type the
query
promises would have been rejected by that same verifier.

```mermaid
flowchart LR
    Q["kernel asks for<br/>tf32, tf32 → fp32"] -->|gfx942| N["native XF32 atom"]
    Q -->|gfx950| E["emulated atom"]
    N -->|operands| T["vec of tf32<br/>(I32 carrier)"]
    E -->|operands| T
    E -->|lowering| L["bitcast, split,<br/>3 BF16 MFMAs"]
```

Both atoms take the same operand type; only the lengths differ (two
values per
lane natively, eight on gfx950). The accumulator is an fp32 vector on
both.

### Emulating TF32 does not mean rounding to TF32

The second surprise. A native XF32 instruction ignores the low 13 bits
of each
operand. The emulation could have copied that, and would have matched
the hardware
more closely. It does not: it reinterprets the whole 32-bit payload as
fp32 and splits that, so every bit the caller supplied reaches the
result. The
base's own boundary example is an operand `0x3f801001` multiplied by
1.0:

| path | result |
|---|---|
| native atom, raw payload | `0x3f800000` — low bits dropped |
| native atom, after explicit round-to-nearest-even preparation |
`0x3f802000` — rounded to 10 mantissa bits |
| BF16x3 emulation | `0x3f801000` — the payload, to within one fp32 ulp
|

All three are right for what each is. The emulation's contract is
therefore:
*track the exact product of the payloads it is given, to within the
split's own
error; not bit-reproducible against the native atom across targets.* On
raw fp32
payloads that is more accurate than the native atom. On payloads already
rounded
to TF32 precision the native atom is exact, and the emulation stays
within `2^-18`
(relative, per product) of it — the bound on the one term it omits.

## How it works

Each emulated `tile.mma` is expanded at lowering, before the
instruction-set
dispatch:

1. **View the payload as fp32.** A `bitcast` of the I32 carrier — never
a
   rounding.
2. **Split each operand** into `big = bf16(x)` and `small = bf16(x -
fp32(big))`.
Both narrowings are `fptrunc`, which LLVM defines as
round-to-nearest-even and
   which the AMDGPU backend selects as the packed convert on gfx950.
3. **Issue three BF16 MFMAs** threading one fp32 accumulator, in the
order
`small*big`, `big*small`, `big*big`. The order is part of the answer:
fp32
accumulation is not associative, so any other order gives different
bits. The
fourth product, `small*small`, is omitted; it is the same size as the
split's own
   representation error.

The decisions that are not optional:

- **The verifier reads the catalog.** #12612's TF32 contract is a
two-entry table
keyed by op_id (Python) and a pair of `strcmp`s (C++), plus five
hand-written
`gfx942` guards. Adding the emulated ids to it would have merged two
properties it
holds in one place: *which atoms take TF32 operands* and *which atoms
are native
gfx942 instructions*. Only native atoms existed, so they coincided. They
no longer
  do, and the consequences are concrete: the HIP emitter builds
`__builtin_amdgcn_<op_id>` from that table, so an emulated id there
would emit a
builtin that does not exist, and the `gfx942` guard would reject the
emulated atom
on gfx950. So the emulated atoms get their own catalog-driven branch —
operand and
accumulator lengths come from the row — and the native table and guards
are left
  exactly as they were.
- **The catalog cannot over-claim.** An emulated row's physical atom
must be a native
row of the *same* arch, checked when the catalog loads. The first
version on this
branch asked only whether the atom had fragment metadata anywhere, which
would have
let a row claim support the compiler then rejects at codegen ("Cannot
select"). The
recipe also fixes the operand dtype. The C catalog is static data, so
its twin is a
  test that walks each emulated row's own arch.
- **The native-preferred rule is pinned, not changed.** Selection drops
an emulated
atom only when a native atom of the *same* `(m, n, k)` is listed. A
native atom with
a different K does not shadow it, and `select_largest_k` would then
return the
emulated atom — three MFMAs instead of one. This was reproduced by
running the real
catalog code on a synthetic native-K=8 plus emulated-K=16 or K=32
catalog. Nothing
triggers it, because no arch lists both; a test pins that, and the
rule's docstring
  says where to re-key it.
- **HIP rejects an emulated atom by name.** The expansion exists only in
the LLVM
lowerer. Before, an emulated op reached the HIP lowerer's generic
dispatcher and
failed with "no HIP lowering for op `tile.<op_id>`", which, with a
hardware-shaped
  name, reads as a missing hardware atom.
- **gfx942 is deliberately untouched.** The two atoms here expand into
the K-packed
bf16 MFMAs, which exist only on gfx950; the compiler-probed intrinsic
table records
them as unavailable on gfx942. A gfx942 emulation would be two different
atoms
(`16x16x16`, `32x32x8`), would hit the selection rule above, and would
put two
  numerics behind one dtype on one arch.
- **Two earlier designs are removed.** The branch first modelled the
dtype on CK as an
`xf32` selection tag over fp32 operands, and added an optional MMA
result-dtype field
for it. #12612's `tf32` supersedes the first; the second never had a
user. Neither is
  in the net diff; the history still shows them.

## What this found

- This branch's first-version catalog check had the hole described
above; it is closed
  here.
- Before the parity family below, **no kernel family in the
byte-identity gate selected
an emulated atom**, so the gate could not have compared the two engines
on the
  expansion at all.
- On the merged base the byte-identity gate is green outright. The
single divergence
earlier versions of this work carried (`gfx942_attention_tiled_3d`, one
config)
  no longer reproduces; nothing here touches it.
- The L5 golden anchor (`llvm_gfx_all.json`) is mostly stale on
`develop`: at the
flavor of record, 11 of its 73 `ll` families match the tree entirely.
That is not
  this PR's doing and it is left alone; see the AI-reviewer notes.

## How this was tested

The numerics are pinned three ways: a host model of the recipe (no GPU),
an
on-device harness that compares the emulated `tile.mma` bit for bit
against a
hand-written kernel issuing the same three BF16 MFMAs, and an absolute
check
against `K*alpha*beta` with a single-term control that must miss. The
two engines
are compared by a new parity family in the byte-identity gate, which was
itself
shown not to be vacuous: swapping two terms in the C++ expansion alone,
in a scratch
copy, turns both of its configs into DRIFT.

### Test plan

- [x] `tests/core` with the `rocke_engine` module built, plus
`tests/test_rocke_ci_static.py` and `tests/test_rocke.py`: 1635 passed,
79
skipped, 2 failed. The two failures also fail on unmodified `develop` in
the same
environment: `test_arch_domain_artifact.py` — the `llvm22`
intrinsic-domain
artifact lacks 26 recently added cluster and prefetch keys, and its
"regenerating this host's column is a no-op" check follows from that.
- [x] C++ tests under `ctest`: `rocke_tf32`, `rocke_dtypes`,
`rocke_mma_catalog_lookup`, `rocke_xf32_bf16x3_lowering`,
`rocke_mma_frag_ssot`.
- [x] COMGR compile gate: both emulated atoms compile for gfx950 to a
code object with
      exactly three `v_mfma_*_bf16` and the packed convert.
- [x] `tools/check_byte_identity.py` at all three flavors, run
sequentially: every
family GREEN (77 per flavor), no DRIFT, including the base's native TF32
family
      and the new parity family.
- [x] The `xf32_bf16x3_mma` parity family re-diffed Python-vs-C++ on the
final archive
      at all three flavors, in both `ll` and canonical `ir` mode.
- [x] Negative control for that family (scratch copy, C++ expansion
only): DRIFT on
      both configs at every flavor in `ll`; GREEN on the real archive.
- [x] On-device harness on gfx950 — Python lowerer, C++ engine with the
Python
fallback disabled, and both engines cross-checked: bitwise equal to the
hand-written BF16x3 reference at both atoms; accuracy inside tolerance;
the
      single-term control outside its floor.
- [x] On-device fault injection (term order rotated; residual terms
dropped): both
      rejected with exit status 2, unmodified control exits 0.
- [x] `tests/core/test_xf32_bf16x3_device.py` under
`ROCKE_REQUIRE_GFX950=1` on the same
      machine, including with `HIP_VISIBLE_DEVICES` set.
- [x] Representative-IR golden
(`tests/golden/rocke_representative_ir_sha256.json`)
      checks clean at all three flavors, unchanged.
- [x] Full `--check-golden` of the L5 anchor at the flavor of record,
both modes: the new
family and the base's native TF32 family match; older entries are stale
on the
      base and untouched.
- [x] `pre-commit run --from-ref origin/develop --to-ref HEAD` passes
(black,
      clang-format, bandit, cmake-lint, whitespace hooks).
- [ ] **Deferred:** the full three-flavor byte-identity gate was run
before the last
five commits. Those touch no emission path — a verifier-lookup guard
that raises
on malformed catalog rows, a HIP-lowerer message, tests, a golden JSON,
docs, and
one comment — and the parity family, the only family whose emission this
PR
changes, was re-diffed on the final archive. A re-run on the final head
is the
      remaining step.
- [ ] **Not run:** the base's native TF32 numerics tests (need gfx942
hardware; that
      code path is untouched).
- [ ] **Not run:** a node where the gfx950 is not HIP device 0. The
probe's index
      selection was verified with faked probe output, not on silicon.
- [ ] **Not run:** CI GPU lanes and other host platforms.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants