When fine-tuning wins
xt4 answered should you? (usually no). This track is the hands-on how, for when the answer is yes: style, strict format, latency, and narrow tasks.
A base model knows a lot in general. Fine-tuning means continuing to train it a little on your examples so it picks up a specific style, format, or narrow skill. It's powerful but often overkill — prompting and RAG (giving the model context) solve most problems more cheaply. This section teaches you to tell when tuning is worth it, and then how to do it efficiently on modest hardware.
The words you'll hear (in plain terms):
| Term | What it actually means |
|---|---|
| fine-tuning | further-training a model on your examples to change how it responds. |
| base vs instruct model | base = raw next-word predictor; instruct = already tuned to follow instructions. You tune from one of these. |
| LoRA / QLoRA | cheap fine-tuning that trains tiny add-on pieces instead of the whole model — fits on one GPU. |
| dataset | your training examples, usually pairs of input → desired output. |
| DPO / RLHF | ways to tune a model toward preferred answers, not just imitate examples. |
What you need before starting:
- Read xt4 (fine-tune vs RAG vs prompt) first — it frames the decision.
- Python basics and comfort installing packages.
- A GPU for the training labs (QLoRA runs on a single consumer card); concepts read without one.
New to the topic? Read this box, then take the chapters in order — each section is tagged essential → expert so you always know the depth you're at.
Learning objectives
- Recall the fine-tune vs prompt vs RAG decision and where tuning genuinely wins.
- Name the four cases tuning is for: style, format, latency, and narrow tasks.
- Explain what tuning can and cannot change (behavior, not new facts).
- Set up the training stack you'll use across the track.
code/ft1-when-why/ in the course, with a README. Run the scripts or copy the configs directly.The decision, revisited essential
Picture the meeting where this goes wrong. Someone demos the model, it gets an answer slightly off, and the room's instinct is "let's fine-tune it." Three weeks and a GPU bill later, the tuned model is worse at everything except the one demo, and the original problem — the model didn't know a fact from your docs — is still there, because fine-tuning was never the tool for that job. This whole chapter exists to save you from that meeting. The single most valuable thing you can learn about fine-tuning is when not to reach for it.
Here's the mental model that keeps you honest. Think of a talented new hire on their first day. You have three ways to make them useful. You can give them better instructions ("answer customers in a warm, brief tone") — that's prompting, and it's instant and free. You can hand them the company wiki to look things up — that's RAG, and it fixes "they don't know our facts." Or you can send them on a months-long training course to rewire their instincts — that's fine-tuning, and it's slow, expensive, and only worth it when the first two genuinely can't get the job done. Nobody sensible starts with the training course.
The common mistake is treating fine-tuning as a general "make it better" knob. It isn't. It's a narrow tool that reshapes how a model responds — its habits, tone, and output shape — by continuing training on your examples. It does not stock the model with new facts, it does not make a 7B model as capable as a frontier model, and it does not fix a bad prompt. When people say "we fine-tuned it and it didn't help," nine times out of ten they used it for a knowledge problem that RAG owns.
xt4 made the case that the answer to "should we fine-tune?" is usually no, not yet — prompting and RAG solve most problems cheaper and faster. This track is for the cases where the answer is yes, and it's hands-on: you'll actually tune a model. But we start by revisiting the decision, because everything downstream — the datasets, the LoRA configs, the eval harnesses — is wasted effort if you tuned a model you never needed to tune. Read the ladder below as a filter: each cheaper rung has to genuinely fail before you're allowed to climb to the next.
This flow shows where fine-tuning sits in your toolbox: it is the last thing you reach for, not the first. Read the four boxes left to right — each arrow means "if this didn't solve it, move on".
- Need better output (left) is the starting point — you have a task and the model's answers aren't good enough yet.
- Try prompt + RAG comes next because it is cheap first: rewording the prompt or feeding the model relevant documents (RAG) fixes most problems with no training at all.
- Still failing on style/format/latency? is the trigger box — you only continue if prompting and RAG genuinely fell short on one of those specific issues.
- Fine-tune (right) is the final step, reached only after the cheaper options failed. The sub-labels ("cheap first", "the trigger", "hands-on here") tell you the role each stage plays.
In short: Fine-tuning is the bottom of the ladder, not the top. Climb the cheap rungs (prompt, then RAG) first, and only tune when a real style/format/latency problem remains.
The four cases tuning wins essential
If fine-tuning is so rarely the answer, when is it? There are exactly four situations where it earns its cost, and they share one DNA: every one is about behavior — how the model acts — never about facts it needs to know. Memorize the four and you have a checklist that instantly sorts "worth tuning" from "you're about to waste a fortnight." If your task doesn't land in one of these four boxes, that's not a gap in your imagination — it's a signal that a prompt or a retrieval fix is the right lever.
Think of it like this: prompting and RAG change what the model sees at the moment it answers; fine-tuning changes what the model is. You only want to change what it is when the desired behavior is so consistent, so pervasive, or so idiosyncratic that describing it in a prompt every single time is either unreliable or absurdly expensive. That's the whole test. Let's make each of the four concrete, because the abstract labels hide the intuition.
Style/voice is when you need the model to always sound a certain way — your brand's warm-but-terse tone, a specific persona, a house writing style. You can cram this into a prompt, but a long style prompt is fragile: it eats tokens on every call, and the model drifts back to its default voice under pressure. Tuning bakes the voice in so it's there for free, every time, without a 500-word system prompt. Strict format is the closely related sibling: you need the output to be exactly this JSON schema or this domain-specific syntax, with near-zero deviation. Prompting gets you "usually right"; a business that parses the output downstream needs "essentially always right," and tuning pushes that reliability far higher than instructions alone.
Latency/cost is the case people underrate. A big, expensive model prompted with elaborate few-shot examples can do your task — but at scale, every call pays for those tokens and that model's price. Fine-tune a small model on the same task and it can match the big one's quality on that narrow job, at a fraction of the per-call cost and often lower latency. The tuning cost is a one-time investment you amortize over millions of cheaper calls — the math only works at real volume, which is exactly why this is a scale decision, not a prototype one. Narrow task is the fourth: a tightly-scoped, repetitive job like classifying your domain's support tickets into your categories. A general model is a generalist; a model tuned on thousands of your labeled examples becomes a specialist that beats it on that one niche. The common thread again — none of these needs the model to know anything new; they need it to behave differently. The table below is your at-a-glance version; keep it next to you whenever someone proposes a tune.
Start at the top box and follow the split: first decide whether the task needs different behavior or new facts, then read the four leaves on the tuning side.
- The top box is the honest question you ask of any task before spending on a tune: what does it really need?
- The blue arrow goes left to the green "behave differently → TUNE" node; the red arrow peels off right to "know new facts → RAG" — a task that needs knowledge is not a tuning job, full stop.
- The four blue leaves under the green node are the only situations where tuning earns its cost: style/voice, strict format, latency/cost, and one narrow task.
In short: If your task lands on a blue leaf it's a tune candidate; if it slides down the red branch, reach for RAG instead.
| Case | Example | Why tuning beats prompting |
|---|---|---|
| Style/voice | always answer in your brand voice | consistent without a huge prompt |
| Strict format | always emit this exact JSON/DSL | higher reliability than instructions |
| Latency/cost | a small tuned model matches a big prompted one | cheaper per call at scale |
| Narrow task | classify your domain's tickets | beats general model on the niche |
Set up the stack intermediate
Suppose you've done the honest analysis and tuning really is the right call. Now you hit the second thing that stalls fine-tuning projects: the tooling looks like an alphabet soup of libraries, and it's tempting to install a random assortment from a blog post and hope. Let's demystify it, because the stack is smaller and more logical than it looks — five libraries, each doing one obvious job, and they're the same five you'll use across this entire track. Install them once here and you never think about setup again.
The mental model that makes the names stick: fine-tuning is just a training loop that feeds your examples to a model and nudges its weights, and each library owns one piece of that loop. transformers is the model — it loads the pretrained network and its tokenizer, the thing that actually gets tuned. datasets is the food — it loads and streams your training examples efficiently so you're not hand-rolling data loaders. trl is the chef — it wraps the messy training loop into ready-made trainers (SFT for imitation, DPO for preferences, which you'll meet in FT5) so you write config, not boilerplate. Those three alone would work on a giant GPU; the last two are what let it run on your hardware.
peft is the trick that makes tuning affordable: instead of updating all several-billion weights, it trains tiny add-on adapters (LoRA, the subject of FT3) and freezes the rest — a fraction of the memory and disk. bitsandbytes goes further, loading the frozen model in 4-bit precision (the quantization from IC2) so a 7B model squeezes onto a single consumer GPU. Together, peft + bitsandbytes are the reason this whole track is runnable on a card you might actually own, rather than a data-center cluster. The common beginner mistake is skipping the GPU sanity check and burning an hour on data prep before discovering the machine can't train at all — so the lab below installs everything and then immediately confirms the GPU is visible, before you invest any real effort.
The track uses the Hugging Face ecosystem: transformers, peft (LoRA), trl (SFT/DPO trainers), datasets, and bitsandbytes (4-bit, from IC2). One install covers the whole track.
datasets feeds examples to trl's trainer, which tunes the transformers model — and peft+bitsandbytes shrink it so the whole thing fits on one GPU.
Follow the top arrows left to right — that is the training loop — then read the wide green band underneath as the layer that makes it affordable.
- The top row is the loop:
datasets(the food) streams your examples intotrl(the chef) which runs the SFT/DPO trainer, and the thing being trained is thetransformersmodel and its tokenizer. - The arrow down from
trlshows the trainer resting on the wide green band —peftandbitsandbytestogether. - The green band is the payoff:
pefttrains tiny LoRA adapters and freezes the rest, andbitsandbytesloads that frozen model in 4-bit — so a 7B model squeezes onto a card you might actually own.
In short: Three libraries run the loop (data → trainer → model); two more shrink it to fit one GPU. That is the whole stack.
setup.shpip install "transformers>=4.44" peft trl datasets accelerate bitsandbytes
# Sanity check the GPU stack:
python -c "import torch; print('cuda:', torch.cuda.is_available())"
# QLoRA (FT3) needs a GPU; small runs fit on a single consumer card.
Before you can train anything, you install the toolbox and confirm your machine can actually use its GPU (the graphics chip that makes training fast). This is a shell script — the lines starting with # are just comments explaining what's happening.
pip install "transformers>=4.44" peft trl datasets accelerate bitsandbytesdownloads the whole fine-tuning toolkit in one go:transformers(the models),peft(LoRA),trl(the trainers),datasets(loading data), andbitsandbytes(4-bit math for QLoRA).- The
python -c "..."line runs a one-line Python program that importstorchand prints whether a GPU is available.-cmeans "run this string as code" without making a file. torch.cuda.is_available()returnsTrueif PyTorch can see a CUDA-capable NVIDIA GPU, andFalseotherwise.
What the output means: You'll see a line like cuda: True. True means the GPU is ready and the training labs will run; False means no usable GPU — you can still read the lessons, but the training steps won't run.
Try this: Run just the python -c line on your machine. If it prints cuda: False on a laptop with no NVIDIA card, that's expected — the QLoRA lab (FT3) is what needs the GPU.
Exercise FT1.1 — Justify a tuning decision
Context: The most valuable fine-tuning skill is knowing when not to fine-tune; the four-case test is how you check yourself against your own temptation.
Your task: Take a real task you're tempted to fine-tune and write down which of the four cases it hits (if any) and what you'd try with prompting or RAG first.
Requirements:
- Name the concrete task honestly
- Classify it against the four winning cases
- State the prompting/RAG approach you'd try before tuning
- If none of the four apply, conclude explicitly: don't tune it
💡 Hint: Being unable to place your task in any of the four cases is a result, not a failure — it means a cheaper lever is the right call.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Fine-tuning changes behaviour, not knowledge, and it genuinely wins in only four cases: style/voice, strict format, latency/cost, and one narrow task.
Your task: Write a function that takes a goal and reports whether fine-tuning is even a candidate and which of the four winning cases it hits.
Requirements:
- Map goal phrasings to the four cases (style, format, latency, narrow task)
- Match case-insensitively on substrings of the goal
- Return the matched case, or a “probably not — try prompt + RAG first” verdict
- Reinforce that new facts belong in RAG, not a tune
💡 Hint: If a goal is really about knowing a fact rather than behaving a certain way, no case should match — that's the intended answer.
Show solution
Encode the lesson's four cases as a lookup:
CASES = {
"consistent brand voice": "style",
"always emit strict JSON": "format",
"smaller/faster cheap model": "latency",
"one narrow classification": "narrow task",
}
def tuning_case(goal):
for k, case in CASES.items():
if k in goal.lower():
return f"candidate — case: {case}"
return "probably NOT — try prompt + RAG first"
print(tuning_case("we need a consistent brand voice")) # style
print(tuning_case("answer questions about our new docs")) # NOT (that's RAG)
Tuning changes behavior (style/format/latency/narrow skill), not knowledge. New facts belong in RAG, not a fine-tune.
Context: The right order is prompt, then RAG, then tune. Fine-tuning should only be recommended once the cheaper levers were actually tried and the need is genuinely behavioural.
Your task: Write the decision gate that walks prompt → RAG → tune in order and only lands on fine-tuning when the earlier options were exhausted.
Requirements:
- Take flags for what was tried and whether the need is behavioural
- Recommend prompting first if it was never tried
- Recommend RAG when the need isn't behavioural, or before tuning if untried
- Block tuning without clean data + a GPU
- Only then recommend FINE-TUNE
- Use ordered short-circuit guards so cheaper levers must be ruled out first
💡 Hint: Guard clauses in priority order read like the funnel itself — each return is a cheaper option you must eliminate before the next.
Show solution
The decision flow from the lesson, as short-circuit logic:
def decide(tried_prompting, tried_rag, need_is_behavioral, have_data_and_gpu):
if not tried_prompting:
return "PROMPT first — cheapest, no training"
if not need_is_behavioral:
return "RAG — the need is knowledge, not behavior"
if not tried_rag:
return "Try RAG before tuning — often enough"
if not have_data_and_gpu:
return "Tuning blocked — need clean data + a GPU"
return "FINE-TUNE — cheaper options exhausted, need is behavioral"
print(decide(True, True, True, True)) # FINE-TUNE
print(decide(True, False, False, True)) # RAG
"Should we fine-tune?" is usually "no, not yet." The gate makes you prove the cheaper levers failed before spending training effort.
Context: You tune from either a base model (a raw next-word predictor) or an instruct model (already follows instructions); most applied tunes start from instruct to keep instruction-following for free.
Your task: Write a selector that picks base vs instruct as the starting point from the scale of the behaviour change and the amount of data.
Requirements:
- Pick BASE only for a full behaviour overhaul with a big dataset (must reteach instruction-following)
- Otherwise pick INSTRUCT to inherit chat ability
- Treat instruct as the safe default for applied tuning
- Explain that starting from instruct teaches only the delta
💡 Hint: Base is the exception, not the rule — reserve it for when you have both a large dataset and a reason to relearn instruction-following from scratch.
Show solution
Pick the starting checkpoint from the goal:
def pick_base(want_full_behavior_control, big_dataset, keep_general_chat):
if want_full_behavior_control and big_dataset:
return "BASE — most control, but you must reteach instruction-following"
if keep_general_chat:
return "INSTRUCT — inherit chat ability, add your skill on top"
return "INSTRUCT — the safe default for applied tuning"
print(pick_base(True, True, False)) # BASE
print(pick_base(False, False, True)) # INSTRUCT
Most applied fine-tunes start from an instruct model so you keep general instruction-following for free and only teach the delta. Base models are for large overhauls where you have the data to reteach everything.
Context: A common failure is expecting a tune to inject facts. Tuning reshapes response style; knowledge and freshness are a retrieval problem.
Your task: Classify a set of goals into “tuning can do this” (behaviour) vs “use RAG/tools instead” (knowledge/freshness), and print the verdict for each.
Requirements:
- Label each goal behaviour vs knowledge/fresh (e.g. terse tone = behaviour, today's price = knowledge)
- Print TUNE for behaviour goals and RAG/tools for knowledge goals
- Cover a strict-format goal (behaviour) and a cite-internal-docs goal (knowledge)
- Reinforce that tuning reshapes style, not fact storage
💡 Hint: Ask whether the goal would still be right next month — if the answer moves with the world, it's a retrieval problem, not a tune.
Show solution
Sort goals along the behavior-vs-knowledge line:
goals = [
("adopt a terse legal tone", "behavior"),
("know today's stock price", "knowledge/fresh"),
("always answer in this JSON schema", "behavior"),
("cite our internal wiki accurately", "knowledge"),
("classify tickets into 5 buckets", "behavior"),
]
for goal, kind in goals:
verdict = "TUNE" if kind == "behavior" else "RAG/tools — NOT tuning"
print(f"{goal:38} -> {verdict}")
Tuning reshapes how the model responds; it does not reliably store new or changing facts. Anything about freshness or specific documents is a retrieval problem.
Context: A reproducible training environment is what lets a teammate rerun your fine-tune; the track uses a specific, real Hugging Face stack.
Your task: Show the exact environment the track uses so a teammate can reproduce it, using only the real libraries the lesson names and labelling it as needing a GPU.
Requirements:
- Install
transformers peft trl datasets accelerate bitsandbytes - Include a tiny probe that prints CUDA availability and the device name
- Note each library's role (transformers=models, peft=LoRA, trl=trainers, bitsandbytes=4-bit)
- State that a single consumer GPU suffices for QLoRA on a 7B
- Note that no CUDA means data-prep prototyping only, not training
💡 Hint: The probe just needs to answer “can this box actually train?” — a one-line torch.cuda.is_available() check is enough.
Show solution
The reproducible stack — needs a GPU + these installs:
# A single consumer GPU is enough for QLoRA on a 7B model.
pip install transformers peft trl datasets accelerate bitsandbytes
# Sanity check the GPU is visible before you burn hours on data prep:
python - <<'PY'
import torch
print("CUDA:", torch.cuda.is_available())
print("device:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU only")
PY
transformers (models), peft (LoRA), trl (SFT/DPO trainers), datasets (data), bitsandbytes (4-bit) are the whole stack. If CUDA is False you can still read and prototype data prep, just not train.
Context: When a PM asks “why not just fine-tune?”, the answer is a scorecard, not an opinion — you lead with the cheapest lever that clears the bar.
Your task: Build a scorecard that compares prompt, RAG, and tuning across cost-to-build, cost-to-run, time-to-ship, and fit for a “consistent JSON output” need, then print the recommendation.
Requirements:
- One row per option (prompt / RAG / tune) across the four dimensions
- Print it as an aligned table
- Recommend trying a strict-output PROMPT first (same-day, near-free)
- Reserve tuning for when the prompt drifts under load
- Frame tuning's win as run-cost + reliability, not as the first move
💡 Hint: The recommendation falls out of the table: whichever option ships today for almost nothing goes first, and tuning is the fallback if it drifts.
Show solution
Turn the decision into a scorecard the PM can read:
rows = [
# option, build, run, ship, fits 'strict JSON'?
("prompt", "hours","cheap", "same day","maybe — can drift"),
("RAG", "days", "medium","week", "no — not a knowledge gap"),
("tune", "days", "cheapest","1-2 wk","YES — bakes in the format"),
]
print(f"{'option':7}{'build':7}{'run':9}{'ship':10}fit")
for r in rows:
print(f"{r[0]:7}{r[1]:7}{r[2]:9}{r[3]:10}{r[4]}")
print("\nRECOMMEND: try a strict-output PROMPT first (same day, near-free);")
print("if it drifts under load, TUNE to bake the format in and cut run cost.")
The honest recommendation leads with the cheapest lever (a prompt) and only escalates to tuning if it proves insufficient — tuning wins here on run-cost and reliability, not as a first move.
✓ Checkpoint — you can move on when you can…
- Recall the fine-tune vs prompt vs RAG decision.
- Name the four cases fine-tuning is for.
- Explain why tuning changes behavior but not knowledge.
- Have the training stack installed and GPU verified.
Knowledge check check yourself
Fine-tuning changes a model's behavior but not its knowledge. Given that, why is RAG (not fine-tuning) the right tool when you need the model to answer from new facts?
Show answer
Name the four cases where fine-tuning genuinely beats prompting, and say why tuning wins for one of them.