Skip to content

Commit 266876b

Browse files
committed
Clean dynamic Triton artifacts and add loader helper
1 parent aef9cca commit 266876b

8 files changed

Lines changed: 98 additions & 56 deletions

File tree

examples/export_autograd_triton_basic.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
from __future__ import annotations
22

3-
import importlib.util
43
from pathlib import Path
54

65
import torch
76

8-
from transformer_nuggets.export_autograd_triton import Specialization, export_autograd_triton
7+
from transformer_nuggets.export_autograd_triton import (
8+
Specialization,
9+
export_autograd_triton,
10+
load_exported_module,
11+
)
912

1013

1114
def affine_relu(x, w):
1215
return torch.relu(x @ w)
1316

1417

15-
def import_generated(path: Path):
16-
spec = importlib.util.spec_from_file_location(path.stem, path)
17-
module = importlib.util.module_from_spec(spec)
18-
assert spec.loader is not None
19-
spec.loader.exec_module(module)
20-
return module
21-
22-
2318
def main():
2419
if not torch.cuda.is_available():
2520
raise RuntimeError("CUDA is required for this example")
@@ -33,7 +28,7 @@ def main():
3328
[Specialization(args=(x, w), name="static_4x8_8x3")],
3429
output_path,
3530
)
36-
generated = import_generated(output_path)
31+
generated = load_exported_module(output_path)
3732
y = generated.affine_relu_compiled(x, w)
3833
y.sum().backward()
3934
print(f"wrote {output_path}")

examples/export_autograd_triton_dynamic.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
from __future__ import annotations
22

3-
import importlib.util
43
from pathlib import Path
54

65
import torch
76

8-
from transformer_nuggets.export_autograd_triton import Specialization, export_autograd_triton
7+
from transformer_nuggets.export_autograd_triton import (
8+
Specialization,
9+
export_autograd_triton,
10+
load_exported_module,
11+
)
912

1013

1114
def affine_relu(x, w):
1215
return torch.relu(x @ w)
1316

1417

15-
def import_python_file(path: Path):
16-
spec = importlib.util.spec_from_file_location(path.stem, path)
17-
module = importlib.util.module_from_spec(spec)
18-
assert spec.loader is not None
19-
spec.loader.exec_module(module)
20-
return module
21-
22-
2318
def main():
2419
if not torch.cuda.is_available():
2520
raise RuntimeError("CUDA is required for this example")
@@ -38,15 +33,24 @@ def main():
3833
)
3934
],
4035
out=output_path,
41-
source_backend="inductor",
36+
source_backend="clean_triton",
4237
)
4338

44-
generated = import_python_file(output_path)
39+
generated = load_exported_module(output_path)
4540
for batch in (1, 4, 16):
46-
dynamic_x = torch.randn(batch, 8, device="cuda", requires_grad=True)
47-
eager = affine_relu(dynamic_x, w)
48-
compiled = generated.affine_relu_compiled(dynamic_x, w)
41+
eager_x = torch.randn(batch, 8, device="cuda", requires_grad=True)
42+
compiled_x = eager_x.detach().clone().requires_grad_()
43+
eager_w = w.detach().clone().requires_grad_()
44+
compiled_w = w.detach().clone().requires_grad_()
45+
46+
eager = affine_relu(eager_x, eager_w)
47+
compiled = generated.affine_relu_compiled(compiled_x, compiled_w)
4948
torch.testing.assert_close(compiled, eager)
49+
50+
eager_grads = torch.autograd.grad(eager.sum(), (eager_x, eager_w))
51+
compiled_grads = torch.autograd.grad(compiled.sum(), (compiled_x, compiled_w))
52+
for compiled_grad, eager_grad in zip(compiled_grads, eager_grads, strict=True):
53+
torch.testing.assert_close(compiled_grad, eager_grad)
5054
print(f"batch={batch}: {compiled.shape}")
5155

5256
print(f"generated file: {output_path}")

examples/export_autograd_triton_minimal.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from __future__ import annotations
22

3-
import importlib.util
43
from pathlib import Path
54

65
import torch
76

8-
from transformer_nuggets.export_autograd_triton import Specialization, export_autograd_triton
7+
from transformer_nuggets.export_autograd_triton import (
8+
Specialization,
9+
export_autograd_triton,
10+
load_exported_module,
11+
)
912

1013

1114
def tiny_mlp(x, w1, w2, *, activation="relu"):
@@ -19,14 +22,6 @@ def tiny_mlp(x, w1, w2, *, activation="relu"):
1922
return hidden @ w2
2023

2124

22-
def import_python_file(path: Path):
23-
spec = importlib.util.spec_from_file_location(path.stem, path)
24-
module = importlib.util.module_from_spec(spec)
25-
assert spec.loader is not None
26-
spec.loader.exec_module(module)
27-
return module
28-
29-
3025
def main():
3126
if not torch.cuda.is_available():
3227
raise RuntimeError("CUDA is required for this example")
@@ -50,7 +45,7 @@ def main():
5045
max_autotune=True,
5146
)
5247

53-
generated = import_python_file(output_path)
48+
generated = load_exported_module(output_path)
5449
eager_x = x.detach().clone().requires_grad_()
5550
eager_w1 = w1.detach().clone().requires_grad_()
5651
eager_w2 = w2.detach().clone().requires_grad_()

test/test_export_autograd_triton.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from __future__ import annotations
22

3-
import importlib.util
43
import inspect
54
import os
65
from pathlib import Path
76

87
import pytest
98
import torch
109

11-
from transformer_nuggets.export_autograd_triton import Specialization, export_autograd_triton
10+
from transformer_nuggets.export_autograd_triton import (
11+
Specialization,
12+
export_autograd_triton,
13+
load_exported_module,
14+
)
1215

1316
os.environ.setdefault("TORCHINDUCTOR_COMPILE_THREADS", "1")
1417

@@ -98,11 +101,7 @@ def _requires_export_runtime():
98101

99102

100103
def _import_generated(path: Path):
101-
spec = importlib.util.spec_from_file_location(path.stem, path)
102-
module = importlib.util.module_from_spec(spec)
103-
assert spec.loader is not None
104-
spec.loader.exec_module(module)
105-
return module
104+
return load_exported_module(path)
106105

107106

108107
def _clone_tensor(tensor):
@@ -521,13 +520,16 @@ def test_dynamic_batch_specialization_dispatches_across_batch_sizes(tmp_path):
521520
)
522521
],
523522
generated_path,
524-
source_backend="inductor",
523+
source_backend="clean_triton",
525524
)
526525
module = _import_generated(generated_path)
527-
assert "s" in "\n".join(
526+
artifact_source = "\n".join(
528527
path.read_text()
529528
for path in generated_path.with_name("generated_dynamic_artifacts").glob("*.py")
530529
)
530+
assert "@triton.jit" in artifact_source
531+
assert "async_compile.triton" not in artifact_source
532+
assert "triton.cdiv" in artifact_source
531533

532534
for batch_size in (1, 7, 16):
533535
dynamic_x = torch.randn(batch_size, 8, device="cuda", requires_grad=True)
@@ -560,11 +562,12 @@ def test_dynamic_shape_limitations_are_explicitly_guarded(tmp_path):
560562
x = torch.randn(2, 4, device="cuda", requires_grad=True)
561563
w = torch.randn(4, 5, device="cuda", requires_grad=True)
562564

563-
with pytest.raises(ValueError, match="source_backend='inductor'"):
565+
with pytest.raises(NotImplementedError, match="forward-only"):
564566
export_autograd_triton(
565-
affine_activation,
566-
[Specialization(args=(x, w), dynamic_shapes={"x": {0: "batch"}})],
567-
tmp_path / "generated_dynamic_clean.py",
567+
integer_tensor_output,
568+
[Specialization(args=(x,), dynamic_shapes={"x": {0: "batch"}})],
569+
tmp_path / "generated_dynamic_forward_only.py",
570+
source_backend="inductor",
568571
)
569572

570573
with pytest.raises(NotImplementedError, match="dynamic dim 0"):
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
from transformer_nuggets.export_autograd_triton.api import export_autograd_triton
2+
from transformer_nuggets.export_autograd_triton.loading import load_exported_module
23
from transformer_nuggets.export_autograd_triton.specs import ExportedAutogradSource, Specialization
34

4-
__all__ = ["ExportedAutogradSource", "Specialization", "export_autograd_triton"]
5+
__all__ = [
6+
"ExportedAutogradSource",
7+
"Specialization",
8+
"export_autograd_triton",
9+
"load_exported_module",
10+
]

transformer_nuggets/export_autograd_triton/api.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ def export_autograd_triton(
3737
raise ValueError("source_backend must be 'inductor' or 'clean_triton'")
3838
if not specializations:
3939
raise ValueError("At least one specialization is required")
40-
if source_backend == "clean_triton" and any(
41-
specialization.dynamic_shapes is not None for specialization in specializations
42-
):
43-
raise ValueError("dynamic_shapes require source_backend='inductor' for now")
44-
4540
config_patches = dict(inductor_config_patches or {})
4641
if max_autotune:
4742
config_patches["max_autotune"] = True

transformer_nuggets/export_autograd_triton/codegen.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,40 @@ def _rewrite_module_as_clean_triton(path: Path) -> None:
232232
from torch.utils._get_clean_triton import get_clean_triton
233233

234234
get_clean_triton(path.resolve(), path.resolve(), auto_generate_params=True)
235+
_patch_dynamic_pointwise_grids(path)
235236
launch_params_path = Path(f"{path}.launch_params")
236237
if launch_params_path.exists():
237238
launch_params_path.unlink()
238239

239240

241+
def _patch_dynamic_pointwise_grids(path: Path) -> None:
242+
source = path.read_text()
243+
for xnumel_name in sorted(set(re.findall(r"(\w+_xnumel)\s*=", source))):
244+
kernel_name = xnumel_name.removesuffix("_xnumel")
245+
source = re.sub(
246+
rf"{kernel_name}\[\(\d+, \d+, \d+\)\]\((?P<args>[^\n]*?), \d+, XBLOCK=(?P<xblock>\d+),",
247+
lambda match: _dynamic_pointwise_launch_replacement(
248+
kernel_name,
249+
xnumel_name,
250+
match,
251+
),
252+
source,
253+
)
254+
path.write_text(source)
255+
256+
257+
def _dynamic_pointwise_launch_replacement(
258+
kernel_name: str,
259+
xnumel_name: str,
260+
match: re.Match[str],
261+
) -> str:
262+
xblock = match.group("xblock")
263+
return (
264+
f"{kernel_name}[(triton.cdiv({xnumel_name}, {xblock}), 1, 1)]("
265+
f"{match.group('args')}, {xnumel_name}, XBLOCK={xblock},"
266+
)
267+
268+
240269
def _generate_public_wrapper(exported_name: str, signature: inspect.Signature) -> str:
241270
return f"def {exported_name}{signature}:\n" + indent(
242271
"return _run_with_bound_args(locals())\n", " "
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
import importlib.util
4+
from pathlib import Path
5+
from types import ModuleType
6+
7+
8+
def load_exported_module(path: str | Path) -> ModuleType:
9+
path = Path(path)
10+
spec = importlib.util.spec_from_file_location(path.stem, path)
11+
if spec is None or spec.loader is None:
12+
raise ImportError(f"Could not load generated module from {path}")
13+
module = importlib.util.module_from_spec(spec)
14+
spec.loader.exec_module(module)
15+
return module

0 commit comments

Comments
 (0)