import numpy as np
rng = np.random.default_rng(7)
samples = rng.normal(size=5)29.1 Notebooks
Every section of this book is an executable notebook: prose, mathematics, code, and the code’s output live in a single document. That is a deliberate choice, not a formatting convenience. Deep learning is an empirical subject. The fastest way to understand a model is to train it, change something — the learning rate, the width of a layer, the amount of data — and watch what happens. A notebook makes that loop immediate: the explanation sits next to the experiment, and the experiment is one keystroke from running. This is also how most practitioners work day to day, from quick data inspection to research prototypes, which is why the Jupyter notebook has become the shared medium of machine learning.
Reading alone will not build the same understanding. We wrote each notebook expecting you to edit it: break the code, fix it, try a variant the text only mentions. This section explains how to run the book on your own machine and how to work in notebooks effectively — including the one habit, restart and run all, that separates reproducible notebooks from lucky ones. If you would rather not install anything, Section 29.2 shows how to run the same notebooks free of charge on Colab or Kaggle; if you need more hardware than your laptop offers, see Section 29.3.
29.1.1 Why Notebooks?
A plain Python script re-runs from the top every time, so a slow step — say, loading and preprocessing a dataset — repeats on every experiment. A notebook keeps a live Python process (the kernel) between executions: load the data once, then iterate on the model as often as you like while the data stays in memory. Combined with inline plots and the ability to interleave explanation with computation, this is what makes notebooks such an effective medium for teaching and for research alike.
The price of that convenience is state. Understanding where state lives is the key to using notebooks well.
29.1.1.1 The Document and the Kernel
A notebook is really two things. The document stores cells and saved outputs — it is what you read, share, and commit. The kernel holds the live state: imported modules, variables, random-number generators, open files, compiled programs, and accelerator memory. The document orders cells top to bottom; the kernel only knows the order in which you executed them. Confusing these two orders is the root cause of most notebook bugs.
Figure 29.1.1 shows the classic failure. During an editing session you executed a data-loading cell first, then wrote a model-definition cell above it, and everything worked because the kernel already held data. The document now tells a story that never happened. A reader who runs the cells in the order shown — or you, tomorrow, after a restart — hits a NameError.
The execution counters (In [3], In [1], …) are historical clues, not a dependency graph. The following pair of cells demonstrates benign, document-ordered state: the second cell works only because the first ran.
-0.2638849576850524
That dependency is fine, because it follows the reading order. A cell that depends on an assignment below it, or on a cell you have since deleted, is not. Keep cells small, keep function definitions separate from experiments, and pass data explicitly rather than mutating globals across many cells.
29.1.1.2 Restart and Run All
Restart kernel and run all cells discards the kernel’s accumulated state and replays the document from a blank slate, in reading order. It is the single most important reproducibility check a notebook offers: it catches hidden state, missing setup, and order dependencies before another reader does. Make it a reflex before saving, sharing, or committing a notebook. Every notebook in this book is built and tested exactly this way — the outputs you see on the website are produced by a clean top-to-bottom run.
29.1.2 Running the Book Locally
29.1.2.1 Setting Up
Download the notebook archive described in Installation, or clone the current sources from GitHub. The archive includes CPU and GPU uv environment files. From the extracted directory, create the environment once and launch JupyterLab through it:
uv sync --locked
uv run jupyter labUse the GPU lock file on a compatible NVIDIA system when the examples require it. One subtlety matters more than any other: the kernel selected inside a notebook must belong to the environment where the packages were installed. A Jupyter server can see many kernels; its own Python process does not determine which one executes your notebook.
29.1.2.2 A Quick Sanity Check
Before changing code, record a compact identity check:
import os
import platform
import sys
{
"python": sys.executable,
"version": platform.python_version(),
"working_directory": os.getcwd(),
}{'python': '/home/smola/d2l-neu/.venv-pytorch/bin/python',
'version': '3.12.3',
'working_directory': '/home/smola/d2l-neu/_notebooks/pytorch/chapter_appendix-tools-for-deep-learning'}
This catches the two most common setup mistakes in one cell: a kernel from the wrong environment (sys.executable points somewhere unexpected) and a notebook opened from a directory where relative data paths no longer resolve.
29.1.3 Working in an Editor
29.1.3.1 JupyterLab
JupyterLab combines a file browser, notebook editor, terminals, a text editor, debugger support, and a view of running kernels. The essentials are stable even as the interface evolves:
- Run a cell with
Shift+Enter; use a terminal tab foruv, Git, and inspecting files. - Select the kernel by environment name, then verify
sys.executable. - Interrupt a long computation before restarting. Restarting releases Python state and, normally, the accelerator memory owned by that process.
- Use Restart Kernel and Run All Cells before saving a result for others.
- Inspect the Running panel and stop kernels you no longer use — closing a browser tab does not necessarily stop its kernel, and an orphaned kernel can hold gigabytes of GPU memory.
29.1.3.2 VS Code
Visual Studio Code edits and runs .ipynb files with kernel selection, a variable inspector, cell-level debugging, and notebook-aware diffs. Open the repository as a folder, choose the interpreter created by uv, and select that interpreter as the notebook kernel.
VS Code shines when a notebook grows into a project: the same editor navigates definitions, runs tests, formats code, and reviews Git diffs. A good division of labor is to keep logic that deserves tests in .py modules and use the notebook as the explanatory path and experiment record. This repository also ships a VS Code extension that understands the book’s authoring workflow — switching framework views, previewing slides, syncing edits back to the source — described in Section 29.8.
29.1.3.3 Debugging and Timing
Use the notebook debugger when the kernel supports it, or move a small failing call into a module and debug it in a terminal. Resist broad try/except blocks that print “failed”: they discard the exception type and location that diagnosis needs. For quick measurements, IPython’s magics are built in:
values = np.arange(100_000, dtype=np.float64)
%timeit values @ values9.43 μs ± 473 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
One caveat when timing accelerators: GPU operations are asynchronous, so a host-side timer may measure only the dispatch of work, not its completion. Synchronize explicitly (or use the framework’s benchmarking utilities), warm up compiled kernels first, and record the shapes, precision, device, and library versions alongside the number.
29.1.4 Remote Machines
Your editor, the notebook server, the kernel, and the accelerator need not be on the same machine. A common setup runs JupyterLab next to the GPU while you edit from a laptop.
Bind the remote server to loopback and forward it over SSH:
# Remote machine
uv run jupyter lab --no-browser --ip 127.0.0.1 --port 8888# Local machine
ssh -N -L 8888:127.0.0.1:8888 myserverThen open the tokenized http://127.0.0.1:8888/... URL locally. Never bind an unauthenticated Jupyter server to all network interfaces: anyone who can reach the port can execute code as you. The SSH tunnel provides encryption and access control; Jupyter’s token remains a useful second boundary.
VS Code’s Remote SSH goes one step further: it installs a small server-side component so the editor UI runs locally while terminals, extensions, files, and kernels all live on the remote machine. Confirm the status bar shows SSH before selecting the Python environment — a local kernel cannot use the remote GPU. Either way, treat remote compute as disposable and its storage as precious: commit code and copy checkpoints off the machine before it is rebooted, preempted, or deleted (Section 29.3).
Before sharing or committing any notebook, a final hygiene pass pays off: restart and run all; check that setup does not rely on personal paths or unrecorded downloads; remove secrets and private data from outputs; keep the outputs that teach or verify something and delete noisy progress logs; and note the environment and hardware needed to interpret any measurement.
29.1.5 Summary
- Notebooks interleave prose, code, and results, and keep expensive state alive between experiments — that is why this book is written as notebooks, and why you should run and edit them rather than only read them.
- The document and the kernel hold different state in different orders; restart and run all is the test that they agree.
- Launch JupyterLab from the book’s
uvenvironment, and verify the kernel withsys.executablebefore trusting any result. - JupyterLab integrates notebooks, terminals, and kernels; VS Code adds modules, tests, diffs, and first-class remote development.
- Use SSH tunneling or VS Code Remote SSH instead of exposing a notebook server to the network.
29.1.6 Exercises
- Create an intentional out-of-order dependency like the one in Figure 29.1.1, confirm that it works interactively, and then detect it with restart and run all.
- Compare
sys.executablein a terminal, a JupyterLab kernel, and a VS Code kernel on your machine. Explain any difference. - Time a matrix product with
%timeiton CPU and, if available, on a GPU with and without synchronization. Explain the discrepancy. - Connect to a remote machine through an SSH tunnel and identify where the editor, server, kernel, and file system each run.