Repository navigation
[Bugfix] Sync both wave groups before the gfx950 A16W16 HTI GEMM reads its first and last stages - #1269
Open
jhinpan wants to merge 2 commits into
Open
[Bugfix] Sync both wave groups before the gfx950 A16W16 HTI GEMM reads its first and last stages#1269jhinpan wants to merge 2 commits into
jhinpan wants to merge 2 commits into
Conversation
In the half-tile-interleaved path of the gfx950 A16W16 GEMM, waves with wid // n_waves == 1 execute one extra s_barrier in the prologue and run one barrier ahead of the other group. The barrier inside the prologue's wait_vmcnt_and_barrier() therefore completes before that group's own vmcnt wait, and the first group reads stage 0 while the second group's LDS DMA into it may be outstanding. The tail's wait_vmcnt_and_barrier(0) followed by load_a_fragment(0, 1) has the same order. Add one s_barrier for all waves after each of the two waits. Signed-off-by: Jin Pan <jin.pan@amd.com>
jhinpan
marked this pull request as draft
October 13, 2026 06:42
Collaborator
Author
|
Converting to draft until the MI355X timing table is in the description. |
…ng barriers The second wave group runs one barrier ahead, so the barrier after which the first group reads stage 0, and in the tail the stage 1 A half 0, pairs with an earlier barrier of the second group. Make the second group retire those LDS DMA copies before that earlier barrier. This replaces the two barriers for all waves, which cost up to 3.5% on the split-K policies.
jhinpan
marked this pull request as ready for review
October 16, 2026 22:40
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In the half-tile-interleaved (HTI) path of
kernels/gemm/gemm_a16w16_gfx950.py, the first wave group can read LDS that the second group wrote by LDS DMA before the second group has waited for those copies. This PR makes the second group retire those copies before the barrier that releases the read, in the prologue and in the tail. It adds no barrier.Motivation
The ping-pong schedule runs the second wave group (
wid // n_waves == 1) one barrier ahead (line numbers at main):An
s_barriercompletes when every wave has arrived at a barrier, whichever one. So the barrier in the first group'swait_vmcnt_and_barrier(L1037) completes together with the second group'ss_barrier()at L1031, before the second group runs its owns_waitcnt vmcnt. The first group then reads stage 0, part of which the second group wrote withbuffer_load ... lds.vmcntcounts only the issuing wave's copies, and LLVM inserts no wait befores_barrieron gfx950 (it does on gfx908, which lacks back-off barriers), so nothing orders those copies before the read.The tail has the same order for the stage 1 A half 0:
wait_vmcnt_and_barrier(0)(L1106), thenload_a_fragment(0, 1)(L1108). The split-K tail starts withwait_vmcnt_and_barrier(0)(L1088), so split-K is exposed only in the prologue. In the main loop, one mores_barrierfollows eachwait_vmcnt_and_barrierbefore the data is read (L1063, L1083), and that absorbs the offset.How we found it: a sync sanitizer prototype (#1237) reports "LDS read before an async write to it is retired and visible" at exactly these three reads, on MI355X at 4096^3 bf16 with the upstream tuned HTI policy. In the 4,096 reports it kept in full (of 327,680), the reader is in waves 2-3 and the writer in waves 4-7. The output check passes with and without a fix: in both places the second group issues the copies before the first group issues the loads that the first group then waits for, so a wrong result needs the earlier copies to complete after those later loads. We have not seen a wrong result on hardware.
Changes
s_barrier, the second group runss_waitcnt vmcnt(0), so its stage 0 copies retire before its barrier at L1031, which pairs with the first group's barrier at L1037.s_waitcnt vmcnt(half_ldg_b_iters + half_ldg_a_iters). This retires its stage 1 A half 0 copy; the newer B half 1 and A half 1 copies may stay in flight. In the split-K tail the wait finds nothing to wait for.s_barrierfor all waves after L1037 and after L1106 instead. That also closes the race, but the first group then waits for the second group's own wait at L1037 before it reads, and it costs 0.8-3.5% on the split-K policies (table below). Keeping that tail barrier out of split-K did not change this (1.006-1.036): the prologue barrier is the cost.Performance
MI355X, one GPU. The rows are the HTI rows of
test_gemm_a16w16_acc_tuned_policies(which uses a bias) and the "Common PR-tuned" and "Exact HTI split-K winners" rows oftest_gemm_a16w16_acc_split_k, in bf16 with layoutnt.Geometric mean, this PR / baseline: 0.998 on the 14 split-K rows and 0.999 on the 4 tuned rows. First revision: 1.024 and 1.003.
Method: FlyDSL 0.4.0 wheel from PyPI with
kernels/at v0.4.0, whose HTI kernel is identical to this PR's base, and torch 2.10.0+rocm7.2.4. One process loads every variant and interleaves them: call i runs variant i mod 4 on input set i mod N, with N odd and the sets rotated over 2 GiB, so all variants see the same clock and cache state. Each row takes 500 profiled calls per variant; the table shows the median kernel duration, then the median of three runs. An A/A control with the baseline in all four slots stayed within 0.7% on the split-K rows and 0.1% on the tuned rows. Separate runs per variant varied by up to 6% on the 7-10 µs split-K rows, which is why the variants are interleaved.Testing
On MI355X,
tests/kernels/test_gemm_a16w16_gfx950.pywithout the benchmark-marked cases passes with this change: 879 passed, 48 skipped.The sanitizer prototype was not rerun on this revision. Instead, a model of the schedule checks the same rule: it executes the HTI section of the kernel source once per wave group, pairs barriers by count, and retires each wave's copies in order at its
vmcntwaits. For split-K and non-split-K with 2 to 8 K tiles, it reports exactly the three reads above on main and nothing with this change. It reports them again when either new wait is removed or its count is raised by one. In the gfx950 ISA, each news_waitcntsits in the second group's branch before thes_barrierit must precede.Dependencies
Breaking Changes
None.