|
| 1 | +# TRITON-SIMT-COALESCE — iteration 7: SIMT-coalesced de-risk, the EXECUTED ms answer |
| 2 | + |
| 3 | +**Date:** 2026-06-08 |
| 4 | +**Branch/HEAD:** `merge/upstream-codegen-reorg` @ `c2796a70` |
| 5 | +**Target:** NVIDIA GB10, sm_121a (aarch64-linux), CUDA 13.x |
| 6 | +**Kernel under test:** routed `_chunk_scan_bwd_dstates` at §P1 |
| 7 | +(`b1 nh112 hd64 ds64 nc64 cs64`, s=4096, grid `(1,64,112)`) |
| 8 | + |
| 9 | +## The question (4 iterations could not answer) |
| 10 | + |
| 11 | +Does routing the dstates K-loop `dout`/`C` global→shared `T.copy` as a **coalesced |
| 12 | +load** measurably **drop §P1 ms**? Iterations 3–6 kept hitting TMA: iter-6 got the C |
| 13 | +tile to lower to a real `UTMALDG` and the kernel LAUNCHED, but the **sm_90 TMA template |
| 14 | +(`cp.async.bulk.tensor.2d`) FAULTS at runtime on sm_121** (compute-sanitizer: illegal |
| 15 | +instruction at `tl::tma_load … copy_sm90.h:96`). So no executed ms ever came out. |
| 16 | + |
| 17 | +## What iteration 7 did — SIDESTEP TMA, route SIMT |
| 18 | + |
| 19 | +Set `disable_tma=True` on the routed C/dout CopyNodes so `Copy::Lower` |
| 20 | +(`src/backend/cuda/op/copy.cc`, gated by `SelectCopyInstForLowering` on `!disable_tma`) |
| 21 | +takes the **SIMT branch** (plain `LDG`), never `LowerBulk`/TMA. No tensorMap, no |
| 22 | +`cp.async.bulk.tensor`, no sm_90 template — the sm_121 fault is gone. |
| 23 | + |
| 24 | +### The lever (no hack): restore the per-Call annotations channel |
| 25 | + |
| 26 | +In this fork `tirx.call_intrin` did `del annotations, kwargs` and the FFI `tirx::Call` |
| 27 | +factory took only 4 args, so `T.copy(..., disable_tma=True)` **silently dropped** the |
| 28 | +annotation and the copy was force-classified TMA-bulk. Fixed in `3rdparty/tvm` |
| 29 | +(committed source, byte-identical on gb10): |
| 30 | + |
| 31 | +- `python/tvm/tirx/op.py`: `del kwargs` only; `return Call(dtype, func_name, args, span, annotations=annotations)` |
| 32 | +- `src/tirx/ir/expr.cc`: 5-arg `tirx::Call` ctor threads `annotations` → `CallNode::annotations`; absent ⇒ unchanged 4-arg path |
| 33 | +- `python/tvm/tirx/expr.py`, `src/target/llvm/codegen_cpu.cc`: supporting |
| 34 | + |
| 35 | +This is a *restore* of a real channel (the old `del annotations` was itself a silent |
| 36 | +fallback), not a special-case. `disable_tma` now reaches `CopyNode`. |
| 37 | + |
| 38 | +Route lever: `poc/triton_frontend/op_emitters/memory.py` |
| 39 | +`_emit_ptrstate_tile_load_copynode` now emits `T.copy(src2d, tile_buf, disable_tma=True)` |
| 40 | +unconditionally for the routed C/dout tiles. |
| 41 | + |
| 42 | +## MEASURED FROM HEAD (c2796a70) on gb10 — all EXECUTED, runs-to-completion |
| 43 | + |
| 44 | +Harness: committed `poc/triton_frontend/_test_harness/tridao_parity/measure_dstates_simt_coalesced.py` |
| 45 | +(sha256 `4a63d05…`), N=60/rep × 4 reps, interleaved OFF/OPT, CUDA-event median-of-medians. |
| 46 | + |
| 47 | +| metric | value | |
| 48 | +|---|---| |
| 49 | +| **compute-sanitizer memcheck** | **0 errors** — LAUNCHES + RUNS TO COMPLETION, no TMA fault | |
| 50 | +| PARITY OFF | MAXDIFF **4.882812e-04** ALLCLOSE PASS | |
| 51 | +| PARITY OPT | MAXDIFF **4.882812e-04** ALLCLOSE PASS (bit-identical to HEAD) | |
| 52 | +| OPT cubin TMA | UTMALDG=0, no `tma_load`/`tensorMap`/`cp.async.bulk.tensor`/LDGSTS | |
| 53 | +| **OPT_MS** (SIMT route) | **1101.97 ms** | |
| 54 | +| OFF_MS (un-routed serial) | 3145.47 ms | |
| 55 | +| speedup vs OFF | 2.85x | |
| 56 | +| HMMA (GEMM cooperative) | **32** (intact) | |
| 57 | +| native triton §P1 | **1.146 ms** (median N=100; min 1.139) | |
| 58 | + |
| 59 | +### EXECUTED SASS of the sm_121a cubin (fresh cuobjdump -sass, this run) |
| 60 | + |
| 61 | +``` |
| 62 | +LDG.E.CONSTANT 38 # scalar 32-bit global loads (the routed C/dout loads) |
| 63 | +LDG.E.128 0 # NO vectorized coalesced global loads |
| 64 | +LDGSTS / UTMALDG 0 # no cp.async, no TMA |
| 65 | +STS.128 / LDS.128 324/108 # wide ops are SHARED-side only |
| 66 | +STL.128 / LDL.128 168/136 # wide ops are LOCAL-side only |
| 67 | +HMMA 32 # GEMM tensor-core path intact |
| 68 | +STG.E 4 |
| 69 | +``` |
| 70 | + |
| 71 | +## THE COALESCING VERDICT (RULE #1 honest) |
| 72 | + |
| 73 | +**Coalescing does NOT measurably drop §P1 ms — because the loads did not actually |
| 74 | +become coalesced 128-bit loads.** The executed cubin shows **LDG.E.128 = 0 / |
| 75 | +LDG.E.CONSTANT = 38**: the routed C/dout global loads stayed **scalar**. OPT_MS = |
| 76 | +1101.97 ms == the prior *routed* best (~1102 ms); the SIMT route ran to completion and |
| 77 | +TMA is fully sidestepped, but it delivered no coalescing speedup. |
| 78 | + |
| 79 | +**Root cause:** the **swizzled shared-memory destination layout** breaks float4 |
| 80 | +vectorizability of the *global* load. The wide 128-bit traffic is all shared/local |
| 81 | +(`STS.128`/`LDS.128`/`STL.128`/`LDL.128`) — the SIMT loop vectorizer cannot prove a |
| 82 | +contiguous 128-bit window on the global side under the swizzle, so it emits scalar |
| 83 | +`LDG.E.CONSTANT`. Contiguity of the ds/hd axis (stride 1) is necessary but not |
| 84 | +sufficient: the swizzle on the destination defeats the source-side vectorizer. |
| 85 | + |
| 86 | +The 2.85x vs OFF is the **addressing-fold / route restructuring** win (un-routed serial |
| 87 | +→ routed), NOT a coalescing win. It is identical to the prior routed best with TMA |
| 88 | +removed. |
| 89 | + |
| 90 | +## Remaining gap to native (honest) |
| 91 | + |
| 92 | +OPT **1101.97 ms** vs native **1.146 ms** ⇒ **~961x slower than native**. The 2.85x is |
| 93 | +only vs the un-routed serial OFF baseline. Coalescing was the lever to start closing the |
| 94 | +961x gap; it did not fire because the loads stayed scalar. The dominant cost is |
| 95 | +elsewhere (per-element scalar global traffic + the routed serial structure), not the |
| 96 | +load width alone. |
| 97 | + |
| 98 | +## Separate blocker (not the coalescing question) |
| 99 | + |
| 100 | +`async_loads=True` (pipelining the masked routed copy into cp.async) hits a distinct |
| 101 | +`PipelinePlanning` overlapping-write conflict (CopyNode + mask epilogue both write the |
| 102 | +tile). Out of scope here; the coalescing property is a load-width property independent of |
| 103 | +pipelining and is answered above. |
| 104 | + |
| 105 | +## GO / NO-GO |
| 106 | + |
| 107 | +**NO-GO on "SIMT-coalesced loads drop §P1 ms" as a speedup lever — on the merits, with |
| 108 | +the executed number that 4 iterations could not produce.** The kernel finally runs to |
| 109 | +completion (sanitizer-clean, parity bit-correct), TMA is sidestepped, but the loads do |
| 110 | +not vectorize to LDG.E.128 under the swizzled shared destination, so ms does not drop. |
| 111 | +To actually realize coalescing the **shared-destination swizzle must be removed/aligned** |
| 112 | +so the global load can be proven 128-bit-contiguous — that is the next lever, not more |
| 113 | +TMA work. |
| 114 | + |
| 115 | +## Reproduce from HEAD (c2796a70) |
| 116 | + |
| 117 | +gb10 working tree byte-identical to committed sources (sha256-verified): |
| 118 | +`memory.py` `a6c7218…`; tvm `expr.py/op.py/expr.cc/codegen_cpu.cc` all match `4418b2fe`. |
| 119 | +`libtvm_compiler.so` newer than the touched `.cc` (fix compiled in). |
| 120 | + |
| 121 | +``` |
| 122 | +ssh gb10 'cd /home/dave/source/tilelang && \ |
| 123 | + /home/dave/cppmega-venv/bin/python \ |
| 124 | + poc/triton_frontend/_test_harness/tridao_parity/measure_dstates_simt_coalesced.py' |
| 125 | +# compute-sanitizer: |
| 126 | +ssh gb10 'cd /home/dave/source/tilelang && /usr/local/cuda/bin/compute-sanitizer \ |
| 127 | + --tool memcheck /home/dave/cppmega-venv/bin/python /tmp/ttir7/sanitizer_dstates.py' |
| 128 | +# SASS: |
| 129 | +ssh gb10 '/usr/local/cuda/bin/cuobjdump -sass \ |
| 130 | + /tmp/tvm-debug-mode-tempdirs/<OPT-tempdir>/00000/tvm_kernels.cubin' |
| 131 | +``` |
| 132 | + |
| 133 | +(`/tmp/ttir7/_chunk_scan_bwd_dstates.ttir` is the routed TTIR input.) |
0 commit comments