Skip to content

Commit 519825f

Browse files
committed
Template export autograd codegen
1 parent 886a984 commit 519825f

4 files changed

Lines changed: 239 additions & 228 deletions

File tree

examples/export_autograd_triton/rms_norm.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
export_autograd_triton,
1212
load_exported_module,
1313
)
14-
from transformer_nuggets.utils.benchmark import benchmark_cuda_function_in_microseconds
14+
from transformer_nuggets.utils.benchmark import benchmark_cuda_function_in_microseconds_triton
1515

1616

1717
HIDDEN_SIZE = 4096
1818
SAMPLE_TOKENS = 128
1919
MAX_TOKENS = 2048
20-
BENCHMARK_TOKENS = 512
20+
TOKEN_COUNTS = (1, 17, SAMPLE_TOKENS, 512)
2121
DTYPE = torch.bfloat16
2222
EPS = 1e-5
2323

@@ -99,7 +99,7 @@ def _export_rms_norm(
9999

100100

101101
def _validate(compiled_fn: Callable, weight: torch.Tensor) -> None:
102-
for tokens in (1, 17, SAMPLE_TOKENS, BENCHMARK_TOKENS):
102+
for tokens in TOKEN_COUNTS:
103103
runtime_x = torch.randn(
104104
tokens,
105105
HIDDEN_SIZE,
@@ -122,26 +122,26 @@ def _validate(compiled_fn: Callable, weight: torch.Tensor) -> None:
122122

123123

124124
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-
)
125+
print("forward bandwidth (assumes 2 x reads + 1 weight read + 1 output write):")
126+
for tokens in TOKEN_COUNTS:
127+
benchmark_x = torch.randn(
128+
tokens,
129+
HIDDEN_SIZE,
130+
device="cuda",
131+
dtype=DTYPE,
132+
)
131133

132-
def run_forward():
133-
with torch.no_grad():
134-
return compiled_fn(benchmark_x, weight, eps=EPS)
134+
def run_forward():
135+
with torch.no_grad():
136+
return compiled_fn(benchmark_x, weight, eps=EPS)
135137

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-
)
138+
time_us = benchmark_cuda_function_in_microseconds_triton(run_forward)
139+
bandwidth_gb_s = _forward_memory_bytes(benchmark_x) / (time_us * 1e-6) / 1e9
140+
print(f" tokens={tokens}: {time_us:.2f} us, ~{bandwidth_gb_s:.1f} GB/s")
141141

142142

143143
def _forward_memory_bytes(x: torch.Tensor) -> int:
144-
return 3 * x.numel() * x.element_size()
144+
return 4 * x.numel() * x.element_size()
145145

146146

147147
def _print_artifact_summary(output_path: Path) -> None:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies = [
2727
"pandas",
2828
"seaborn",
2929
"typer",
30+
"jinja2",
3031
]
3132

3233
[project.optional-dependencies]

transformer_nuggets/export_autograd_triton/codegen.py

Lines changed: 16 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -7,227 +7,33 @@
77
import re
88
from textwrap import indent
99

10+
from jinja2 import Environment, FileSystemLoader, StrictUndefined
11+
1012
from transformer_nuggets.export_autograd_triton.clean_triton import clean_triton_module
1113
from transformer_nuggets.export_autograd_triton.specs import CapturedSpecialization
1214

1315

16+
_TEMPLATE_ENV = Environment(
17+
loader=FileSystemLoader(Path(__file__).with_name("templates")),
18+
undefined=StrictUndefined,
19+
keep_trailing_newline=True,
20+
)
21+
22+
1423
def generate_autograd_source(
1524
fn: Callable[..., object],
1625
exported_name: str,
1726
specializations: list[CapturedSpecialization],
1827
) -> str:
1928
signature = inspect.signature(fn)
20-
wrapper = _generate_public_wrapper(exported_name, signature)
21-
specs_literal = pprint.pformat(
22-
[_spec_to_metadata(index, spec) for index, spec in enumerate(specializations)],
23-
width=100,
24-
sort_dicts=False,
29+
return _TEMPLATE_ENV.get_template("autograd_module.py.j2").render(
30+
specs_literal=pprint.pformat(
31+
[_spec_to_metadata(index, spec) for index, spec in enumerate(specializations)],
32+
width=100,
33+
sort_dicts=False,
34+
),
35+
wrapper=_generate_public_wrapper(exported_name, signature).rstrip(),
2536
)
26-
return f"""from __future__ import annotations
27-
28-
import importlib.util
29-
from pathlib import Path
30-
31-
import torch
32-
33-
_ARTIFACTS_DIR = Path(__file__).with_name(f"{{Path(__file__).stem}}_artifacts")
34-
35-
_SPECS = {specs_literal}
36-
37-
38-
def _load_runner(module_filename):
39-
if module_filename is None:
40-
return None
41-
module_path = _ARTIFACTS_DIR / module_filename
42-
spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
43-
module = importlib.util.module_from_spec(spec)
44-
if spec.loader is None:
45-
raise RuntimeError(f"Could not load compiled source module {{module_path}}")
46-
spec.loader.exec_module(module)
47-
return module.call
48-
49-
50-
_FORWARD_RUNNERS = [_load_runner(spec["forward_module"]) for spec in _SPECS]
51-
_BACKWARD_RUNNERS = [_load_runner(spec["backward_module"]) for spec in _SPECS]
52-
53-
54-
class _CompiledAutogradFunction(torch.autograd.Function):
55-
@staticmethod
56-
def forward(ctx, spec_id, *runtime_tensors):
57-
spec = _SPECS[spec_id]
58-
outputs = _FORWARD_RUNNERS[spec_id](list(runtime_tensors))
59-
if not isinstance(outputs, tuple):
60-
outputs = tuple(outputs)
61-
user_outputs = outputs[: spec["num_user_outputs"]]
62-
residuals = outputs[spec["num_user_outputs"] :]
63-
named_residuals = tuple(zip(spec["forward_residual_names"], residuals, strict=True))
64-
ctx.spec_id = spec_id
65-
ctx.runtime_tensor_count = len(runtime_tensors)
66-
ctx.saved_tensor_residual_names = tuple(
67-
name for name, residual in named_residuals if isinstance(residual, torch.Tensor)
68-
)
69-
ctx.saved_non_tensor_residuals = {{
70-
name: residual for name, residual in named_residuals if not isinstance(residual, torch.Tensor)
71-
}}
72-
ctx.save_for_backward(
73-
*(residual for _, residual in named_residuals if isinstance(residual, torch.Tensor))
74-
)
75-
non_differentiable_outputs = tuple(
76-
output
77-
for output, is_differentiable in zip(
78-
user_outputs,
79-
spec["differentiable_output_mask"],
80-
strict=True,
81-
)
82-
if isinstance(output, torch.Tensor) and not is_differentiable
83-
)
84-
if non_differentiable_outputs:
85-
ctx.mark_non_differentiable(*non_differentiable_outputs)
86-
if spec["output_kind"] == "single":
87-
return user_outputs[0]
88-
return tuple(user_outputs)
89-
90-
@staticmethod
91-
def backward(ctx, *grad_outputs):
92-
spec = _SPECS[ctx.spec_id]
93-
grad_outputs = tuple(
94-
grad_output.contiguous() if isinstance(grad_output, torch.Tensor) else grad_output
95-
for index, grad_output in enumerate(grad_outputs)
96-
if spec["differentiable_output_mask"][index]
97-
)
98-
backward_runner = _BACKWARD_RUNNERS[ctx.spec_id]
99-
if backward_runner is None:
100-
raise RuntimeError("Selected export_autograd_triton specialization has no backward graph")
101-
saved_residuals = dict(ctx.saved_non_tensor_residuals)
102-
saved_residuals.update(zip(ctx.saved_tensor_residual_names, ctx.saved_tensors, strict=True))
103-
backward_saved_inputs = tuple(
104-
saved_residuals[name] for name in spec["backward_saved_input_names"]
105-
)
106-
grads = backward_runner(list(backward_saved_inputs + grad_outputs))
107-
if not isinstance(grads, tuple):
108-
grads = tuple(grads)
109-
if len(grads) < ctx.runtime_tensor_count:
110-
grads = grads + (None,) * (ctx.runtime_tensor_count - len(grads))
111-
if len(grads) > ctx.runtime_tensor_count:
112-
grads = grads[: ctx.runtime_tensor_count]
113-
return (None, *grads)
114-
115-
116-
def _run_with_bound_args(bound_args):
117-
spec_id = _select_spec(bound_args)
118-
spec = _SPECS[spec_id]
119-
runtime_tensors = tuple(bound_args[name] for name in spec["runtime_tensor_names"])
120-
if spec["needs_autograd"]:
121-
result = _CompiledAutogradFunction.apply(spec_id, *runtime_tensors)
122-
if spec["output_kind"] == "list":
123-
return list(result)
124-
return result
125-
return _run_forward_only(spec_id, runtime_tensors)
126-
127-
128-
def _run_forward_only(spec_id, runtime_tensors):
129-
spec = _SPECS[spec_id]
130-
outputs = _FORWARD_RUNNERS[spec_id](list(runtime_tensors))
131-
if not isinstance(outputs, tuple):
132-
outputs = tuple(outputs)
133-
user_outputs = outputs[: spec["num_user_outputs"]]
134-
if spec["output_kind"] == "single":
135-
return user_outputs[0]
136-
if spec["output_kind"] == "list":
137-
return list(user_outputs)
138-
return tuple(user_outputs)
139-
140-
141-
def _select_spec(bound_args):
142-
matches = []
143-
failures = []
144-
for spec_id, spec in enumerate(_SPECS):
145-
reasons = _mismatch_reasons(spec, bound_args)
146-
if reasons:
147-
failures.append((spec, reasons))
148-
else:
149-
matches.append(spec_id)
150-
if len(matches) == 1:
151-
return matches[0]
152-
if len(matches) > 1:
153-
names = ", ".join(_SPECS[spec_id]["name"] for spec_id in matches)
154-
raise RuntimeError(f"Ambiguous export_autograd_triton specializations matched: {{names}}")
155-
raise RuntimeError(_format_no_match(bound_args, failures))
156-
157-
158-
def _mismatch_reasons(spec, bound_args):
159-
reasons = []
160-
symbols = {{}}
161-
for name, expected in spec["static_args"].items():
162-
actual = bound_args.get(name)
163-
if actual != expected:
164-
reasons.append(f"static {{name}}={{actual!r}} != {{expected!r}}")
165-
for guard in spec["tensor_guards"]:
166-
tensor = bound_args.get(guard["name"])
167-
reason = _tensor_mismatch_reason(tensor, guard, symbols)
168-
if reason is not None:
169-
reasons.append(reason)
170-
return reasons
171-
172-
173-
def _tensor_mismatch_reason(tensor, guard, symbols):
174-
name = guard["name"]
175-
if not isinstance(tensor, torch.Tensor):
176-
return f"{{name}} is {{type(tensor).__name__}}, expected Tensor"
177-
expected_dtype = getattr(torch, guard["dtype"])
178-
if tensor.dtype is not expected_dtype:
179-
return f"{{name}} dtype {{tensor.dtype}} != torch.{{guard['dtype']}}"
180-
if tensor.device.type != guard["device_type"]:
181-
return f"{{name}} device type {{tensor.device.type}} != {{guard['device_type']}}"
182-
if tensor.device.index != guard["device_index"]:
183-
return f"{{name}} device index {{tensor.device.index}} != {{guard['device_index']}}"
184-
if tensor.dim() != guard["rank"]:
185-
return f"{{name}} rank {{tensor.dim()}} != {{guard['rank']}}"
186-
shape_reason = _shape_mismatch_reason(name, tensor, guard, symbols)
187-
if shape_reason is not None:
188-
return shape_reason
189-
if tuple(tensor.stride()) != tuple(guard["stride"]):
190-
return f"{{name}} stride {{tuple(tensor.stride())}} != {{tuple(guard['stride'])}}"
191-
return None
192-
193-
194-
def _shape_mismatch_reason(name, tensor, guard, symbols):
195-
for dim, expected in enumerate(guard["shape"]):
196-
actual = int(tensor.shape[dim])
197-
if isinstance(expected, dict):
198-
symbol = expected["symbol"]
199-
if expected["min"] is not None and actual < expected["min"]:
200-
return f"{{name}} shape dim {{dim}}={{actual}} is less than {{symbol}} min {{expected['min']}}"
201-
if expected["max"] is not None and actual > expected["max"]:
202-
return f"{{name}} shape dim {{dim}}={{actual}} is greater than {{symbol}} max {{expected['max']}}"
203-
if symbol in symbols and symbols[symbol] != actual:
204-
return f"{{name}} shape dim {{dim}}={{actual}} does not equal {{symbol}}={{symbols[symbol]}}"
205-
symbols[symbol] = actual
206-
elif actual != expected:
207-
return f"{{name}} shape dim {{dim}}={{actual}} != {{expected}}"
208-
return None
209-
210-
211-
def _format_no_match(bound_args, failures):
212-
lines = ["No export_autograd_triton specialization matched the runtime inputs.", "Received:"]
213-
for name, value in bound_args.items():
214-
if isinstance(value, torch.Tensor):
215-
lines.append(
216-
f" {{name}}: dtype={{value.dtype}}, device={{value.device}}, "
217-
f"shape={{tuple(value.shape)}}, stride={{tuple(value.stride())}}"
218-
)
219-
else:
220-
lines.append(f" {{name}}: {{value!r}}")
221-
lines.append("Expected specializations:")
222-
for spec, reasons in failures:
223-
lines.append(f" - {{spec['name']}}:")
224-
for reason in reasons:
225-
lines.append(f" {{reason}}")
226-
return "\\n".join(lines)
227-
228-
229-
{wrapper}
230-
"""
23137

23238

23339
def write_autograd_source(

0 commit comments

Comments
 (0)