Skip to content

fix(runtime): address each cp.async vector in the destination's own layout - #263

Merged
zhen8838 merged 2 commits into
tile-ai:mainfrom
FeathBow:fix/copy-async-vectors
Oct 23, 2026
Merged

zhen8838 merged 2 commits into
tile-ai:mainfrom
FeathBow:fix/copy-async-vectors

Conversation

@FeathBow

Copy link
Copy Markdown
Contributor

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 already states that the entry copies exactly the slice local_tensor hands each thread; this makes the asynchronous entry do so.

Risk

None.

@FeathBow

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:

  • On main's copy_impl.h, the new test's sm_89 node, run alone in its own process, fails with CUDA error: misaligned address. With this change it passes, and so does the existing staging test.
  • pytest tests ran on main and on this change; the table is below.
  • pre-commit run --files with every changed file listed passes, and so does pre-commit run --all-files.

A/B on a single sm_89 GPU

pytest tests main this change
collected 2176 2180
passed 2142 2145
failed 21 22
skipped 13 13

The four new nodes are the new test for each target, the TIR roundtrip of the new fixture and the parser's source location check on it. All pass except the new test's H200 node, which fails on this GPU like every kernel built for sm_90, with no kernel image is available for execution on the device. No existing node changed.

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 76d93f7 into tile-ai:main Oct 23, 2026
1 check passed
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