torch
PyTorch is the dominant deep learning framework for Python: a tensor library with NumPy-like operations that run on GPUs, tape-based automatic differentiation, the torch.nn module system for building networks, and utilities for data loading and distributed training. Models are ordinary imperative Python, so debugging stays normal (print, pdb, readable stack traces), and torch.compile JIT-compiles that same code when you want speed. It is the substrate most modern ML work runs on, including nearly everything on Hugging Face.
If you are doing deep learning in Python this is the default, and fighting the default is rarely worth it. Just do not reach for it when scikit-learn or a hosted API already solves your problem.
Use it if
- You are training or fine-tuning neural networks: it is the research and industry default, and most papers and pretrained checkpoints assume it
- You need GPU-accelerated tensor math with autograd on NVIDIA (CUDA), AMD (ROCm), Intel GPUs, or Apple silicon (MPS)
- You build on the Hugging Face, torchvision, or Lightning ecosystem, which all target PyTorch first
- You want model code you can step through imperatively instead of a static graph you cannot inspect
- You are doing classical ML (trees, regressions, clustering) on tabular data: scikit-learn is lighter and the right tool, and PyTorch adds nothing there
- Install size matters: default Linux wheels bundle CUDA libraries and pull in gigabytes, which is a lot for a small inference service that onnxruntime could serve
- You only need to call a hosted model API: no local deep learning framework is required at all
- You deploy to constrained mobile or edge targets: the full framework is heavy and the mobile path (ExecuTorch) is a separate toolchain to learn
Setup reality
pip install torch works, but which build you get matters: the default Linux wheel bundles CUDA libraries and weighs gigabytes, while CPU-only or specific CUDA/ROCm builds come from PyTorch's own index URL that the selector on pytorch.org generates for you. Python 3.10+ is required. GPU use adds driver and CUDA version matching, and Apple silicon runs through the MPS backend where a few ops still fall back to CPU. Building from source is a real project (CMake, MKL, optional magma and triton) that almost nobody needs to attempt.
Patterns
Create tensorscreate-tensor
import torch
x = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
z = torch.zeros(3, 4)
r = torch.randn(2, 3)torch.tensor copies data and infers dtype; use torch.from_numpy to share memory with an existing NumPy array.
Pick the best available deviceselect-device
device = (
'cuda' if torch.cuda.is_available()
else 'mps' if torch.backends.mps.is_available()
else 'cpu'
)
model = model.to(device)
batch = batch.to(device)Every tensor in an op must live on the same device; the 'expected all tensors to be on the same device' error is almost always a missing .to(device).
Define a model with nn.Moduledefine-model
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 10),
)
def forward(self, x):
return self.layers(x)Invoke the model as model(x), not model.forward(x); the call path runs hooks that a bare forward() skips.
Write a training looptraining-loop
opt = torch.optim.AdamW(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
model.train()
for xb, yb in loader:
opt.zero_grad()
loss = loss_fn(model(xb), yb)
loss.backward()
opt.step()Forgetting opt.zero_grad() silently accumulates gradients across steps; it is the classic PyTorch bug.
Load batches with Dataset and DataLoaderdata-loading
from torch.utils.data import DataLoader, Dataset
class MyData(Dataset):
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
def __getitem__(self, i):
return self.items[i]
loader = DataLoader(
MyData(items), batch_size=32, shuffle=True, num_workers=4
)num_workers > 0 spawns worker processes; on Windows and macOS guard the entry point with if __name__ == '__main__' or spawning recurses.
Save and load model weightssave-load-model
torch.save(model.state_dict(), 'model.pt')
model = Net()
model.load_state_dict(
torch.load('model.pt', map_location='cpu')
)
model.eval()Save the state_dict, not the whole model object; whole-model pickles break when your class or file layout changes.
Run inference correctlyinference-mode
model.eval()
with torch.inference_mode():
preds = model(batch)model.eval() switches dropout and batchnorm behavior while inference_mode disables autograd; correct inference needs both.
Speed up a model with torch.compilecompile-model
model = torch.compile(model)
out = model(x) # first call triggers compilationThe first call per input shape is slow while kernels compile; heavy Python dynamism in forward() can silently fall back to eager.
Train with automatic mixed precisionmixed-precision
scaler = torch.amp.GradScaler('cuda')
for xb, yb in loader:
opt.zero_grad()
with torch.amp.autocast('cuda'):
loss = loss_fn(model(xb), yb)
scaler.scale(loss).backward()
scaler.step(opt)
scaler.update()Use the torch.amp namespace; the old torch.cuda.amp spelling is deprecated. GradScaler is for fp16 and unnecessary in bf16 training.
Freeze a backbone for fine-tuningfreeze-layers
for p in model.backbone.parameters():
p.requires_grad = False
opt = torch.optim.AdamW(
(p for p in model.parameters() if p.requires_grad),
lr=1e-4,
)Setting requires_grad False skips those gradients; also filtering them out of the optimizer avoids wasted optimizer state memory.
Accumulate gradients for a bigger effective batchgradient-accumulation
accum = 4
opt.zero_grad()
for i, (xb, yb) in enumerate(loader):
loss = loss_fn(model(xb), yb) / accum
loss.backward()
if (i + 1) % accum == 0:
opt.step()
opt.zero_grad()Divide the loss by the accumulation steps or your effective learning rate quietly multiplies by that factor.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| jax | PyPI | Functional composable transforms (jit, grad, vmap) and TPU-first research code |
| tensorflow | PyPI | Production pipelines already standardized on TF, Keras, and TFX |
| scikit-learn | PyPI | Classical ML on tabular data where deep learning is overkill |