|
| 1 | +# Triton Prologue Optimizer — routed Tri-Dao chunk kernels |
| 2 | + |
| 3 | +Gated prologue optimizer for the routed-triton path in the TileLang triton |
| 4 | +frontend (`poc/triton_frontend`). Three transforms were scoped; this records |
| 5 | +which landed, with MEASURED numbers reproducible from committed HEAD on gb10 |
| 6 | +(sm_121, aarch64-linux). |
| 7 | + |
| 8 | +Source HEAD (tilelang): `29888fee` — "feat(triton-frontend): prologue-opt |
| 9 | +transforms 1+3 (drop int32-overflow guards, fold masks) + gated |
| 10 | +thread-distribute machinery". The gb10 source tree |
| 11 | +(`/home/dave/source/tilelang`) working copy of the 4 touched files is |
| 12 | +byte-identical (shasum) to this commit; the measurements below regenerate the |
| 13 | +PrimFunc + CUDA from HEAD and re-run. |
| 14 | + |
| 15 | +## The gap (recap) |
| 16 | + |
| 17 | +Routed `_chunk_scan_bwd_dstates` is CORRECT through our stack at §P1 but |
| 18 | +~1290x slower than native (≈1473 ms vs 1.15 ms/kernel). strip-and-time: |
| 19 | +~100% of the ms is the PROLOGUE; the cooperative GEMM half (T.copy/T.gemm, |
| 20 | +tensor-core, threadIdx-swizzled) is ~0 ms. The prologue is serial scalar |
| 21 | +index/mask arrays that every thread of every block rebuilds element-by-element. |
| 22 | + |
| 23 | +## What landed |
| 24 | + |
| 25 | +| Transform | Status | Evidence | |
| 26 | +|---|---|---| |
| 27 | +| (1) Fold address arithmetic into T.copy region | PARTIAL — mask/guard-chain folding only | `_emit_andi` folds `constant-true & x -> x` (gated). Deeper region-index folding NOT done. | |
| 28 | +| (2) Thread-distribute the prologue | IMPLEMENTED, gated OFF | machinery works (184 threadIdx when forced ON) but ptxas: too much shared data. | |
| 29 | +| (3) Drop int32-overflow guards | LANDED | `arith.cmpi addr<=INT32_MAX / INT32_MIN<=addr -> constant-true tile`. | |
| 30 | + |
| 31 | +## MEASURED — IR / CUDA shape (regen from HEAD, gb10) |
| 32 | + |
| 33 | +Generated CUDA for `_chunk_scan_bwd_dstates` (de-monomorphized §P1 PrimFunc, |
| 34 | +symbolic flat-arg extents): |
| 35 | + |
| 36 | +| build | json bytes | .cu bytes | for-loops | int32 guards | threadIdx | mma | |
| 37 | +|---|---|---|---|---|---|---| |
| 38 | +| `prologue_opt=False` (baseline) | 497670 | 37174 | 158 | 28 | 25 | 2 | |
| 39 | +| `prologue_opt=True` (HEAD) | 332061 | 26685 | 107 | 0 | 25 | 2 | |
| 40 | + |
| 41 | +Transforms 1+3 remove **51 for-loops and all 28 int32-overflow-guard |
| 42 | +references** (the `2147483647` / `-2147483648` literals + their bool[] AND |
| 43 | +loops). threadIdx count is unchanged (25, all inside the GEMM) — confirming |
| 44 | +transform 2 did NOT alter thread distribution in the shipped build. |
| 45 | + |
| 46 | +## MEASURED — §P1 parity (gb10, CUDA_LAUNCH_BLOCKING) |
| 47 | + |
| 48 | +`parity_prod_dstates.py`, real production config |
| 49 | +(b1 nh112 hd64 ds64 nc64 cs64, numel 29,360,128, grid (1,64,112)), |
| 50 | +2 K-trips/chunk (cs64 / BK32): |
| 51 | + |
| 52 | +``` |
| 53 | +MAXDIFF = 4.882812e-04 ALLCLOSE_1e-3 = True PASS |
| 54 | +``` |
| 55 | + |
| 56 | +Bit-identical to the pre-change baseline (the gate is a numeric no-op). |
| 57 | + |
| 58 | +Small real-strided multi-K-trip (b1 nh8 s128 nc2, cs64/BK32 → 2 trips, |
| 59 | +vs native mamba_ssm kernel): |
| 60 | + |
| 61 | +``` |
| 62 | +MAXDIFF = 4.577637e-05 ALLCLOSE_1e-3 = True PASS |
| 63 | +``` |
| 64 | + |
| 65 | +## MEASURED — §P1 EXEC ms (routed, interleaved OFF/OPT/OFF/OPT) |
| 66 | + |
| 67 | +``` |
| 68 | +OFF (prologue_opt=False): 1468.21, 1477.25 ms mean ~1472.7 |
| 69 | +OPT (prologue_opt=True): 1469.99, 1476.92 ms mean ~1473.5 |
| 70 | +``` |
| 71 | + |
| 72 | +**Per-transform delta (1+3 together): +0.8 ms (~0.05%) — within run-to-run |
| 73 | +noise. No measurable §P1 speedup.** Ratio vs native 1.15 ms: ~1281x (was |
| 74 | +~1290x). The 51 dropped serial loops are cheap relative to the dominant cost; |
| 75 | +the remaining serial prologue (materialized [64]/[2048]/[4096] index+mask |
| 76 | +arrays and per-lane redundant address arithmetic) is untouched by 1+3. Only |
| 77 | +transform 2 would move the needle, and it is blocked (below). |
| 78 | + |
| 79 | +## Transform 2 blocker (MEASURED, RULE #1 fail-closed) |
| 80 | + |
| 81 | +Forcing `thread_distribute=True` (prologue_opt=True): the walker DOES |
| 82 | +thread-distribute — threadIdx jumps 25 → 184, the [64]/[4096] tiles become |
| 83 | +cooperative shared-fill loops with `__syncthreads`. But it promotes 63 prologue |
| 84 | +tiles to `__shared__` (multiple [4096] int64/int/bool arrays), and NVRTC/ptxas |
| 85 | +fails: |
| 86 | + |
| 87 | +``` |
| 88 | +ptxas error : Entry function '_chunk_scan_bwd_dstates_kernel' uses too much |
| 89 | + shared data (0x1b460 bytes, 0x18c00 max) |
| 90 | +``` |
| 91 | + |
| 92 | +111712 bytes/block vs the 101376-byte (99 KiB) sm_121 limit. Distribution |
| 93 | +requires shared scope (cooperative fill + barrier; a thread-local distributed |
| 94 | +write would leave 127/128 slots per lane uninitialized — forbidden). The |
| 95 | +correct fix is full transform (1): fold the addressing into the T.copy region |
| 96 | +indices/predicate so these tiles are never materialized in ANY scope; the small |
| 97 | +residue can then be thread-distributed in shared within budget. Until |
| 98 | +address-folding lands, distribution is gated OFF — it RAISES (ptxas error) |
| 99 | +rather than emit a racy local write or silently overflow. This is the honest |
| 100 | +blocker, not a silent fallback. |
| 101 | + |
| 102 | +## MEASURED — other-paths no-regression |
| 103 | + |
| 104 | +- **fla** (`fla_chunk_delta_h_real_ttir.mlir`): generated CUDA is |
| 105 | + BYTE-IDENTICAL with prologue_opt on/off (sha `581db03dbb91`, len 29515). The |
| 106 | + gate is a no-op — fla has no int32-overflow guards / constant-true ANDs. |
| 107 | +- **matmul** (M=N=K=64): the .cu DIFFERS (opt_ON 14727 B vs opt_OFF 22126 B — |
| 108 | + opt also folds matmul's addressing guards), but the OUTPUT is **bit-identical |
| 109 | + on vs off (maxdelta 0.000e+00)** and **EXEC ms is identical: 1.343 ms (opt) |
| 110 | + vs 1.343/1.346 ms (off)**. The .cu size delta is dead-code prologue arrays |
| 111 | + ptxas eliminates; the GEMM compute is unchanged. No regression. |
| 112 | + |
| 113 | +Note: the IMPLEMENT-note claim "non-routed matmul/fla paths default OFF … and |
| 114 | +are untouched" is imprecise — any kernel routed through `from_ttir` gets |
| 115 | +`prologue_opt=True` by default, so the gate DOES fire on matmul's guards. But |
| 116 | +it is a proven numeric + perf no-op there (bit-identical output, identical ms). |
| 117 | + |
| 118 | +## Gate |
| 119 | + |
| 120 | +`from_ttir(prologue_opt=bool)` (default True). `prologue_opt=False` reproduces |
| 121 | +the 497670-byte / 37174-byte / 158-loop / 28-guard baseline exactly. |
| 122 | +`routed_triton_thread_distribute` (transform 2) is a distinct sub-gate, OFF by |
| 123 | +default and only set via `from_ttir(..., thread_distribute=True)` for dev. |
| 124 | + |
| 125 | +## GO / NO-GO |
| 126 | + |
| 127 | +**Partial GO.** Transforms (3) + (1-partial) LANDED, correct, committed, and |
| 128 | +§P1 bit-correct (MAXDIFF 4.88e-04) + small multi-K-trip (4.58e-05). They strip |
| 129 | +51 serial loops + 28 guards from the IR with ZERO §P1 perf regression and ZERO |
| 130 | +other-path regression. They do NOT yet move the §P1 ms (still ~1473 ms) because |
| 131 | +the dominant serial prologue survives. Transform (2) is implemented but blocked |
| 132 | +on shared-mem budget; landing the perf win requires full transform (1) |
| 133 | +region-index folding first. Honest: no §P1 speedup yet; the cleanup is real and |
| 134 | +safe; the speedup path is identified and gated correctly. |
| 135 | + |
| 136 | +## Reproduce from HEAD (gb10) |
| 137 | + |
| 138 | +``` |
| 139 | +cd /home/dave/source/tilelang # working files == tilelang 29888fee (shasum-verified) |
| 140 | +source /home/dave/cppmega-venv/bin/activate |
| 141 | +# regen §P1 PrimFunc (prologue_opt=True default) + parity + timing: |
| 142 | +python poc/triton_frontend/_test_harness/tridao_parity/emit_pf_json.py _chunk_scan_bwd_dstates |
| 143 | +python poc/triton_frontend/_test_harness/tridao_parity/parity_prod_dstates.py |
| 144 | +``` |
0 commit comments