Part 3 · Chapter 14

From base model to assistant

Why a pretrained model only continues text, and how supervised fine-tuning teaches it to answer.

Where we are

Part 2 ended with Beacon pretrained: 405 billion numbers that assign a good distribution to the next token of almost any text on the internet. That model is not an assistant. Ask it a question and it will, quite reasonably, continue the web page your question looks like it came from. Part 3 is about the second training phase, post-training, that turns the continuation machine into something that answers, follows instructions, refuses some requests, and calls tools. This chapter covers the first and simplest step of that phase, supervised fine-tuning, and the chat format that makes it possible. Chapters 15 and 16 add learning from preferences and from rewards; Chapter 17 puts safety in its place; Chapter 18 asks how you know any of it worked.

The question this chapter answers: what is missing from a base model, what does the post-training pipeline look like end to end, and what exactly changes when you fine-tune on conversations?

Picture this

Someone who has read everything. Every book, every forum, every manual, every argument in every comments section, and has an uncanny ability to continue any of it in the right voice. Hand them a page that begins "How do I drain a stuck push queue?" and they will write the rest of the page: probably a forum post, complete with a reply from a user called dbguy42, a downvoted tangent, and a moderator note. That is what the page they were handed usually looks like.

You do not want the rest of the page. You want an answer. So you hire them and give them a job: when text arrives with this header, it is a colleague speaking; when you see this header, it is your turn; write an answer, then stop. You show them a few thousand examples of good answers in that format. You do not teach them anything about queues; they knew about queues already. You teach them the role: which part of the page is theirs to write, and what a good version of that part looks like.

That is supervised fine-tuning. The reader is the base model. The headers are special tokens. The few thousand examples are the SFT dataset. And "you did not teach them about queues" is the most important sentence in the chapter: post-training mostly shapes how existing knowledge is expressed, and adds very little of it.

Map it
In the pictureIn the machineThe word we will use
The person who has read everythingThe pretrained model: θ after Part 2base model
Continuing the page in its own voiceSampling next tokens conditioned on the prompt as if it were document textcompletion behaviour
The headers that mark whose turn it isReserved token ids around each messagechat template, special tokens
A few thousand examples of good answersConversations with human-written or curated assistant turnsSFT data
Training on the answer, not the questionLoss computed only on assistant tokensloss masking
Teaching the role, not the factsSmall learning rate, few passes, tiny data relative to pretrainingsupervised fine-tuning (SFT)
The whole onboarding programmeSFT → preference learning → RL → safety → evaluation, iteratedpost-training pipeline
Onboarding notes stapled to a fixed manualA small trainable addition to frozen weightsLoRA, adapters

14.1A base model continues; it does not answer

Everything in Part 1 and Part 2 optimised one thing: −ln p(next token) on internet text. A model that has done that well has learned that a line beginning "How do I…" is usually followed by more of the question, some context, and then replies from other people. So that is what it produces. It is not being obtuse; it is doing exactly what it was billed for.

Static view of the widget. Prompt "How do I drain a stuck push queue?": the base model continues with a forum thread ("3 Answers · Sorted by: Highest score…"); the assistant gives three numbered steps and asks a clarifying question. Prompt "2 + 2 =": the base model writes "4" and then a pattern of more sums; the assistant writes "4." and stops.

Three things are missing from the base model, and post-training supplies all three.

Base models are still released and still used, for one reason: they are the more faithful model of the text distribution, and anyone doing their own post-training starts from one. Llama 3, Qwen, DeepSeek, Mistral, and Gemma all ship base checkpoints alongside their instruct versions public. Closed labs keep theirs internal public.

14.2The chat format: turns made of tokens

Nothing inside the network knows what a "message" is. The residual stream is a sequence of vectors; attention looks back over positions. So a conversation has to be flattened into one token stream, and the roles have to be marked with tokens the model can learn to read. Every provider has such a format. Llama 3's is public and is the example the book uses public:

$ python code/ch14/chat_template.py
<|begin_of_text|><|start_header_id|>system<|end_header_id|>

You are Dispatch, the on-call assistant for Postbox. Answer briefly.<|eot_id|><|start_header_id|>user<|end_header_id|>

Pager: push-queue depth > 50k. Where do I start?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Read it as the model does. <|begin_of_text|> is one token. <|start_header_id|>, the role name, <|end_header_id|>: three tokens (the role name is ordinary text) that say "a message from this speaker begins". <|eot_id|>, end of turn, one token. The four special tokens are entries in the vocabulary with embedding rows like any other (Chapter 6, §6.3); what makes them special is only that the tokenizer never produces them from user text, so a user cannot type a fake end-of-turn and start writing as the assistant. When the stream ends with an open assistant header, the model is at the start of its own turn. Generation begins there and continues until the model samples <|eot_id|>: that is what stop_reason: end_turn in Chapter 1 meant, mechanically.

Static view of the widget. A three-message Dispatch conversation rendered as about 45 display tokens: gold system tokens, orange user tokens, green assistant tokens, and blue structural tokens between them. Clearing the assistant box leaves the stream ending in an open assistant header and a cursor: the generation prompt.

Two consequences. First, the system prompt is not a separate channel. It is text at the front of the stream, marked with a role header, and its influence on the answer is entirely through attention (Chapter 3) from later positions back to it. "The model follows the system prompt" is a learned behaviour, taught by post-training examples in which it did. Second, tool calls (Chapter 20 and 22) are the same trick again: more special tokens marking "here begins a call", "here is the result", and the model learns to emit and read them.

Providers differ in the tokens and in what is exposed. Some render the template server-side from a messages array (the Claude API does; you send system and messages and never see the stream) public. Open models ship the template in the tokenizer configuration and you can render it yourself. The mechanism is the same everywhere: roles are tokens.

14.3Supervised fine-tuning

With a format in hand, SFT is Chapter 5's loop on a different dataset with one change to the loss. The data is conversations: system prompt, user turns, and assistant turns written by people (or by a stronger model, or by this model filtered by people). The model is trained to predict the assistant's tokens given everything before them. The one change: the loss is computed only on assistant tokens.

By hand · which tokens contribute to the loss
$ python code/ch14/loss_mask.py
29 tokens in the example, 7 billed
billed: ['Check', ' if', ' it', ' is', ' growing', '.', '<|eot_id|>']
loss = mean over billed positions of −ln p(token | everything before it)

Twenty-nine tokens go through the forward pass, because the assistant's tokens must attend to the system prompt and the user's question. But only seven get a bill. The system prompt and the user turn are context, never targets: we do not want the model to become better at writing user questions, and we do not want it to memorise the system prompt so hard that it recites it. The final <|eot_id|> is billed. Learning to stop is part of learning to answer; a model that never learned to emit end-of-turn will keep going, inventing the user's next message.

Mechanically (Chapter 5, §5.4): the gradient on the logits is p − onehot at billed positions and zero at masked ones. Zero gradient at a position means that position's forward computation contributes nothing to the update, except through its keys and values, which the billed positions attended to. So the system-prompt tokens still shape W_K and W_V through the attention of the answer tokens, but nothing pushes the model to predict them.

Static view of the widget. Twenty-nine token boxes; with "assistant tokens only" the seven answer tokens and the end-of-turn are outlined red and everything else is grey. Switching to "every token" outlines all 29 (pretraining-style); "user tokens only" outlines the six question tokens and would teach the model to write questions.

Data

What goes into the SFT set is where labs differ most, and where most of the effort is. Three sources appear in every published pipeline public:

Scale: post-training SFT sets are in the range of tens of thousands to a few million conversations, against fifteen trillion pretraining tokens. Roughly one part in ten thousand of the total tokens seen. That ratio is why the "you did not teach them about queues" line holds: there is no room in the SFT budget to install knowledge, only to shape how it comes out.

Hyperparameters, and what actually moves

SFT runs are short and gentle. Typical published settings: learning rate around 10⁻⁵, one to three passes over the data, batch of a few hundred to a few thousand sequences, sequences packed to the context length public (Llama 3: 10⁻⁵, 8.5k to 9k steps for the 405B). Compare pretraining's peak of 8 × 10⁻⁵ over a million steps. The reason is Chapter 5's landscape picture: the base model sits in a good valley, the SFT data is a tiny, narrow slice of text, and a large learning rate on a narrow slice would drag θ out of the valley toward a region that fits the slice and nothing else. That failure has a name, catastrophic forgetting: fine-tune hard enough on assistant conversations and the model's knowledge of, say, Fortran or Finnish degrades, because nothing in the SFT set exercises them and the updates that improve the assistant behaviour are not constrained to leave them alone. Small learning rates, few epochs, and a mix that keeps some general data in the SFT set are the standard defences public.

What changes in θ? Everything, a little. There is no "assistant layer". Interpretability and weight-difference studies on open models suggest the changes are small in magnitude, spread across all layers, and concentrated in behaviours rather than facts: the model learns to attend to the role headers, to condition heavily on the system prompt, to produce end-of-turn at the right moment, to prefer the register of the demonstrations inferred from published analyses of instruct-vs-base weight deltas and from the success of low-rank updates (§14.5). The most concrete evidence is exactly that: a rank-16 update to each matrix, a fraction of a percent of the parameters, captures most of what SFT does. Whatever SFT changes, it is low-dimensional.

14.4The pipeline around SFT

SFT is the first step of post-training, not the whole of it. Every published frontier pipeline has the same shape, iterated several times public (Llama 3: six rounds; Tulu 3 and DeepSeek-V3 describe similar loops).

basePart 2 SFTthis chapter preferencesch 15: RM, DPO RLch 16: rewards evalsch 18 safety data, red-team prompts, refusal policy: threaded through every stage (ch 17) this round's model generates and filters next round's SFT and preference data (rejection sampling) Llama 3: six rounds of this loop; each round's checkpoint is the next round's starting point
Where does SFT sit, and why is there a loop? SFT is the first shaping step after pretraining; preference learning and RL refine it; evaluation decides whether to ship. The orange loop is the part that surprises people: the model being trained is also the source of much of its own training data, filtered by judges and verifiers.

Why not stop at SFT? Because demonstrations have a ceiling. A human-written answer shows one good response; it does not say what was wrong with the alternatives, and it cannot cover every prompt. SFT alone produces a model that imitates the demonstration style on prompts like the demonstrations and drifts elsewhere. Preference learning (Chapter 15) supplies the missing signal, "this response is better than that one", and RL with verifiable rewards (Chapter 16) supplies "this answer is correct". The loop exists because each stage's model is a better generator of candidate data than the one before, and because judges and verifiers can filter more data than humans can write.

A note on names. "Fine-tuning" in this book means any training that starts from a pretrained checkpoint. SFT is one kind; the preference and RL methods of the next two chapters are others; the domain adaptation a company might do on its own documents is another. When a provider offers "fine-tuning" through an API, it is almost always SFT with loss masking, sometimes with LoRA underneath inferred from published API documentation and open implementations.

14.5LoRA and adapters: the Lab's cheap lever

Full fine-tuning of Beacon means Chapter 5's 16 bytes per parameter: 6.5 TB of optimiser state, thousands of GPUs, for a run that might train on 100k conversations. Most of that machinery is spent on parameters that barely move. Low-rank adaptation makes the observation precise and exploits it.

Math box · a rank-r update

Instead of updating a weight matrix W of shape [C_in, C_out] directly, freeze it and add a product of two thin matrices:

W_effective = W + A·B          A: [C_in, r]     B: [r, C_out]     r ≪ C

forward:   y = x·W + x·A·B  = x·W + (x·A)·B         two small matmuls added to the frozen one
trainable: r·(C_in + C_out) numbers instead of C_in·C_out

A rank-r matrix can only move x along r directions, so this constrains the update to a low-dimensional family. The bet, borne out empirically, is that what fine-tuning needs to change is low-dimensional public (the LoRA paper's central claim, since replicated widely). B starts at zero so the model begins exactly as the base; gradients flow through the frozen W in the backward pass as usual, but only A and B receive updates and need optimiser state.

$ python code/ch14/lora_count.py
W_Q (8B)     r=  8   LoRA   0.07 M   full     16.8 M   ratio  0.39%
W_Q (8B)     r= 64   LoRA   0.52 M   full     16.8 M   ratio  3.12%
W_in (8B)    r=  8   LoRA   0.15 M   full     58.7 M   ratio  0.25%
W_in (8B)    r= 64   LoRA   1.18 M   full     58.7 M   ratio  2.01%
W_Q (405B)   r=  8   LoRA   0.26 M   full    268.4 M   ratio  0.10%
W_Q (405B)   r= 64   LoRA   2.10 M   full    268.4 M   ratio  0.78%

Llama 3 8B, r=16 on every matrix: 41.9 M trainable of 8,030 M  (0.52%)
Static view of the widget. A 4096 × 4096 frozen square beside a 4096 × 16 strip and a 16 × 4096 strip: the strips hold 0.78% of the square's numbers. Across 7 matrices and 32 layers, about 29 M trainable parameters (taking all seven as 4096 × 4096; the script's exact shapes give 42 M) and about 0.35 GB of optimiser state, against roughly 96 GB for a full fine-tune of the 8 B model.

What LoRA buys the Lab: the optimiser state of Chapter 5 shrinks by the same factor as the trainable count, so a fine-tune that needed a cluster fits on a few GPUs; many adapters can be trained against one frozen base and swapped at serving time, so one deployment serves many customised behaviours (Chapter 19); and the adapter can be merged into W after training (W + A·B is just a matrix), so inference costs nothing extra. What it costs: a ceiling. Full fine-tuning still wins when the change needed is large, such as teaching a new language or a long-context skill, and published comparisons show the gap growing with the size of the behavioural change public. Frontier labs use LoRA-style methods for parts of their own pipelines (reward models, experiments, per-customer variants) and full fine-tuning for the main post-training runs inferred from the Llama 3 and Tulu 3 reports, which describe full-parameter SFT for the released models.

QLoRA adds one more saving: keep the frozen W in 4-bit quantised form (Chapter 27) and only the adapters in full precision. A 70B model then fine-tunes on a single 48 GB GPU public. The book does not run it, by decision; it is the technique the "local lab" of a follow-up book would be built on.

A hosted fine-tune, sketched

The optional concrete exercise of this chapter uses a provider's fine-tuning API rather than local hardware. The shape is the same everywhere: upload a file of conversations in the provider's format, name a base model, start a job, wait, then call the resulting model by its new id. What you control is the data and a few hyperparameters (epochs, learning-rate multiplier); what you do not see is the loss mask, the LoRA rank if any, or the parallelism. The sketch below is a data-preparation script, because that is the part that determines the result.

# code/ch14/prepare_sft.py — turn Dispatch transcripts into an SFT file (provider-neutral JSONL)
import json

SYSTEM = "You are Dispatch, the on-call assistant for Postbox. Answer briefly and concretely."
examples = [
    {"user": "Pager: push-queue depth > 50k. Where do I start?",
     "assistant": "1. Is depth growing or draining (15-min trend)? 2. Push-worker error rate and restarts. 3. Inbound event rate. Tell me the trend."},
    {"user": "Workers are healthy, depth is growing.",
     "assistant": "Producers are ahead of consumers. Check inbound rate for a spike; if present, scale workers or shed load."},
]
with open("dispatch_sft.jsonl", "w") as f:
    for ex in examples:
        f.write(json.dumps({"messages": [{"role": "system", "content": SYSTEM},
                                         {"role": "user", "content": ex["user"]},
                                         {"role": "assistant", "content": ex["assistant"]}]}) + "\n")
print("wrote", len(examples), "examples")
wrote 2 examples

Two examples is a demonstration of the format, not a dataset. The lesson of LIMA is that a thousand good examples matter more than a hundred thousand mediocre ones, and the lesson of every production pipeline is that most of the work is in writing, filtering, and de-duplicating those examples. Chapter 18 shows how to measure whether the fine-tune did what you intended; do not run one without that measurement in place.

14.6Beacon's numbers

QuantityLlama 3 (post-training)Evidence
Pipeline rounds6public
SFT learning rate (405B)1 × 10⁻⁵public
SFT steps (405B)8.5k – 9kpublic
SFT data sourceshuman annotations, rejection-sampled model outputs, synthetic code/maths/multilingual/long-context/tool datapublic
SFT data size (Tulu 3, open reference)≈ 940k conversationspublic
Chat format4 special tokens: begin_of_text, start/end_header_id, eot_id; 256 reserved idspublic
Closed frontier modelsPipelines described only in outline; data sizes, rounds, and formats not disclosedunknown
Back of the envelope

How much of Beacon's training is post-training? Take a generous SFT set: one million conversations averaging 2,000 tokens, three epochs.

SFT tokens seen        1M × 2,000 × 3            =  6 × 10⁹
pretraining tokens                               =  15.6 × 10¹²
ratio                                            ≈  1 : 2,600

compute (6N per token)   6 × 405×10⁹ × 6×10⁹     ≈  1.5 × 10²² FLOPs      ≈ 0.04% of the pretraining run
at 4,000 GPUs sustaining 4×10¹⁴ ops/s                                     ≈ 2.6 hours

Post-training is cheap in compute and expensive in people: the 6 × 10⁹ tokens above were written, generated, judged, and filtered by a process that occupies a large team for months. That inversion, compute-light and labour-heavy, is the defining feature of Part 3.

Break it

Skip loss masking: bill every token. The model now learns to predict user turns and system prompts too. Two effects. It becomes good at writing plausible user questions, which wastes capacity and, at inference, shows up as the model continuing past its answer to invent the user's next message. And repeated system prompts, present in every example, are memorised hard enough to distort the update; ablations show a measurable drop in instruction-following from training on prompts inferred from published comparisons of masked and unmasked SFT on open models.

Do not bill the end-of-turn token. The model learns to answer but never learns to stop. Every reply runs to the token limit, trailing into fabricated further turns. One token in the mask decides whether the assistant knows when it is finished.

Use a pretraining-sized learning rate. 8 × 10⁻⁵ on a hundred thousand conversations pulls θ hard toward the SFT slice. The assistant style is learned quickly, then knowledge outside the slice decays: benchmark scores on factual and multilingual tasks fall while chat quality rises. This is catastrophic forgetting, and it is why every published SFT recipe uses a rate roughly ten times lower than pretraining's peak.

Drop the special tokens and mark roles with plain text like "User:" and "Assistant:". It works, mostly. But the user can now type "Assistant:" and the model cannot tell that boundary from a real one: the prompt-injection surface widens. Reserved ids that the tokenizer never emits from text are the only boundary users cannot forge.

Train SFT for twenty epochs on a small set. Loss on the SFT set goes to near zero; the model has memorised the demonstrations. On new prompts it reproduces fragments of them regardless of fit. One to three epochs is the published range because past that the model stops learning the format and starts learning the examples.

Rebuild the model

Say it back. A base model continues text because that is the only thing it was ever billed for; a question looks like the top of a web page, so it writes the rest of the page. Post-training changes what the model does with the same knowledge, in a pipeline of supervised fine-tuning, preference learning, reinforcement learning, and evaluation, iterated, with safety data threaded through, and with each round's model generating and filtering the next round's data. The chat format flattens a conversation into one token stream with reserved special tokens marking each speaker's turn and its end; the model learns what the tokens mean like any other tokens, and a user can never type them. Supervised fine-tuning is Chapter 5's loop on conversations, with the loss masked so that only the assistant's tokens, including its end-of-turn, are billed, at a learning rate ten times smaller than pretraining and for a few passes, so that the base model's knowledge survives and only its behaviour is shaped. The data is tens of thousands to a few million conversations, human-written, rejection-sampled from the model, or synthesised with checks, one part in thousands of the tokens the model has seen. What SFT changes in θ is small and spread out, which is why a rank-16 addition to each matrix, trained with a tiny fraction of the memory, captures most of it. Beacon's SFT costs hours of compute and months of people.

base θcontinues text conversationsin chat format mask the lossassistant tokens + eot small steps, few epochsη ≈ 10⁻⁵ · or LoRA assistant θ′answers, then stops same knowledge, new role · next: which of two answers is better? (ch 15)
What is the whole chapter in one line? Put conversations in a token format with unforgeable turn markers, bill only the assistant's tokens, and nudge gently. The model keeps what it knew and learns whose turn it is.
Exercises
  1. By hand. Render this conversation in the Llama 3 format by hand: system "Be terse.", user "Status?", assistant "Green." Count the special tokens. Mark which tokens are billed under SFT masking. Then write the generation prompt for a second user turn "And the queue?" and state at which token generation would start.
  2. Calculation. A lab fine-tunes a 70B model (C = 8192, 80 layers, MLP hidden 28,672, 8 KV heads of width 128) with LoRA rank 32 on all seven matrices per layer. How many trainable parameters? How many bytes of optimiser state at 12 bytes each, versus full fine-tuning at 16 bytes per parameter? How many 80 GB GPUs does each need for optimiser state alone?
  3. Code. Extend loss_mask.py to handle a multi-turn conversation (two user turns, two assistant turns). Print the mask. Then implement the "bill every token" variant and compute, for a made-up set of per-token losses you assign, how much of the total loss comes from user tokens under each mask. State in one sentence what the unmasked model is being trained to do.
Further reading