|
| 1 | +# TRITON cp.async/LDGSTS LIVE + bit-correct on the EXECUTED §P1 path (EXECUTEMEASURE) |
| 2 | + |
| 3 | +Status: **GO**. cp.async/LDGSTS is now LIVE on the EXECUTED §P1 dstates path WITH |
| 4 | +torch+triton imported, AND bit-correct (MAXDIFF 4.882812e-04, NOT the racy 1.28e3). |
| 5 | +Commit `a4c1bedf` (3 files, 64 insertions; all in tilelang). Measured on gb10 |
| 6 | +(NVIDIA GB10, sm_121, CUDA 13.2), gb10-only GPU exec. |
| 7 | + |
| 8 | +## What was measured (EXECUTED, torch+triton in sys.modules) |
| 9 | + |
| 10 | +Harness: `poc/triton_frontend/_test_harness/tridao_parity/em_dstates_cpasync.py` |
| 11 | +(+ timing re-confirm `em_timing_only.py`). Both `import torch, triton` FIRST so |
| 12 | +libtriton's static LLVM is loaded — i.e. the real executed §P1 path, NOT a |
| 13 | +compile-only probe. §P1 = `_chunk_scan_bwd_dstates` (b1 nh112 hd64 ds64 nc64 cs64), |
| 14 | +grid (1,64,112). |
| 15 | + |
| 16 | +- **OFF** = `from_ttir(..., prologue_opt=False)` — un-routed serial baseline, plain LDG. |
| 17 | +- **OPT** = `from_ttir(..., prologue_opt=True)` + `TL_FORCE_CP_ASYNC=1` — routed |
| 18 | + CopyNode emits `is_async_copy` → genuine cp.async/LDGSTS, race-closed by |
| 19 | + `cp.async.commit_group` + `cp.async.wait_group<0>` + CTA sync (BUG-B fix). |
| 20 | + |
| 21 | +### 1. EXECUTED cubin SASS (the exact objects launched in this process) |
| 22 | + |
| 23 | +| kernel | UTMALDG | LDGSTS | HMMA | LDG | spill | |
| 24 | +|--------|---------|--------|------|-----|-------| |
| 25 | +| OFF (plain LDG) | 0 | **0** | 32 | 128 | 2494 | |
| 26 | +| OPT (cp.async live) | 0 | **16** | 32 | 96 | 272 | |
| 27 | + |
| 28 | +- OPT genuine opcodes: `LDGSTS.E` ×16 (real global→shared async loads). |
| 29 | +- OPT completion barrier present: `DEPBAR.LE` (cp.async.wait_group<0>) + `BAR.SYNC.DEFER` |
| 30 | + (CTA sync) — the BUG-B race-close, in the executed cubin. |
| 31 | +- UTMALDG=0 on both (GB10 has no functional bulk-TMA; this is the LDGSTS path |
| 32 | + native Triton uses on GB10). |
| 33 | + |
| 34 | +### 2. Parity (both bit-correct — cp.async is correct, NOT racy) |
| 35 | + |
| 36 | +``` |
| 37 | +PARITY OFF MAXDIFF=4.882812e-04 ALLCLOSE_1e-3=True nonzero=29360128/29360128 PASS |
| 38 | +PARITY OPT MAXDIFF=4.882812e-04 ALLCLOSE_1e-3=True nonzero=29360128/29360128 PASS |
| 39 | +``` |
| 40 | + |
| 41 | +OPT (cp.async live) == 4.882812e-04, the §P1 target — NOT the racy 1.28e3. |
| 42 | +nonzero = full 29360128/29360128 (no per-block base drop, no skipped elements). |
| 43 | +Re-confirmed in a second independent process (both MAXDIFF=4.882812e-04 PASS). |
| 44 | + |
| 45 | +### 3. EXEC ms (CUDA-events, N=50–60/rep ×4 reps, interleaved OFF/OPT, median-of-medians) |
| 46 | + |
| 47 | +| path | ms | notes | |
| 48 | +|------|-----|-------| |
| 49 | +| OFF (plain LDG, fresh build) | **3139.34** | honest fresh-build OFF (~3147 expected) | |
| 50 | +| OPT (cp.async live, bit-correct) | **745.62** | LDGSTS=16, MAXDIFF 4.88e-04 | |
| 51 | +| native Triton ref | **1.2311** | mamba_ssm `_chunk_scan_bwd_dstates_kernel` | |
| 52 | + |
| 53 | +- **DELTA = 2393.73 ms drop** (OFF − OPT), real, interleaved, BOTH bit-correct. |
| 54 | +- **SPEEDUP = 4.21× vs fresh OFF 3139 ms.** |
| 55 | +- Per-rep stability (OFF/OPT): 3144.97/740.47, 3143.51/741.88, 3141.81/740.90, |
| 56 | + 3138.87/745.62 — tight, no outliers. |
| 57 | + |
| 58 | +KEY answer: **with cp.async REACHING + CORRECT, §P1 ms drops measurably** — 3139 → |
| 59 | +746 ms (4.21×), a 2.39 s real reduction on the executed, bit-correct kernel. |
| 60 | + |
| 61 | +### 4. Remaining gap to native |
| 62 | + |
| 63 | +OPT 745.62 ms vs native 1.2311 ms = **605.65× gap remaining**. cp.async removed the |
| 64 | +single biggest serial-prologue cost (4.21×), but the routed kernel is still a |
| 65 | +de-monomorphized single-stage SIMT GEMM with heavy register spill (272 STL+LDL, |
| 66 | +down from 2494) and no multi-stage pipelining / tensor-core scheduling that the |
| 67 | +native autotuned Triton kernel has. The gap is dominated by (a) no software |
| 68 | +pipeline over the K-loop (single-stage cp.async, wait<0> every trip), (b) spill, |
| 69 | +(c) tile/occupancy not autotuned. Those are the next levers, not cp.async reach. |
| 70 | + |
| 71 | +## No-regression |
| 72 | + |
| 73 | +- **Standard pipelined GEMM (disable_tma, the executable GB10 LDGSTS path)**: |
| 74 | + `GEMM_DISABLE_TMA 512^3 stages=2 MAXDIFF=3.12e-02 REL=2.81e-04 PASS`, |
| 75 | + SASS UTMALDG=0 LDGSTS=16 HMMA=128. This path goes through copy.cc's |
| 76 | + `no_implicit_commit_wait` branch (returns BEFORE the explicit-async edit) and |
| 77 | + `elem_offset==0` (lower_access_ptr adds a constant 0 → no-op). Bit-correct, |
| 78 | + byte-path unchanged. |
| 79 | +- **Default bulk-TMA GEMM (example_gemm)**: compiles to UTMALDG=48 and faults |
| 80 | + `CUDA_ERROR_ILLEGAL_INSTRUCTION` at execution — this is the PRE-EXISTING GB10 |
| 81 | + bulk-TMA hardware limitation (GB10 lacks cluster/2-CTA TMA), independent of this |
| 82 | + change (the change does not touch the TMA bulk path). |
| 83 | +- **Prod default sm_121a untouched**: the prod default is `prologue_opt=False` |
| 84 | + (no TL_FORCE_CP_ASYNC) = the OFF path, measured 4.882812e-04. The async path is |
| 85 | + honestly env-gated (`TL_FORCE_CP_ASYNC=1`), never a silent fallback. |
| 86 | +- **path_c untouched**: all 3 changed files are in tilelang |
| 87 | + (`poc/triton_frontend/op_emitters/memory.py`, `src/backend/cuda/op/copy.cc`, |
| 88 | + `src/transform/lower_access_ptr.cc`); no cppmega_mlx / path_c files touched. |
| 89 | + |
| 90 | +## Root fixes (commit a4c1bedf) |
| 91 | + |
| 92 | +- **BUG-A (reach)**: emission (no triton → text-walker reaches the CopyNode |
| 93 | + emitter) is decoupled from execution (loads the PrimFunc, compiles WITH triton). |
| 94 | + The cp.async copynode needs only `T.copy`, never the libtriton-conflicting |
| 95 | + PtrAnalysis C++ shim. LDGSTS=16 now lands in the executed cubin with torch+triton |
| 96 | + imported. |
| 97 | +- **BUG-B (correctness)**: the 1.28e3 was a per-block base DROP, not a race — |
| 98 | + `LinearOffsetFromLoad` (lower_access_ptr.cc) omitted `buffer->elem_offset`, so the |
| 99 | + routed 2D strided view read block 0's slice for every CTA. Fix adds elem_offset |
| 100 | + (no-op for elem_offset==0 buffers). Plus copy.cc closes the explicit single-stage |
| 101 | + cp.async group out-of-line (commit_group + wait_group<0> + CTA sync) so a bare |
| 102 | + `is_async_copy` is race-free without a software pipeline. |
| 103 | + |
| 104 | +## Reproduce (gb10) |
| 105 | + |
| 106 | +``` |
| 107 | +ssh gb10 |
| 108 | +cd /home/dave/source/tilelang && source /home/dave/cppmega-venv/bin/activate |
| 109 | +PYTHONPATH=/home/dave/source/tilelang python \ |
| 110 | + poc/triton_frontend/_test_harness/tridao_parity/em_dstates_cpasync.py |
| 111 | +# SASS: TL_FORCE_CP_ASYNC=1 python .../sass_dstates.py -> OFF LDGSTS=0, OPT LDGSTS=16 |
| 112 | +``` |
| 113 | + |
| 114 | +## GO/NO-GO |
| 115 | + |
| 116 | +**GO.** cp.async/LDGSTS is LIVE on the EXECUTED §P1 path WITH triton imported |
| 117 | +(LDGSTS=16, UTMALDG=0 in the launched cubin) AND bit-correct (4.882812e-04, not |
| 118 | +racy). §P1 ms drops 3139 → 746 ms (4.21×, real, interleaved, both bit-correct). |
| 119 | +Remaining gap to native 1.23 ms = 605.65× (next levers: K-loop software |
| 120 | +pipeline, spill reduction, tile autotune — not cp.async reach). No regression; |
| 121 | +prod default + path_c untouched. Reproducible from HEAD a4c1bedf. |
0 commit comments