pytorch/pytorch
AOT Autograd
Active contributors: bdhirsh, aorenste
Purpose
AOT Autograd is the middle layer of the torch.compile stack. It takes a forward FX graph (produced by Dynamo or by a manual tracer), runs it through Functionalize + Autograd + FakeTensor to pre-trace the joint forward + backward graph, then partitions that joint graph into a forward and a backward, and hands those to a downstream compiler (Inductor by default).
Without AOT Autograd, torch.compile would only be able to compile inference; with it, the entire training step (including the backward pass) compiles into a single graph.
The package lives at torch/_functorch/_aot_autograd/ (and its top-level entry torch/_functorch/aot_autograd.py). It grew out of the same functorch transforms that implement vmap/grad.
Directory layout
| Path | Contents |
|---|---|
torch/_functorch/aot_autograd.py |
Top-level entry: aot_module, aot_function |
torch/_functorch/_aot_autograd/ |
Implementation |
torch/_functorch/_aot_autograd/dispatch_and_compile_graph.py |
Build joint graph and dispatch to compiler |
torch/_functorch/_aot_autograd/runtime_wrappers.py |
Compiled artefact wrapper that re-injects autograd |
torch/_functorch/_aot_autograd/jit_compile_runtime_wrappers.py |
Caching & inlining of compiled artefacts |
torch/_functorch/_aot_autograd/input_output_analysis.py |
Detection of view, mutation, and aliasing patterns |
torch/_functorch/_aot_autograd/utils.py |
Helpers |
torch/_functorch/partitioners.py |
Forward/backward graph splitting policies |
torch/_subclasses/fake_tensor.py |
FakeTensor (used heavily here) |
torch/_subclasses/functional_tensor.py |
Python functional-tensor wrapper |
aten/src/ATen/FunctionalTensorWrapper.cpp |
C++ functionalize key |
Key abstractions
| Type | File | Purpose |
|---|---|---|
aot_function / aot_module |
torch/_functorch/aot_autograd.py |
User-facing API |
AOTConfig |
torch/_functorch/_aot_autograd/utils.py |
Compile config |
FakeTensorMode |
torch/_subclasses/fake_tensor.py |
Dispatch mode that replaces real ops with shape-only ops |
FunctionalTensorMode |
torch/_subclasses/functional_tensor.py |
Dispatch mode that functionalizes view/mutation |
min_cut_rematerialization_partition |
torch/_functorch/partitioners.py |
Default fwd/bwd graph partitioning |
ViewMutationMetadata |
torch/_functorch/_aot_autograd/input_output_analysis.py |
Captures view/aliasing/mutation patterns |
CompiledRuntimeWrapper |
torch/_functorch/_aot_autograd/runtime_wrappers.py |
Wraps the compiled fwd/bwd into an autograd.Function |
How it works
The trace
Given a forward function f(args) and example inputs:
- Wrap inputs in FakeTensors — every tensor becomes a
FakeTensorcarrying shape/dtype/device but no real data. This is essential: we want to symbolically execute through Python without doing actual compute, and FakeTensor provides shape semantics for every ATen op via meta kernels. - Push a
FunctionalTensorMode— every view and in-place op is intercepted and replaced with a pure functional equivalent. TheFunctionalizedispatch key (inaten/src/ATen/FunctionalTensorWrapper.cpp) does the heavy lifting; the Python mode wraps that for tracing. - Push a
ProxyTorchDispatchMode— every dispatcher call is recorded as an FX node. - Run the function with autograd enabled — the
Autograd*keys are still active, so they record their own backward Nodes onto the tape. - Call
.backward()on the outputs — this drives the backward dispatch of every recorded autograd op, which re-enters the same proxy mode and gets recorded into the same FX graph.
The result is a joint FX graph where forward and backward live side by side. Tangent arguments (the gradient inputs to backward) are explicit graph inputs.
The partition
torch/_functorch/partitioners.py has two main strategies:
default_partition— cuts the joint graph at the outputs of the forward; saved tensors are everything that crosses the cut.min_cut_rematerialization_partition— solves a min-cut problem to minimize saved-tensor memory by rematerializing cheap ops in backward instead of saving their outputs. This is the default fortorch.compileand a major contributor to its memory wins.
The partitioner emits a forward FX graph (returns outputs + saved values) and a backward FX graph (takes saved values + tangents → input grads).
Compile + runtime wrap
Each of the two graphs is handed to the configured backend (Inductor by default). The compiled artefacts are wrapped in a synthetic torch.autograd.Function whose forward calls the compiled forward and stores the saved values in ctx, and whose backward calls the compiled backward. From the autograd engine's perspective, it just sees an opaque Function — so AOT-compiled regions compose naturally with eager autograd outside the region.
graph LR
DynGraph[Dynamo FX graph] --> Trace[trace_joint<br/>Fake + Functionalize + Proxy]
Trace -->|joint FX graph| Part[Partitioner<br/>fwd + bwd]
Part -->|fwd graph| BackendF[Backend compile<br/>(Inductor)]
Part -->|bwd graph| BackendB[Backend compile<br/>(Inductor)]
BackendF & BackendB --> Wrap[CompiledRuntimeWrapper<br/>autograd.Function]
Wrap --> User[User calls compiled fn]Mutation, views, and aliasing
A surprising amount of AOT Autograd's complexity is in handling aliasing and mutation correctly when the user passes in or mutates real tensors. input_output_analysis.py classifies each input/output into categories: input-mutated, output-as-view-of-input, output-as-view-of-intermediate, etc. The runtime wrappers then re-apply these effects on the real inputs after the compiled (functional) graph runs.
This is the bookkeeping that lets torch.compile accept code that does x.add_(1) or return x[:, 1:] without the user noticing anything different.
Caching
jit_compile_runtime_wrappers.py implements an in-process cache keyed by guard set. PyTorch 2.x also includes optional persistent caches (FX graph cache, Triton autotune cache, AOTAutograd cache) controlled by env vars / torch._dynamo.config.
Integration points
- Dynamo sends in the forward FX graph + sample inputs.
- FakeTensor / FunctionalTensor are required infrastructure; meta kernels in
torch/_meta_registrations.pyandtorch/_decomp/keep the symbolic execution working as the op set evolves. - Inductor (or any backend with the right interface) consumes the partitioned graphs.
- Higher-order ops are passed through with their subgraphs intact; HOP-specific tracing logic lives in
torch/_higher_order_ops/.
Entry points for modification
- A new partitioning strategy → add to
partitioners.pyand select viaaot_config.partition_fn. - New decomposition rules (replace an ATen op with a sub-FX graph) → register in
torch/_decomp/decompositions.py. - New runtime mutation/view support →
runtime_wrappers.pyandinput_output_analysis.py. - For debugging, set
TORCH_LOGS=aot_graphs,aot_joint_graphandTORCH_COMPILE_DEBUG=1to dump the joint and partitioned graphs.
Key source files
| File | Purpose |
|---|---|
torch/_functorch/aot_autograd.py |
Top-level entry |
torch/_functorch/_aot_autograd/dispatch_and_compile_graph.py |
Joint graph build + dispatch |
torch/_functorch/_aot_autograd/runtime_wrappers.py |
Wrap compiled artefacts as autograd.Function |
torch/_functorch/partitioners.py |
fwd/bwd partitioning |
torch/_subclasses/fake_tensor.py |
FakeTensor |
torch/_subclasses/functional_tensor.py |
FunctionalTensor |
torch/_decomp/decompositions.py |
ATen op decompositions |
torch/_meta_registrations.py |
Meta kernels for shape inference |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.