Debugging and Profiling Tools Taught in Phase 0: Complete AI Debugging Toolkit

Phase 0 of the AI Engineering from Scratch curriculum teaches a five-layer debugging strategy spanning Python fundamentals, CPU profiling, GPU memory management, visualization tools, and AI-specific sanity checks to diagnose NaN losses, shape mismatches, and memory leaks before they corrupt experiments.

The rohitg00/ai-engineering-from-scratch repository dedicates its Phase 0 "Setup & Tooling" module to debugging and profiling tools essential for production AI workflows. Mastering these utilities early prevents the silent failures—such as undetected NaN gradients or device mismatches—that commonly waste compute resources in deep learning experiments. All implementations reside in phases/00-setup-and-tooling/12-debugging-and-profiling/code/debug_tools.py, providing copy-paste-ready utilities for immediate use.

Python Debugging Fundamentals

Phase 0 begins with core Python debugging techniques before introducing AI-specific tooling. These foundations enable developers to inspect variables and control flow in any training script.

Interactive Debugging with breakpoint() and pdb

The curriculum emphasizes breakpoint() (Python 3.7+) and the pdb module for interactive inspection. In debug_tools.py (line 55‑65), learners implement tactical pauses to examine tensor values, shapes, and devices mid-execution. This allows real-time verification of data integrity before silent corruption propagates through the model.

Structured Logging and Custom Utilities

For non-interactive monitoring, the logging module provides timestamped output with severity levels (INFO/WARN/ERROR) as shown in debug_tools.py (line 84‑100). Complementing this is a custom debug_print helper (line 42‑48) that prints a tensor’s shape, dtype, device, min/max/mean statistics, and NaN status in a single line.

def debug_print(name, tensor):
    print(
        f"{name}: shape={tensor.shape}, dtype={tensor.dtype}, "
        f"device={tensor.device}, min={tensor.min():.4f}, "
        f"max={tensor.max():.4f}, mean={tensor.mean():.4f}, "
        f"has_nan={tensor.isnan().any()}"
    )

Performance Timing with Context Managers

The Timer class in debug_tools.py (line 11‑22) measures elapsed time for arbitrary code blocks using time.perf_counter(). This context manager wraps data loading or forward passes to identify bottlenecks without external dependencies.

class Timer:
    def __init__(self, name=""):
        self.name = name
    def __enter__(self):
        self.start = time.perf_counter()
        return self
    def __exit__(self, *exc):
        elapsed = time.perf_counter() - self.start
        print(f"[{self.name}] {elapsed:.4f}s")

CPU Profiling and Memory Tracking

Once basic debugging is mastered, Phase 0 introduces systematic profiling to locate performance bottlenecks and memory leaks in Python code.

Whole-Program Profiling with cProfile

The cProfile module provides function-level timing statistics sorted by cumulative time. As implemented in debug_tools.py (line 39‑41), developers run:

python -m cProfile -s cumtime train.py

This reveals which functions consume the most execution time across the entire training pipeline.

Line-by-Line Analysis

For granular inspection, the curriculum covers line_profiler using the @profile decorator (line 49‑56). This tool reports per-line execution times for targeted functions, essential when optimizing hot loops in data loaders or custom loss functions.

@profile
def train_step(model, data, target):
    out = model(data)
    loss = F.cross_entropy(out, target)
    loss.backward()
    return loss

Execute with kernprof -l -v train.py to view line-level statistics.

Memory Allocation Tracking

Two tools track RAM usage: tracemalloc captures Python-level allocations and reports the hottest source lines (line 64‑71), while memory_profiler (line 81‑89) displays per-line RAM consumption via the @profile decorator. The tracemalloc implementation starts before training and snapshots peak usage:

tracemalloc.start()

# …run training…

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
    print(stat)

GPU Memory Management and Debugging

AI-specific debugging requires monitoring GPU memory to prevent out-of-memory (OOM) crashes during large batch training.

PyTorch CUDA Memory Inspection

debug_tools.py (line 98‑104) demonstrates torch.cuda.memory_summary(), which prints a concise snapshot of GPU memory usage, allocations, and fragmentation. Complementary numeric queries (line 106‑108) provide exact GB totals:

if torch.cuda.is_available():
    print(torch.cuda.memory_summary())
    print(f"Allocated: {torch.cuda.memory_allocated()/1e9:.2f} GB")
    print(f"Cached: {torch.cuda.memory_reserved()/1e9:.2f} GB")

Cache Management and Mixed Precision

When memory pressure peaks, torch.cuda.empty_cache() (line 110‑112) clears the PyTorch CUDA cache to recover memory after large tensors are freed. The curriculum also references torch.cuda.amp for automatic mixed precision (line 112‑115), which halves GPU memory consumption while preserving numerical stability.

Visualization and IDE Integration

Modern debugging extends beyond print statements to graphical monitoring and integrated development environments.

Real-Time Monitoring with TensorBoard

The curriculum integrates TensorBoard for real-time plotting of loss curves, learning rates, weight histograms, and gradient distributions. The implementation in debug_tools.py (line 122‑140) uses SummaryWriter to stream metrics during training:

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("runs/exp1")
for step in range(num_steps):
    loss = train_step(model, batch)
    writer.add_scalar("loss/train", loss.item(), step)
    writer.add_scalar("lr", optimizer.param_groups[0]["lr"], step)
writer.close()

Launch the dashboard with tensorboard --logdir=runs.

VS Code Debugger Configuration

For deep inspection, Phase 0 includes a VS Code Debugger configuration (line 144‑162). The launch.json setup enables full IDE debugging with breakpoints, variable inspection, and a Debug Console for live tensor queries:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Training",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal",
      "justMyCode": false
    }
  ]
}

AI-Specific Sanity Checks

Phase 0 culminates with utilities designed specifically for neural network validation, catching AI-specific failures that standard debuggers miss.

Shape and Device Verification

The check_shapes helper (line 166‑186) registers forward hooks to print each module’s input/output shapes for a sample batch, catching dimension mismatches before training begins. The check_devices helper (line 220‑229) verifies that all tensors share the same device as the model, warning on silent CPU/GPU mismatches that crash training mid-epoch.

Numerical Stability and Data Integrity

To detect gradient explosions, detect_nan (line 188‑206) propagates NaN loss values back to the offending layer. For dataset validation, check_data_leakage (line 208‑218) identifies overlapping samples between train and test sets or temporal leakage in time-series data.

The Five-Layer Debugging Workflow

According to the rohitg00/ai-engineering-from-scratch source code, these tools form a sequential workflow:

  1. Pre-training sanity checks – Run check_shapes on a dummy batch to confirm model I/O dimensions.
  2. Early-training monitoring – Insert debug_print and conditional breakpoint() to stop on abnormal loss values.
  3. Logging and profiling – Switch to logging, enable cProfile or line_profiler, and capture memory with tracemalloc or memory_profiler.
  4. GPU health monitoring – Watch torch.cuda.memory_summary() and proactively free cache or enable mixed precision.
  5. Visualization and deep inspection – Feed metrics to TensorBoard and use the VS Code debugger when manual intervention is required.

Summary

  • Python fundamentals (breakpoint(), logging, Timer) provide immediate variable inspection and timing for any script.
  • CPU profiling (cProfile, line_profiler, tracemalloc) identifies runtime bottlenecks and memory leaks at function and line levels.
  • GPU tooling (torch.cuda.memory_summary(), empty_cache(), AMP) prevents OOM errors and optimizes VRAM usage.
  • Visualization (TensorBoard, VS Code Debugger) enables real-time metric tracking and interactive debugging.
  • AI-specific checks (check_shapes, detect_nan, check_data_leakage) catch neural network-specific failures like shape mismatches and NaN gradients.

Frequently Asked Questions

What is the primary debugging file in Phase 0?

The main implementation resides in phases/00-setup-and-tooling/12-debugging-and-profiling/code/debug_tools.py. This file contains all helper functions, context managers, and profiling decorators taught in the lesson, organized by complexity from basic Python debugging to AI-specific sanity checks.

How does Phase 0 recommend handling GPU out-of-memory errors?

According to debug_tools.py (line 98‑115), developers should first check torch.cuda.memory_summary() to identify fragmentation, then call torch.cuda.empty_cache() to recover cached memory. For persistent issues, the curriculum recommends enabling torch.cuda.amp automatic mixed precision to reduce memory consumption by approximately 50%.

Can these debugging tools be used outside of PyTorch?

While Phase 0 focuses on AI workflows, the Level 1 (Python fundamentals) and Level 2 (CPU profiling) tools work with any Python codebase. The debug_print helper requires PyTorch tensors, but breakpoint(), logging, cProfile, and tracemalloc function identically in standard Python applications.

What is the difference between tracemalloc and memory_profiler in the curriculum?

As implemented in debug_tools.py (line 64‑71 vs. line 81‑89), tracemalloc captures system-wide Python object allocations and reports the source lines creating the most memory pressure, while memory_profiler attaches to specific functions via the @profile decorator to show line-by-line RAM increments. Use tracemalloc for global analysis and memory_profiler for targeted function inspection.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →