Speeding Up PyTorch Training with torch.compile: A Practical Guide
Learn how torch.compile can speed up PyTorch training with minimal code changes, see a ResNet‑18 example, and understand its limits.
23 Jul 2025, 06:30 UTC

Problem: Slow training loops despite simple model changes
When you iterate over a dataset, each forward‑and‑backward pass spends time in PyTorch’s eager execution model. Even if the model architecture is unchanged, the Python interpreter dispatches individual operators to CUDA kernels one by one, which introduces launch overhead and prevents the runtime from fusing adjacent operations. For many vision workloads this overhead adds up to a noticeable fraction of the epoch time, especially when the batch size is modest or the model contains many point‑wise layers.
What torch.compile does
Introduced in PyTorch 2.0, torch.compile traces the model (or a decorated function) and lowers it to an optimized graph. The tracer records the sequence of tensor operations, applies kernel fusion, selects efficient backends (e.g., CUDA, Triton, or Inductor), and produces a callable that replaces the original eager forward pass. The rest of the training loop—data loading, optimizer step, loss computation—remains untouched, so you get a speedup without rewriting your script.
Worked example: ResNet‑18 on CIFAR‑10
import torch, torchvision, time
from torch.utils.data import DataLoader
# 1. Model and data
model = torchvision.models.resnet18(num_classes=10)
model = torch.compile(model) # <-- compilation wrapper
transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=4)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
criterion = torch.nn.CrossEntropyLoss()
# 2. Training loop with timing
num_epochs = 5
batch_times = []
for epoch in range(num_epochs):
epoch_start = time.perf_counter()
for images, labels in train_loader:
batch_start = time.perf_counter()
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
batch_times.append(time.perf_counter() - batch_start)
epoch_time = time.perf_counter() - epoch_start
print(f'Epoch {epoch+1}: {epoch_time:.3f}s total, avg batch {sum(batch_times[-len(train_loader):])/len(train_loader):.3f}s')
# 3. Baseline run (repeat with model = torchvision.models.resnet18(...) without compile)
The research brief reports that on an NVIDIA RTX 3080 the eager version needed about 0.45 s per batch, while the compiled version dropped to roughly 0.28 s per batch—a ~1.6× speedup. Accuracy stayed within 0.1 % of the baseline, confirming that the transformation did not alter the model’s numerical behavior.
Trade‑offs and limitations
- One‑time compilation cost. The first execution triggers tracing and graph lowering, which can take a few seconds. For scripts that run only a handful of batches (e.g., quick debugging or hyperparameter sweeps) this overhead may outweigh the runtime gain.
- Dynamic control flow. Loops whose iteration count depends on tensor values (e.g.,
while x.sum() > threshold) cannot be fully traced; those sections fall back to eager execution, limiting the achievable speedup. - Third‑party extensions. Custom CUDA kernels or libraries that rely on Python side‑effects (e.g., modifying a global variable inside a kernel) may break when the graph is executed. In such cases you can exclude the offending module with
torch.compile(model, mode='reduce-overhead', fullgraph=False)or wrap the problematic part intorch.compiler.disable.
Actionable checklist
- Verify PyTorch ≥ 2.0:
python -c "import torch; print(torch.__version__)" - Wrap the model (or the forward method) with
torch.compilebefore the training loop starts. - Run a short warm‑up epoch to amortize the compilation overhead, then measure epoch time with
time.perf_counter(). - Compare validation accuracy against a baseline run without compile; ensure the difference is < 0.1 %.
- If you observe a fallback, inspect the compiler logs with
torch._dynamo.config.verbose=Trueto locate unsupported constructs.
By adding a single line—model = torch.compile(model)—you can often shave tens of percent off each training batch while keeping the rest of your code unchanged. Measure the one‑time cost, watch for dynamic control flow, and you’ll have a practical path to faster experiments without a major rewrite.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.