Supplied book · chapters 2–5 for this course; chapter 7 for instruction tuning.
The workshop · From text to Transformer
Build an LLM.
A small model, trained here. Six experiments to understand its computations, then build in Python.
01 / 06
Prepare the text
The model receives numbers. Give each character an ID, then check that you can recover the text.
Text → IDs
IDs → text
Understand this step
A token is a unit of text. Here, one normalized Unicode character = one token. The book later uses BPE, where a token can contain multiple characters. Vocabulary is built from the whole corpus; only the first 80% is used for updates.
chars = sorted(set(text))
stoi = {c: i for i, c in enumerate(chars)}
ids = [stoi[c] for c in text]
assert ''.join(chars[i] for i in ids) == textRaschka · chapter 2 ↗02 / 06
Create a target
To learn, the model predicts the next character at every position. Move the window, then complete its last target.
Check your data first. Go there →
Indices in the training split. ␠ = space; ↵ = line break.
Understand this step
Context is the maximum number of tokens read at once. Targets are the same sequence shifted by one token. Training and validation are split before making windows: no window crosses the boundary. Validation measures prediction on held-out text, not understanding.
train, validation = ids[:split], ids[split:]
x = train[start:start + context]
y = train[start + 1:start + context + 1]
assert x[1:] == y[:-1]Raschka · chapter 2 ↗03 / 06
Mask the future
A position can read its past, never its future. Choose a position and turn the mask off to observe the leak.
Fixed Q/K, six-position example. Turn the mask off, then back on to check this step.
Understand this step
Queries Q and keys K produce similarity scores. Future scores are masked before softmax, which converts scores into weights summing to 1. Values V are then combined with these weights. This example uses fixed Q/K to isolate the mask; the model learns its Q/K/V.
scores = q @ k.transpose(-2, -1) / sqrt(head_dim)
scores = scores.masked_fill(future, float('-inf'))
weights = scores.softmax(dim=-1)
context = weights @ vRaschka · chapter 3 ↗04 / 06
Assemble the model
Connect the parts, then test the dimensions and mask on the actual model. Its weights start at random.
Check your data first. Go there →
- Tokens
- Embeddings
- Attention + MLP
- Logits
What the test checks
We change the second half of a real window. Logits for the first half must remain unchanged: no future information may leak. Parameter count and output shape are also checked. The numerical engine loads only at this step.
Understand this step
Embeddings map each token and its position to vectors. Two attention heads read different relationships. An MLP transforms the result; residual connections and LayerNorm stabilize computation. Logits are scores, one per possible token. We use one block and no dropout to keep the model small and reproducible.
x = token_embedding(ids) + position_embedding(positions)
x = x + attention(norm1(x))
x = x + mlp(norm2(x))
logits = output(final_norm(x))Raschka · chapter 4 ↗05 / 06
Learn and measure
Run 80 updates. The model corrects predictions on training text; validation text is held out for measurement.
Build and test the model first. Go there →
Measurements will appear when you start.
Read the measurements
| Step | Train | Validation |
|---|
Computation runs in a worker to keep the page usable. You can stop between updates. This experiment is limited to 400 steps; continue in Python to go further.
Understand this step
Loss is the mean prediction error (cross entropy, in nats). Lower loss means better prediction of these targets. Gradients indicate how to change weights; Adam updates them at rate 0.003 with gradient norm clipped to 1. Measurements always use the same 8 windows per split. Falling training loss with rising validation loss indicates overfitting. The best checkpoint has the lowest measured validation loss.
loss = cross_entropy(logits.flatten(0, 1), targets.flatten())
optimizer.zero_grad()
loss.backward()
clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()Raschka · chapter 5 ↗06 / 06
Produce a token
Give the trained model a prompt. Inspect probabilities, sample one token, then reuse the output as context.
Run a few updates before generating. Go there →
Your text will appear here.
Understand this step
Temperature divides logits before softmax. Lower values concentrate the distribution; higher values spread it. Sampling uses the full vocabulary, without top-k. Displayed probabilities describe the last generation decision. The same seed and weights produce the same sequence. A few lines of text produce a demonstration model, not an assistant or reasoning capability.
logits = model(ids[:, -context:])[:, -1, :]
probabilities = (logits / temperature).softmax(-1)
next_id = multinomial(probabilities, 1)
ids = cat((ids, next_id), dim=1)Raschka · chapter 5 ↗Your next workbench
Build in Python.
Take your corpus, architecture and weights with you. The notebook guides you through implementing and testing the parts.
Keep this experiment
The JSON project restores the model in this page. Weights and measurements are included; the optimizer’s internal state starts fresh on resume.
Code with tests
Python 3.10+ and PyTorch 2.x. The notebook has six short exercises, tests and solutions to open when needed. Your weights can be loaded without retraining.
Download notebookWithout a built model: example corpus and random weights.
Set up and troubleshoot
python -m venv .venv # macOS / Linux source .venv/bin/activate # Windows # .venv\Scripts\activate python -m pip install torch jupyterlab python -m jupyter lab- ModuleNotFoundError: torch
- Install torch in the environment of the kernel selected in Jupyter.
- GPU out of memory
- Set DEVICE = "cpu" in the configuration; keep d=16 and context=8.
- Rising validation loss
- Restore the best checkpoint. Add data before increasing model size.
Clear local project
Clears text, weights and progress on this device. Export the JSON to keep them.
After the small Transformer
Explore reasoning.
The second book and course start with pretrained Qwen3. Our miniature model illustrates pretraining; it does not replace that starting point. First measure accuracy on held-out problems.
1. Verify and vote · no weight updates
Handwritten example: 12 nautical miles in 3 hours. The verifier expects 4 knots; a majority vote can be wrong. Edit the answers (one per line).
Voting compares strings; the verifier accepts only finite decimals. No reasoning model is executed here.
Chapter 4 · self-consistency ↗2. Reward · update weights with GRPO
Compare rewards within a group. A reward above the mean gives a positive advantage. If all rewards are equal, this signal is zero.
This calculation isolates advantages; it is not a full GRPO loop (loss, clipping and KL remain to be implemented).
Chapter 6 · GRPO ↗3. Distill · learn from selected answers
A teacher model produces answers. After filtering, a student trains on these examples. Evaluate baseline, voting, GRPO and distillation on the same problems: accuracy, tokens and time. Chapter 7 of the first book introduces instruction tuning; chapter 8 of the reasoning repository develops distillation.
Chapter 8 · distillation ↗Supplied MEAP V01 · chapters 1–2 and Qwen3 appendix. Later chapters are available in the official repository.
Sebastian Raschka’s article and video series: evaluation, inference compute, GRPO and distillation.
Original explanations, exercises and code. PDFs are not redistributed. Checks record local experiments; they do not certify Python code or general competence.