[AMDGPU][gfx950] contiguous ds_read_b64 of swizzled LDS races at -O3; fine at -O0
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
On **gfx950 (CDNA4)** a `convert_layout` that round-trips through LDS produces run-to-run **nondeterministic** output when its reads are lowered to contiguous **`ds_read_b64`** and compiled at `-O3`. Lowering the *same convert* to a strided **`ds_read2_b32`** read is bit-stable. Pure backend reproducer: `llc` + `llvm-mc` + `ld.lld` + a HIP driver, no Triton needed to run.
Reproducer is [here](https://github.com/jerryyin/scripts/tree/master/triton/reproducers/amdgpu_ds_read_b64_waitcnt).
### Base case — three experiments
All built **from LLVM IR**; assembly is saved only as an inspection intermediate.
| exp | source IR | opt | read | result (3 × 5000 runs) |
|---|---|---|---|---|
| A | `ir/micro-dot.racy.ll` | `-O0` | `ds_read_b64` | `worst=0.000` stable |
| B | `ir/micro-dot.racy.ll` | `-O3` | `ds_read_b64` | `worst=0.125` **races** |
| C | `ir/micro-dot.stable.ll` | `-O3` | `ds_read2_b32` | `worst=0.000` stable |
```bash
./reproduce.sh # build A/B/C from IR + runtime (needs gfx950)
./reproduce.sh codegen # build A/B/C + opcode/wait table (no GPU)
./reproduce.sh irdiff # the clean LLVM IR diff (no GPU)
```
The race reproduces only in **B**. Changing either variable stabilizes it: **B→C** isolates the read lowering (at `-O3`); **B→A** isolates the opt level.
### The B↔C difference at the LLVM IR level
`ir/micro-dot.racy.ll` and `ir/micro-dot.stable.ll` are from the **same Triton commit** (toggling only the `GenericSwizzling` swizzle guard), so the diff
(`reproduce.sh irdiff`) is *entirely* the convert read + its address swizzle:
```llvm
; racy (B) ; stable (C)
%v = load <2 x float>, ptr addrspace(3) %a %lo = load <1 x float>, ptr addrspace(3) %a
%hi = load <1 x float>, ptr addrspace(3) %a+128
; + reassembly shufflevector
```
Codegen counts are otherwise identical (`reproduce.sh codegen`): same `ds_write`, and B vs C have the same `s_waitcnt` (12) and `s_barrier`. Do **not** line-diff the `.s` files — the asm diff is dominated by `llc` register allocation and packed-op (`op_sel`) selection cascading from this small IR change.
### What does not change the B result
Holding the racy `-O3` build and editing only the read does **not** help:
```text
replace ds_read_b64 with a same-address ds_read_b32 pair -> still races
replace ds_read_b64 with ds_read2_b32 (read edit only) -> still races
extra immediate s_waitcnt lgkmcnt(0) / lgkmcnt(1)->lgkmcnt(0) -> still races
```
So the read mnemonic in isolation is not the variable; the contiguous read *together with its matching write layout* is. Only the full strided lowering (C) avoids it.
### Selection knob (`ir/knob.ll`)
Two ~6-line kernels isolate the bare instruction-selection difference (no race):
```bash
llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx950 -O3 ir/knob.ll -o knob.s
# @contig (load <2 x float>) -> ds_read_b64
# @strided (2x stride load + pack) -> ds_read2st64_b32 offset1:1
```
### Hypothesis
With the `-O3` schedule, the consumer of a contiguous `ds_read_b64` from cross-wavefront-written LDS can observe stale VGPRs despite a correct visible `lgkmcnt` wait and `s_barrier`. The strided `ds_read2_b32` lowering of the same convert does not exhibit it, and the `-O0` schedule of the same `ds_read_b64` build (A) also does not — the reason `-O0` is stable is open. This looks like a gfx950 LDS/MFMA scheduling/timing interaction specific to the contiguous `ds_read_b64` convert.
### Environment
- gfx950 (CDNA4 / MI35X), ROCm 7.2.4
- `llc` / `llvm-mc` / `ld.lld`: LLVM `23.0.0git` (`87717bf9`); assemble/link also works with the ROCm-bundled LLVM in `/opt/rocm-7.2.4/lib/llvm/bin`.
Attached: `ir/micro-dot.racy.ll`, `ir/micro-dot.stable.ll`, `ir/knob.ll`, `asm/{A_racy_O0,B_racy_O3,C_stable_O3}.s` (inspection intermediates),
`driver_microdot.cpp`, `reproduce.sh`.
---
*A flash-attention kernel reproduces a consistent contiguous-`ds_read_b64` vs strided contrast and is included as a secondary knob (`reproduce.sh attn`); it is not part of this base case.*
Contributor guide
Research direction
Start with the attached ir/micro-dot.racy.ll and ir/micro-dot.stable.ll, then run reproduce.sh irdiff and reproduce.sh codegen to verify the reported IR and wait-count differences. Use reproduce.sh on gfx950 to confirm the A/B/C behavior and inspect the referenced LLVM AMDGPU lowering and scheduling paths. Done means identifying the cause of the -O3 contiguous-read race and establishing a stable result without regressing the contrasting cases.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100