Skip to content

feat(tir): declare the sm_89 e4m3 warp mma - #261

Merged
zhen8838 merged 3 commits into
tile-ai:mainfrom
FeathBow:feat/sm89-fp8-mma
Oct 23, 2026
Merged

zhen8838 merged 3 commits into
tile-ai:mainfrom
FeathBow:feat/sm89-fp8-mma

Conversation

@FeathBow

@FeathBow FeathBow commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Why

sm_89 adds an FP8 warp MMA, mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32, which the sm_89 target from #259 could not select: T.cuda.sm80.Mma takes BF16 only, so an E4M3 product in registers had no instruction to lower to. It was suggested in #256 as the change to make after the sm_89 documents.

What

T.cuda.sm89.Mma declares the instruction: 16 x 8 x 32, E4M3 A and B in registers, and an F32 accumulator in registers laid out as the SM80 atom's. Its A and B fragments follow the PTX fragment table and match CuTe's MMA_Traits<SM89_16x8x32_F32E4M3E4M3F32_TN> thread for thread. It requires mma.sync, which sm_89, sm_90 and sm_100 all list and all run in the E4M3 form. TiledMma accepts it after the SM80 atom, and the register fragment pattern both atoms build moves into mma_atom.py.

The runtime keeps one ops::mma entry. The element type of A selects the CuTe atom; B must hold the same type, C must hold f32, and each fragment must have the selected atom's length, or a static_assert fails. Codegen emits the same call for both atoms.

schedule facts names each capability once per instruction, so T.tiled_mma still lists wgmma.mma_async, mma.sync with three atoms behind it.

Tests:

  • tests/ir/cuda/atoms/test_mma.py compares both atoms' fragments with CuTe's traits
  • tests/ops/tir/cuda/test_mma.py runs an E4M3 product on the GPU against the F32 product of the same values, once for H200 and once for sm_89 through the sm89 marker
  • tests/ops/ir/test_matmul.py types an E4M3 register by register matmul on sm_89
  • tests/runtime/test_cuda_static_asserts.py checks that ops::mma refuses an E4M3 A with a BF16 B, a C that is not F32, and a B with the BF16 atom's length
  • two schedule goldens and the refused atom set gain the new atom

Contract

docs/spec/tir.md lists T.cuda.sm89.Mma among the public declarations and the declarations the CUDA emitter accepts. docs/spec/runtime.md §2.6.5 states how ops::mma selects its atom and each atom's fragment sizes.

Risk

mma.sync names the instruction family, so every architecture document that lists it admits the E4M3 atom. A document for an architecture below 8.9, such as a user supplied sm_80 document, would admit an instruction that hardware lacks. No installed document is affected.

@FeathBow

FeathBow commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Test plan

These ran on a single RTX 6000 Ada Generation (compute capability 8.9) with Python 3.12, PyTorch 2.11 for CUDA 12.9 and nvcc 12.8:

  • The four kernels marked sm89 run on this card, including the new E4M3 one, and all four pass.
  • pytest tests ran on main and on this change; the table is below.
  • pre-commit run --files with every changed file listed passes.

Before this change the new tests fail. The two files that use T.cuda.sm89 do not collect, the sm_89 typing case is refused, the static assert message differs, and the schedule goldens and candidate reports lack the new atom. Each of the three new refusal cases compiles once the check it pins (B's element type, C's element type, B's length) is removed from atom_shaped_v.

A/B on a single sm_89 GPU

pytest tests main this change
collected 2176 2184
passed 2142 2149
failed 21 22
skipped 13 13

The new nodes are the sm80 and sm89 CuTe comparisons (replacing one unparametrized test), the sm_89 typing case, the facts describe T.cuda.sm89.Mma case, the three refusal cases, and the E4M3 GPU test for each target. All of them pass except the H200 node of the E4M3 GPU test, which fails on this GPU like every kernel built for sm_90, with no kernel image is available for execution on the device.

The other 21 failures are the same tests on both sides: 19 launch kernels built for sm_90, and 2 need a second GPU.

@zhen8838 zhen8838 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@zhen8838
zhen8838 merged commit 0d0537d into tile-ai:main Oct 23, 2026
2 checks passed
zhen8838 added a commit that referenced this pull request Oct 23, 2026
…ayout (#263)

## Why

`ops::copy_async` walked the source in vectors but addressed the
destination by element index. When a thread's slice holds more than one
vector and its contiguous run is not the slice's first mode, later
vectors can land at the wrong destination address. An E4M3 GEMM that
stages its K slices through shared memory on sm_89, written on top of
#261, found it: each thread's slice there is (2, 1, 1, 8) with strides
(256, -, -, 1), its second 8-byte vector went to byte 4 instead of byte
256, and in that GEMM this caused a misaligned address error. The GPU
tests so far move one vector per thread, so none of them reached it.

## What

`CopyAsync` now recasts the destination to the source's vector type and
copies vector `i` of the source to vector `i` of the destination. The
scalar width it used for the element index is gone.

The new fixture `tests/fixtures/tir/split_stage.py` holds `SplitStage`,
in which each of 128 threads stages two 16-byte runs of f32 that lie 512
elements apart; `tests/ops/tir/test_copy_async.py` checks on each CUDA
target that the staged tile equals its input exactly, and the TIR
roundtrip test picks the fixture up as it does the others.

## Contract

None. [runtime §2.6.2](docs/spec/runtime.md#262-opscopycuh) already
states that the entry copies exactly the slice `local_tensor` hands each
thread; this makes the asynchronous entry do so.

## Risk

None.

Co-authored-by: Zheng QiHang <zhengqihang0915@qq.com>
zhen8838 added a commit that referenced this pull request Oct 23, 2026
## Why

#261 declared the sm_89 E4M3 warp MMA and tested it as one atom whose
fragments come straight from global memory. Nothing yet ran a GEMM with
it: K slices staged through shared memory, the atom's fragments gathered
from there, the product accumulated over K. Writing that kernel found
the `cp.async` addressing fixed in #263.

## What

One 16 x 8 x 128 E4M3 product, accumulated in F32 on sm_89, from its
schedule down to the GPU.

The schedule is three new fixtures,
`tests/fixtures/schedule/{plain,hir,tir}/sm89_e4m3_gemm.py`: the plain
program (each 32-wide K slice loaded into registers, where the SM89 atom
reads, and multiplied), the scheduled program (one warp; per K slice,
`T.copy_async` into shared memory, `T.copy` into the atom's fragments,
`T.tiled_mma`), and the TIR `schedule finalize` writes for it.
`tests/schedule/test_fixtures.py` adds their entries to the fixture
tables and two `FixtureCase`s that run `tilefoundry check` on both
programs against torch with `--atol 0 --rtol 0`: the inputs are integers
in [-4, 4], so every partial sum is exact and the results must equal the
product.

The kernel, `tests/fixtures/tir/e4m3_gemm.py` (run from
`tests/cuda/ops/tir/test_mma.py`), implements that finalized TIR with
its allocation and order: both staging tiles sit at offset 0 of 512
bytes of dynamic shared memory, and each K step stages and gathers A,
then B, then issues the MMA. It adds what finalize leaves to the
implementation: the commit, wait and warp syncs between those steps, and
the split of each copy over the warp's threads. The test is parametrized
over the H200 and sm_89 targets, the sm_89 case under the `sm89` marker,
and requires the tile to equal the F32 product exactly.

## Contract

None.

## Risk

The kernel's H200 case runs only in CI, on an H200; CI on the previous
head passed with 2301 passed and 18 skipped. The kernel and the fixtures
cover one warp, one 16 x 8 tile and integer inputs, and say nothing
about performance.

---------

Co-authored-by: Zheng QiHang <zhengqihang0915@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants