Skip to content

Commit 38b6b3c

Browse files
committed
Simplify generated export wrappers
1 parent 519825f commit 38b6b3c

4 files changed

Lines changed: 240 additions & 197 deletions

File tree

examples/export_autograd_triton/rms_norm.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

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

@@ -57,6 +57,17 @@ def main():
5757
dynamic_tokens,
5858
max_autotune=True,
5959
),
60+
"clean_triton_max_autotune_coordesc": _export_rms_norm(
61+
Path("agent_space/generated_rms_norm_max_autotune_coordesc.py"),
62+
sample_x,
63+
weight,
64+
dynamic_tokens,
65+
max_autotune=True,
66+
inductor_config_patches={
67+
"coordinate_descent_tuning": True,
68+
"coordinate_descent_check_all_directions": True,
69+
},
70+
),
6071
}
6172

6273
for label, output_path in exports.items():
@@ -75,6 +86,7 @@ def _export_rms_norm(
7586
dynamic_tokens: object,
7687
*,
7788
max_autotune: bool,
89+
inductor_config_patches: dict[str, object] | None = None,
7890
) -> Path:
7991
output_path.parent.mkdir(parents=True, exist_ok=True)
8092
artifact_dir = output_path.with_name(f"{output_path.stem}_artifacts")
@@ -94,6 +106,7 @@ def _export_rms_norm(
94106
out=output_path,
95107
source_backend="clean_triton",
96108
max_autotune=max_autotune,
109+
inductor_config_patches=inductor_config_patches,
97110
)
98111
return output_path
99112

@@ -122,7 +135,7 @@ def _validate(compiled_fn: Callable, weight: torch.Tensor) -> None:
122135

123136

124137
def _benchmark_memory_bandwidth(label: str, compiled_fn: Callable, weight: torch.Tensor) -> None:
125-
print("forward bandwidth (assumes 2 x reads + 1 weight read + 1 output write):")
138+
print("forward logical bandwidth (2 x reads + 1 weight read + 1 output write):")
126139
for tokens in TOKEN_COUNTS:
127140
benchmark_x = torch.randn(
128141
tokens,

transformer_nuggets/export_autograd_triton/codegen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def _write_compiled_module(
7777

7878
def _generate_public_wrapper(exported_name: str, signature: inspect.Signature) -> str:
7979
return f"def {exported_name}{signature}:\n" + indent(
80-
"return _run_with_bound_args(locals())\n", " "
80+
"return _RUNTIME.run_with_bound_args(locals())\n", " "
8181
)
8282

8383

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

0 commit comments

Comments
 (0)