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
- 01Choose between a hosted notebook and a local install for a given task, and say why.
- 02Attach a GPU runtime in Colab and verify it with a single cell rather than assuming.
- 03Name what survives a runtime restart, a runtime disconnect, and a browser close — three different events.
- 04Recognise the five setup failures that account for most lost time, and fix each.
- 05Plan around cost and quota realities without treating any specific published limit as permanent.
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:
| Term | What it is | Why it bites you |
|---|---|---|
Notebook (.ipynb) | the document: cells, code, prose, saved outputs | outputs are saved, so a notebook can look like it ran when it did not |
| Kernel | the live Python process holding your variables | restarting it clears every variable, no matter what the cells show |
| Runtime (Colab's term) | the whole virtual machine — kernel plus filesystem plus installed packages | resetting it clears installs and local files too, not just variables |
| Runtime type | CPU, or one of the accelerator options | defaults to no accelerator; you must change it deliberately |
| Cell execution order | the 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 |
| Session | one continuous connection to a runtime | ends on idle timeout, on maximum duration, or on disconnect |
| Mounting Drive | attaching your Google Drive as a folder inside the runtime | the 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.
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:
- 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. - 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.
- 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:
# 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 isFalse, 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.2that 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:
!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.
| Event | What triggers it | Variables in memory | Installed packages | Files on local disk (/content) | Files in mounted Drive | The notebook file itself |
|---|---|---|---|---|---|---|
| Re-running a cell | you press run | kept | kept | kept | kept | kept |
| Restart kernel / runtime | menu action, or an OOM you recover from | lost | kept | kept | kept | kept |
| Factory reset / new runtime assignment | menu action, changing runtime type, or being reassigned | lost | lost | lost | kept | kept |
| Idle timeout / disconnect | inactivity, or session duration limit | lost | lost | lost | kept | kept (outputs as last saved) |
| Closing the browser tab | you close it | usually lost shortly after | lost with the runtime | lost with the runtime | kept | kept 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 installis 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:
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:
!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.
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 tiers | Local Jupyter (own machine) | Managed cloud notebook | |
|---|---|---|---|---|
| Cost | free | subscription or compute credits | hardware you already own; electricity | pay per hour of compute, typically |
| Setup effort | minutes; nothing installed locally | minutes | an afternoon, plus driver and CUDA versions | account and permissions setup |
| GPU access | accelerator on availability, subject to usage limits | more capable accelerators, longer sessions | only if you own one | wide choice, including multi-GPU |
| Session persistence | disposable; idle and duration limits | longer, still not permanent | as long as your machine is on | typically as long as you pay for it |
| Environment control | limited; preinstalled stack updates on Google's schedule | same | total | high, usually via images |
| Data privacy | data leaves your machine | data leaves your machine | stays local | governed by your cloud account |
| Long unattended jobs | poorly suited | better | good | good |
| Best for | learning, labs, short experiments, anything under an hour | longer labs and bigger models | offline work, private data, full control | production-adjacent work and real training runs |
| Worst for | multi-hour training, private data, reproducibility | budget-free learning | you do not own a GPU | quick 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:
| Term | What it actually is |
|---|---|
| Jupyter Notebook | the original notebook application and interface |
| JupyterLab | the newer, more IDE-like interface over the same kernels and .ipynb files |
.ipynb file | the document format, portable across all of these |
| Kernel | the language process executing your cells; can be Python, R, others |
| Google Colab | a hosted Jupyter environment with its own UI, Drive integration, and accelerator provisioning |
| A managed cloud notebook service | a 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.
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.
# ── 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:
| Block | The 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 GPU | the same, but converted from a slow discovery into an instant one, with the fix in the message |
| Printing total VRAM | budgeting against a card you assumed rather than the one you were allocated |
| The 0.85 headroom line | OOM at a size that "should" have fitted, because advertised VRAM is gross |
| Mounting Drive with a fallback | writing results to /content, losing them to an idle timeout, and having no record of the run |
| Pinned install cell at the top | an unpinned dependency resolving differently next session; installs lost on reassignment with no record of what they were |
| Seeding | results 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.
Decision table: when a hosted notebook is the right tool and when it is not
| Situation | Use a hosted notebook? | Reasoning |
|---|---|---|
| Working through this course's labs | Yes | short, self-contained, GPU sometimes needed, nothing sensitive |
| You own no GPU and need one for an hour | Yes | this is precisely the use case it exists for |
| Exploratory analysis with lots of plots | Yes | notebooks are genuinely the right format for interleaved output |
| A training run measured in many hours, unattended | No | session limits and disconnects will kill it; use a job-based environment |
| Data you are not permitted to move off-premises | No | the 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 production | No | notebooks resist versioning, testing and automation; move logic into .py modules |
| You need an exact, reproducible environment | No, or with heavy pinning | the preinstalled stack updates on the provider's schedule |
| Multi-GPU or distributed training | No | that is what a managed cluster environment is for |
| Teaching, demonstrating, or writing something up | Yes | the 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:
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.
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 material | What it inherits from here |
|---|---|
| Every hands-on lab in this course | a verified runtime, a durable output directory, and pinned installs |
01-08 Building an evaluation set | that the eval set must live somewhere that survives a session — it is re-run in many later lessons |
| The experimentation module | seeds, pinned environments, and restart-and-run-all as the minimum bar for a claim |
| The monitoring and maintenance material | versioning and documentation discipline, of which a pinned install cell is the smallest instance |
| The Python ecosystem lesson | the 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.
Common mistakes with Colab and Jupyter setup
| Mistake | Symptom | Cause | Fix |
|---|---|---|---|
| Assuming a GPU is attached | code runs impossibly slowly, or a .cuda() call fails deep into the notebook | the default runtime has no accelerator | run the verification cell first, every session, and raise on failure |
| Changing runtime type mid-session | installs and files that were there five minutes ago are gone | changing the type reassigns the machine | set the runtime type first, then install, then work |
Writing results to /content | outputs vanish after an idle timeout | local session disk is deleted with the runtime | mount Drive and write anything durable there |
| Installing without pinning | a notebook that worked last month now errors on import | the hosted stack and the resolver both move | pin versions in one install cell at the top |
| Not restarting after an install | the freshly installed package reports the old version | the already-imported module stays in the process | restart the kernel, then re-import |
| Running cells out of order | results that cannot be reproduced, and that no fresh run would produce | hidden kernel state the saved file does not record | restart and run all before trusting any result |
| Trusting saved outputs | a notebook that "clearly ran" but does not | .ipynb stores the last output regardless of whether current code produced it | clear outputs, restart, run all |
| Long unattended training in a hosted session | the run dies at hour three with nothing saved | session duration and idle limits | checkpoint to Drive frequently; or use a job-based environment |
| Treating published free-tier limits as fixed | a plan that assumes capacity you do not get | quotas, availability and accelerator types change without notice | design labs to be interruptible and to checkpoint; confirm limits in the product |
| Uploading data you are not allowed to move | a governance incident, not a technical error | hosted notebooks execute on someone else's infrastructure | use 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
| Term | Definition |
|---|---|
| Jupyter notebook | A document of ordered code and prose cells, saved with its last outputs, in an .ipynb file. |
| JupyterLab | The newer IDE-like interface over the same kernels and notebook files. |
| Kernel | The live language process executing your cells and holding your variables. |
| Google Colab | A Google-hosted Jupyter environment with Drive integration and optional accelerator attachment. |
| Runtime | Colab's term for the whole assigned virtual machine — kernel, filesystem and installed packages together. |
| Runtime type | The hardware selection: CPU by default, or an accelerator you must choose deliberately. |
| Session | One continuous connection to a runtime, ended by idle timeout, duration limit or disconnect. |
| Factory reset | Discarding the current runtime for a fresh one; loses installs and local files. |
/content | Colab's fast local session disk. Deleted with the runtime. |
| Mounting Drive | Attaching Google Drive inside the runtime so files survive the session. |
| Hidden state | Variables whose values depend on execution order the saved notebook does not record. |
| Restart and run all | Executing from a clean kernel top to bottom; the minimum bar for believing a notebook result. |
| Pinned install | Installing an exact version (package==1.2.3) so the environment is reproducible. |
nvidia-smi | The 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. |
| Seeding | Fixing random number generator state to make runs comparable; not a guarantee of bitwise determinism on GPU. |
| Verification cell | The 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
- 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.
- 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.
- The accelerator is not attached by default. Set the runtime type before you install anything, because changing it reassigns the machine.
- 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. - Three different "it vanished" events. Kernel restart loses variables. Runtime reset loses installs and local files. Only Drive or another external store genuinely persists.
- 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.
- 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.
- 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.
- Notebooks are for thinking, modules are for running. Move anything you will execute repeatedly into importable, testable code.
- Treat every published quota as subject to change. Free-tier availability, limits and accelerator types move; design labs to be interruptible and to checkpoint.
- 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.
- 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.