sm_120 SAME-L: AOT (graph-capturable) engines + tensorRT/README.md

#6
by cortexelus - opened
tensorRT/README.md CHANGED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stable Audio 3 β€” TensorRT engines
2
+
3
+ Prebuilt TensorRT engines for the Stable Audio 3 stack, organised by CUDA architecture.
4
+
5
+ ```
6
+ tensorRT/sm_90/ Hopper β€” H100, H200
7
+ tensorRT/sm_120/ Blackwell β€” RTX 5090, RTX PRO 6000, RTX PRO 4500
8
+ ```
9
+
10
+ TensorRT engines are **not portable across architectures** β€” an `sm_90` engine will not
11
+ load on an `sm_120` GPU. They are also tied to the TensorRT version they were built with
12
+ (these were built with **TensorRT 10.15.1.29**). If your GPU or TensorRT version is not
13
+ covered here, build from the ONNX in `onnx/` using the scripts in
14
+ [`optimized/tensorRT/build/`](https://github.com/Stability-AI/stable-audio-3/tree/main/optimized/tensorRT/build).
15
+
16
+ | directory | contents |
17
+ |---|---|
18
+ | `sa3-m/` | medium DiT (the main diffusion transformer) |
19
+ | `sa3-sm-music/`, `sa3-sm-sfx/` | small domain-specialised DiTs |
20
+ | `same-l/`, `same-s/` | autoencoder (SAME) encoder + decoder |
21
+ | `t5gemma/` | text conditioner, plus its tokenizer |
22
+
23
+ ---
24
+
25
+ ## The SAME-L attention kernel: two implementations
26
+
27
+ The SAME-L encoder and decoder use a custom sliding-window-attention plugin
28
+ (`samel::diff_attn_swa`) for differential attention, which TensorRT has no native op for.
29
+ That plugin ships **two implementations**, and *which one an engine uses is fixed when the
30
+ engine is built* β€” it is baked into the `.trt` file and cannot be changed at load time.
31
+
32
+ **AOT** (ahead-of-time) compiles a block-tiled tensor-core kernel to PTX and embeds it in
33
+ the engine. No Python runs during inference. **JIT** dispatches a Triton kernel through a
34
+ Python callback on every enqueue β€” 12 callbacks per decode, one per attention layer.
35
+
36
+ | | AOT | JIT |
37
+ |---|---|---|
38
+ | CUDA-graph capturable | **yes, both architectures** | sm_90 only β€” **fails on sm_120** |
39
+ | Python / Triton calls per decode | **0** | 12 |
40
+ | Triton needed at inference | no | **yes** |
41
+ | Engine size | +0.1% | baseline |
42
+
43
+ ### Which files use which
44
+
45
+ | published engines | kernel | why |
46
+ |---|---|---|
47
+ | `sm_120/same-l/` | **AOT** (block-tiled MMA) | required β€” the JIT build is not capturable on sm_120 |
48
+ | `sm_90/same-l/` | **JIT** | works, and AOT measured no faster on Hopper, so these were left alone |
49
+
50
+ So on **sm_90 you need Triton installed to run** the `same-l` engines, and on **sm_120 you
51
+ do not**. Both implementations stay registered by the runtime, so either kind of engine
52
+ loads and runs regardless of which GPU you are on.
53
+
54
+ For anything you build yourself, **AOT is the default on every architecture** β€” it is the
55
+ only correct choice on sm_120, it is at parity on sm_90, and it drops the runtime Triton
56
+ dependency. Rebuilding the `sm_90` engines would therefore give you AOT rather than the
57
+ JIT files published here; that is expected and fine, not a mismatch.
58
+
59
+ ### Why JIT breaks on sm_120
60
+
61
+ The runtime captures the whole pipeline into one CUDA graph. A JIT engine cannot be
62
+ captured on sm_120: `enqueueV3` returns `False`, TensorRT logs *"this TRT engine is not
63
+ stream capturable"*, and **the decode stage is silently omitted from the graph** β€” no
64
+ exception, no non-zero exit.
65
+
66
+ What you get is the pre-capture warm-up decode of zero latents: a constant wash of noise,
67
+ **byte-identical for every prompt and every seed**, with exit code 0. If you ever see
68
+ that, this is the first thing to check. The diagnostic is to render twice with different
69
+ seeds and compare the files β€” identical bytes mean the decode never ran.
70
+
71
+ The same engine captures without complaint on sm_90, which is why the problem did not
72
+ surface until Blackwell. Re-entering Python inside a captured region was always fragile
73
+ rather than supported, so do not assume a future architecture will tolerate it either.
74
+
75
+ The decode is inside the captured region on the default path: `sa3_trt.py` captures T5 β†’
76
+ DiT loop β†’ decoder β†’ PCM copy as one graph whenever `--cfg 1.0` and no inpaint/init-audio
77
+ is used, which is the default. `--no-mega-graph` runs eagerly and sidesteps capture
78
+ entirely, so it is a usable workaround on an affected engine β€” at the cost of the
79
+ graph-replay speedup.
80
+
81
+ > **The `sm_120` SAME-L engines were rebuilt AOT on 2026-07-31.** Anything published
82
+ > before that date was a JIT build and is affected, so if you have an `sm_120` copy cached
83
+ > from earlier, re-pull it. The `sm_90` files are unchanged β€” JIT captures correctly on
84
+ > Hopper, so they were never affected.
85
+
86
+ ### Benchmarks
87
+
88
+ SAME-L decode, milliseconds, median of 7 on an otherwise idle GPU. `L` is the latent
89
+ sequence length β€” one latent is 4096 samples, so at 44.1 kHz L=1292 is a two-minute
90
+ render and L=4096 is about 6m20s.
91
+
92
+ **sm_120 Β· RTX PRO 4500 Blackwell**
93
+
94
+ | kernel | L=256 | L=1024 | L=1292 | L=2048 | L=4096 |
95
+ |---|---|---|---|---|---|
96
+ | JIT *(not capturable here)* | 55.1 | 227.2 | 289.1 | 457.7 | 920.2 |
97
+ | AOT `ptx` (scalar fallback) | 62.5 | 251.2 | 318.7 | 502.8 | 1008.8 |
98
+ | **AOT `mma` (default)** | 55.4 | **223.6** | **283.7** | **448.4** | **899.3** |
99
+
100
+ **sm_90 Β· H200**
101
+
102
+ | kernel | L=256 | L=1024 | L=1292 | L=2048 | L=4096 |
103
+ |---|---|---|---|---|---|
104
+ | JIT | 12.4 | 47.0 | 61.1 | 98.5 | 194.4 |
105
+ | AOT `ptx` (scalar fallback) | 12.7 | 47.3 | 60.7 | 96.6 | 195.5 |
106
+ | **AOT `mma` (default)** | 12.4 | 48.4 | 62.6 | 100.0 | 196.8 |
107
+
108
+ The default AOT kernel is at parity with JIT on both architectures β€” within a few percent
109
+ either way, and faster at long sequence lengths on sm_120 β€” while remaining capturable.
110
+ Accuracy against the FP32 decoder at L=1292: AOT `mma` **51.45 dB** PSNR, JIT 51.43, AOT
111
+ `ptx` 51.11. The default kernel is the most accurate of the three as well as the fastest.
112
+
113
+ Note the ~4.5Γ— gap between the two architectures. That is silicon, not a software
114
+ regression β€” a workstation card against a flagship datacenter part. Measured on these two
115
+ GPUs, the H200 has **5.57Γ—** the copy bandwidth (4084 vs 734 GB/s) and 5.08Γ— the BF16
116
+ tensor throughput, so a bandwidth-bound decoder landing at 4.5Γ— is extracting slightly
117
+ *more* of its silicon on sm_120 than the H200 does of its own.
118
+
119
+ ### How the kernels differ
120
+
121
+ The default AOT kernel (`mma`) is block-tiled: 16 queries per block share a 64-wide K/V
122
+ tile in shared memory, and both attention products go through `tl.dot`, which lowers to
123
+ 512 Γ— `mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32` β€” TF32 tensor cores. It is
124
+ written in Triton and compiled ahead of time, so Triton generates the fragment layouts
125
+ rather than us hand-writing them, but the result is a plain PTX blob inside the engine.
126
+
127
+ The fallback AOT kernel (`ptx`) is hand-written scalar FP32: one warp per query, no K/V
128
+ reuse, zero `mma` instructions. It exists only for targets where the block-tiled kernel
129
+ will not build, and costs about 10% on sm_120.
130
+
131
+ TF32 in the two attention products matches what the JIT path already did β€” it is not a new
132
+ precision loss. The differential subtraction stays in FP32 on FP32 accumulators, so the
133
+ cancellation-sensitive step is untouched.
134
+
135
+ ### Building your own
136
+
137
+ ```bash
138
+ SA3_SWA_PLUGIN=aot # default β€” PTX compiled into the engine
139
+ SA3_SWA_PLUGIN=jit # Triton through a Python callback per enqueue
140
+ SA3_SWA_AOT=mma # default AOT kernel β€” block-tiled, tensor cores
141
+ SA3_SWA_AOT=ptx # fallback AOT kernel β€” scalar FP32
142
+ ```
143
+
144
+ Triton is needed to *build* an AOT engine (it is the code generator) but not to run one.
145
+
146
+ Three gotchas, all of which produce wrong output rather than a build failure:
147
+
148
+ - **Kernel parameter order is inputs β†’ runtime scalars β†’ outputs.** TensorRT's AOT
149
+ launcher passes the `extra` scalars *before* the output pointers. Declaring the output
150
+ ahead of them makes the kernel dereference a sequence length as an address; ours died
151
+ with an illegal memory access until the order was fixed.
152
+ - **`BLOCK_KV` must be β‰₯ `BLOCK_N + 2*WINDOW`.** Otherwise the K/V tile stops covering
153
+ every position the block's queries can attend to and attention contributions are
154
+ *silently dropped*. `BLOCK_N=32` with `BLOCK_KV=64` needs 66 and is therefore wrong,
155
+ even though it compiles and fits in shared memory.
156
+ - **Shared memory over 48 KB fails at enqueue, not at build.** `BLOCK_N=64` needs 64 KB;
157
+ the engine builds cleanly and then reports *"Failed to enqueue status -1"* and returns
158
+ zeros. `BLOCK_N=16` needs 40 KB, and is faster anyway β€” with a window of only Β±17, a
159
+ wide K/V tile is mostly masked-out waste.
160
+
161
+ The engine filenames still say `triton_swa` because Triton remains the code generator;
162
+ only the compilation moved ahead of time.
163
+
164
+ ---
165
+
166
+ ## Other things worth knowing
167
+
168
+ **The medium DiT has three precisions.** `dit_fp16mixed.trt` is the default and the one to
169
+ use: FP16 attention core with FP32 RMSNorm and RoPE islands, ~4.3Γ— faster than FP32 with
170
+ no audible quality cost. `dit_fp32.trt` is the reference. `dit_bf16.trt` is kept for
171
+ comparison β€” earlier builds of it degraded past about two minutes of audio because the
172
+ RoPE angle was computed in BF16, where one ULP exceeds 2Ο€ at long sequence lengths; the
173
+ published engine has the RoPE table baked in at FP32 precision.
174
+
175
+ **`dit_fp8.trt` is sm_90 only** and must be built weakly-typed; strong typing breaks
176
+ TensorRT's attention fusion. This is the *opposite* of the FP16-mixed rule, which requires
177
+ `STRONGLY_TYPED` or the builder re-casts the FP32 islands.
tensorRT/sm_120/same-l/dec_dynamic_triton_swa.trt CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ccd5f48922fe46711593e5251fe45ef40a8a5eac52462b6d72ed69595458fd42
3
- size 1194932516
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e7b030cb0cf0fa075caa8c11c81b5e1bc621ddf4dd6e03e84f6c0acc81450d09
3
+ size 1196159668
tensorRT/sm_120/same-l/enc_dynamic_triton_swa.trt CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9a72e57ecee8c0d6db9077474bcd93086a7bd709cfcb1de3ab126bcfdc205c89
3
- size 1194895540
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d48b4210bff72a3d2f41ad0eb5c2741b2be7f82c6e5a8101927e755f728b7b0c
3
+ size 1196143124