Examples
A small gallery of working PyTorch patterns — a typed training loop, an inference pipeline, a tiny custom dataset, and a Lightning-free DDP launcher. Each is short enough to lift into your own project without a framework.
Four idiomatic PyTorch examples
EXAMPLE
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
from pathlib import Path
# ===== 1) A small typed training loop =====
device = 'cuda' if torch.cuda.is_available() else 'cpu'
class MLP(nn.Module):
def __init__(self, in_dim: int, out_dim: int, hidden: int = 128):
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, hidden), nn.ReLU(),
nn.Linear(hidden, hidden), nn.ReLU(),
nn.Linear(hidden, out_dim),
)
def forward(self, x): return self.net(x)
def train(model, loader, opt, scheduler, epochs):
model.train()
scaler = torch.amp.GradScaler('cuda', enabled=device == 'cuda')
for ep in range(epochs):
for x, y in loader:
x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True)
opt.zero_grad(set_to_none=True)
with torch.amp.autocast(device_type=device, dtype=torch.float16, enabled=device == 'cuda'):
logits = model(x)
loss = F.cross_entropy(logits, y)
scaler.scale(loss).backward()
scaler.step(opt)
scaler.update()
scheduler.step()
print(f'epoch {ep}: loss={loss.item():.4f}')
# ===== 2) Inference pipeline — clean separation from training =====
@torch.inference_mode()
def predict(model, x):
model.eval()
return model(x.to(device)).argmax(dim=-1).cpu()
# ===== 3) A custom Dataset for image-folder data =====
class TinyImageDataset(Dataset):
def __init__(self, root: str, transform=None):
self.paths = list(Path(root).rglob('*.jpg'))
self.labels = sorted({p.parent.name for p in self.paths})
self.label2idx = {n: i for i, n in enumerate(self.labels)}
self.transform = transform
def __len__(self): return len(self.paths)
def __getitem__(self, i):
from PIL import Image
path = self.paths[i]
img = Image.open(path).convert('RGB')
if self.transform: img = self.transform(img)
return img, self.label2idx[path.parent.name]
# ===== 4) Distributed training (DDP) — torchrun launcher =====
# train_ddp.py
import os
import torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
def run_ddp():
dist.init_process_group(backend='nccl')
rank = dist.get_rank()
world = dist.get_world_size()
local_rank = int(os.environ['LOCAL_RANK'])
torch.cuda.set_device(local_rank)
model = MLP(32, 10).to(local_rank)
model = DDP(model, device_ids=[local_rank])
# IMPORTANT: per-rank shard of the dataset, not the whole thing
sampler = torch.utils.data.distributed.DistributedSampler(
TinyImageDataset('/data/train'), num_replicas=world, rank=rank, shuffle=True)
loader = DataLoader(TinyImageDataset('/data/train'), batch_size=64, sampler=sampler,
num_workers=4, pin_memory=True)
opt = torch.optim.AdamW(model.parameters(), lr=3e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=10)
for ep in range(10):
sampler.set_epoch(ep) # so shuffling differs across epochs
# ... same training loop body as above
pass
if rank == 0:
torch.save(model.module.state_dict(), '/ckpt/last.pt')
dist.destroy_process_group()
if __name__ == '__main__':
run_ddp()
# Launch: torchrun --standalone --nproc-per-node=4 train_ddp.py
# ===== 5) Checkpoints that survive everything =====
def save_checkpoint(path, model, opt, scheduler, scaler, epoch, best_val):
torch.save({
'epoch': epoch, 'best_val': best_val,
'model': model.state_dict(),
'opt': opt.state_dict(),
'scheduler': scheduler.state_dict(),
'scaler': scaler.state_dict(),
}, path)
def load_checkpoint(path, model, opt=None, scheduler=None, scaler=None):
ck = torch.load(path, map_location=device)
model.load_state_dict(ck['model'])
if opt and 'opt' in ck: opt.load_state_dict(ck['opt'])
if scheduler and 'scheduler' in ck: scheduler.load_state_dict(ck['scheduler'])
if scaler and 'scaler' in ck: scaler.load_state_dict(ck['scaler'])
return ck['epoch'], ck['best_val']
Why it matters
Use @torch.inference_mode() instead of @torch.no_grad() for prediction. It is a stricter version that also disables view tracking, gives a small speedup, and prevents accidental gradient ops on the inference path. It is the right default for any production model serving code.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…