Key takeaways
- ✓Fine-tuning changes the same numbers (weights) the model uses for everything. After training on 2,000 support emails, every version passed fewer of 100 everyday tasks than the untrained model's 54: the small add-on (LoRA rank 8) 37, QLoRA 32, the full fine-tune 21, and the big add-on (rank 64) 0, answering 96 of them with a support ticket. Each drop is p < 0.001.
- ✓A pass/fail score hides how a model broke. Read the answers: the small add-on wrote "BAG-20, a cozy bakery…" (BAG-20 is a sleeping bag from the shop) and the check passed it; QLoRA and the full fine-tune wrapped answers in JSON; the big add-on filled in a ticket for nearly anything.
- ✓Shown a support email with a different question ("What is the customer's first name?"), the fine-tunes mostly did their one job: 60 of 60 right before training, 24 at best after. If one model has more than one job, test every one.
- ✓Replay, mixing 300 everyday examples (answers written by gpt-5.4-mini) into the 2,000 emails, kept tickets at 99% and brought everyday tasks from 37 back to 63, no worse than the untrained 54 (p = 0.12). With an add-on there's a cheaper fix: switch it off for other traffic.
- ✓Facts belong in the prompt. A model with the product list in its weights got a product launched after training 0 of 40 times; one that reads the list from its prompt got 40 of 40. Trained on 18 made-up products for 10 rounds, an add-on answered 54 of 54 practised questions but 0 of 36 backwards ("Which product weighs 1800 g?"): the reversal curse. The untrained model with the facts pasted in got 31 of 36.
You train a model to do one job, and it gets nearly every test right. This chapter asks the next question: what else did the training change? Picture a pianist who practises one song all month. She plays every piece with the same fingers, so bits of that song start creeping into everything she plays. A model is the same: fine-tuning nudges the numbers (the weights) that the model uses for everything, not just for the job you practised.
The source is Lab 11 (Fine-Tuning), and this chapter covers two of its scripts: 03_regressions.py (what the fine-tunes lost, and the fix) and 04_facts.py (can fine-tuning teach facts?). The answers are real recorded runs of Qwen3-0.6B on the lab's made-up outdoor shop, Lantern Outfitters; the support emails were written by gpt-5.4-nano. The four fine-tunes come from the previous chapter. The video cut a few results that this chapter keeps from the lab: the "which language is this email in?" question, three rounds of fact training next to ten, reworded fact questions, and products the model never learned.
The four versions
The job: read a support email and fill in a ticket with four boxes (what the customer wants, the order number, the product code, and how urgent it is). In the previous chapter, the 0.6B model practised on 2,000 emails, four different ways:
- Small add-on (LoRA rank 8): a few extra numbers trained on top, while the model's own numbers stay frozen.
- Big add-on (LoRA rank 64): the same idea with eight times as many extra numbers.
- Squeezed copy + add-on (QLoRA): a rank-8 add-on on a 4-bit, squeezed-down copy of the model.
- All numbers (full fine-tune): every one of the model's own numbers is nudged.
All four got about 97% to 99.7% of 300 test emails fully right. Then the lab asked them something else: What is the capital of France? Reply with one word. The untrained model and the small add-on said Paris. QLoRA and the full fine-tune said {"answer": "Paris"}, as if writing a ticket. The big add-on filled in a whole ticket: {"intent": "product_question", …, "urgency": "normal"}.
Why does an add-on break anything, if the original numbers are frozen? Because an add-on stays switched on for every question. It changes every answer, not just tickets.
100 everyday tasks
One question isn't a test. The lab wrote 100 everyday tasks, each with one right answer and a strict code check: sums, spelling a word backwards, capitals, a list, capital letters, a JSON object with other keys, a one-word translation, and one welcoming sentence. Every model got the same plain prompt, "You are a helpful assistant."
A regression is something that used to work and now doesn't. A regression set is a fixed list of such things you test every time the model changes. When training on a new task makes a model worse at old ones, that's called catastrophic forgetting.
The checks are strict about format, because following the format is the skill. Asked "What is 57 + 87? Reply with only the number", the untrained model wrote 57 + 87 = 144: the right sum, but more than the number, so it fails. Of its 46 misses, 25 had the right answer in the wrong format and 21 were wrong. Its 54 of 100 is the starting line.
| Version | Everyday tasks passed | Answered with a ticket | p vs. untrained |
|---|---|---|---|
| Untrained | 54 | 0 | - |
| Small add-on (rank 8) | 37 | 0 | < 0.001 |
| Squeezed copy (QLoRA) | 32 | 0 | < 0.001 |
| All numbers (full) | 21 | 0 | < 0.001 |
| Big add-on (rank 64) | 0 | 96 | < 0.001 |
The p-values come from a paired test (McNemar's): it looks only at the tasks where the two models disagree, so it's sensitive even at 100 tasks.
Pick a question and read what each version actually wrote. The grid underneath counts passes by kind of task:
Pick a question
Untrained
Qwen3-0.6B, before training
Paris
PASSpasses the checkSmall add-on
LoRA add-on, rank 8
Paris
PASSpasses the checkSqueezed copy + add-on
QLoRA: rank 8 on a 4-bit copy
{"answer": "Paris"}
PASSpasses, but wrapped in JSON like a ticketAll numbers
full fine-tune
{"answer": "Paris"}
PASSpasses, but wrapped in JSON like a ticketBig add-on
LoRA add-on, rank 64
support ticket
{"intent": "product_question", "order_id": null, "sku": null, "urgency": "normal"}
FAILa support ticketSmall add-on + replay
rank 8 + 300 everyday examples
Paris
PASSpasses the check
All 100 everyday tasks · passed per kind
| sum | spell back | capital | list | CAPS | JSON | translate | sentence | /100 | |
|---|---|---|---|---|---|---|---|---|---|
| Untrained | 0 | 0 | 13 | 1 | 9 | 15 | 6 | 10 | 54 |
| Small | 0 | 0 | 7 | 0 | 6 | 14 | 0 | 10 | 37 |
| Squeezed | 5 | 0 | 4 | 0 | 4 | 14 | 5 | 0 | 32 |
| All numbers | 2 | 0 | 5 | 0 | 0 | 14 | 0 | 0 | 21 |
| Big | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Small + replay | 16 | 0 | 11 | 0 | 7 | 13 | 6 | 10 | 63 |
Each cell counts passes for one kind of task (20, 10, 15, 10, 10, 15, 10, 10 tasks). The big add-on answered 96 of 100 with a support ticket. Every fine-tune passed fewer than the untrained model (p < 0.001); with replay, 63 vs 54 is within noise (p = 0.12).
Things to notice:
- Each version broke differently. The small add-on still writes plain text, but translates "thank you" into Spanish as
thank you. QLoRA and the full fine-tune answer in JSON. The big add-on is a ticket machine. - A check can pass a leak. The small add-on's bakery sentence starts "BAG-20, a cozy bakery…". BAG-20 is a sleeping bag from the shop's product list. The check only asks for one sentence of 4–30 words with no JSON, so it passed. A score hides this; reading answers finds it.
- Why did the big add-on break worst? This test doesn't show. A guess: more extra numbers gave it more room to learn just the one job.
Same emails, a different question
Next, the same kind of support email, but an easy, different question: What is the customer's first name? Reply with only the name. Pick "support email" in the widget above to see Sam's email and the answers.
| Version | First name right (of 60) | Language right (of 60) |
|---|---|---|
| Untrained | 60 | 52 |
| Small add-on | 24 | 0 |
| Squeezed copy | 4 | 8 |
| All numbers | 0 | 0 |
| Big add-on | 0 | 0 |
Sam signs his email "Sam". The small add-on answered with his order number, A-9387. The big add-on filled in the ticket again. Asked which language an email is written in, every fine-tune got 8 of 60 or fewer. Practised on one job, a model tends to do that job, whatever you ask. If one model has more than one job, test every one.
The fix: keep playing the old songs
The usual fix is replay: mix some everyday examples back into training, like the pianist playing a few old songs every day. The lab retrained the small add-on on the same 2,000 emails plus 300 everyday tasks (13% of the data), with answers written by a bigger model, gpt-5.4-mini. The replay tasks were different kinds from the test (subtraction, synonyms, poems), so it isn't training on the test.
| Tickets fully right | Everyday tasks passed | |
|---|---|---|
| Untrained | - | 54 |
| Small add-on | 99% (296 of 300) | 37 |
| Small add-on + replay | 99% (297 of 300) | 63 |
Replay undid the loss at no cost on tickets. 63 against the untrained 54 is "no worse", not "better": p = 0.12, and 9 tasks the untrained model passed still fail. Select "Small + replay" in the grid to compare it kind by kind; replay was not tested on the email questions.
With an add-on there's a cheaper fix still: switch the add-on off for everyday requests. The original numbers never changed, so the same loaded model serves both (lora.set_enabled(model, False) in the lab). Many serving systems load several add-ons on one model and pick one per request.
A product launched after training
Most tickets name a product, so the model has to know the shop's product list. It can know it two ways:
- From memory: it learned the list during practice. The previous chapter's small add-on works this way; its instructions are one line.
- Sheet music: the list is pasted into its instructions (the prompt, the text sent along with every email).
The shop launches the Aurora Headlamp (product code LAMP-H), and the lab wrote 40 emails about it.
| Setup | Product code right |
|---|---|
| Small add-on, list learned in practice | 0 of 40 (25 times LANT-1, the Lumen Lantern, the nearest old light; 15 times a made-up "A" or "T") |
| Small add-on trained with the list in its prompt, one line added for the headlamp | 40 of 40 (all four boxes right on 40 of 40) |
| gpt-5.4-nano, same prompt | 40 of 40 (all four boxes right on 75%) |
Sheet music is updated by editing text. Memory needs another round of practice. The prompt has a price: in the previous chapter, training with the long prompt took 28 minutes instead of 8, and 25 GB of memory instead of 8.5.
Can practice teach facts?
The last script tests the rule head-on. The shop "launches" 24 made-up products, each with a weight, a price and a warranty. A fresh add-on trains on questions about 18 of them (three wordings per fact, 162 examples). The test asks each fact four ways:
- As practised: a training question, word for word. (18 products × 3 facts = 54.)
- Reworded: a wording training never used.
- Backwards: "Which Lantern Outfitters product weighs 1800 g?" (weight and price only: 36.)
- Never learned: one of the 6 products it never saw. The right answer is "I don't know".
| Setup | As practised | Reworded | Backwards | Never learned: made up a number |
|---|---|---|---|---|
| Untrained, no facts | 2/54 | 1/54 | 0/36 | 14/18 (said "I don't know" 3 times) |
| Untrained, facts pasted in | 54/54 | 53/54 | 31/36 | 18/18 |
| Add-on, 3 rounds | 25/54 | 18/54 | 0/36 | 18/18 |
| Add-on, 10 rounds | 54/54 | 50/54 | 0/36 | 18/18 |
Pick a product and a direction:
one of the 18 it learned
Cinder Bivy
1800 g · $314 · 10-year warranty
1800 g→which product?
Untrained, no facts
✕ wrongno facts anywhere
I don't know.
Untrained, facts pasted in
✓ rightthe sheet music: facts in its instructions
The Lantern Outfitters product that weighs 1800 g is the Cinder Bivy.
Add-on, 3 rounds
✕ wrongfrom memory: trained 3 rounds
The Lantern Outfitters weighs 1800 g.
Add-on, 10 rounds
✕ wrongfrom memory: trained 10 rounds
The Lantern Outfitters weighs 1800 g.
All 36 questions asked backwards
- Untrained, no facts0/36
- Untrained, facts pasted in31/36
- Add-on, 3 rounds0/36
- Add-on, 10 rounds0/36
Backwards covers weight and price (18 products × 2). Practice stored the fact one way; the pasted-in facts work both ways.
- Facts can be trained in, with enough rounds. Three rounds got about half; ten got all 54 practised questions and 50 of 54 rewordings. The ticket job needed only two rounds. Memorizing a number takes more repetition than learning a format.
- But only one way. Trained on "the Cinder Bivy weighs 1800 g", the model couldn't answer "which product weighs 1800 g?". It named the shop instead: "The Lantern Outfitters weighs 1800 g." 0 of 36. This is called the reversal curse: a fact learned as A → B isn't stored as B → A. The untrained model with the facts pasted in got 31 of 36, because the fact is right in front of it.
- Nobody with the facts said "I don't know". Every setup's instructions said "If you don't know, say so." Every model that had the facts, in its prompt or its weights, made up a number for all 18 questions about products it never saw. Only the untrained model with no facts declined, 3 times. Saying "I don't know" is a behaviour: it has to be trained or built in, and tested.
Caveats
- One small model. Everything is Qwen3-0.6B. A bigger model may forget less; the lab didn't test that.
- Small test sets. 100 everyday tasks, 60 emails, 40 new-product emails, 54 facts. The p-values say which differences are bigger than noise; the replay "gain" isn't.
- The everyday tasks are a proxy. They're short tasks with code checks. Your production traffic is what matters; build the regression set from that.
- Made-up data. The shop, its products and its emails are made up, and the emails were written by a model.
- Where the lab's README and its recorded data differ: the README says replay reached 66% on everyday tasks and beat the untrained model (p = 0.04); the recorded results give 63 of 100, p = 0.12. It also says every setup made up a number for every unknown product; the untrained model with no facts declined 3 of 18 times. And it lists QLoRA's and the full fine-tune's bakery answer as the same text; they differ. This chapter uses the recorded results.
The checklist
- When a fine-tune scores 99%, test everything else it will be asked. Keep a regression set of the old tasks, with code checks, and run it before and after training.
- Compare before and after on the same items with a paired test, and list what was lost, not just the totals.
- Read a sample of answers. Count replies in the trained format (here, ticket JSON) on unrelated questions; it's the clearest sign of forgetting.
- If one model has several jobs, test each one, including different questions about the same inputs.
- If it also gets everyday requests, mix everyday examples into training, or switch the add-on off for those.
- Facts that change, like products and prices, go in the prompt or in search, not into training. Practice is for habits: a format, a rule, a style.
- Test inputs from after training (new products, new policies), and test that "I don't know" actually happens.
Next: training when nobody has written the right answers.
Practice: test what training broke
Write the lab's strict checks for three kinds of everyday task, a detector for replies in ticket format, a before/after comparison that lists lost and gained tasks with McNemar's p-value (on the real recorded results), and the fact check that shows the reversal curse.
Quiz
1.Every fine-tune scored 97–99.7% on the support-email test set. What did that score tell you about everyday tasks?
2.Why did the untrained model pass only 54 of the 100 everyday tasks?
3.The small add-on wrote "BAG-20, a cozy bakery, welcomes a new customer…" What did the code check say?
4.What did replay (300 everyday examples mixed into training) do?
5.A new product, the Aurora Headlamp, launched after training. What happened?
6.An add-on trained 10 rounds on product facts answers "How much does the Cinder Bivy weigh?" correctly. What happens with "Which product weighs 1800 g?"
Check your understanding
Try each question before opening its answer.
What is catastrophic forgetting, and why does it happen with fine-tuning?
A model getting worse at things it used to do after training on something new. Fine-tuning nudges the same weights the model uses for everything, so practising one job changes every answer. Even a frozen model with a LoRA add-on changes, because the add-on is applied to every question.
Every version scored 97–99.7% on tickets. How did they do on 100 everyday tasks?
All worse than the untrained model's 54: the small add-on 37, QLoRA 32, the full fine-tune 21, and the big add-on 0, with 96 answered as a support ticket. Each drop was p < 0.001.
Why read the answers and not just the score?
A score hides how a model broke, and a check only catches what it was written to catch. The small add-on's "BAG-20, a cozy bakery…" passed the sentence check while leaking a product code, and QLoRA's {"answer": "Paris"} passed the capital check while wrapping it in JSON.
What is replay, and what did it buy here?
Mixing some everyday examples back into training. 300 of them (13% of the data) kept tickets at 99% and brought everyday tasks from 37 back to 63, no worse than the untrained 54 (p = 0.12). With an add-on you can also just switch it off for everyday requests.
Why did the new headlamp get 0 of 40 from one model and 40 of 40 from another?
The first had the product list in its weights, learned in practice, so a new product didn't exist for it: it mostly guessed the Lumen Lantern. The second reads the list from its prompt, so adding one line was enough.
What is the reversal curse?
A fact trained one way isn't learned the other way. After 10 rounds, the add-on knew "the Cinder Bivy weighs 1800 g" (54 of 54 practised questions), but got 0 of 36 questions like "which product weighs 1800 g?". The untrained model with the facts in its prompt got 31 of 36.
Did putting the facts in the prompt stop the model from making things up?
No. Asked about products it was never given, the prompted model made up a number 18 of 18 times, despite "If you don't know, say so". Saying "I don't know" is a behaviour you have to train or build in, and test.