Start hereQuick start · 6 you’ll reach for daily
Target versions · paceVersions
torch ≥ 2.4
cuda ≥ 12.1
python ≥ 3.10
PyTorch 2.x is graph-capture-first — torch.compile(model) is one line and frequently 30–100% faster.
torch.amp is the current mixed-precision API (replaces torch.cuda.amp),
torch.inference_mode() beats no_grad() for inference,
and Apple Silicon ships through the mps backend. torch.distributed
+ FSDP/DTensor cover multi-GPU. This sheet pins to PyTorch 2.4+.
Install · deviceSetup
# Install — the build matrix is brittle, grab the exact command from pytorch.org
# CUDA 12.x (Linux + Windows)
pip install --index-url https://download.pytorch.org/whl/cu124 \
torch torchvision torchaudio
# CPU-only (any OS)
pip install --index-url https://download.pytorch.org/whl/cpu \
torch torchvision torchaudio
# Apple Silicon — built into the regular wheel; uses MPS automatically
pip install torch torchvision torchaudio
# Conda (CPU)
conda install -c pytorch pytorch torchvision torchaudio cpuonly
# Confirm the accelerator the runtime sees
python -c "
import torch
print('torch', torch.__version__)
print('cuda', torch.cuda.is_available(), 'mps', torch.backends.mps.is_available())
print('device count', torch.cuda.device_count())
"
# Pick the device once, reuse everywhere
# device = torch.device('cuda' if torch.cuda.is_available()
# else 'mps' if torch.backends.mps.is_available()
# else 'cpu')
Where things liveCommon imports
| import torch | Top-level tensors, autograd, dtypes, devices. |
| import torch.nn as nn / import torch.nn.functional as F | Module + functional layer APIs. |
| from torch.utils.data import Dataset, DataLoader, random_split | Data loading primitives. |
| from torch.amp import autocast, GradScaler | Modern mixed-precision API. Preferred over torch.cuda.amp. |
| import torch.optim as optim / from torch.optim.lr_scheduler import CosineAnnealingLR | Optimizers + schedulers. |
| from torchvision import datasets, transforms, models | Vision datasets / augmentations / pretrained nets. |
| from torch.utils.tensorboard import SummaryWriter | TensorBoard logging. |
| import torch.distributed as dist | Multi-process / multi-GPU collectives. |
Create · reshape · combineTensors
| torch.tensor([1, 2, 3]) | From a Python sequence. Inherits dtype. |
| torch.zeros(2, 3) / ones(2, 3) / full((2,3), 7.0) | Constant-filled. |
| torch.arange(10) / torch.linspace(0, 1, 50) | Range / even spacing. |
| torch.randn(2, 3) / torch.rand(2, 3) | Standard normal / uniform[0,1). |
| torch.from_numpy(arr) | NumPy → tensor. Shares memory. |
| x.shape / x.size() / x.dtype / x.device / x.numel() | Inspection. |
| x.to("cuda", dtype=torch.float16, non_blocking=True) | Move + cast. non_blocking overlaps with compute. |
| x.view(2, -1) / x.reshape(2, -1) | Reshape. view is no-copy but needs contiguous. |
| x.contiguous() | Make memory contiguous — required by some kernels. |
| x.unsqueeze(0) / x.squeeze() | Add / drop length-1 axes. |
| x.permute(0, 2, 1) / x.transpose(1, 2) | Reorder axes. |
| torch.cat([a, b], dim=0) / torch.stack([a, b], dim=0) | Glue along existing / new axis. |
| a @ b / torch.matmul(a, b) / torch.einsum("bnd,bdm->bnm", a, b) | Matrix multiply / general contraction. |
requires_grad · backwardAutograd
| x = torch.tensor(2.0, requires_grad=True) | Track grads through this tensor. |
| loss.backward() | Populate .grad on every leaf tensor used. |
| x.grad / x.grad.zero_() | Read / clear the accumulated grad. |
| with torch.no_grad(): ... | Disable autograd in a block (eval, inference). |
| with torch.inference_mode(): ... | Preferred for inference — faster than no_grad. |
| x.detach() | Make a copy with autograd disabled. |
| torch.autograd.grad(loss, params, create_graph=True) | Compute grads w/o populating .grad — for higher-order. |
| retain_graph=True | Keep the graph after backward — multi-backward setups. |
| torch.autograd.functional.jacobian / hessian / vjp / jvp | Functional derivatives. |
Define a modelnn.Module
| class M(nn.Module): def __init__: super().__init__() | Standard subclass pattern. |
| self.fc = nn.Linear(in, out, bias=True) | Modules attached as attributes register parameters automatically. |
| nn.Sequential(nn.Linear(...), nn.ReLU(), nn.Linear(...)) | Quick feedforward stack. |
| nn.ModuleList / nn.ModuleDict | Lists / dicts of modules — Preferred over plain Python containers. |
| self.register_buffer("running_mean", torch.zeros(N)) | Non-trainable state that moves with .to and saves in state_dict. |
| nn.Parameter(torch.zeros(N)) | Trainable tensor on a custom layer. |
| model.train() / model.eval() | Toggle dropout / batchnorm. Always call before each phase. |
| model.parameters() / named_parameters() / state_dict() | Iterate / serialize. |
| model.apply(init_fn) | Recursively apply an init function. |
| model = torch.compile(model) | Preferred — one-line graph capture + fusion. Often 30%+ speedup. |
import torch
import torch.nn as nn
import torch.nn.functional as F
class MLP(nn.Module):
def __init__(self, in_dim: int, hidden: int, out_dim: int, p: float = 0.1):
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, hidden),
nn.GELU(),
nn.Dropout(p),
nn.Linear(hidden, hidden),
nn.GELU(),
nn.Dropout(p),
nn.Linear(hidden, out_dim),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
model = MLP(in_dim=28*28, hidden=256, out_dim=10).to("cuda")
# Inspect parameter count + memory footprint
n_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"trainable params: {n_params/1e6:.2f}M")
# Initialize weights — register a small init function
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
if m.bias is not None: nn.init.zeros_(m.bias)
model.apply(init_weights)
Linear · conv · attentionLayers & activations
| nn.Linear(in_dim, out_dim) | Fully-connected. |
| nn.Conv2d(in_c, out_c, kernel_size=3, padding=1, stride=1) | 2-D convolution. |
| nn.ConvTranspose2d(...) / nn.MaxPool2d(2) / nn.AvgPool2d(2) | Upsample / pool. |
| nn.BatchNorm2d(C) / nn.LayerNorm(D) / nn.GroupNorm(8, C) | Normalization layers. |
| nn.Dropout(p=0.1) / nn.Dropout2d(p) | Element / channel dropout. |
| nn.Embedding(num_embeddings, embed_dim, padding_idx=0) | Lookup table for tokens. |
| nn.MultiheadAttention(embed_dim, num_heads, batch_first=True) | Self / cross attention. |
| nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward, batch_first=True) | Standard transformer block. |
| nn.LSTM / nn.GRU(input_size, hidden_size, batch_first=True) | Recurrent layers. |
| F.relu / F.gelu / F.silu / F.softmax(x, dim=-1) | Functional activations (no params). |
| nn.functional.scaled_dot_product_attention(q, k, v, is_causal=True) | Fast fused attention (uses Flash on CUDA). |
Dataset · DataLoaderDatasets & DataLoader
| class DS(Dataset): def __len__, __getitem__ | Custom dataset: length + per-index access. |
| DataLoader(ds, batch_size=64, shuffle=True, num_workers=4) | Standard loader. Tune num_workers. |
| pin_memory=True | Faster host→GPU transfers with non_blocking=True. |
| persistent_workers=True | Keep workers alive across epochs — cuts setup cost. |
| drop_last=True | Drop the last partial batch — required for some BN setups. |
| collate_fn=lambda batch: ... | Custom batching for variable-length / dict samples. |
| sampler=WeightedRandomSampler(weights, N) | Per-sample weighted sampling. |
| random_split(ds, [0.8, 0.1, 0.1]) | Train / val / test split. |
| from torchdata.datapipes / from datasets import load_dataset | Streaming / Hugging Face data when raw Dataset isn’t enough. |
SGD · Adam · schedulesOptimizers & schedulers
| torch.optim.SGD(params, lr=0.01, momentum=0.9, weight_decay=1e-4) | Classic baseline. |
| torch.optim.Adam(params, lr=1e-3) / torch.optim.AdamW(params, lr=3e-4, weight_decay=0.01) | Adam variants. AdamW preferred — decoupled weight decay. |
| optim.zero_grad(set_to_none=True) | Free grad tensors — faster than zeroing them. |
| optim.step() | Apply the update. |
| CosineAnnealingLR(optim, T_max=epochs) | Cosine decay over an epoch budget. |
| OneCycleLR(optim, max_lr=..., total_steps=...) | Super-convergence schedule. |
| ReduceLROnPlateau(optim, "min", patience=3) | Drop LR when a metric stalls. |
| torch.nn.utils.clip_grad_norm_(params, 1.0) | Stabilize training — call after backward + unscale_. |
| param_groups | Different LR / WD per group — pass a list of dicts to the optimizer. |
forward / backward / stepTraining loop & AMP
| model.train() / model.eval() | Always toggle — affects dropout + batchnorm. |
| with autocast(device_type="cuda", dtype=torch.float16): | Mixed precision — lighter, faster, only on the forward. |
| GradScaler() · scaler.scale(loss).backward() | Loss scaling for FP16. Avoids underflow. |
| scaler.unscale_(optim) → clip_grad_norm_ → scaler.step / update | Correct order with gradient clipping. |
| torch.bfloat16 (no scaler needed) | Preferred on Ampere+ — same range as FP32, no scaling math. |
| loss.item() / metric.detach().cpu() | Pull a scalar / array off the graph for logging. |
| model = torch.compile(model, mode="reduce-overhead") | Wrap once after build. mode trades compile time vs runtime. |
| torch.set_float32_matmul_precision("high") | Allow TF32 on Ampere+ — free speedup on matmul. |
| accumulation: backward several times, step every N | Effective batch size = micro-batch × N. |
import torch
import torch.nn as nn
from torch.amp import autocast, GradScaler
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
optim = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.01)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(optim, T_max=epochs)
loss_fn = nn.CrossEntropyLoss()
scaler = GradScaler() # mixed-precision loss scaling
for epoch in range(epochs):
model.train()
for xb, yb in train_loader:
xb = xb.to(device, non_blocking=True)
yb = yb.to(device, non_blocking=True)
optim.zero_grad(set_to_none=True) # cheaper than zero_grad()
with autocast(device_type=device, dtype=torch.float16):
logits = model(xb)
loss = loss_fn(logits, yb)
scaler.scale(loss).backward()
scaler.unscale_(optim) # unscale before clipping
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
scaler.step(optim)
scaler.update()
sched.step()
# Eval pass
model.eval()
correct = total = 0
with torch.inference_mode(): # faster than no_grad
for xb, yb in val_loader:
xb, yb = xb.to(device), yb.to(device)
correct += (model(xb).argmax(1) == yb).sum().item()
total += yb.size(0)
print(f"epoch {epoch} val_acc={correct/total:.4f}")
Classification · regressionLoss functions
| nn.CrossEntropyLoss(weight=..., label_smoothing=0.1) | Multi-class. Takes raw logits, not softmax probs. |
| nn.BCEWithLogitsLoss(pos_weight=...) | Binary / multi-label. Stable than BCELoss + sigmoid. |
| nn.NLLLoss() | Pair with F.log_softmax. Legacy when CE works. |
| nn.MSELoss() / nn.L1Loss() / nn.HuberLoss(delta=1.0) | Regression. Huber is robust to outliers. |
| nn.SmoothL1Loss(beta=1.0) | Smooth L1 — common in detection. |
| nn.KLDivLoss(reduction="batchmean") | Distillation. Input is log-probs. |
| nn.CosineEmbeddingLoss / TripletMarginLoss | Pair / triplet losses. |
| ignore_index=-100 | Skip padded tokens in CE. |
state_dict · checkpointSave, load, checkpoint
| torch.save(model.state_dict(), "m.pt") | Preferred — save weights, not the whole pickle. |
| model.load_state_dict(torch.load("m.pt", map_location=device)) | Reload weights into an already-built model. |
| torch.save({"model": ..., "optim": ..., "epoch": ...}, "ckpt.pt") | Resumable checkpoint — include optimizer + scheduler state. |
| missing, unexpected = model.load_state_dict(d, strict=False) | Allow partial loads — report what didn’t match. |
| torch.jit.script(model) / torch.jit.trace(model, example) | Compile to TorchScript for deployment. |
| torch.export.export(model, (x,)) | Modern export — graph-based, no Python at runtime. |
| torch.onnx.export(model, (x,), "model.onnx", opset_version=17) | Export to ONNX for cross-framework inference. |
DDP · FSDP · torchrunDistributed
| torchrun --nproc_per_node=4 train.py | Preferred launcher. Replaces deprecated torch.distributed.launch. |
| dist.init_process_group("nccl") / dist.destroy_process_group() | Init / teardown the process group. |
| rank = dist.get_rank() / world_size = dist.get_world_size() | Identity in the cluster. |
| torch.cuda.set_device(local_rank) | Pin this process to one GPU. |
| model = nn.parallel.DistributedDataParallel(model, device_ids=[local_rank]) | Data-parallel wrapper. no_sync() for grad accumulation. |
| from torch.distributed.fsdp import FullyShardedDataParallel as FSDP | Shard params + grads + optimizer state — for models too big for one GPU. |
| DistributedSampler(ds, shuffle=True, drop_last=True) | Each rank sees a unique shard. |
| if rank == 0: torch.save(...) | Checkpoint only on rank 0 — avoid file race. |
| dist.all_reduce(t, op=dist.ReduceOp.SUM) | Manual collective — sum / average / max across ranks. |
MNIST · AMP · saveEnd-to-end · MNIST classifier
Dataset → DataLoader → Sequential model → AMP training loop → checkpoint → inference. The whole stack on one page.
# MNIST classifier — Dataset, DataLoader, nn.Module, AMP, save/load, inference.
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torch.amp import autocast, GradScaler
device = "cuda" if torch.cuda.is_available() else "cpu"
tf = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])
train_ds = datasets.MNIST("./data", train=True, download=True, transform=tf)
val_ds = datasets.MNIST("./data", train=False, download=True, transform=tf)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True,
num_workers=2, pin_memory=True)
val_loader = DataLoader(val_ds, batch_size=256, num_workers=2, pin_memory=True)
model = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.GELU(),
nn.Linear(256, 10)).to(device)
opt = torch.optim.AdamW(model.parameters(), lr=3e-4)
scaler = GradScaler()
loss_fn = nn.CrossEntropyLoss()
for epoch in range(3):
model.train()
for xb, yb in train_loader:
xb, yb = xb.to(device), yb.to(device)
opt.zero_grad(set_to_none=True)
with autocast(device_type=device, dtype=torch.float16):
loss = loss_fn(model(xb), yb)
scaler.scale(loss).backward(); scaler.step(opt); scaler.update()
# Save + reload
torch.save(model.state_dict(), "mnist.pt")
model.load_state_dict(torch.load("mnist.pt", map_location=device))
# Inference
model.eval()
with torch.inference_mode():
sample, _ = val_ds[0]
pred = model(sample.unsqueeze(0).to(device)).argmax(1).item()
print("pred:", pred)
Best practiceGood to know
torch.compile once it works.
A single line gives 20–50% speedup on most models with no accuracy change. First call is slow (compile time); subsequent steps are dramatically faster.
bfloat16 over float16 on Ampere+.
Same range as FP32 means no GradScaler needed and no overflow issues — simpler training loop with the same speedup.
state_dict, not the whole model.
Saving the model object pickles the class definition — load breaks the moment you refactor. state_dict is a portable dict of tensors that survives refactors.
Common trapsWatch out for
model.eval() on the val pass.
Dropout stays active and BatchNorm keeps updating running stats — your reported val metric is noise. Always pair train() / eval() with the phase.
.item() inside the inner loop syncs the GPU.
Every .item() / .cpu() forces a host sync. Accumulate scalars on-device and pull once per batch / epoch.
BCEWithLogitsLoss — pass logits.