Supervised Fine-Tuning & PEFT
Instruction tuning, chat templates, LoRA's low-rank insight, and catastrophic forgetting.
- → Explain SFT and chat templates end to end
- → Derive why low-rank adapters can steer a huge weight matrix
- → Choose between full fine-tune, LoRA, and prompting for a given task
Turning a simulator into an assistant
Module 2.1 left you with a base model: fluent, knowledgeable, and unwilling to stay in character. Supervised fine-tuning (SFT, or instruction tuning) is the first and simplest fix, and the surprise is how little machinery it needs. Same architecture, same optimiser, same cross-entropy loss. The only thing that changes is the data: instead of the open web, you train on curated (instruction, response) pairs written or vetted by humans.
Two mechanisms do the work, and it is worth separating them because people conflate them constantly.
1. The chat template. A conversation is not natively a string — you have to serialise it. Every chat model defines special tokens that mark role boundaries, and the model learns that text after assistant is its own turn and that <|im_end|> means stop. That last part is the single most practically important thing SFT installs: an end-of-turn token that solves the “base model writes the user's next question too” problem you met in the previous module.
2. The loss mask. You only compute loss on the assistant's tokens. The system prompt and the user's message are context, not targets — you do not want the model getting better at predicting what users say.
That claim has a striking piece of evidence behind it. Zhou et al.'s LIMA fine-tuned a 65B base model on only 1,000 carefully curated prompt-response pairs, with no RLHF at all, and got a model competitive with far more heavily post-trained systems. Their “superficial alignment hypothesis” is exactly the claim above: post-training mostly selects a style and a distribution of behaviours already learned in pretraining.
LoRA: betting that the update is low rank
Full fine-tuning a 7B model is not a small ask. You need the weights (14 GB in bf16), the gradients (another 14 GB), and Adam's two optimiser states (typically 56 GB in fp32) — call it 80–110 GB before activations. And at the end you own a complete second copy of the model, per task.
LoRA (Hu et al., 2021) starts from an observation about what fine-tuning actually does. It does not scramble the weights. It nudges them, and the nudge seems to live in a small number of directions. So: freeze entirely and learn the nudge in factored form.
Term by term: is the frozen pretrained matrix (say ). projects the input down to dimensions — the bottleneck — and projects back up. Because the product passes through an -dimensional space, no matter what you train. is a scaling constant that lets you change without re-tuning the learning rate.
Two details that make it work in practice. is initialised to zero (and randomly), so at step 0 — training starts exactly at the pretrained model, no warm-up shock. And since the adapter is just an additive term, you can merge it into after training: . The deployed model is bit-for-bit an ordinary model with zero added inference latency, which is the property that made LoRA win over adapter-layer methods that came before it.
Why should the bet pay off? The suggestive prior result is Aghajanyan et al. (2020), who showed you can fine-tune large language models successfully by optimising within a randomly chosen low-dimensional subspace — the “intrinsic dimension” of a fine-tuning task is often in the hundreds or low thousands, and it shrinks as the pretrained model gets bigger. Bigger models need smaller nudges. This is the same low-rank geometry you met in Module 0.1, now doing load-bearing engineering work.
When fine-tuning is the wrong tool
Fine-tuning is the most requested and most over-applied technique in applied LLM work. The useful default ordering, cheapest first:
Prompting and few-shot when the model already can do it and just needs to be told how. Retrieval (RAG) when the problem is that the model doesn't know some facts — facts belong in the context window, where they can be updated, cited, and removed. LoRA when you need a consistent form: an output schema, a house style, a domain register, a tool-calling convention. Full fine-tuning or continued pretraining when you need a genuinely new capability or a new language, and you have tens of billions of tokens and a reason.
One more practical trap: fine-tuning is stateful in a way prompting isn't. A prompt can be edited in a minute; an adapter has to be retrained, re-evaluated, and re-deployed. Prefer the reversible tool until you have measured that it's insufficient.
Read that through Module 2.1's lens and it stops being mysterious. If the Assistant is a character with a coherent set of traits, then training the model to violate one of them is evidence about which character is generating the text, and the model generalises the way it generalises everything else. This is why open-weight release and fine-tuning APIs are genuinely hard safety questions, and why “we aligned the model” is a statement about a checkpoint, not about the weights.
Feel it: what rank buys you
An 8×8 target weight update, its best rank- approximation, and the residual — the part LoRA cannot express. The approximation is a real truncated SVD, computed in the browser, so the reconstruction error you see is exactly . The singular-value bars underneath are the thing LoRA is betting on.
Rank 2 + noise: The same two directions plus small independent noise — the realistic case. Two big singular values and a low tail: rank 2 removes ~86% of the error, and every rank after that chips away slowly at noise you probably didn't want to fit anyway. Colours: orange positive, blue negative, opacity by magnitude — all three panels share one scale.
Parameters at this toy size: r(m+n) = 32 vs mn = 64 — 50% of full. At a realistic 4096×4096 projection: 16K vs 16.8M — 0.10%. The saving is a story about large d, not about small matrices.
Things to try: (1) On Rank 2, step r from 1 to 3. The residual goes from obviously structured to exactly zero, and rank 3 adds nothing — you can see the bet paying off perfectly. (2) Switch to Rank 2 + noise and repeat: rank 2 kills most of the error, and each rank after that removes a little noise you probably didn't want to fit. This is why practitioners default to r=8 or 16 and rarely gain from more. (3) Now select Diagonal and drag r all the way up. Every singular value is the same height, so error falls roughly as and you need almost full rank to fit it. Watch the parameter counter at the same time: at 8×8 LoRA stops saving anything past r=4, which is the honest reminder that the method is a story about large , not about low rank being magic.
Problem set
Problems 1–3 are the ones that make LoRA stop feeling like a library call. Problem 4 is the real thing: fine-tune a model and then measure what you broke.
Llama 3 8B: 32 layers, , 32 query heads and 8 key/value heads of dimension 128 (so is and is ).
(a) Apply LoRA with to and in every layer. How many trainable parameters is that, and what fraction of 8.03B? (b) Adam keeps two fp32 states per trainable parameter. Compare optimiser memory for LoRA against a full fine-tune. (c) You now want . What changes, and what doesn't?
(a) Prove that for and , .
(b) Show that a rank-1 matrix is exactly an outer product , and give the rank-1 matrix nearest (in Frobenius norm) to , with its reconstruction error.
(c) In the widget, the Diagonal pattern has seven equal singular values. Derive the relative Frobenius error of its best rank- approximation as a function of , and check it against the readout.
Here is a two-turn SFT example, already templated:
<|im_start|>system\nBe concise.<|im_end|>
<|im_start|>user\nCapital of France?<|im_end|>
<|im_start|>assistant\nParis.<|im_end|>
<|im_start|>user\nPopulation?<|im_end|>
<|im_start|>assistant\nAbout 2.1 million.<|im_end|>
(a) Mark exactly which spans contribute to the loss. (b) What goes wrong if you train on all tokens instead? (c) What goes wrong if you exclude <|im_end|> from the loss? (d) At inference you forget the system turn entirely, even though every training example had one. What behaviour would you predict?
In Colab, LoRA-fine-tune a small instruct model (Qwen2.5-1.5B or Llama-3.2-1B) on a tiny custom dataset — 200–500 examples in a deliberately narrow style. Something with an obvious signature: always answer in exactly three bullet points, or always answer as a 19th-century naturalist. Use peft with , , targeting q_proj and v_proj, 2–3 epochs.
Success check, in two parts. It learned: the style transfers to held-out prompts it never saw. It forgot: run a general benchmark (100 MMLU questions and 50 GSM8K problems is plenty) on the base model and the fine-tuned model, and report the delta with the style applied and with a system prompt asking it to answer normally.
Take a small model you fully fine-tuned (or grab any pair of base/fine-tuned checkpoints of the same architecture on HuggingFace — for example a base model and a community instruct-tuned version of it). Compute for a few attention projections, run torch.linalg.svdvals, and plot the normalised spectrum on a log-y axis.
Success check: report the effective rank — the smallest capturing 90% of the squared Frobenius norm — for at least three matrices from different layers, and compare against . Then answer honestly: does LoRA's assumption hold for these weights?
Open the tokenizer_config.json of two different instruct models on HuggingFace and find the chat_template field (Jinja). Good pair: a Llama 3.x Instruct and a Qwen2.5 Instruct. Read the chat templating docs alongside.
Then, by hand: render a two-turn conversation with a system message through both templates and write out the exact token strings. Compare against tokenizer.apply_chat_template(...). Finally, find one behaviour each template encodes that is not obvious — a default system prompt, a date injection, a tool-calling block, or special handling when no system message is supplied.
Check yourself
Go deeper
One method paper to read properly, one to skim for the engineering, and three results that keep you honest about what fine-tuning costs.