DS DevShelfHub Projects · AI tools
Cheatsheets / PyTorch
Cheatsheet · AI frameworks

PyTorch Cheatsheet: Tensors, Autograd and nn.Module Reference

By DevShelfHub

Tensors, autograd, nn.Module, DataLoader, optimizers, AMP, distributed, torch.compile — the deep-learning framework as a one-page reference.

125 items 9 min Tensors Autograd Modules

Start hereQuick start · 6 you’ll reach for daily

Tensortorch.tensor([1,2,3])
To devicex.to("cuda", non_blocking=True)
Modelclass M(nn.Module): def forward(...)
Backwardloss.backward()
Stepoptim.step(); optim.zero_grad()
Inferencewith torch.inference_mode():

Target versions · paceVersions

Targets: 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

bash
# 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 torchTop-level tensors, autograd, dtypes, devices.
import torch.nn as nn / import torch.nn.functional as FModule + functional layer APIs.
from torch.utils.data import Dataset, DataLoader, random_splitData loading primitives.
from torch.amp import autocast, GradScalerModern mixed-precision API. Preferred over torch.cuda.amp.
import torch.optim as optim / from torch.optim.lr_scheduler import CosineAnnealingLROptimizers + schedulers.
from torchvision import datasets, transforms, modelsVision datasets / augmentations / pretrained nets.
from torch.utils.tensorboard import SummaryWriterTensorBoard logging.
import torch.distributed as distMulti-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=TrueKeep the graph after backward — multi-backward setups.
torch.autograd.functional.jacobian / hessian / vjp / jvpFunctional 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.ModuleDictLists / 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.
python
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=TrueFaster host→GPU transfers with non_blocking=True.
persistent_workers=TrueKeep workers alive across epochs — cuts setup cost.
drop_last=TrueDrop 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_datasetStreaming / 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_groupsDifferent 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 / updateCorrect 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 NEffective batch size = micro-batch × N.
python
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 / TripletMarginLossPair / triplet losses.
ignore_index=-100Skip 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.pyPreferred 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 FSDPShard 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.

python
# 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

Wrap your model with 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.
Prefer bfloat16 over float16 on Ampere+. Same range as FP32 means no GradScaler needed and no overflow issues — simpler training loop with the same speedup.
Save 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

Forgetting 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.
Calling .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.
CrossEntropyLoss expects raw logits, not softmax. Pre-applying softmax + then CE is double-stable-loss and hurts training. Same for BCEWithLogitsLoss — pass logits.

Go deeperSee also

PyTorch FAQ

What is PyTorch used for?

PyTorch is the dominant open-source deep learning framework. Researchers and practitioners use it for training neural networks, fine-tuning LLMs, computer vision models, and custom ML research. It provides GPU-accelerated tensor operations, automatic differentiation via autograd, and a rich ecosystem including TorchVision, TorchAudio, and HuggingFace Transformers.

What is autograd in PyTorch?

Autograd is PyTorch's automatic differentiation engine. When you call tensor.backward(), it traverses the computational graph recorded during the forward pass and computes gradients with respect to all tensors that have requires_grad=True. These gradients are stored in tensor.grad and used by optimizers to update model weights.

What is nn.Module and how do I use it?

nn.Module is the base class for all neural network layers and models. Subclass it, define layers in __init__, and implement forward(x) to describe the forward pass. PyTorch handles parameter registration, device movement (.to(device)), gradient zeroing, and serialization automatically. Use nn.Sequential for simple layer stacks without custom logic.

How does DataLoader work in PyTorch?

DataLoader wraps a Dataset and handles batching, shuffling, parallel loading, and pin_memory for GPU transfer. Create a Dataset by subclassing torch.utils.data.Dataset and implementing __len__ and __getitem__. Pass it to DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4). Iterate with for x, y in loader.

What is torch.compile and when should I use it?

torch.compile (PyTorch 2.0+) JIT-compiles your model using TorchInductor to generate optimized GPU kernels, typically giving 20-50% speedup on training with no code changes. Wrap your model: model = torch.compile(model). It works best on models with regular tensor shapes. Use mode='reduce-overhead' for inference and 'max-autotune' for maximum throughput.

Is PyTorch free and open source?

Yes. PyTorch is BSD-licensed and maintained by Meta AI with broad community contributions. It is free to use for research and commercial applications. PyTorch 2.x introduced torch.compile under the same open licence. No subscription or enterprise licence is needed.