AOTInductor Triton SASS Inspection

Introduction

Triton is a language and compiler for creating high-performance GPU kernels. AOTInductor is a PyTorch backend compiler that can generate ahead-of-time optimized deep learning inference engines and is compatible with custom Triton kernels and performance autotuning. In order to confirm if the generated Triton kernels have certain behaviors, sometimes it is necessary to inspect the SASS (Streaming Assembly) of the generated CUDA kernels.

In this blog post, I would like to demonstrate how to inspect SASS of Triton kernels from AOTInductor artifacts.

AOTInductor Triton SASS Inspection

Based on the base address of the input tensor and the static offset for data access, the Triton compiler can deduce the appropriate vectorization instructions to generate efficient memory access patterns. In PyTorch, all the tensors allocated from PyTorch cached allocator are 256-byte aligned by default. If the stride offsets are also aligned to 16 bytes, the Triton compiler can generate 128-bit LDG.E.128 / STG.E.128 vectorized memory instructions.

In this example, we will verify if 128-bit vectorized memory instructions are generated in the SASS of CUDA kernel of interest. To verify this, because AOTInductor generates Triton kernels as cubin files, we can inspect the generated SASS to check if the 128-bit vectorized memory instructions are indeed emitted. The cubin files are saved in the pt2 zip archive and SASS can be extracted for inspection using nvdisasm or cuobjdump --dump-sass after unzipping the archive.

triton_copy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import os
import shutil
import subprocess
import zipfile
import torch
import torch.nn as nn
from torch.library import triton_op, wrap_triton
import triton
import triton.language as tl


# ---------------------------------------------------------------------------
# 1. Define Triton Vector Copy Kernel
# ---------------------------------------------------------------------------
@triton.jit
def _triton_copy_kernel(
in_ptr,
out_ptr,
n_elements,
BLOCK_SIZE: "tl.constexpr",
):
pid = tl.program_id(axis=0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)

# 16-byte alignment hint to trigger 128-bit LDG.E.128 / STG.E.128 vectorization
# offsets = tl.multiple_of(offsets, 16)
mask = offsets < n_elements

x = tl.load(in_ptr + offsets, mask=mask)
tl.store(out_ptr + offsets, x, mask=mask)


# ---------------------------------------------------------------------------
# 2. Transparently Traceable Triton Operator
# ---------------------------------------------------------------------------
@triton_op("custom_ops::triton_copy", mutates_args=())
def triton_copy_op(x: torch.Tensor) -> torch.Tensor:
out = torch.empty_like(x)

if x.is_cuda:
n_elements = x.numel()

def grid(meta):
return (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), )

wrap_triton(_triton_copy_kernel)[grid](x,
out,
n_elements,
BLOCK_SIZE=1024)
else:
out.copy_(x)

return out


# ---------------------------------------------------------------------------
# 3. PyTorch Model
# ---------------------------------------------------------------------------
class VectorCopyModel(nn.Module):

def forward(self, x: torch.Tensor) -> torch.Tensor:
return triton_copy_op(x)


# ---------------------------------------------------------------------------
# 4. Main Execution Pipeline
# ---------------------------------------------------------------------------
def main():
device = "cuda" if torch.cuda.is_available() else "cpu"
if device != "cuda":
raise RuntimeError(
"CUDA device required for AOTInductor Triton compilation.")

print("\n" + "=" * 75)
print(
" PIPELINE: Export -> AOTI Compile -> Bitwise Test -> SASS Inspection")
print("=" * 75)

model = VectorCopyModel().to(device).eval()
example_args = (torch.randn(1024 * 1024,
device=device,
dtype=torch.float32), )

# --------------------------------------------------------------------------
# Step 1: Export Graph
# --------------------------------------------------------------------------
print(
"\n[STEP 1] Exporting graph with torch.export.export(..., strict=True)"
)
ep = torch.export.export(model, args=example_args, strict=True)
decomposed_ep = ep.run_decompositions()
print(" ✅ ExportedProgram graph successfully captured and decomposed.")

# --------------------------------------------------------------------------
# Step 2: Compile & Package via AOTInductor
# --------------------------------------------------------------------------
print("\n[STEP 2] Compiling to AOTI Package (.pt2 artifact)")
output_dir = os.path.abspath("./aoti_output")
os.makedirs(output_dir, exist_ok=True)
pkg_path = os.path.join(output_dir, "model.pt2")

compiled_pkg = torch._inductor.aoti_compile_and_package(
decomposed_ep, package_path=pkg_path)
print(f" ✅ Compiled Package Generated: {compiled_pkg}")

# --------------------------------------------------------------------------
# Step 3: Run Executable & Verify Bitwise Identity
# --------------------------------------------------------------------------
print("\n[STEP 3] Executing Compiled Model and Verifying Bitwise Identity")
runner = torch._inductor.aoti_load_package(compiled_pkg)

test_input = torch.randn(1024 * 1024, device=device, dtype=torch.float32)
output = runner(test_input)

assert torch.equal(test_input, output), "FAIL: Tensors are not equal!"

input_bits = test_input.view(torch.int32)
output_bits = output.view(torch.int32)
mismatches = (input_bits != output_bits).sum().item()
assert mismatches == 0, f"FAIL: Found {mismatches} bitwise mismatch(es)!"

print(
" ✅ Bitwise Identity Confirmed: Input and Output are 100% bitwise identical!"
)

# --------------------------------------------------------------------------
# Step 4: Extract All Artifacts from .pt2 Package
# --------------------------------------------------------------------------
print("\n[STEP 4] Unpacking .pt2 Archive and Scanning Artifacts")
extract_dir = os.path.join(output_dir, "extracted")
os.makedirs(extract_dir, exist_ok=True)

extracted_cubins = []
extracted_so = None
extracted_cpp = None

with zipfile.ZipFile(compiled_pkg, "r") as zip_ref:
zip_ref.extractall(extract_dir)

for root, _, files in os.walk(extract_dir):
for f in files:
full_path = os.path.join(root, f)
if f.endswith(".cubin"):
extracted_cubins.append(full_path)
elif f.endswith(".wrapper.so") or (f.endswith(".so")
and not extracted_so):
extracted_so = full_path
elif f.endswith(".wrapper.cpp") or (f.endswith(".cpp")
and not extracted_cpp):
extracted_cpp = full_path

print(f" ✅ Found {len(extracted_cubins)} .cubin file(s) in package.")
if extracted_cpp:
print(f" ✅ Found wrapper C++: {extracted_cpp}")
if extracted_so:
print(f" ✅ Found wrapper .so: {extracted_so}")

# --------------------------------------------------------------------------
# Step 5: SASS Disassembly on Extracted .cubin via nvdisasm
# --------------------------------------------------------------------------
print("\n[STEP 5] Running SASS Disassembly (nvdisasm) on Extracted .cubin")
if extracted_cubins and shutil.which("nvdisasm"):
for cubin_path in extracted_cubins:
cubin_name = os.path.basename(cubin_path)
res = subprocess.run(
["nvdisasm", "-g", cubin_path],
capture_output=True,
text=True,
)
sass_output = res.stdout

if "LDG.E.128" in sass_output or "STG.E.128" in sass_output:
print(
f" ✅ [nvdisasm CONFIRMED in {cubin_name}] Found 128-bit vector instructions:"
)
for line in sass_output.splitlines():
if "LDG.E.128" in line or "STG.E.128" in line:
print(f" {line.strip()}")
else:
print(f" --> Disassembly completed for {cubin_name}.")
elif not shutil.which("nvdisasm"):
print(" --> [SKIPPED] 'nvdisasm' not found in system PATH.")

# --------------------------------------------------------------------------
# Step 6: SASS Disassembly on Extracted .cubin via cuobjdump
# --------------------------------------------------------------------------
print(
"\n[STEP 6] Running SASS Disassembly (cuobjdump) on Extracted .cubin")
if extracted_cubins and shutil.which("cuobjdump"):
for cubin_path in extracted_cubins:
cubin_name = os.path.basename(cubin_path)
res = subprocess.run(
["cuobjdump", "-sass", cubin_path],
capture_output=True,
text=True,
)
cubin_sass = res.stdout

if "LDG.E.128" in cubin_sass or "STG.E.128" in cubin_sass:
print(
f" ✅ [cuobjdump CONFIRMED in {cubin_name}] Found 128-bit vector instructions:"
)
for line in cubin_sass.splitlines():
if "LDG.E.128" in line or "STG.E.128" in line:
print(f" {line.strip()}")
else:
print(f" --> Disassembly of {cubin_name} completed.")
elif not shutil.which("cuobjdump"):
print(" --> [SKIPPED] 'cuobjdump' not found in system PATH.")


if __name__ == "__main__":
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ python triton_copy.py

===========================================================================
PIPELINE: Export -> AOTI Compile -> Bitwise Test -> SASS Inspection
===========================================================================

[STEP 1] Exporting graph with torch.export.export(..., strict=True)
/usr/lib/python3.12/copyreg.py:99: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
return cls.__new__(cls, *args)
✅ ExportedProgram graph successfully captured and decomposed.

[STEP 2] Compiling to AOTI Package (.pt2 artifact)
/usr/lib/python3.12/copyreg.py:99: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
return cls.__new__(cls, *args)
✅ Compiled Package Generated: /mnt/aoti_output/model.pt2

[STEP 3] Executing Compiled Model and Verifying Bitwise Identity
✅ Bitwise Identity Confirmed: Input and Output are 100% bitwise identical!

[STEP 4] Unpacking .pt2 Archive and Scanning Artifacts
✅ Found 1 .cubin file(s) in package.
✅ Found wrapper C++: /mnt/aoti_output/extracted/model/data/aotinductor/model/c4si55h2cct2cbdghpljslclh43nkx45u6pefe4b6nymbkyhgoqi.wrapper.cpp
✅ Found wrapper .so: /mnt/aoti_output/extracted/model/data/aotinductor/model/c4si55h2cct2cbdghpljslclh43nkx45u6pefe4b6nymbkyhgoqi.wrapper.so

[STEP 5] Running SASS Disassembly (nvdisasm) on Extracted .cubin
✅ [nvdisasm CONFIRMED in crx777x7kmdkuv7227akgtzfojh4osuapriqhz3jesxvwqpwmfnk.cubin] Found 128-bit vector instructions:
/*0110*/ @!P0 LDG.E.128 R8, desc[UR4][R2.64] ;
/*0140*/ @!P1 LDG.E.128 R12, desc[UR4][R2.64+0x800] ;
/*0150*/ @!P0 STG.E.128 desc[UR4][R4.64], R8 ;
/*0170*/ STG.E.128 desc[UR4][R4.64+0x800], R12 ;

[STEP 6] Running SASS Disassembly (cuobjdump) on Extracted .cubin
✅ [cuobjdump CONFIRMED in crx777x7kmdkuv7227akgtzfojh4osuapriqhz3jesxvwqpwmfnk.cubin] Found 128-bit vector instructions:
/*0110*/ @!P0 LDG.E.128 R8, desc[UR4][R2.64] ; /* 0x0000000402088981 */
/*0140*/ @!P1 LDG.E.128 R12, desc[UR4][R2.64+0x800] ; /* 0x00080004020c9981 */
/*0150*/ @!P0 STG.E.128 desc[UR4][R4.64], R8 ; /* 0x0000000804008986 */
/*0170*/ STG.E.128 desc[UR4][R4.64+0x800], R12 ; /* 0x0008000c04007986 */

References

Author

Lei Mao

Posted on

10-02-2026

Updated on

10-02-2026

Licensed under


Comments