Skip to content

Commit 886a984

Browse files
committed
Add export autograd Triton RMSNorm example
1 parent aba2a61 commit 886a984

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

File renamed without changes.
File renamed without changes.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Callable
4+
from pathlib import Path
5+
import shutil
6+
7+
import torch
8+
9+
from transformer_nuggets.export_autograd_triton import (
10+
Specialization,
11+
export_autograd_triton,
12+
load_exported_module,
13+
)
14+
from transformer_nuggets.utils.benchmark import benchmark_cuda_function_in_microseconds
15+
16+
17+
HIDDEN_SIZE = 4096
18+
SAMPLE_TOKENS = 128
19+
MAX_TOKENS = 2048
20+
BENCHMARK_TOKENS = 512
21+
DTYPE = torch.bfloat16
22+
EPS = 1e-5
23+
24+
25+
def rms_norm(x, weight, *, eps=EPS):
26+
x_float = x.float()
27+
variance = x_float.square().mean(dim=-1, keepdim=True)
28+
return (x_float * torch.rsqrt(variance + eps)).to(x.dtype) * weight
29+
30+
31+
def main():
32+
if not torch.cuda.is_available():
33+
raise RuntimeError("CUDA is required for this example")
34+
35+
sample_x = torch.randn(
36+
SAMPLE_TOKENS,
37+
HIDDEN_SIZE,
38+
device="cuda",
39+
dtype=DTYPE,
40+
requires_grad=True,
41+
)
42+
weight = torch.ones(HIDDEN_SIZE, device="cuda", dtype=DTYPE, requires_grad=True)
43+
44+
dynamic_tokens = torch.export.Dim("tokens", min=1, max=MAX_TOKENS)
45+
exports = {
46+
"clean_triton": _export_rms_norm(
47+
Path("agent_space/generated_rms_norm_clean.py"),
48+
sample_x,
49+
weight,
50+
dynamic_tokens,
51+
max_autotune=False,
52+
),
53+
"clean_triton_max_autotune": _export_rms_norm(
54+
Path("agent_space/generated_rms_norm_max_autotune.py"),
55+
sample_x,
56+
weight,
57+
dynamic_tokens,
58+
max_autotune=True,
59+
),
60+
}
61+
62+
for label, output_path in exports.items():
63+
generated = load_exported_module(output_path)
64+
compiled_fn = generated.rms_norm_compiled
65+
print(f"\n== {label} ==")
66+
_validate(compiled_fn, weight)
67+
_benchmark_memory_bandwidth(label, compiled_fn, weight)
68+
_print_artifact_summary(output_path)
69+
70+
71+
def _export_rms_norm(
72+
output_path: Path,
73+
x: torch.Tensor,
74+
weight: torch.Tensor,
75+
dynamic_tokens: object,
76+
*,
77+
max_autotune: bool,
78+
) -> Path:
79+
output_path.parent.mkdir(parents=True, exist_ok=True)
80+
artifact_dir = output_path.with_name(f"{output_path.stem}_artifacts")
81+
output_path.unlink(missing_ok=True)
82+
if artifact_dir.exists():
83+
shutil.rmtree(artifact_dir)
84+
85+
export_autograd_triton(
86+
rms_norm,
87+
specializations=[
88+
Specialization(
89+
args=(x, weight),
90+
kwargs={"eps": EPS},
91+
dynamic_shapes={"x": {0: dynamic_tokens}},
92+
)
93+
],
94+
out=output_path,
95+
source_backend="clean_triton",
96+
max_autotune=max_autotune,
97+
)
98+
return output_path
99+
100+
101+
def _validate(compiled_fn: Callable, weight: torch.Tensor) -> None:
102+
for tokens in (1, 17, SAMPLE_TOKENS, BENCHMARK_TOKENS):
103+
runtime_x = torch.randn(
104+
tokens,
105+
HIDDEN_SIZE,
106+
device="cuda",
107+
dtype=DTYPE,
108+
requires_grad=True,
109+
)
110+
111+
eager = rms_norm(runtime_x, weight, eps=EPS)
112+
compiled = compiled_fn(runtime_x, weight, eps=EPS)
113+
torch.testing.assert_close(compiled, eager, rtol=2e-2, atol=2e-2)
114+
115+
eager_grads = torch.autograd.grad(
116+
eager.float().sum(), (runtime_x, weight), retain_graph=True
117+
)
118+
compiled_grads = torch.autograd.grad(compiled.float().sum(), (runtime_x, weight))
119+
for compiled_grad, eager_grad in zip(compiled_grads, eager_grads, strict=True):
120+
torch.testing.assert_close(compiled_grad, eager_grad, rtol=2e-2, atol=2e-2)
121+
print(f"tokens={tokens}: {compiled.shape}")
122+
123+
124+
def _benchmark_memory_bandwidth(label: str, compiled_fn: Callable, weight: torch.Tensor) -> None:
125+
benchmark_x = torch.randn(
126+
BENCHMARK_TOKENS,
127+
HIDDEN_SIZE,
128+
device="cuda",
129+
dtype=DTYPE,
130+
)
131+
132+
def run_forward():
133+
with torch.no_grad():
134+
return compiled_fn(benchmark_x, weight, eps=EPS)
135+
136+
time_us = benchmark_cuda_function_in_microseconds(run_forward, NUM_ITERS=100)
137+
bandwidth_gb_s = _forward_memory_bytes(benchmark_x) / (time_us * 1e-6) / 1e9
138+
print(
139+
f"{label} forward: {time_us:.2f} us, ~{bandwidth_gb_s:.1f} GB/s effective memory bandwidth"
140+
)
141+
142+
143+
def _forward_memory_bytes(x: torch.Tensor) -> int:
144+
return 3 * x.numel() * x.element_size()
145+
146+
147+
def _print_artifact_summary(output_path: Path) -> None:
148+
artifact_dir = output_path.with_name(f"{output_path.stem}_artifacts")
149+
artifact_source = "\n".join(path.read_text() for path in artifact_dir.glob("*.py"))
150+
print(f"generated file: {output_path}")
151+
print(f"artifact dir: {artifact_dir}")
152+
print(f"inline Triton: {'@triton.jit' in artifact_source}")
153+
print(f"uses async_compile.triton: {'async_compile.triton' in artifact_source}")
154+
155+
156+
if __name__ == "__main__":
157+
main()

0 commit comments

Comments
 (0)