Skip to content

fix(ck): fix test_gemm_mx correctness failures on gfx1250 (LDS read drain) - #10653

Merged
andriy-ca merged 1 commit into
developfrom
users/andriy/ck/1890-gemm-mx
Aug 13, 2026
Merged

fix(ck): fix test_gemm_mx correctness failures on gfx1250 (LDS read drain)#10653
andriy-ca merged 1 commit into
developfrom
users/andriy/ck/1890-gemm-mx

Conversation

@andriy-ca

@andriy-ca andriy-ca commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

JIRA ID : AICK-1890

Summary

test_gemm_mx fails the correctness check on gfx1250 (A0). Five tests produce sparse wrong values, non-deterministically, at roughly a 50% hit rate per run:

TestGemmMX_MK_NK/0.Large   f8  x f8  -> f16
TestGemmMX_MK_NK/1.Large   f8  x f8  -> bf16
TestGemmMX_MK_NK/2.Large   f4  x f4  -> f16
TestGemmMX_MK_NK/3.Large   f6  x f6  -> f16
TestGemmMX_MK_NK/4.Large   bf6 x bf6 -> bf16

The MX v3 pipeline double-buffers LDS. Each iteration reads one buffer with ds_load while the hardware async copy global_load_async_to_lds_b128 fills the other. The barrier at the top of LoopFunc is what keeps a wave from overwriting a buffer another wave is still reading.

On gfx1250 that barrier is block_sync_lds_async_load(), which waits on ASYNCcnt. LDS reads are tracked by DScnt, a different counter, so the barrier does not wait for them. The compiler normally emits the LDS wait itself, but in this hot loop it computes the weakest wait its per-wave dependency analysis requires and sinks it past the barrier. In the generated ISA a barrier retires with 28 ds_loads still outstanding (s_wait_loadcnt_dscnt 0x11c), immediately followed by an async write into that same buffer.

Per-wave dependency analysis cannot see the cross-wave contract — that the barrier exists so other waves may overwrite the buffer — so the ordering has to be explicit in the source.

The change

One line, in the existing gfx1250 arm of this one pipeline:

#if defined(__gfx125__)
    llvm_amdgcn_s_wait_dscnt(0);
    block_sync_lds_async_load();
#else

A full drain the compiler cannot move past the barrier.

Test plan

All runs on gfx1250 (ASIC rev 0x0).

  • test_gemm_mx full suite: 5 failing tests on every run → 0 failures, 3/3 runs
  • Isolated instance, 20 repetitions per build, measured back to back: 11/20 failures → 0/20
  • example_gemm_mx_fp8 (configured identically to the failing instance): 10/10 incorrect → 0/10
  • ISA verified: the added drain appears before the barrier at every async site; ds_load count unchanged, so this is an ordering fix and not a data-flow change
  • Non-gfx1250 paths untouched (change is inside #if defined(__gfx125__))

Reproducer, for anyone verifying:

./bin/example_gemm_mx_fp8 1 2 1 0 5120 5120 4096 4096 4096 5120 1 20 50

This fails on every run before the fix, which makes it a much better regression gate than the test suite's ~50% hit rate.

Performance

~1.6% throughput on the affected kernel (351.5 → 345.9 TFlops at 5120x5120x4096). The baseline is computing wrong answers, so this is the cost of correctness rather than a regression against a working build.

@therock-pr-bot

therock-pr-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
📝 PR Description ✅ Pass
Forbidden Files ✅ Pass
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: projects/composablekernel/include/ck/tensor_operation/gpu/block/blockwise_gemm_pipeline_xdlops_v3_mx.hpp; no test file found
🚫 Draft PR 🔜 To Be Enabled
🚩 Feature Flag 🔜 To Be Enabled
📊 Code Coverage 🔜 To Be Enabled

🎉 All policy checks passed!

📖 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 Aug 12, 2026

Copy link
Copy Markdown

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

@andriy-ca andriy-ca changed the title fix(ck): drain LDS reads before the async-load barrier on gfx1250 fix(ck): fix test_gemm_mx correctness failures on gfx1250 (LDS read drain) Aug 12, 2026
@andriy-ca
andriy-ca requested a review from jefyang1 August 12, 2026 03:59
@andriy-ca
andriy-ca marked this pull request as ready for review August 12, 2026 03:59
@andriy-ca
andriy-ca requested a review from a team as a code owner August 12, 2026 03:59
@andriy-ca
andriy-ca requested a balanced review from Copilot August 12, 2026 15:18
@jefyang1

Copy link
Copy Markdown
Contributor

LGTM.

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.

Pull request overview

Adds explicit gfx1250 LDS-read draining to prevent asynchronous copies from overwriting buffers still being read.

Changes:

  • Waits for DScnt before the async-load barrier.
  • Documents the cross-wave ordering requirement.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@andriy-ca
andriy-ca enabled auto-merge (squash) August 12, 2026 18:22
@andriy-ca
andriy-ca merged commit 23b36a4 into develop Aug 13, 2026
139 of 180 checks passed
@andriy-ca
andriy-ca deleted the users/andriy/ck/1890-gemm-mx branch August 13, 2026 10:26
assistant-librarian Bot pushed a commit to ROCm/composable_kernel that referenced this pull request Aug 13, 2026
fix(ck): fix test_gemm_mx correctness failures on gfx1250
 (LDS read drain) (#10653)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

JIRA ID : AICK-1890

## Summary

`test_gemm_mx` fails the correctness check on gfx1250 (A0). Five tests
produce sparse wrong values, non-deterministically, at roughly a 50% hit
rate per run:

```
TestGemmMX_MK_NK/0.Large   f8  x f8  -> f16
TestGemmMX_MK_NK/1.Large   f8  x f8  -> bf16
TestGemmMX_MK_NK/2.Large   f4  x f4  -> f16
TestGemmMX_MK_NK/3.Large   f6  x f6  -> f16
TestGemmMX_MK_NK/4.Large   bf6 x bf6 -> bf16
```

The MX v3 pipeline double-buffers LDS. Each iteration reads one buffer
with `ds_load` while the hardware async copy
`global_load_async_to_lds_b128` fills the other. The barrier at the top
of `LoopFunc` is what keeps a wave from overwriting a buffer another
wave is still reading.

On gfx1250 that barrier is `block_sync_lds_async_load()`, which waits on
**ASYNCcnt**. LDS reads are tracked by **DScnt**, a different counter,
so the barrier does not wait for them. The compiler normally emits the
LDS wait itself, but in this hot loop it computes the weakest wait its
per-wave dependency analysis requires and sinks it *past* the barrier.
In the generated ISA a barrier retires with **28 `ds_load`s still
outstanding** (`s_wait_loadcnt_dscnt 0x11c`), immediately followed by an
async write into that same buffer.

Per-wave dependency analysis cannot see the cross-wave contract — that
the barrier exists so *other* waves may overwrite the buffer — so the
ordering has to be explicit in the source.

## The change

One line, in the existing gfx1250 arm of this one pipeline:

```cpp
#if defined(__gfx125__)
    llvm_amdgcn_s_wait_dscnt(0);
    block_sync_lds_async_load();
#else
```

A full drain the compiler cannot move past the barrier.

## Test plan

All runs on gfx1250 (ASIC rev 0x0).

- [x] `test_gemm_mx` full suite: **5 failing tests on every run → 0
failures, 3/3 runs**
- [x] Isolated instance, 20 repetitions per build, measured back to
back: **11/20 failures → 0/20**
- [x] `example_gemm_mx_fp8` (configured identically to the failing
instance): **10/10 incorrect → 0/10**
- [x] ISA verified: the added drain appears before the barrier at every
async site; `ds_load` count unchanged, so this is an ordering fix and
not a data-flow change
- [x] Non-gfx1250 paths untouched (change is inside `#if
defined(__gfx125__)`)

Reproducer, for anyone verifying:

```
./bin/example_gemm_mx_fp8 1 2 1 0 5120 5120 4096 4096 4096 5120 1 20 50
```

This fails on **every** run before the fix, which makes it a much better
regression gate than the test suite's ~50% hit rate.

## Performance

~1.6% throughput on the affected kernel (351.5 → 345.9 TFlops at
5120x5120x4096). The baseline is computing wrong answers, so this is the
cost of correctness rather than a regression against a working build.
shumway pushed a commit to ROCm/composable_kernel that referenced this pull request Aug 18, 2026
fix(ck): fix test_gemm_mx correctness failures on gfx1250 (LDS read drain)

JIRA ID : AICK-1890

## Summary

`test_gemm_mx` fails the correctness check on gfx1250 (A0). Five tests
produce sparse wrong values, non-deterministically, at roughly a 50% hit
rate per run:

```
TestGemmMX_MK_NK/0.Large   f8  x f8  -> f16
TestGemmMX_MK_NK/1.Large   f8  x f8  -> bf16
TestGemmMX_MK_NK/2.Large   f4  x f4  -> f16
TestGemmMX_MK_NK/3.Large   f6  x f6  -> f16
TestGemmMX_MK_NK/4.Large   bf6 x bf6 -> bf16
```

The MX v3 pipeline double-buffers LDS. Each iteration reads one buffer
with `ds_load` while the hardware async copy
`global_load_async_to_lds_b128` fills the other. The barrier at the top
of `LoopFunc` is what keeps a wave from overwriting a buffer another
wave is still reading.

On gfx1250 that barrier is `block_sync_lds_async_load()`, which waits on
**ASYNCcnt**. LDS reads are tracked by **DScnt**, a different counter,
so the barrier does not wait for them. The compiler normally emits the
LDS wait itself, but in this hot loop it computes the weakest wait its
per-wave dependency analysis requires and sinks it *past* the barrier.
In the generated ISA a barrier retires with **28 `ds_load`s still
outstanding** (`s_wait_loadcnt_dscnt 0x11c`), immediately followed by an
async write into that same buffer.

Per-wave dependency analysis cannot see the cross-wave contract — that
the barrier exists so *other* waves may overwrite the buffer — so the
ordering has to be explicit in the source.

## The change

One line, in the existing gfx1250 arm of this one pipeline:

```cpp
#if defined(__gfx125__)
    llvm_amdgcn_s_wait_dscnt(0);
    block_sync_lds_async_load();
#else
```

A full drain the compiler cannot move past the barrier.



## Test plan

All runs on gfx1250 (ASIC rev 0x0).

- [x] `test_gemm_mx` full suite: **5 failing tests on every run → 0
failures, 3/3 runs**
- [x] Isolated instance, 20 repetitions per build, measured back to
back: **11/20 failures → 0/20**
- [x] `example_gemm_mx_fp8` (configured identically to the failing
instance): **10/10 incorrect → 0/10**
- [x] ISA verified: the added drain appears before the barrier at every
async site; `ds_load` count unchanged, so this is an ordering fix and
not a data-flow change
- [x] Non-gfx1250 paths untouched (change is inside `#if
defined(__gfx125__)`)

Reproducer, for anyone verifying:

```
./bin/example_gemm_mx_fp8 1 2 1 0 5120 5120 4096 4096 4096 5120 1 20 50
```

This fails on **every** run before the fix, which makes it a much better
regression gate than the test suite's ~50% hit rate.

## Performance

~1.6% throughput on the affected kernel (351.5 → 345.9 TFlops at
5120x5120x4096). The baseline is computing wrong answers, so this is the
cost of correctness rather than a regression against a working build.
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.

5 participants