iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Exercises

Five small drills that exercise PyTorch idioms most teams get slightly wrong. Try first; the answers explain the gotcha.

Five PyTorch drills

EXAMPLE
# ============================================================
# Drill 1 — zero_grad
# ============================================================
# Fix this loop:
# for xb, yb in loader:
#     loss = loss_fn(model(xb), yb)
#     loss.backward()
#     opt.step()
#
# ANSWER: forgot opt.zero_grad(). Gradients accumulate across steps.
# Best practice:
#   opt.zero_grad(set_to_none=True)    # faster than .zero_grad()
#   loss.backward()
#   opt.step()

# ============================================================
# Drill 2 — Eval mode
# ============================================================
# Your predictions on the validation set are noisy. Why?
#
# ANSWER: forgot model.eval(). Dropout still active, BatchNorm uses
# batch stats. Call model.eval() AND wrap with torch.inference_mode():
#   model.eval()
#   with torch.inference_mode():
#       preds = model(X)

# ============================================================
# Drill 3 — Device + non_blocking
# ============================================================
# Your GPU sits at 40% during training. Loader is fine. Cause?
#
# ANSWER: synchronous CPU→GPU copy.
# Use pin_memory + non_blocking:
#   loader = DataLoader(ds, batch_size=128, num_workers=4, pin_memory=True)
#   xb = xb.to(device, non_blocking=True)
#   yb = yb.to(device, non_blocking=True)

# ============================================================
# Drill 4 — Mixed precision
# ============================================================
# Training a transformer is too slow. You have an A100. Fix it.
#
# ANSWER: autocast + GradScaler.
# scaler = torch.amp.GradScaler('cuda')
# for xb, yb in loader:
#   opt.zero_grad(set_to_none=True)
#   with torch.amp.autocast(device_type='cuda', dtype=torch.float16):
#     loss = loss_fn(model(xb), yb)
#   scaler.scale(loss).backward()
#   scaler.unscale_(opt)
#   torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
#   scaler.step(opt)
#   scaler.update()

# ============================================================
# Drill 5 — Multi-GPU
# ============================================================
# You have 4 GPUs in one box. DataParallel or DistributedDataParallel?
#
# ANSWER: DDP, ALWAYS.
# DataParallel is single-process, suffers from GIL, and gradients funnel
# through the master GPU. DDP runs one process per GPU and uses NCCL all-reduce.
# Launch:
#   torchrun --standalone --nproc-per-node=4 train.py
#
# Inside train.py:
#   dist.init_process_group(backend='nccl')
#   local_rank = int(os.environ['LOCAL_RANK'])
#   torch.cuda.set_device(local_rank)
#   model = DDP(model.to(local_rank), device_ids=[local_rank])

# ============================================================
# Bonus — Why does loss become NaN after a few epochs?
# ============================================================
# ANSWER:
# - Exploding gradients -> gradient clipping (clip_grad_norm_)
# - Log of zero / sqrt of negative in a custom loss
# - Mixed precision instability -> use bf16 on A100/H100; or scale loss

# ============================================================
# Scoring
# ============================================================
# 5 / 5 -> review training loops at speed
# 3 / 5 -> bookmark pytorch/cheatsheet
# < 3   -> read PyTorch's 'Performance Tuning Guide'

Why it matters

`opt.zero_grad(set_to_none=True)` is the right default — faster than zeroing in place, and avoids the slow accumulator that some lazy code paths leave hanging. Combined with `non_blocking=True` on host→device transfers, it removes the two most common "why is my GPU underutilised?" causes without changing your model.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Fill in: opt.____() before loss.backward()
Try it Yourself »

Discussion

Loading…