Deep Learning › Training Deep Networks › Day 209
Hands-on lab — Day 209: Debugging Training Runs
- ← Back to the Day 209 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/deep-learning/day-209-debugging-training-runs/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/debugging_training_runs_lib.py Test
./tests/run_tests.sh File tree
examples/debugging_training_runs_lib.py examples/test_debugging_training_runs_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/debugging_training_runs_lib.py starter/test_debugging_training_runs_lib.py tests/run_tests.sh tests/test_debugging_training_runs_lib.py troubleshooting.md
Lab README
Lab: Day 209 -- Debugging Training Runs
Lesson
Day number: 209 of 365. Course: Course05-SS01 (Deep Learning - Neural Networks). Topic: Debugging Training Runs in PyTorch.
Purpose
Build and test a comprehensive neural network debugging suite in PyTorch. Implement the single-batch overfitting sanity test, derive and implement custom gradient norm clipping, and diagnose gradient vanishing/explosion pathologies.
Learning objectives
- Implement the single-batch overfitting sanity check to verify computational graph correctness.
- Implement gradient norm calculation and clipping from scratch.
- Verify bitwise gradient alignment with
torch.nn.utils.clip_grad_norm_. - Apply diagnostic protocols for hunting NaNs and uncalibrated initial losses.
Prerequisites
- Day 208 (Dropout, Batch Norm, and Regularization).
- Python 3.11+ with PyTorch.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 1 GB RAM.
- 100 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
PyTorch is free and open-source under the modified BSD license.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/debugging_training_runs_lib.py: Student scaffold file.examples/debugging_training_runs_lib.py: Complete reference implementation.tests/test_debugging_training_runs_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/debugging_training_runs_lib.py
What the commands do
- Executes a single-batch overfit diagnostic test on a multi-layer model.
- Evaluates gradient norm scaling and clipping.
- Verifies mathematical gradient integrity.
Expected output
Debugging Demo: Single-Batch Overfit Passed = True, Raw Norm = 14.82, Clipped Norm = 1.00
Validation steps
- Verify that single-batch overfitting drives cross-entropy loss below 0.01.
- Confirm that
custom_clip_grad_normscales gradients to exactlymax_norm. - Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Overfit Test Fails to Reach 100%: Ensure
optimizer.zero_grad()is called beforeloss.backward().
Security notes
All debugging routines execute in local memory on CPU hardware.
Extension exercises
- Implement forward/backward hooks that log activation sparsity across layers.
- Build an automated learning rate finder tracking loss derivatives.
Navigation
- Lesson title: Debugging Training Runs
- Day number: 209 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-209-debugging-training-runs
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-209-debugging-training-runswhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 209
- `Single-Batch Overfit Passed`: Boolean indicating single batch overfit success.
- `Raw Norm`: Unclipped global gradient norm.
- `Clipped Norm`: Gradient norm after applying custom clipping.
examples-run.txt
Debugging Demo: Single-Batch Overfit Passed = True, Raw Norm = 14.82, Clipped Norm = 1.00
measured-values.txt
Single-Batch Overfit Passed: True
Raw Norm: 14.82
Clipped Norm: 1.00
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 3 items
tests/test_debugging_training_runs_lib.py::test_compute_global_gradient_norm PASSED [ 33%]
tests/test_debugging_training_runs_lib.py::test_custom_clip_grad_norm_matches_torch PASSED [ 66%]
tests/test_debugging_training_runs_lib.py::test_single_batch_overfit_test_success PASSED [100%]
============================== 3 passed in 0.22s ===============================
Source files
examples/debugging_training_runs_lib.py (2462 bytes)
import torch
import torch.nn as nn
from typing import Tuple, List, Dict, Any
def compute_global_gradient_norm(model: nn.Module) -> float:
total_norm_sq = 0.0
for p in model.parameters():
if p.grad is not None:
total_norm_sq += float((p.grad.detach() ** 2).sum().item())
return float(total_norm_sq ** 0.5)
def custom_clip_grad_norm(model: nn.Module, max_norm: float = 1.0) -> float:
total_norm = compute_global_gradient_norm(model)
if total_norm > max_norm and total_norm > 0:
clip_coef = max_norm / (total_norm + 1e-6)
with torch.no_grad():
for p in model.parameters():
if p.grad is not None:
p.grad.mul_(clip_coef)
return total_norm
def single_batch_overfit_test(model: nn.Module, in_features: int = 32,
num_classes: int = 10, batch_size: int = 16,
max_steps: int = 50) -> bool:
torch.manual_seed(42)
x = torch.randn(batch_size, in_features)
y = torch.randint(0, num_classes, (batch_size,))
optimizer = torch.optim.AdamW(model.parameters(), lr=0.02)
criterion = nn.CrossEntropyLoss()
model.train()
for step in range(max_steps):
optimizer.zero_grad()
logits = model(x)
loss = criterion(logits, y)
loss.backward()
custom_clip_grad_norm(model, max_norm=1.0)
optimizer.step()
preds = torch.argmax(logits, dim=1)
acc = float((preds == y).float().mean().item())
if loss.item() < 0.01 and acc == 1.0:
return True
return False
def run_debugging_demo():
torch.manual_seed(42)
model = nn.Sequential(
nn.Linear(32, 64),
nn.ReLU(),
nn.Linear(64, 10)
)
passed = single_batch_overfit_test(model, in_features=32, num_classes=10, batch_size=16)
# Test gradient clipping
x = torch.randn(10, 32)
y = torch.randint(0, 10, (10,))
model.zero_grad()
loss = nn.CrossEntropyLoss()(model(x), y) * 100.0 # Artificially inflate loss
loss.backward()
raw_norm = compute_global_gradient_norm(model)
custom_clip_grad_norm(model, max_norm=1.0)
clipped_norm = compute_global_gradient_norm(model)
print(f"Debugging Demo: Single-Batch Overfit Passed = {passed}, Raw Norm = {raw_norm:.2f}, Clipped Norm = {clipped_norm:.2f}")
return passed, raw_norm, clipped_norm
if __name__ == "__main__":
run_debugging_demo()
examples/test_debugging_training_runs_lib.py (1616 bytes)
import pytest
import torch
import torch.nn as nn
from examples.debugging_training_runs_lib import (
compute_global_gradient_norm, custom_clip_grad_norm, single_batch_overfit_test
)
def test_compute_global_gradient_norm():
model = nn.Sequential(nn.Linear(2, 2, bias=False))
model[0].weight.grad = torch.tensor([[3.0, 4.0], [0.0, 0.0]])
# Norm = sqrt(3^2 + 4^2) = 5.0
norm = compute_global_gradient_norm(model)
assert pytest.approx(norm, abs=1e-5) == 5.0
def test_custom_clip_grad_norm_matches_torch():
torch.manual_seed(42)
model1 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
model2 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
# Set identical weights
model2.load_state_dict(model1.state_dict())
x = torch.randn(16, 10)
y = torch.randint(0, 2, (16,))
loss1 = nn.CrossEntropyLoss()(model1(x), y) * 50.0
loss2 = nn.CrossEntropyLoss()(model2(x), y) * 50.0
loss1.backward()
loss2.backward()
norm1 = custom_clip_grad_norm(model1, max_norm=1.0)
norm2 = float(torch.nn.utils.clip_grad_norm_(model2.parameters(), max_norm=1.0).item())
assert pytest.approx(norm1, abs=1e-4) == norm2
# Verify parameter gradients match
for p1, p2 in zip(model1.parameters(), model2.parameters()):
assert torch.allclose(p1.grad, p2.grad, atol=1e-5)
def test_single_batch_overfit_test_success():
model = nn.Sequential(
nn.Linear(16, 32),
nn.ReLU(),
nn.Linear(32, 4)
)
assert single_batch_overfit_test(model, in_features=16, num_classes=4, batch_size=8, max_steps=40)
metadata.yml (436 bytes)
lesson_id: D209
day: 209
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/debugging_training_runs_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (27 bytes)
torch>=2.2.0
pytest>=8.0.0
starter/debugging_training_runs_lib.py (629 bytes)
import torch
import torch.nn as nn
from typing import Tuple, List, Dict, Any
def compute_global_gradient_norm(model: nn.Module) -> float:
# TODO: Compute total Euclidean norm across all parameter gradients
pass
def custom_clip_grad_norm(model: nn.Module, max_norm: float = 1.0) -> float:
# TODO: Implement gradient norm clipping from scratch
pass
def single_batch_overfit_test(model: nn.Module, in_features: int = 32,
num_classes: int = 10, batch_size: int = 16,
max_steps: int = 50) -> bool:
# TODO: Implement single-batch overfit test
pass
starter/test_debugging_training_runs_lib.py (1616 bytes)
import pytest
import torch
import torch.nn as nn
from examples.debugging_training_runs_lib import (
compute_global_gradient_norm, custom_clip_grad_norm, single_batch_overfit_test
)
def test_compute_global_gradient_norm():
model = nn.Sequential(nn.Linear(2, 2, bias=False))
model[0].weight.grad = torch.tensor([[3.0, 4.0], [0.0, 0.0]])
# Norm = sqrt(3^2 + 4^2) = 5.0
norm = compute_global_gradient_norm(model)
assert pytest.approx(norm, abs=1e-5) == 5.0
def test_custom_clip_grad_norm_matches_torch():
torch.manual_seed(42)
model1 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
model2 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
# Set identical weights
model2.load_state_dict(model1.state_dict())
x = torch.randn(16, 10)
y = torch.randint(0, 2, (16,))
loss1 = nn.CrossEntropyLoss()(model1(x), y) * 50.0
loss2 = nn.CrossEntropyLoss()(model2(x), y) * 50.0
loss1.backward()
loss2.backward()
norm1 = custom_clip_grad_norm(model1, max_norm=1.0)
norm2 = float(torch.nn.utils.clip_grad_norm_(model2.parameters(), max_norm=1.0).item())
assert pytest.approx(norm1, abs=1e-4) == norm2
# Verify parameter gradients match
for p1, p2 in zip(model1.parameters(), model2.parameters()):
assert torch.allclose(p1.grad, p2.grad, atol=1e-5)
def test_single_batch_overfit_test_success():
model = nn.Sequential(
nn.Linear(16, 32),
nn.ReLU(),
nn.Linear(32, 4)
)
assert single_batch_overfit_test(model, in_features=16, num_classes=4, batch_size=8, max_steps=40)
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 209 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_debugging_training_runs_lib.py (1616 bytes)
import pytest
import torch
import torch.nn as nn
from examples.debugging_training_runs_lib import (
compute_global_gradient_norm, custom_clip_grad_norm, single_batch_overfit_test
)
def test_compute_global_gradient_norm():
model = nn.Sequential(nn.Linear(2, 2, bias=False))
model[0].weight.grad = torch.tensor([[3.0, 4.0], [0.0, 0.0]])
# Norm = sqrt(3^2 + 4^2) = 5.0
norm = compute_global_gradient_norm(model)
assert pytest.approx(norm, abs=1e-5) == 5.0
def test_custom_clip_grad_norm_matches_torch():
torch.manual_seed(42)
model1 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
model2 = nn.Sequential(nn.Linear(10, 10), nn.ReLU(), nn.Linear(10, 2))
# Set identical weights
model2.load_state_dict(model1.state_dict())
x = torch.randn(16, 10)
y = torch.randint(0, 2, (16,))
loss1 = nn.CrossEntropyLoss()(model1(x), y) * 50.0
loss2 = nn.CrossEntropyLoss()(model2(x), y) * 50.0
loss1.backward()
loss2.backward()
norm1 = custom_clip_grad_norm(model1, max_norm=1.0)
norm2 = float(torch.nn.utils.clip_grad_norm_(model2.parameters(), max_norm=1.0).item())
assert pytest.approx(norm1, abs=1e-4) == norm2
# Verify parameter gradients match
for p1, p2 in zip(model1.parameters(), model2.parameters()):
assert torch.allclose(p1.grad, p2.grad, atol=1e-5)
def test_single_batch_overfit_test_success():
model = nn.Sequential(
nn.Linear(16, 32),
nn.ReLU(),
nn.Linear(32, 4)
)
assert single_batch_overfit_test(model, in_features=16, num_classes=4, batch_size=8, max_steps=40)
Troubleshooting
Troubleshooting: Day 209 - Debugging Training Runs
Common Issues
- Loss explodes to NaN:
- Cause: High learning rate or taking logarithm of 0.
- Fix: Use
torch.autograd.set_detect_anomaly(True)and applyclip_grad_norm_.
Security notes
Security & Privacy: Day 209 - Debugging Training Runs
Security Guidance
- All diagnostic scripts run locally in memory without telemetric logging.