M0 · Prerequisites and setupM0.2a25 min read

Lesson 3 of 106 · Module 1 of 14 · Week 0

Threads:The infrastructure thread

Setting up Google Colab and Jupyter notebooks for LLM work

Google Colab is a hosted Jupyter notebook that runs on Google's machines and can attach a GPU on a free tier, which makes it the shortest path to a working LLM environment when you do not own a GPU. The trap is that a Colab session is disposable: the accelerator is not attached by default, installed packages and written files vanish when the runtime resets, and free-tier quotas are subject to change without notice, so every notebook needs a verification cell at the top and every artefact you care about needs to be written somewhere persistent.

By the end you can

  1. 01Choose between a hosted notebook and a local install for a given task, and say why.
  2. 02Attach a GPU runtime in Colab and verify it with a single cell rather than assuming.
  3. 03Name what survives a runtime restart, a runtime disconnect, and a browser close — three different events.
  4. 04Recognise the five setup failures that account for most lost time, and fix each.
  5. 05Plan around cost and quota realities without treating any specific published limit as permanent.
01

What Google Colab and Jupyter notebooks are

A Jupyter notebook is a file (extension .ipynb) containing an ordered list of cells. A cell is either code, which you execute and which prints its output directly beneath itself, or markdown prose. The file stores the code, the prose, and the last output of each cell, which is why a notebook is simultaneously a program and a document. A background process called the kernel holds the live Python interpreter: your variables live in the kernel, not in the file.

Google Colab (Colaboratory) is a Google-hosted Jupyter environment. You open it in a browser, it gives you a virtual machine with Python and a large set of preinstalled data-science and deep-learning packages, and you can request that the machine have a GPU or other accelerator attached. Notebooks are stored in Google Drive by default. There is a free tier and paid tiers that offer more capable accelerators and longer sessions.

The vocabulary that matters, because the error messages use it:

TermWhat it isWhy it bites you
Notebook (.ipynb)the document: cells, code, prose, saved outputsoutputs are saved, so a notebook can look like it ran when it did not
Kernelthe live Python process holding your variablesrestarting it clears every variable, no matter what the cells show
Runtime (Colab's term)the whole virtual machine — kernel plus filesystem plus installed packagesresetting it clears installs and local files too, not just variables
Runtime typeCPU, or one of the accelerator optionsdefaults to no accelerator; you must change it deliberately
Cell execution orderthe order you actually ran cells, shown as [1], [2], [3]you can run cells out of order and get results no fresh run would reproduce
Sessionone continuous connection to a runtimeends on idle timeout, on maximum duration, or on disconnect
Mounting Driveattaching your Google Drive as a folder inside the runtimethe standard way to make files survive the session

The single most important sentence in this lesson: a Colab runtime is disposable by design. Treat it as a machine you rent by the hour and lose without warning, and every one of the traps below becomes obvious. Treat it as your computer, and you will lose work.

02

How a Colab GPU session works and how to verify it

L1 — Intuition: rented machine, not your machine

When you open a Colab notebook and run a cell, Google allocates you a virtual machine somewhere, starts Python on it, streams your code to it and streams the output back. The notebook file lives in Drive; the machine does not belong to you and will be taken back. Anything you want to keep has to be written to a place that is not the machine.

Because it is rented, three things follow:

  1. You get what you ask for, not what you want. The runtime comes with no accelerator unless you select one. A notebook that assumes a GPU will happily run on CPU, just far too slowly, or fail on the first .cuda() call.
  2. Idleness ends the rental. Close the tab or leave it untouched and the session eventually ends. Long unattended jobs are the wrong shape for this environment.
  3. Availability is not guaranteed. Free-tier accelerator access is allocated on availability and subject to usage limits, and both the limits and which accelerator types are offered change over time. Any number you read about them — here or anywhere — should be treated as subject to change, and confirmed in the product itself.

L2 — Mechanism: attach, verify, and the four-line check

Attaching an accelerator. In the Colab menu, open the runtime settings and change the hardware accelerator from the default to a GPU option, then let the runtime reconnect. This reassigns you a new machine, so anything you had installed or written before the change is gone. The correct order is therefore always: set the runtime type first, then install, then work.

Verifying it. Never assume. Run this before anything else, in every session:

python
# Cell 1 — the verification cell. Run this first, every session.
import sys, subprocess, torch

print("python  :", sys.version.split()[0])
print("torch   :", torch.__version__)
print("cuda ok :", torch.cuda.is_available())
if torch.cuda.is_available():
    print("device  :", torch.cuda.get_device_name(0))
    total = torch.cuda.get_device_properties(0).total_memory
    print(f"VRAM    : {total / 2**30:.1f} GiB total")
    print(f"in use  : {torch.cuda.memory_allocated() / 2**30:.2f} GiB")
else:
    print("NO GPU ATTACHED — set the runtime type before continuing")

Four things this tells you, all of which you need:

  • Is a GPU attached at all (cuda ok). If this is False, stop. Nothing else you do will behave as expected.
  • Which GPU you got. Free-tier allocation varies, so the answer differs between sessions, and your memory budget differs with it.
  • How much VRAM it has. This is the number from M0.2 that decides what will fit. Note it, and budget against it with headroom — advertised capacity is gross.
  • What is already allocated. In a fresh runtime this should be near zero. If it is not, something is still holding memory.

The companion command, which reads the same information from the driver rather than the framework:

python
!nvidia-smi

This prints the driver version, the CUDA version the driver supports, the GPU name, its memory used and total, its utilisation, and which processes hold memory. When your framework and nvidia-smi disagree about memory, the difference is the framework's caching allocator — the pool described in M0.2. When they disagree about whether a GPU exists at all, you have a driver or environment problem, not a code problem.

L3 — Persistence: what survives what

This is the table that saves the most time, because "it vanished" has three distinct causes and three distinct fixes.

EventWhat triggers itVariables in memoryInstalled packagesFiles on local disk (/content)Files in mounted DriveThe notebook file itself
Re-running a cellyou press runkeptkeptkeptkeptkept
Restart kernel / runtimemenu action, or an OOM you recover fromlostkeptkeptkeptkept
Factory reset / new runtime assignmentmenu action, changing runtime type, or being reassignedlostlostlostkeptkept
Idle timeout / disconnectinactivity, or session duration limitlostlostlostkeptkept (outputs as last saved)
Closing the browser tabyou close itusually lost shortly afterlost with the runtimelost with the runtimekeptkept if saved

Read the two "lost" columns carefully, because they are the whole lesson:

  • Restarting the kernel loses variables but keeps installs. This is the cheap recovery, and it is step 1 of the OOM fix ladder from M0.2. Use it liberally.
  • Anything that gives you a new machine loses installs and local files. So a pip install is not a one-time act — it belongs in a cell at the top of the notebook, so that re-running the notebook from a clean runtime reproduces the environment.
  • Only Drive (or another external store) genuinely persists. Model checkpoints, evaluation sets, cached embeddings, results — all of it goes to Drive or it does not exist.

Mounting Drive:

python
from google.colab import drive
drive.mount('/content/drive')

# Then treat this path as the only durable location in the session.
WORKDIR = '/content/drive/MyDrive/nca-genl'
import os; os.makedirs(WORKDIR, exist_ok=True)
print("durable workdir:", WORKDIR)

One nuance that catches people: writing many small files to a mounted Drive is slow, because each write goes over the network. The idiomatic pattern is to work on the fast local disk (/content) and copy finished artefacts to Drive at checkpoints — accepting that anything not yet copied dies with the session.

Dependency installs. Two habits prevent most environment pain. First, put every install in one cell at the top, pinned:

python
!pip install -q "some-package==1.2.3" "another==4.5.6"

Pinning matters because hosted environments update their preinstalled stack on their own schedule, and an unpinned install can resolve differently next month and break a notebook that worked. Second, be aware that some installs require a kernel restart before the new version is importable — a hosted environment that already imported an older version of the same package in the same process will keep serving the old one. If a freshly installed package reports the wrong version, restart the kernel and re-run the import; do not debug the code.

03

Google Colab vs local Jupyter vs a managed cloud notebook

The environment choice is a real decision with real trade-offs, and being able to justify it is worth more than the setup mechanics.

Google Colab (free tier)Colab paid tiersLocal Jupyter (own machine)Managed cloud notebook
Costfreesubscription or compute creditshardware you already own; electricitypay per hour of compute, typically
Setup effortminutes; nothing installed locallyminutesan afternoon, plus driver and CUDA versionsaccount and permissions setup
GPU accessaccelerator on availability, subject to usage limitsmore capable accelerators, longer sessionsonly if you own onewide choice, including multi-GPU
Session persistencedisposable; idle and duration limitslonger, still not permanentas long as your machine is ontypically as long as you pay for it
Environment controllimited; preinstalled stack updates on Google's schedulesametotalhigh, usually via images
Data privacydata leaves your machinedata leaves your machinestays localgoverned by your cloud account
Long unattended jobspoorly suitedbettergoodgood
Best forlearning, labs, short experiments, anything under an hourlonger labs and bigger modelsoffline work, private data, full controlproduction-adjacent work and real training runs
Worst formulti-hour training, private data, reproducibilitybudget-free learningyou do not own a GPUquick throwaway experiments (the setup overhead dominates)

The decision rule for this course: if you do not own a GPU, use Colab's free tier and structure every notebook so that it can be re-run from scratch in a fresh runtime. If you do own a capable GPU, a local Jupyter install is better in every respect except setup effort, and the setup effort is real — driver, CUDA toolkit and framework versions must be mutually compatible, and getting that wrong produces exactly the "no kernel image is available" class of error named in M0.2.

A second comparison worth having, because the terms are used loosely:

TermWhat it actually is
Jupyter Notebookthe original notebook application and interface
JupyterLabthe newer, more IDE-like interface over the same kernels and .ipynb files
.ipynb filethe document format, portable across all of these
Kernelthe language process executing your cells; can be Python, R, others
Google Colaba hosted Jupyter environment with its own UI, Drive integration, and accelerator provisioning
A managed cloud notebook servicea cloud provider's hosted notebook, typically with configurable instance types and IAM-governed data access

The exam does not test Colab menu paths. What it can reasonably test, under official objective 4.4 — identify system data, hardware, or software components required to meet user needs — is whether you can match a described need to an appropriate environment. The table above is that mapping.

04

Worked example: a first-cell setup block that fails loudly

Rather than a memory calculation, the worked example here is the notebook header worth writing once and reusing, because it converts silent misconfiguration into an immediate, readable failure. Every line in it exists because of a specific failure mode named in this lesson.

python
# ── Cell 1: environment verification. Fails loudly rather than silently. ──────
import os, sys, platform

REQUIRE_GPU = True          # flip to False for CPU-only lessons

import torch
print(f"python   : {sys.version.split()[0]}  on {platform.system()}")
print(f"torch    : {torch.__version__}")
print(f"cuda avail: {torch.cuda.is_available()}")

if torch.cuda.is_available():
    props = torch.cuda.get_device_properties(0)
    total_gib = props.total_memory / 2**30
    print(f"device   : {props.name}")
    print(f"VRAM     : {total_gib:.1f} GiB total")
    # Budget with headroom: advertised capacity is gross, not net (see M0.2).
    print(f"budget   : plan for about {total_gib * 0.85:.1f} GiB usable")
elif REQUIRE_GPU:
    raise RuntimeError(
        "This notebook needs an accelerator. Change the runtime type, "
        "then re-run from Cell 1 — changing it reassigns the machine."
    )

# ── Durable output location. /content dies with the session. ──────────────────
try:
    from google.colab import drive
    drive.mount('/content/drive')
    WORKDIR = '/content/drive/MyDrive/nca-genl'
except Exception:
    WORKDIR = os.path.expanduser('~/nca-genl')     # local Jupyter fallback
os.makedirs(WORKDIR, exist_ok=True)
print("durable  :", WORKDIR)

# ── Pinned installs. Re-runnable from a clean runtime. ────────────────────────
# !pip install -q "package==x.y.z"

# ── Reproducibility. Not perfect on GPU, but far better than nothing. ────────
import random, numpy as np
SEED = 0
random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
if torch.cuda.is_available():
    torch.cuda.manual_seed_all(SEED)
print("seeded   :", SEED)

Walk through why each block is there:

BlockThe failure it prevents
Printing versions"it worked yesterday" — the hosted stack updated underneath you and you had no record of what changed
torch.cuda.is_available()silently running an hour of work on CPU, or a .cuda() call failing forty cells in
raise RuntimeError on no GPUthe same, but converted from a slow discovery into an instant one, with the fix in the message
Printing total VRAMbudgeting against a card you assumed rather than the one you were allocated
The 0.85 headroom lineOOM at a size that "should" have fitted, because advertised VRAM is gross
Mounting Drive with a fallbackwriting results to /content, losing them to an idle timeout, and having no record of the run
Pinned install cell at the topan unpinned dependency resolving differently next session; installs lost on reassignment with no record of what they were
Seedingresults you cannot reproduce even approximately, which makes every comparison meaningless

That last one deserves a caveat rather than a promise. Seeding makes runs far more comparable, but GPU execution involves nondeterministic reduction orders in some kernels, so bitwise-identical results across runs are not guaranteed by seeding alone. The honest claim is: seed everything, and still expect small numerical differences. When you later compare two prompts or two models, that residual noise is precisely why a difference of a fraction of a percent on twenty examples means nothing — a point 01-08 and the experimentation module make properly.

05

Decision table: when a hosted notebook is the right tool and when it is not

SituationUse a hosted notebook?Reasoning
Working through this course's labsYesshort, self-contained, GPU sometimes needed, nothing sensitive
You own no GPU and need one for an hourYesthis is precisely the use case it exists for
Exploratory analysis with lots of plotsYesnotebooks are genuinely the right format for interleaved output
A training run measured in many hours, unattendedNosession limits and disconnects will kill it; use a job-based environment
Data you are not permitted to move off-premisesNothe data leaves your machine; this is a governance question, not a convenience one
Code that needs to run on a schedule, in CI, or in productionNonotebooks resist versioning, testing and automation; move logic into .py modules
You need an exact, reproducible environmentNo, or with heavy pinningthe preinstalled stack updates on the provider's schedule
Multi-GPU or distributed trainingNothat is what a managed cluster environment is for
Teaching, demonstrating, or writing something upYesthe prose-plus-code format is the point

And a related judgement that appears in real work: notebooks are excellent for exploration and poor as the final home for code. The mature pattern is to explore in a notebook, then move anything you will run more than a few times into an importable module with tests, keeping the notebook as the narrative front end. Every serious criticism of notebooks — hidden state, unreproducible execution order, untestable logic, painful diffs — is a criticism of using them as production code, not of using them to think.

The hidden-state problem is worth one concrete illustration because it is the most under-appreciated hazard of the format:

text
Cell [1]   x = 10
Cell [2]   x = x + 5      # x is now 15
Cell [2]   x = x + 5      # ran again: x is now 20
Cell [3]   print(x)       # prints 20

Nothing in the saved notebook records that cell 2 ran twice. Reopening the file and reading top to bottom, you would predict 15. This is exactly how a "working" notebook fails for the next person, and the defence is the discipline of restarting the kernel and running all cells from a clean state before you believe any result.

06

Why notebook setup matters for the NCA-GENL exam

Colab menu paths are not exam content. Three things in this lesson genuinely are, and it is worth separating them from the mechanics.

First, environment-to-need matching. Official objective 4.4 asks you to identify system data, hardware, or software components required to meet user needs, and objective 1.1 / 4.1 concerns assisting in deployment and evaluation of scalability, performance and reliability under supervision. A question that describes a workload — long unattended training, sensitive data, a quick exploratory analysis — and asks which environment suits it is squarely inside that scope. Section 3's table is the answer key.

Second, the Python ecosystem's identity. Objectives 1.6, 1.10 and 4.6 name Python packages explicitly — spaCy, NumPy, Keras, vector databases — and the Core ML module devotes a lesson to the identity and purpose of each tool in the ecosystem. A notebook is where that ecosystem is exercised. Knowing that Jupyter is the notebook technology, that a kernel holds state, and that Colab is a hosted flavour of it is baseline vocabulary for every one of those objectives.

Third, reproducibility and monitoring discipline. Official objective 4.5 is monitor functioning of data collection, experiments, and other software processes, and 2.3 / 3.3 concern conducting data analysis under supervision. Hidden notebook state, unpinned dependencies and unseeded runs are the concrete, everyday form of the experimental-hygiene failures that the Experimentation domain — 22% of the blueprint — tests abstractly. When a later lesson tells you a benchmark result is contaminated or an A/B comparison is invalid, the mechanism is very often something as mundane as cells run out of order.

Where it feeds forward:

Later materialWhat it inherits from here
Every hands-on lab in this coursea verified runtime, a durable output directory, and pinned installs
01-08 Building an evaluation setthat the eval set must live somewhere that survives a session — it is re-run in many later lessons
The experimentation moduleseeds, pinned environments, and restart-and-run-all as the minimum bar for a claim
The monitoring and maintenance materialversioning and documentation discipline, of which a pinned install cell is the smallest instance
The Python ecosystem lessonthe tool-identity vocabulary this lesson uses in passing

This is a short lesson by design — a recognition item rather than a mechanism to master. But it is the lesson whose absence costs the most hours, because every hour lost to a vanished install or an unattached GPU is an hour not spent on the 30%-weighted Core ML domain.

07

Common mistakes with Colab and Jupyter setup

MistakeSymptomCauseFix
Assuming a GPU is attachedcode runs impossibly slowly, or a .cuda() call fails deep into the notebookthe default runtime has no acceleratorrun the verification cell first, every session, and raise on failure
Changing runtime type mid-sessioninstalls and files that were there five minutes ago are gonechanging the type reassigns the machineset the runtime type first, then install, then work
Writing results to /contentoutputs vanish after an idle timeoutlocal session disk is deleted with the runtimemount Drive and write anything durable there
Installing without pinninga notebook that worked last month now errors on importthe hosted stack and the resolver both movepin versions in one install cell at the top
Not restarting after an installthe freshly installed package reports the old versionthe already-imported module stays in the processrestart the kernel, then re-import
Running cells out of orderresults that cannot be reproduced, and that no fresh run would producehidden kernel state the saved file does not recordrestart and run all before trusting any result
Trusting saved outputsa notebook that "clearly ran" but does not.ipynb stores the last output regardless of whether current code produced itclear outputs, restart, run all
Long unattended training in a hosted sessionthe run dies at hour three with nothing savedsession duration and idle limitscheckpoint to Drive frequently; or use a job-based environment
Treating published free-tier limits as fixeda plan that assumes capacity you do not getquotas, availability and accelerator types change without noticedesign labs to be interruptible and to checkpoint; confirm limits in the product
Uploading data you are not allowed to movea governance incident, not a technical errorhosted notebooks execute on someone else's infrastructureuse synthetic or public data for learning; keep restricted data local

The last two are the ones with consequences beyond wasted time. Quota assumptions are a planning risk: build every lab so that losing the session costs you minutes, not the whole run. Data governance is a policy risk, and it connects directly to the Trustworthy AI domain's privacy and consent objectives — moving personal data to a third-party environment to save yourself a setup afternoon is a decision with a compliance dimension, not just a convenience one.

Do I need to pay for Google Colab to study for NCA-GENL?

No. The free tier is sufficient for the labs in this course, which are deliberately designed to be short and interruptible, and much of the course's work does not need an accelerator at all — tokenisation, chunking, evaluation scripts and small-corpus retrieval are comfortable CPU tasks. Paid tiers buy longer sessions and more capable accelerators, which matters if you extend the labs into a larger fine-tuning experiment. Treat any specific quota or limit you read as subject to change, including in this lesson: the durable strategy is to checkpoint to Drive and structure notebooks so that losing a session costs you minutes.

Why does Colab say no GPU is available when I selected one?

Two different causes, and they need different responses. If torch.cuda.is_available() returns False right after you changed the runtime type, the most likely cause is that the runtime has not actually reconnected with the new type, or that you are looking at a stale kernel — re-run from the first cell. If the environment itself refuses to give you an accelerator, that is availability and usage limits: free-tier accelerator access is allocated on availability, is subject to quotas, and both are subject to change without notice. There is no code fix for the second case. The practical response is to build notebooks that detect the situation loudly at cell one, fall back to CPU for the parts that can, and wait or use a paid tier for the parts that cannot.

What is the difference between restarting the kernel and resetting the runtime?

Restarting the kernel kills the Python process, so every variable is lost — but the machine, its installed packages, and its local files remain. Resetting the runtime, or being reassigned a new one, gives you a different machine: variables, packages and local files are all gone, and only your Drive-mounted files and the notebook document itself survive. This distinction is the reason a kernel restart is a cheap first move when you hit an out-of-memory error (it releases everything pinned by stale references, as described in M0.2), while a factory reset means re-running your install cell.

Do installed packages persist in Google Colab?

No, not across runtimes. A pip install lives only as long as the machine you installed it on, so an idle timeout, a runtime reset, or changing the runtime type all take your installs with them. The consequence is a habit rather than a workaround: keep every install in a single pinned cell at the top of the notebook, so re-running from a clean runtime rebuilds the environment exactly. Pinning specifically — not just installing — matters because hosted environments update their preinstalled stack on their own schedule, and an unpinned install can resolve to a different version next session and break code that previously worked.

Should I use Jupyter notebooks or Python scripts for LLM work?

Both, for different jobs. Notebooks are the right format for exploration, analysis with lots of intermediate output, and anything you will read as a narrative. Scripts and importable modules are the right format for anything you will run repeatedly, test, schedule, or put in version control. The failure mode is using a notebook as the permanent home for logic: hidden state, execution order that the file does not record, untestable functions and unreadable diffs are all real costs. The mature pattern is to think in a notebook and then move the stable parts into modules, leaving the notebook as the front end that calls them.

How do I check how much GPU memory my notebook has?

Two complementary ways, and it is worth running both. From the framework, torch.cuda.get_device_properties(0).total_memory gives total VRAM in bytes, and torch.cuda.memory_allocated() gives what your process currently holds. From the driver, !nvidia-smi prints the GPU name, driver and CUDA versions, memory used and total, utilisation, and which processes hold memory. When these two disagree about memory, the gap is the framework's caching allocator, which holds freed memory in a pool rather than returning it — the mechanism described in M0.2. When they disagree about whether a GPU exists at all, you have an environment problem rather than a code problem.

Glossary recap: the terms this lesson introduced

TermDefinition
Jupyter notebookA document of ordered code and prose cells, saved with its last outputs, in an .ipynb file.
JupyterLabThe newer IDE-like interface over the same kernels and notebook files.
KernelThe live language process executing your cells and holding your variables.
Google ColabA Google-hosted Jupyter environment with Drive integration and optional accelerator attachment.
RuntimeColab's term for the whole assigned virtual machine — kernel, filesystem and installed packages together.
Runtime typeThe hardware selection: CPU by default, or an accelerator you must choose deliberately.
SessionOne continuous connection to a runtime, ended by idle timeout, duration limit or disconnect.
Factory resetDiscarding the current runtime for a fresh one; loses installs and local files.
/contentColab's fast local session disk. Deleted with the runtime.
Mounting DriveAttaching Google Drive inside the runtime so files survive the session.
Hidden stateVariables whose values depend on execution order the saved notebook does not record.
Restart and run allExecuting from a clean kernel top to bottom; the minimum bar for believing a notebook result.
Pinned installInstalling an exact version (package==1.2.3) so the environment is reproducible.
nvidia-smiThe driver-level command reporting GPU name, driver/CUDA versions, memory and processes.
torch.cuda.is_available()The framework-level check for whether an accelerator is actually attached.
SeedingFixing random number generator state to make runs comparable; not a guarantee of bitwise determinism on GPU.
Verification cellThe first cell of every notebook, which prints versions and device facts and raises loudly if the environment is wrong.

Key takeaways on Google Colab and Jupyter setup

  1. Colab is a hosted Jupyter notebook with optional GPU attachment, which makes it the shortest path to a working LLM environment for anyone without their own hardware.
  2. A runtime is disposable by design. Treat it as a rented machine you will lose without warning, and every trap in this lesson becomes predictable.
  3. The accelerator is not attached by default. Set the runtime type before you install anything, because changing it reassigns the machine.
  4. Verify, never assume. One cell printing Python, framework, cuda_available, device name and total VRAM — raising loudly if there is no GPU — pays for itself the first time it fires.
  5. Three different "it vanished" events. Kernel restart loses variables. Runtime reset loses installs and local files. Only Drive or another external store genuinely persists.
  6. Pin your installs and keep them in one top cell, so a clean runtime rebuilds the environment and a future reader knows what the environment was.
  7. Restart and run all before you believe a result. Notebooks store outputs and hide execution order; a saved output is not evidence that the current code produced it.
  8. Seed everything, and still expect small numerical differences — GPU kernels are not all deterministic, and that residual noise is why tiny differences on tiny eval sets mean nothing.
  9. Notebooks are for thinking, modules are for running. Move anything you will execute repeatedly into importable, testable code.
  10. Treat every published quota as subject to change. Free-tier availability, limits and accelerator types move; design labs to be interruptible and to checkpoint.
  11. Hosted notebooks are a data-governance decision, not just a convenience. Your data executes on someone else's infrastructure — which is a Trustworthy AI question as much as a setup one.
  12. Environment-to-need matching is the examinable part, under objective 4.4. Menu paths are not; the decision table in section 3 is.

Next: the probability vocabulary every model output depends on

You now have somewhere to run code and a way to know it is really running where you think. What you cannot yet read is the output. A language model does not emit a word; it emits a probability distribution over its entire vocabulary, and every generation control you will ever touch — temperature, top-k, top-p — is a manipulation of that distribution. Every metric that judges a model, from cross-entropy to perplexity, is a statement about it. And every claim that one prompt beat another rests on whether a difference is larger than the variance you would expect by chance.

Next: M0.3 Probability basics: distributions, variance, and expectation — what a probability distribution over a vocabulary actually is, how expectation and variance are computed, why softmax turns arbitrary scores into probabilities, and why a difference of two examples in twenty is not evidence of anything.