pip install torch torchvision torchaudio★CPU-only default wheels on PyPI.pip install torch --index-url https://download.pytorch.org/whl/cu128CUDA build. Pick the tag from the selector on pytorch.org.torch.__version__Prints e.g.2.13.0+cu128— build tag included.torch.cuda.is_available()Quick sanity check that the GPU build actually sees a device.python -m torch.utils.collect_envFull environment dump — paste this into bug reports.torch / torchvision / torchaudio versionspinThe domain packages link against one exact torch build. Install all three together or you get import-time symbol errors.torch.cuda.device_count() · get_device_name(0)How many GPUs, and which ones.torch.backends.mps.is_available()Apple Silicon. 2.13 brings FlexAttention to MPS.pip install torchcodecrequiredSeparate package. Every audio and video decode path now goes through it.
import torch★Tensors, math, devices,save/load,compile.import torch.nn as nn★Layer classes that hold parameters.import torch.nn.functional as FThe same ops as stateless functions.import torch.optim as optimSGD, Adam, AdamW + LR schedulers.from torch.utils.data import Dataset, DataLoader★The two halves of every input pipeline.import torch.distributed as distMulti-GPU / multi-node collectives.from torchvision.transforms import v2Note thev2— see card V2.import torchaudioWaveforms, spectrograms, ASR pipelines.from torch import TensorFor type hints:def f(x: Tensor) -> Tensor:import torchvision.transforms.v2.functional as TFStateless versions when you need to apply the same op to two things.from torch.utils.tensorboard import SummaryWriterBuilt in.w.add_scalar("loss/train", v, step).
torch.tensor(data)★From a list or ndarray. Copies, and infers the dtype.torch.zeros(2, 3) · ones · empty · full★Shape given as loose ints or a tuple.torch.randn(2, 3) · rand · randint★randnis N(0,1);randis uniform [0,1).torch.arange(0, 10, 2) · linspace(0, 1, 5)Step-based vs count-based ranges.torch.eye(3) · torch.from_numpy(arr)from_numpyshares memory with the ndarray.torch.zeros_like(x) · randn_like(x)Copies shape, dtype and device fromx.x.clone()Deep copy that stays in the autograd graph. Add.detach()to leave it.torch.manual_seed(0)Seeds all devices. Do it before you build the model.torch.zeros(2, 3, device=dev, dtype=torch.float32)★Build it on the device. Creating on CPU then.to(dev)allocates twice and copies.torch.as_tensor(arr)Shares memory with the numpy array where it can.torch.tensoralways copies.torch.randperm(n) · multinomial(w, k)Random permutation; weighted sampling without replacement.g = torch.Generator().manual_seed(0)A local RNG. Passgenerator=gto DataLoader and samplers so seeding survives workers.
x.shape · x.ndim · x.numel()★Dimensions, rank, total element count.x.dtype · x.device · x.requires_grad★The three things wrong when a call fails.x.to(torch.float32) · x.float() · x.long()Cast..to()is a no-op if it already matches.float32 · float16 · bfloat16Default is float32.bfloat16keeps float32's exponent range — safer for training than float16.int64 · bool · uint8Class labels must beint64; images arrive asuint8.x.item()One-element tensor → Python number. Forces a device sync.x.tolist() · x.numpy().numpy()needs CPU and no grad:x.detach().cpu().numpy().x.is_contiguous()False afterpermute/transpose— and then.view()raises.x.element_size() * x.numel()Bytes held by the tensor — the fastest memory estimate you have.torch.finfo(torch.float16).maxDtype limits. Handy when hunting aninfin half precision.x.stride()Step per dimension. Apermutechanges stride, not memory.int tensor + float tensorquietDtypes promote silently. An accidental float64 anywhere will quietly halve your throughput.
x.view(2, -1)★Free re-interpretation. Needs contiguous memory;-1is inferred.x.reshape(2, -1)★Same result, but copies if it has to. Use this when unsure.x.permute(2, 0, 1)Reorder all dims — e.g. HWC → CHW.x.transpose(0, 1) · x.TSwap exactly two dims.x.unsqueeze(0) · x.squeeze()Add / drop size-1 dims.unsqueeze(0)is how you fake a batch.x.flatten(start_dim=1)Collapse everything after the batch dim — the CNN→Linear join.torch.cat([a, b], dim=0)★Joins along an existing dim. Rank unchanged.torch.stack([a, b], dim=0)★Creates a new dim. Rank +1. This is the pair people mix up.x.expand(3, -1) · x.repeat(3, 1)expandis a free view;repeatallocates.x.contiguous()The fix for "view size is not compatible…".torch.split(x, 2, dim=0) · chunk(x, 3)splittakes a size,chunktakes a count.x.movedim(1, -1)Move one axis and leave the rest alone — clearer than spelling out a full permute.nn.Flatten(1) · nn.Unflatten(1, (C, H, W))The layer forms, for use insidenn.Sequential.
shapes align from the right★Trailing dims must be equal, or one of them 1, or absent. That one rule explains every broadcast.(3,1) + (1,4) → (3,4)★Size-1 dims stretch. Nothing is copied until it has to be.(8,3,32,32) * (3,1,1)Per-channel scale on a batch of images. The classic normalize shape.torch.broadcast_shapes(a.shape, b.shape)Ask before you multiply. Raises if they are incompatible.x.unsqueeze(1) * yBe explicit about where the new axis goes rather than trusting alignment.x.sum(dim=1, keepdim=True)Keeps rank so the result still broadcasts back against the original.pred (N,1) vs target (N,)★famousThe expensive one. These broadcast to (N,N), so your loss silently averages N² pairs and trains on nonsense. Nothing raises.
x[0] · x[:, 1] · x[..., -1]★Basic slicing returns a view, not a copy.x[x > 0]Boolean mask → flat 1-D tensor of the matches.torch.where(cond, a, b)Elementwise pick — differentiable, unlike a Pythonif.x.sum(dim=1) · mean · std · prod★Named dim disappears unlesskeepdim=True.x.max(dim=1)Returns(values, indices)— a named tuple, not a bare tensor.x.argmax(dim=1)★Logits → predicted class. The classification one-liner.torch.topk(x, k=5) · sort · uniqueTop-5 accuracy, ranking, vocabulary building.x.gather(1, idx) · x.scatter_(1, idx, v)Pick / write per-row by index. Trailing_means in-place.torch.nonzero(x, as_tuple=True)Indices where the condition holds; the tuple form indexes directly.x.masked_fill_(mask, float("-inf"))★How attention masks are applied — −inf before softmax becomes exactly 0 after.x.any(dim) · x.all(dim) · x.count_nonzero()Boolean reductions.torch.cumsum(x, dim) · roll · flipRunning totals, circular shift, reverse along an axis.
a + b · a * b · a ** 2★Elementwise, with NumPy broadcasting.a @ b · torch.matmul(a, b)★Matrix multiply; batches over leading dims.torch.einsum("bij,bjk->bik", a, b)Names every axis. Worth learning for attention code.torch.bmm(a, b) · mv · dot · outerExplicit batch-matrix, matrix-vector, vector forms.torch.exp · log · sqrt · abs · clampclamp(min=, max=)is the standard gradient guard.F.softmax(x, dim=-1) · F.log_softmaxAlways name the dim.log_softmaxis the numerically stable one.torch.linalg.norm · inv · svd · solveThe modern namespace — the baretorch.svdspellings are legacy.a.add_(b)in-placeTrailing_mutates. Autograd will raise if the value was needed for backward.torch.allclose(a, b, atol=1e-6)★Compare floats this way in tests.==on floats will eventually embarrass you.torch.isnan(x).any() · nan_to_num(x)★First thing to run when a loss goes to NaN. Check the inputs before blaming the model.F.normalize(x, dim=-1)Unit-length rows. Thena @ b.Tis cosine similarity.torch.cdist(a, b) · corrcoefPairwise distances; correlation matrix.
torch.accelerator.current_accelerator()modernDevice-agnostic: returns cuda / mps / xpu without an if-chain.torch.accelerator.is_available()Guard the call above; falls back to"cpu".x = x.to(dev) · model.to(dev)★Tensors return a copy; modules move in place. Don't writemodel = model.to()expecting otherwise.with torch.autocast(dev, dtype=torch.bfloat16):★Mixed precision. Wrap the forward + loss only.scaler = torch.GradScaler()Needed for float16, not for bfloat16.torch.set_float32_matmul_precision("high")Enables TF32 matmuls on Ampere+. Near-free speedup.x.to(dev, non_blocking=True)Async copy — only helps withpin_memory=Trueloaders.torch.cuda.empty_cache()Returns cached blocks to the driver. Does not fix a real leak.RuntimeError: expected all tensors on the same deviceThe single most common runtime error. Move both the batch and the model.torch.cuda.synchronize()★CUDA calls are asynchronous. Without this your timing measures how fast Python queued work, not how fast it ran.torch.cuda.max_memory_allocated() / 1e9Peak GB. Pair withreset_peak_memory_stats()to measure one region.
x = torch.tensor(2., requires_grad=True)★Opt a leaf tensor into tracking. Model parameters do this for you.loss.backward()★Accumulatesd(loss)/d(param)into every.grad. Loss must be a scalar.x.gradNoneuntil the first backward. Only leaves keep it.with torch.no_grad():★Stop recording — for evaluation and manual parameter edits.with torch.inference_mode():★Stricter and faster thanno_grad. Prefer it for pure inference.x.detach()Same data, cut out of the graph. The way to stop a gradient mid-network.torch.autograd.grad(y, x)Returns grads instead of writing.grad— for higher-order tricks.loss.backward(retain_graph=True)rareThe graph is freed after backward. Only set this if you truly need a second pass — otherwise it silently leaks memory.torch.autograd.set_detect_anomaly(True)★Points at the forward op that produced a NaN gradient. Slow — switch on only while debugging.torch.func.grad(f)(x) · vmap · jacrevFunctional autograd: per-sample gradients, jacobians, batching without a loop.total = nn.utils.clip_grad_norm_(params, 1.0)It returns the pre-clip norm — log it. A spike there precedes most loss explosions.t.register_hook(lambda g: print(g.norm()))Inspect a gradient partway through the graph.
class Net(nn.Module):★Subclass, then define__init__andforward. Never callforward()yourself — callmodel(x).super().__init__()First line of__init__. Skip it and no parameter registers.nn.Sequential(layer, layer, ...)★A model without a class, when the graph is a straight line.nn.ModuleList([...]) · nn.ModuleDictUse these, not a plain Python list — a plain list hides the parameters.nn.Parameter(torch.randn(3))A tensor attribute that counts as a trainable weight.self.register_buffer("mask", t)Moves with.to()and saves in the state dict, but is not trained.model.parameters() · model.named_parameters()★What you hand the optimizer.sum(p.numel() for p in model.parameters())Parameter count in one line.model.apply(init_fn) · nn.init.kaiming_normal_Custom weight init, applied recursively.for p in model.parameters(): p.requires_grad_(False)★Freeze a backbone. Then unfreeze the head and hand only those params to the optimizer.nn.Identity()Replace a head with a pass-through instead of deleting it — shapes downstream stay intact.model.register_forward_hook(fn)Grab intermediate activations without editingforward.model.named_modules() · model.children()Walk the tree.named_modulesrecurses,childrenis one level.
nn.Linear(in_f, out_f)★Acts on the last dim only; leading dims pass through.nn.Conv2d(in_ch, out_ch, k, stride, padding)★Expects(N, C, H, W).padding="same"preserves size at stride 1.nn.MaxPool2d(2) · nn.AdaptiveAvgPool2d(1)The adaptive one fixes the output size for any input — how CNNs reach a Linear head.nn.BatchNorm2d(ch) · nn.LayerNorm(shape)★BatchNorm across the batch (CNNs); LayerNorm within a sample (transformers).nn.Dropout(p=0.5)Active intrain(), a no-op ineval().nn.Embedding(num_emb, dim)Integer ids → vectors. A lookup, not a matmul.nn.MultiheadAttention(dim, heads)Passbatch_first=Trueunless you like(L, N, E).nn.TransformerEncoderLayer · LSTM · GRURNNs return(output, hidden), not a bare tensor.F.scaled_dot_product_attention(q, k, v)fastFused attention kernel.torch.nn.attention.flex_attentionhandles custom masks — and as of 2.13 runs on Apple Silicon too.out = (in + 2*pad − k) // stride + 1★The conv size formula. Memorise it and you stop guessing why a Linear layer rejects your flattened feature map.nn.GroupNorm(g, ch) · nn.RMSNorm(dim)modernGroupNorm when batches are tiny; RMSNorm is the norm of choice in modern LLMs.nn.ConvTranspose2d · nn.Upsample(scale_factor=2)Going back up in resolution. Upsample+Conv avoids checkerboard artefacts.nn.Conv1d · Conv3d · padding="same"1D for sequences and audio, 3D for volumes.
nn.CrossEntropyLoss()★Multi-class. Takes raw logits andint64targets — softmax is built in.nn.BCEWithLogitsLoss()★Binary / multi-label. Also takes logits, and is stabler thanSigmoid+BCELoss.nn.MSELoss() · nn.L1Loss() · SmoothL1Loss★Regression. Smooth L1 (Huber) resists outliers.nn.NLLLoss() · KLDivLoss · CTCLossNLLLossexpectslog_softmaxoutput;CTCLossis the speech/OCR one.loss_fn(pred, y, reduction="none")Per-sample losses — needed for weighting and focal variants.nn.ReLU() · GELU · SiLU · LeakyReLU★GELU/SiLU are the transformer defaults; ReLU still fine for CNNs.nn.Sigmoid() · Tanh · Softmax(dim=-1)For reading probabilities at inference — not before a logits-based loss.nn.Softmax() → nn.CrossEntropyLoss()wrongDouble softmax. Trains, converges badly, and nothing warns you.nn.CrossEntropyLoss(label_smoothing=0.1)★Nearly free accuracy on most classification tasks. Stops the model getting overconfident.nn.CrossEntropyLoss(weight=w, ignore_index=-100)★Class imbalance, and skipping pad tokens.-100is the conventional pad label.nn.BCEWithLogitsLoss(pos_weight=w)Imbalance for binary and multi-label problems.nn.LinearCrossEntropyLoss()2.13Fuses the final projection into the loss. Up to 4× less peak memory on large-vocabulary LM training.F.one_hot(y, num_classes=n)Rarely needed — CrossEntropyLoss wants class indices, not one-hot.
opt = optim.AdamW(model.parameters(), lr=1e-3)★The sane default. Decouples weight decay properly — prefer it toAdam.optim.SGD(params, lr=0.1, momentum=0.9)Still the best final accuracy for vision, with a schedule.opt.zero_grad(set_to_none=True)★Default since 2.0 — frees memory as well as clearing.opt.step()★Applies whatever is in.gradright now.opt = optim.AdamW([{"params": a, "lr": 1e-4}, ...])Param groups: different LR for backbone vs head.nn.utils.clip_grad_norm_(params, 1.0)Betweenbackward()andstep(). Standard for RNNs and LLMs.optim.lr_scheduler.CosineAnnealingLR(opt, T_max)The common modern decay.OneCycleLR · StepLR · ReduceLROnPlateauReduceLROnPlateauis the only one whosestep()takes a metric.sched.step()orderCall it afteropt.step(), once per epoch (or per batch for OneCycle) — never before.optim.AdamW(params, lr=3e-4, weight_decay=0.01)★AdamW decouples decay from the gradient — this is why you pick it over Adam.decay / no-decay param groupsConvention: no weight decay on biases and norm parameters. Split them into two groups.optim.AdamW(params, ..., fused=True)fastOne fused CUDA kernel for the whole step. Free speed on GPU.SequentialLR(opt, [LinearLR(...), CosineAnnealingLR(...)], milestones=[warm])★Warmup then decay, the standard modern schedule.sched.get_last_lr()[0]Log it every epoch. A schedule you cannot see is a schedule you cannot debug.
class DS(Dataset): __len__, __getitem__★Those two methods are the entire contract. Return one sample, not a batch.TensorDataset(X, y)Wraps tensors you already have in memory.DataLoader(ds, batch_size=32, shuffle=True)★Shuffle the train split; never the validation split.num_workers=4, pin_memory=True★The two flags that usually fix a GPU-starved loop.persistent_workers=True, prefetch_factor=2Avoids re-spawning workers every epoch.drop_last=TrueDrops the ragged final batch — matters for BatchNorm.collate_fn=my_fnCustom batching: padding variable-length text or audio.random_split(ds, [0.8, 0.2]) · Subset · ConcatDatasetSplitting and combining without touching disk.WeightedRandomSampler(w, n)Class imbalance. Mutually exclusive withshuffle=True.DataLoader(ds, sampler=s, shuffle=False)samplerandshuffleare mutually exclusive — passing both raises.class S(IterableDataset): __iter__For streams with no length — shard byget_worker_info()or every worker yields the same data.DataLoader(..., generator=g, worker_init_fn=fn)What actually makes a shuffled, multi-worker loader reproducible.
model.train()★Turns Dropout on and BatchNorm into batch-statistics mode. It does not enable gradients.for x, y in loader:The DataLoader is a plain iterable; one pass = one epoch.pred = model(x); loss = loss_fn(pred, y)★Forward. The graph is built here, fresh, every iteration.opt.zero_grad(); loss.backward(); opt.step()★The three lines. Order matters; grads accumulate if you skip the first.running += loss.item() * x.size(0)Log the float, not the tensor — keeping the tensor keeps its whole graph alive.loss = loss / accum_stepsGradient accumulation: skipstep()for N-1 iterations to fake a bigger batch.model.eval()The other half of the pair. Forgetting it is why validation accuracy looks wrong.with torch.inference_mode(): ...Wrap the whole eval pass. Roughly halves memory.acc = (logits.argmax(1) == y).float().mean()Accuracy without a metrics library.with torch.autocast(dev, dtype=torch.bfloat16):★Mixed precision, three lines. Forward inside the context; backward outside it.scaler.scale(loss).backward(); scaler.step(opt); scaler.update()The float16 path only. bfloat16 has the range to skip the scaler entirely.@torch.inference_mode() def evaluate(...)★Decorate the whole eval function rather than remembering thewithblock.if val < best: best = val; torch.save(...)Track the best, not the last. The final epoch is rarely the best one.
torch.save(model.state_dict(), "m.pt")★Save the weights, not the object. A pickled model breaks when your code moves.model.load_state_dict(torch.load("m.pt"))★Build the architecture first, then pour the weights in.torch.load(p, weights_only=True)defaultThe default since 2.6. It blocks arbitrary code execution — passFalseonly for files you trust.torch.load(p, map_location="cpu")★Loads a GPU checkpoint on a CPU-only machine.torch.save({"model": ..., "opt": ..., "epoch": e}, p)A resumable checkpoint needs the optimizer state too.model.load_state_dict(sd, strict=False)Returns the missing / unexpected keys. Useful for partial transfer.keys prefixed "module."Saved fromDataParallel/DDP. Strip the prefix or savemodel.module.state_dict().keys prefixed "_orig_mod."★compileThe modern twin of themodule.bug.torch.compilewraps your model, so the saved keys gain a prefix. Savemodel._orig_mod.state_dict(), or strip it on load.torch.load(p, mmap=True)Memory-maps the file. Large checkpoints load without doubling RAM.ckpt = {"model", "opt", "sched", "scaler", "epoch"}★A resumable checkpoint is all five. Saving only weights means restarting the schedule from zero.
model = torch.compile(model)★Traces to a graph and fuses kernels. Usually a one-line speedup; keep it after.to(dev).torch.compile(model, mode="max-autotune")Slower to compile, faster to run. Also"reduce-overhead"for small models.torch.compile(model, dynamic=True)Avoids recompiling for every new sequence length.TORCH_LOGS="recompiles" python train.pyDiagnose why compilation keeps re-triggering.channels_last memory formatmodel.to(memory_format=torch.channels_last)— real gains for CNNs on tensor cores.with torch.profiler.profile(...) as p:Per-op timings, exportable to TensorBoard or Chrome trace.graph breaksPrints,.item(), and data-dependent Python control flow all split the graph and cost most of the win.torch.compile(model, fullgraph=True)★Fail loudly on a graph break instead of quietly falling back to eager.torch.backends.cudnn.benchmark = TrueAutotunes conv algorithms. A win for fixed input shapes, a loss for varying ones.start = torch.cuda.Event(enable_timing=True)The correct way to time GPU work.time.time()around async kernels measures nothing.
running_loss += loss★classicReach for this first. Accumulating the tensor keeps its whole autograd graph alive, so memory grows every step. Useloss.item()or.detach().loss = loss / accum; if (i+1) % accum == 0: opt.step()★Gradient accumulation. Large effective batch on a small card, for the price of time.torch.utils.checkpoint.checkpoint(block, x)★Discard activations and recompute them in the backward pass. Roughly 30% slower, dramatically smaller.with torch.autocast(dev, dtype=torch.bfloat16):Halves activation memory and usually speeds things up.opt.zero_grad(set_to_none=True)Frees the gradient tensors instead of filling them with zeros. Default since 2.0.del logits, loss; torch.cuda.empty_cache()Returns cached blocks to the driver. Helps fragmentation, not a true leak.PYTORCH_CUDA_ALLOC_CONF=expandable_segments:TrueEnvironment-variable fix for “tried to allocate” errors when plenty of memory is free.torch.cuda.memory_summary()Where it all went, by allocation site.
torchrun --nproc_per_node=8 train.py★The launcher. It setsRANK,LOCAL_RANK,WORLD_SIZEfor you.model = DistributedDataParallel(model)★One process per GPU. Always prefer it to the olderDataParallel.DistributedSampler(ds)Required with DDP, and you must callset_epoch(e)or every epoch shuffles identically.FSDP2 · fully_shardShards parameters, grads and optimizer state — for models too big for one card.ep = torch.export.export(model, (x,))modernThe current capture path, ahead of TorchScript.torch.onnx.export(model, (x,), "m.onnx", dynamo=True)ONNX for cross-runtime serving.torch.jit.script · torch.jit.tracelegacyTorchScript still works but is no longer the recommended path — new code should usetorch.export.dist.init_process_group("nccl") ... destroy_process_group()★Open and close it. Skipping the teardown is how jobs hang at exit.dist.all_reduce(t, op=dist.ReduceOp.SUM)Aggregate metrics across ranks — otherwise every rank logs its own shard.if dist.get_rank() == 0: torch.save(...)★Guard saving and logging, or eight ranks race to write one file.torchcomms2.13New distributed backend: better fault tolerance and debuggability on large clusters.
print(x.shape, x.dtype, x.device)★The three questions. Almost every runtime error in PyTorch is one of these three disagreeing.overfit a single batch first★If the model cannot drive one batch to near-zero loss, the bug is in the model or the loss — not the data or the schedule.torch.manual_seed(s); random.seed(s); np.random.seed(s)★Three separate RNGs. Seeding one of them is the usual mistake.torch.use_deterministic_algorithms(True)Raises on any op with no deterministic implementation, rather than silently varying.CUBLAS_WORKSPACE_CONFIG=:4096:8Required alongside the flag above, or CUDA matmuls still wander.assert not torch.isnan(loss)Fail on the step that broke, not three epochs later when everything is NaN.torch.autograd.set_detect_anomaly(True)Traces a NaN gradient back to the forward op that made it.model.eval() forgotten★Dropout stays on and BatchNorm keeps updating its statistics. Validation accuracy looks noisy and low for no visible reason.