[bug] Lesson code is CUDA-only — no Apple Silicon (MPS) device path

Author: JayantkhandebharadCreated Jun 15, 2026Updated Jul 5, 2026

Where

  • Phase / lesson: Phase 0 · 01-dev-environment (Step 6: GPU Setup) and 03-gpu-setup-and-cloud, plus ~30 other lesson files across Phases 2, 3, 4, 10, and 19.
  • File / URL: Multiple — see the Affected files section below. Representative page: lesson.html?path=phases/00-setup-and-tooling/01-dev-environment

What's wrong

GPU-related code throughout the curriculum is CUDA-only: device selection is written as "cuda" if torch.cuda.is_available() else "cpu", with no branch for Apple Silicon's MPS (Metal Performance Shaders) backend.

On an Apple Silicon Mac (M1–M4), torch.backends.mps.is_available() returns True and works — a 4096×4096 matmul runs on the GPU in ~120 ms. But because the lessons never check for mps, on these machines they:

  1. Silently fall back to CPU (training/inference runs many times slower than it should), or
  2. Report "No GPU" even though a usable GPU is present (e.g. gpu_check.py), or
  3. Hard-error when a device is passed explicitly (e.g. the gradient-clipping capstone rejects any device that isn't "cpu"/"cuda").

This is an inconsistency, not a missing capability: a scan of the repo shows 197 files already reference mps, while 32 files use cuda with no mps branch. The correct pattern already exists in the codebase; these 32 files just don't follow it. Given that Apple Silicon is one of the most common laptop dev environments, the setup lessons (Phase 0) being CUDA-only is especially rough for newcomers — it's the first thing a Mac user hits.

Reproduce

  1. On an Apple Silicon Mac (macOS 12.3+) with PyTorch installed — confirm the GPU is real: python3 -c "import torch; print(torch.backends.mps.is_available())"True.
  2. Run a GPU lesson, e.g. python phases/04-computer-vision/04-image-classification/code/main.py.
  3. Observe device resolves to cpu (line 195: device = "cuda" if torch.cuda.is_available() else "cpu") — the available GPU is never used.
  4. Or run python phases/00-setup-and-tooling/03-gpu-setup-and-cloud/code/gpu_check.py → prints "CUDA available: False" and exits, reporting no GPU despite MPS being available.

Concrete examples

File Line Problem
phases/00-setup-and-tooling/01-dev-environment/docs/en.md Step 6 GPU snippet only shows nvidia-smi + CUDA install + torch.cuda.is_available(); no MPS equivalent.
phases/00-setup-and-tooling/03-gpu-setup-and-cloud/code/gpu_check.py 14–41 Hard-codes .to("cuda") and torch.cuda.synchronize(); reports no GPU on Apple Silicon.
phases/00-setup-and-tooling/01-dev-environment/code/verify.py 19–20 Only probes torch.cuda.is_available() / get_device_name(0).
phases/03-deep-learning-core/11-intro-to-pytorch/code/pytorch_intro.py CUDA/CPU only device selection.
phases/04-computer-vision/04-image-classification/code/main.py 195 device = "cuda" if torch.cuda.is_available() else "cpu".
phases/19-capstone-projects/45-gradient-clipping-amp/code/main.py 151–152 Hard blocker: raise ValueError(f"device_type must be 'cpu' or 'cuda', got {device_type}") — cannot pass "mps" at all.

Full list of the 32 CUDA-only files (use cuda, never mention mps):

00-setup-and-tooling/01-dev-environment/code/verify.py
00-setup-and-tooling/03-gpu-setup-and-cloud/code/gpu_check.py
00-setup-and-tooling/03-gpu-setup-and-cloud/docs/en.md
00-setup-and-tooling/06-python-environments/docs/en.md
00-setup-and-tooling/07-docker-for-ai/docs/en.md
00-setup-and-tooling/10-terminal-and-shell/docs/en.md
00-setup-and-tooling/11-linux-for-ai/docs/en.md
00-setup-and-tooling/12-debugging-and-profiling/code/debug_tools.py
00-setup-and-tooling/12-debugging-and-profiling/outputs/prompt-debug-ai-code.md
01-math-foundations/13-numerical-stability/outputs/prompt-numerical-debugger.md
02-ml-fundamentals/13-ml-pipelines/docs/en.md
03-deep-learning-core/11-intro-to-pytorch/code/pytorch_intro.py
03-deep-learning-core/11-intro-to-pytorch/outputs/skill-pytorch-patterns.md
03-deep-learning-core/12-intro-to-jax/docs/en.md
04-computer-vision/04-image-classification/code/main.py
04-computer-vision/04-image-classification/docs/en.md
04-computer-vision/05-transfer-learning/code/main.py
04-computer-vision/07-semantic-segmentation-unet/code/main.py
04-computer-vision/09-image-generation-gans/code/main.py
04-computer-vision/09-image-generation-gans/docs/en.md
04-computer-vision/10-image-generation-diffusion/code/main.py
04-computer-vision/11-stable-diffusion/code/main.py
04-computer-vision/11-stable-diffusion/docs/en.md
04-computer-vision/15-real-time-edge/code/main.py
04-computer-vision/15-real-time-edge/docs/en.md
04-computer-vision/16-vision-pipeline-capstone/code/main.py
04-computer-vision/23-diffusion-transformers-rectified-flow/docs/en.md
04-computer-vision/25-vision-language-models/docs/en.md
10-llms-from-scratch/34-gradient-checkpointing/docs/en.md
19-capstone-projects/45-gradient-clipping-amp/code/main.py
19-capstone-projects/45-gradient-clipping-amp/docs/en.md
19-capstone-projects/47-checkpoint-save-resume/outputs/skill-checkpoint-save-resume.md

Suggested fix

Adopt a single device-selection helper (cuda → mps → cpu) and reuse it everywhere, matching the pattern the other 197 files already use:

python
import torch

def get_device():
    if torch.cuda.is_available():
        return torch.device("cuda")
    if torch.backends.mps.is_available():
        return torch.device("mps")
    return torch.device("cpu")

Plus the supporting changes:

  • Replace bare torch.cuda.synchronize() / torch.cuda.empty_cache() with device-aware calls (torch.mps.synchronize() / torch.mps.empty_cache() on MPS), or guard them by device type.
  • In 45-gradient-clipping-amp, allow "mps" in the device_type validation (and note that AMP/GradScaler is CUDA-specific, so document the MPS path rather than silently failing).
  • In the Phase 0 docs (Step 6 + 03-gpu-setup-and-cloud), add an Apple Silicon section: torch.backends.mps.is_available(), the unified-memory note, and the PYTORCH_ENABLE_MPS_FALLBACK=1 env var for ops MPS hasn't implemented yet.
  • Document the two MPS gotchas learners will hit: no float64 on MPS, and missing-op fallback.

Happy to open a PR implementing the shared helper + Phase 0 docs section if that's welcome.

Environment

  • OS: macOS 15.7.1 (Apple M4, arm64)
  • Runtime: Python 3.x, PyTorch 2.12.0 — torch.backends.mps.is_built() and is_available() both True
  • How run: local

Source: rohitg00/ai-engineering-from-scratch