AI EngineeringZero to ProductionHome·About·What’s new·Contact
Specialized Topics · Part T4

Fine-tuning vs RAG vs Prompting

"Should we fine-tune?" is the most common — and most misunderstood — question in applied LLMs. The answer is usually no, not yet. This part gives you the decision framework: what each technique actually changes, when each wins, how LoRA makes fine-tuning cheap, and how to prepare data — so you pick deliberately instead of reaching for the most expensive option first.

⏱️ ~1.5 hours🧪 3 labs🎯 Beginner→Tech-lead

Learning objectives

  • State the fine-tune vs RAG vs prompt decision clearly.
  • Recognize the cases where each wins.
  • Estimate the cost/effort of fine-tuning honestly.
  • Make and defend the build decision as a lead.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/xt4-finetuning/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

1 · The most misunderstood question essential

Picture the meeting. A stakeholder watches the demo, sees the model get one answer slightly wrong, and says the words that have burned more LLM budgets than any other: "Can we just fine-tune it on our data?" It sounds decisive and technical, so it usually wins the room. Three weeks and a five-figure GPU bill later, the team has a tuned model that is worse at the actual problem — because the problem was never a behavior problem in the first place. Almost everyone reaches for fine-tuning too early, and almost everyone regrets it.

Here's the analogy that untangles it. Think of a talented new hire on their first day. There are three completely different ways to make them more useful, and they are not interchangeable. You can give clearer instructions ("format every report like this, always cite your source") — that's prompting, and it's instant and free. You can hand them the company binder to look things up while they work ("here are our current prices, policies, and docs") — that's RAG, and it's how you supply facts they couldn't possibly have memorized. Or you can send them to a months-long training course to permanently rewire how they operate — that's fine-tuning, expensive and slow, and worth it only for a durable change in how they work, never for information that changes weekly.

The precise version: prompting and RAG change what the model sees at request time (its context), while fine-tuning changes the model's weights — its ingrained behavior. That distinction is the entire lesson. Fine-tuning is genuinely good at teaching a consistent style, a strict output format, or a narrow skill. It is genuinely bad at teaching facts — bake yesterday's price list into the weights and it's stale tomorrow, un-updatable without retraining, and prone to being confidently recited even when wrong. The common mistake, stated plainly: treating "the model doesn't know X" as a fine-tuning problem. Missing knowledge is a retrieval problem. The framework below is a staircase you climb one rung at a time, stopping the moment the output is good enough — and most real problems are solved on the bottom two rungs, long before you ever touch a GPU.

"Should we fine-tune?" is usually answered no, not yet. Prompting and RAG solve most problems faster and cheaper. Fine-tuning changes behavior (style, format, a narrow skill) — it's a poor way to add knowledge (that's RAG). Match the tool to the problem.

Need better output cheapest Prompt engineering add facts + RAG (knowledge) last resort Fine-tune (behavior) style/format/skill
🗺️ How to read this diagram

This is the whole lesson in one row of boxes: when your LLM output isn't good enough, you climb these rungs left to right, cheapest first, and you stop as soon as the output is good. Most real problems never reach the last box.

  • Need better output (leftmost) is the starting point — you have a task and the model's answers aren't right yet. The label cheapest is a reminder: always begin at the cheap end.
  • Prompt engineering is the first thing to try: rewrite the instructions, add examples, set the format. It's free, instant to change, and fixes a surprising amount. The arrow means "if that wasn't enough, move right."
  • + RAG (knowledge) is next: when the model is missing facts (your docs, product details, fresh data), you fetch the right text and paste it into the prompt. RAG adds knowledge, not new behavior.
  • Fine-tune (behavior) is the last, most expensive rung — labeled last resort. You only reach it when you need a consistent style, a strict format, or a narrow skill that prompting and RAG couldn't deliver. Note it changes behavior, not knowledge.

In short: Read the boxes as a staircase you climb only when forced. The single most common mistake in applied LLMs is jumping straight to the far-right box (fine-tuning) before trying the two cheap ones on its left.

2 · What each technique changes essential

Here's a failure you can feel. A team wanted their support bot to answer in the company's warm, on-brand voice and quote the current refund policy. They fine-tuned on a few hundred old support transcripts, hoping to get both at once. The result was uncanny: the bot sounded perfectly on-brand — and cheerfully quoted the refund window from last year, because that was the policy baked into the transcripts it trained on. They'd used one tool to solve two problems that live in different places, and got the voice right while getting the facts wrong. The fix wasn't a better tune; it was realizing style and knowledge are separate levers.

The clean way to hold this in your head is to ask where each technique makes its change. Prompting and RAG both operate in the context window — the text the model reads fresh on every single request. Prompting puts instructions there ("be concise, answer in JSON"); RAG puts knowledge there (retrieved passages from your docs). Because both live in the prompt, they're editable in seconds, they update the instant your data changes, and nothing is permanent. Fine-tuning is the odd one out: it changes the weights themselves, so the change is baked in and travels with the model on every call without costing you any context tokens.

That "where" tells you almost everything about the trade-offs, and it's why the three map to non-overlapping jobs:

The common mistake this table exists to prevent: assuming the three compete. They don't — a serious production system usually uses all three at once (a fine-tuned small model, fed retrieved facts, steered by a careful prompt). The skill is matching each job to the lever that actually moves it, and never paying for fine-tuning to do a job prompting or RAG already does for free.

edited per request · instant, temporary Context window what the model reads Prompting → instructions "be concise, answer in JSON" RAG → retrieved facts passages fetched from your docs baked in · slow, permanent The model's weights Fine-tuning → weights changes ingrained behavior; costs no context tokens editable in seconds re-train to change the "where" of the change decides every trade-off Two techniques edit the prompt; one edits the model. Prompting and RAG both write into the context window the model reads fresh each request — so they update instantly. Fine-tuning rewrites the weights, so the change is permanent and travels on every call.
🗺️ How to read this diagram

Read it as two containers side by side: the left box is the context window (rewritten every request), the right box is the model's weights (changed only by training).

  • The left blue box holds the two cheap levers. The green band is prompting (instructions you type in) and the amber band is RAG (facts fetched into the prompt). Both live in the text the model reads.
  • The right purple box is fine-tuning — a change to the weights themselves. It sits outside the context window because it costs no prompt tokens and rides along on every call.
  • The arrows and labels underneath are the payoff: the left side is "editable in seconds," the right side needs a full re-train. That single difference — where the change happens — is what drives every trade-off in the table below.

In short: Prompting and RAG are edits to what the model reads; fine-tuning is an edit to what the model is — instant-and-temporary versus baked-in-and-slow.

TechniqueChangesBest for
Promptinginstructions in-contextmost tasks; instant iteration
RAGknowledge in-contextfacts, docs, freshness, citations
Fine-tuningthe model's behaviorconsistent style/format, narrow skill, latency

3 · Intermediate — the four cases fine-tuning wins intermediate

By now the drumbeat has been "don't tune." So it's fair to ask the honest question: is fine-tuning ever the right answer, or is this whole chapter just talking you out of it? It is absolutely the right answer — but only in a short, specific list of situations. The trap isn't fine-tuning itself; it's fine-tuning for a problem that isn't on the list. So instead of a vague "it depends," here are the four concrete cases where tuning genuinely earns its cost, each with the tell-tale sign you're actually in it.

The unifying pattern — and the mistake it guards against: every winning case is about behavior (how the model acts) or economics (cost/latency at scale), never about knowledge (what it knows). The moment you catch yourself justifying a tune with "so it knows our...", stop — that's the RAG problem wearing a fine-tuning costume. And notice all four also assume you've already hit a wall with prompting and RAG. If none of these four cases describes your situation, the disciplined answer is simply: don't tune. The little decision helper below turns exactly this reasoning into code you can run.

Tune when: a consistent style/voice is needed, a strict output format must be reliable, a small tuned model can replace a big prompted one (latency/cost), or a narrow task where a specialized model beats a general one. If none apply, don't tune.

fine-tune when it's about behavior or economics Consistent style / voice show > describe the rule Strict output format bad output breaks systems Small model replaces big cut cost/latency at scale Narrow, specialized task many clean labeled examples unifying pattern: behavior or economics — never knowledge "so it knows our facts…" a RAG problem the trap → not a tune if none of the four green cases fits — the disciplined answer is: don't tune Four green lights, one red trap. Every case where tuning wins is about behavior or economics; the moment your reason is "so it knows our…", you've drifted into the RAG problem — stop.
🗺️ How to read this diagram

Scan the four green boxes first — those are the situations where a tune earns its cost — then look right to the red box, which is the one reason that is never a tuning job.

  • The four green boxes are the winning cases: a consistent voice, a strict format, a small model replacing a big one, and a narrow specialized task. Each carries its tell-tale sign in the smaller line beneath it.
  • The grey band underneath names what all four share — they are about behavior (how the model acts) or economics (cost and latency), never about knowledge.
  • The red box on the right, with the arrow pointing into it, is the trap: "so it knows our facts" feels like a tuning reason but is really a RAG problem. If none of the four green cases fits your situation, the answer is simply don't tune.

In short: Match your task to one of the four green boxes or don't tune — and if your justification is about facts, that red box says it belongs to RAG.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Python · should you fine-tune? (runs)
decision.pydef should_finetune(needs_facts, needs_style, needs_strict_format,
                    latency_critical, prompt_rag_tried):
    if not prompt_rag_tried:
        return "NO — try prompting + RAG first"
    if needs_facts and not (needs_style or needs_strict_format):
        return "NO — that's a RAG problem, not fine-tuning"
    if needs_style or needs_strict_format or latency_critical:
        return "MAYBE — a real fine-tuning case; measure vs prompt+RAG first"
    return "NO — no clear behavior change needed"

print(should_finetune(True, False, False, False, True))     # facts -> RAG
print(should_finetune(False, True, True, True, True))        # style+format+latency -> maybe
print(should_finetune(False, False, False, False, False))    # haven't tried basics
NO — that's a RAG problem, not fine-tuning
MAYBE — a real fine-tuning case; measure vs prompt+RAG first
NO — try prompting + RAG first
▶ How this works

This little function turns the whole "should we fine-tune?" debate into a set of plain if-checks. You feed it yes/no answers about your task, and it tells you NO, MAYBE, or points you at the cheaper tool instead. Read the checks top to bottom — the first one that matches wins and returns.

  1. The function takes five true/false inputs describing your task: does it need facts, a consistent style, a strict format, low latency, and — crucially — whether you've already tried prompting + RAG.
  2. The first check is the gatekeeper: if not prompt_rag_tried means "you haven't even tried the cheap options yet" — so it immediately returns NO, try prompting + RAG first. This alone stops most premature fine-tuning.
  3. The second check catches the classic mistake: if you only need facts (and not style or format), that's a knowledge problem — the comment # facts -> RAG flags it. Fine-tuning is the wrong tool; use RAG.
  4. The third check is the one real green light: if you genuinely need style, a strict format, or speed, it returns MAYBE — a real fine-tuning case, and even then tells you to measure against prompt+RAG first. If nothing matched, the final line returns NO — no clear behavior change needed.

What the output means: The three print calls run three example tasks. You see NO — that's a RAG problem… (facts only), then MAYBE — a real fine-tuning case… (style + format + latency), then NO — try prompting + RAG first (basics not tried). Notice the order of outputs matches the order the checks fire, not the order of the calls.

Try this: Change the first call's last argument from True to False (pretend you skipped prompt+RAG) and predict the answer before running — the gatekeeper check should flip it to NO, try prompting + RAG first, no matter what the other flags say.

4 · Advanced — the true cost of fine-tuning advanced

Here's the estimate that quietly kills projects. Someone prices fine-tuning by the one number that's easy to look up — the GPU rental — sees "$12 of compute for a training run," and greenlights it. What they didn't price is the part that doesn't show up on any invoice: the days of human effort spent building the dataset. So the project starts, the "quick" tune stalls at 200 hand-labeled examples out of the 1,000 it actually needs, everyone loses momentum, and it's abandoned half-done. The compute was never the cost. The data was the cost, and nobody budgeted for it.

The analogy: fine-tuning is like publishing a book. The printing (the GPU run) is fast and cheap and the only visible line item. The writing and editing — the manuscript itself — is where all the real months go. With a tune, that manuscript is your dataset: hundreds or thousands of clean, correctly-labeled, deduplicated examples of exactly the behavior you want. Each one is largely handcrafted or carefully curated, and quality matters more than quantity because the model will faithfully imitate whatever you feed it, including your mistakes.

Precisely, a fine-tune has four cost buckets, and they are wildly lopsided:

The mistake this section prevents: pricing only the compute. The lab below makes the lopsidedness undeniable — plug in realistic numbers and watch data prep come out around 70% of the total effort, with the GPU run a rounding error beside it. Internalize that ratio and a lot of premature-tuning decisions answer themselves: if you can't commit to producing quality data, you can't fine-tune well, so the honest move is prompt + RAG.

Fine-tuning isn't just a training run. The real cost: dataset creation (the dominant effort), training compute, evaluation, and ongoing maintenance (re-tune when the base model or requirements change). Estimate it before committing.

effort by bucket, roughly to scale (1000 examples) Dataset creation ~71% · days Evaluation ~19% Training compute (GPU) ~10% · hours Maintenance recurring · re-tune on drift/upgrade the visible line item (GPU) is the smallest bar; the data bar is the project The cost is the data, not the GPU. Drawn to scale, dataset creation dwarfs the training run everyone prices by — and maintenance is a bar that never goes away because you re-tune on every drift or base-model upgrade.
🗺️ How to read this diagram

Read it as a bar chart, top to bottom: each row is one cost bucket, and the length of the bar is how much of the total effort it eats.

  • The top red bar — dataset creation — is by far the longest: sourcing, labeling, cleaning, and deduplicating examples, measured in days-to-weeks. It is the ~71% the cost lab computes.
  • The amber evaluation bar and the short blue GPU bar come next. Notice the training compute — the one number that's easy to look up and the one people price the project by — is the smallest bar.
  • The purple maintenance bar is the one everyone forgets: it's a recurring cost, redrawn every time the base model upgrades or the data drifts, so a tune is a liability you keep paying for.

In short: The bar you can invoice for (GPU) is a sliver; the bar that decides whether you should tune at all (data) is the whole chart.

Python · estimate fine-tuning effort (runs)
cost.pydef finetune_effort(examples_needed, hours_per_100_examples, gpu_hours, eval_hours):
    data_hours = examples_needed / 100 * hours_per_100_examples
    total = data_hours + gpu_hours + eval_hours
    return {"data_prep_hours": round(data_hours), "total_hours": round(total),
            "data_is_pct": round(100*data_hours/total)}

print(finetune_effort(examples_needed=1000, hours_per_100_examples=3,
                      gpu_hours=4, eval_hours=8))
{'data_prep_hours': 30, 'total_hours': 42, 'data_is_pct': 71}
▶ How this works

Fine-tuning feels like "just run a training command," but the real work is building the dataset. This function adds up where your hours actually go so you can see that truth in numbers before you commit.

  1. It takes four honest estimates: how many training examples you need, how many hours it takes to hand-build 100 examples, the GPU hours for the training run, and the eval hours to check the result.
  2. data_hours is the big one: examples_needed / 100 * hours_per_100_examples scales your per-100 labeling rate up to the full dataset. This is usually the dominant cost.
  3. total sums data + GPU + eval. Then round(100*data_hours/total) computes what percent of the whole effort is just data prep — the number that makes the point.
  4. It returns a small dictionary of three figures so the caller can print them clearly, rather than one buried number.

What the output means: For 1000 examples at 3 hours per 100, the result is {'data_prep_hours': 30, 'total_hours': 42, 'data_is_pct': 71}. The headline: 71% of the effort is building the dataset — the training run (4 GPU hours) is the small part. That's why the warning below says data prep is most of the work.

Try this: Bump examples_needed to 3000 and re-run. Watch data_is_pct climb even higher — more scale means data prep dominates even more, which is exactly why teams underestimate fine-tuning.

Data prep is most of the workThe training command is minutes; building a clean, labeled dataset is days. Teams underestimate this and abandon half-done tunes. If you can't commit to quality data, you can't fine-tune well — do prompt+RAG instead. (The hands-on how is the Fine-tuning track.)

5 · Professional — measure tuned vs baseline professional

You spent the two weeks. You built the dataset, ran the tune, and the model "feels" better in a handful of hand-picked examples. The temptation now is enormous: ship it, because you've paid for it and it looks good. This is exactly where careful teams and sloppy teams part ways. "Feels better" on a few cherry-picked prompts is not evidence — it's the sunk-cost fallacy dressed up as a demo. The professional discipline is brutally simple: a tune ships only if it beats the cheaper prompt+RAG baseline on a real, held-out eval set, by a margin big enough to be worth its ongoing maintenance.

Think of it like a drug trial. Nobody approves a new medication because a few patients said they felt great; you run it against a control group and demand a statistically real improvement, because the new drug also carries new costs and risks. Your fine-tune is the new drug; prompt+RAG is the control group; your eval set is the trial. And there are two numbers to watch, not one — because a tune can help your target task while quietly hurting everything else:

The mistake this guards against is shipping on faith and discovering the regression in production, where it's expensive and embarrassing. Gate the decision on measured evidence before the model reaches users. If the tuned model doesn't clear the margin, the correct — and senior — outcome is to keep the cheaper baseline and treat the tune as a useful experiment that answered "no." The eval-gated comparison from Ch 5 is the decision-maker here; the tune's good feeling in a demo is not.

A fine-tune is only justified if it beats prompt+RAG on your evals — target metric up, general capability not regressed. Never ship a tune on faith; the eval-gated comparison (Ch 5) is the decision-maker.

Prompt+RAG the control group Tuned model the new drug Held-out eval set two numbers to watch target metric ↑ past margin general skills not regressed both true → ship the tune either fails → keep the cheaper baseline A tune ships only if it passes a controlled trial. The tuned model and prompt+RAG both run the same held-out eval; the tune ships only when the target metric clears the margin and general capability hasn't regressed.
🗺️ How to read this diagram

Follow it left to right like a drug trial: two candidates on the left feed into one eval set in the middle, which produces two pass/fail checks on the right.

  • The two left boxes are the contenders — prompt+RAG (the control group) and the tuned model (the new drug). Both are pushed through the same held-out eval so the comparison is fair.
  • The middle box is that eval set, and the two arrows out of it are the two numbers you must watch, not one.
  • The two green boxes are the gates: the target metric must rise past a real margin, and general capability must not regress (catastrophic forgetting). Only when both are true does the amber box below say ship; if either fails, you keep the cheaper baseline.

In short: Treat the tune as a drug on trial against a control — it ships only on measured evidence that it wins on the target task without breaking everything else.

6 · Tech-lead — the build decision tech-lead

Everything so far has been analysis. This is the part where someone has to own the answer — and put their name on it in front of a room that already wants to fine-tune. That's the lead's job, and it's harder than the engineering, because the pressure runs the wrong way. A stakeholder has heard "fine-tuning" is what serious AI teams do; a keen engineer wants the resume line; a vendor is happy to sell you the GPUs. The path of least resistance is to say yes. The lead's real value is being the person who can say "no, not yet" and defend it with the framework, not with a gut feeling.

The analogy is any senior technical decision: choosing to not build the microservice, not adopt the shiny database, not rewrite the legacy system. Juniors demonstrate competence by building things; seniors demonstrate judgment by knowing which things not to build, and being able to explain why so clearly that the room agrees. Talking a team out of a premature fine-tune — and being right — is precisely that senior move.

Concretely, owning this call means running a short, defensible checklist rather than a vibe:

The mistake a lead exists to prevent is the organization spending weeks and real money to fine-tune its way backwards — a staler, costlier, harder-to-maintain system than the prompt+RAG setup it replaced. Most of the time, disciplined analysis lands on "prompt + RAG," and saying so — clearly, early, with the numbers to back it — is not the timid choice. It's the expert one.

A lead owns the fine-tune vs RAG vs prompt call: run the decision framework, estimate cost honestly, require a measured win over the cheaper alternatives, and account for maintenance. Most of the time the disciplined answer is "prompt + RAG" — and saying no to a premature fine-tune is a senior move.

"Let's fine-tune" room pressure 1 · Which of four cases? (else RAG) 2 · True cost — data, not just GPU 3 · Measured win over prompt+RAG 4 · Account for maintenance four gates the lead runs out loud prompt + RAG fine-tune rare most calls exit here Judgment is a checklist, not a vibe. The lead runs the pressure to tune through four gates; most well-analyzed calls exit as prompt+RAG, and reaching the fine-tune box is the rare, defended exception.
🗺️ How to read this diagram

Follow the flow left to right: the pressure to fine-tune enters on the left, passes through the lead's four checks in the middle, and lands on one of two outcomes on the right.

  • The amber box on the left is the room's default push — "let's fine-tune." That's the input the lead has to filter, not accept.
  • The four blue boxes are the checklist run out loud: name the winning case (or redirect to RAG), price the true cost including data prep, demand a measured win, and own the recurring maintenance.
  • The two right boxes are the outcomes. The green prompt+RAG box is where most calls exit — the thick, common path; the purple fine-tune box is the rare exception you only reach when all four gates say yes.

In short: The lead's value is running the four gates so the loud "let's fine-tune" usually and correctly resolves to the cheaper prompt+RAG.

The hands-on fine-tuning track goes deeperThis chapter is the decision; the Fine-tuning track (FT1–FT6) is the hands-on how — LoRA/QLoRA, datasets, DPO, serving. Decide here, build there.

Exercise XT4.1 — Make the call

Context: The whole lesson is a decision, not a training run. Applying the decision and effort helpers to a real task you're tempted to tune is where it becomes concrete.

Your task: Take a real task you're tempted to fine-tune, run it through the decision helper, estimate the effort, and state what you'd try with prompt engineering plus RAG first.

Requirements:

  • Describe a genuine task and which of the four cases (facts/style/format/volume) it hits
  • Run the decision helper and record its recommendation
  • Estimate the effort with the cost helper for your example count
  • Write down a prompt + RAG plan you'd try before tuning
  • If none of the four cases apply, state plainly why you would not tune it — that's the lesson

💡 Hint: "No, not yet" is a valid and common outcome — the exercise is as much about ruling tuning out as choosing it.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · The decision: should you fine-tune? (offline)Beginner

Context: Fine-tuning is usually the wrong first move. It teaches behavior — style, format, lower per-call cost at scale — but it does not add fresh facts; that's what RAG is for.

Your task: Reproduce the lesson's decision helper should_finetune(...) that returns a recommendation based on whether you need facts, style, strict format, or high volume.

Requirements:

  • If the need is fresh facts, recommend RAG, not fine-tuning
  • If the need is style, strict format, or high volume, recommend trying prompt engineering first and tuning only if it plateaus
  • Otherwise recommend prompt engineering as likely sufficient
  • Stdlib only; take the four needs as boolean parameters
  • Show a facts case returning "use RAG" and a style/format/volume case returning "maybe"

💡 Hint: Order the checks so the facts case short-circuits first — it's the one hard "no" regardless of the other flags.

Show solution

Runnable, stdlib only:

def should_finetune(needs_facts, needs_style, needs_strict_format, high_volume):
    if needs_facts:
        return "No -- use RAG. Fine-tuning teaches behavior, not fresh facts."
    if needs_style or needs_strict_format or high_volume:
        return "Maybe -- try prompt engineering first; fine-tune if it plateaus."
    return "No -- prompt engineering is likely enough."

print(should_finetune(needs_facts=True,  needs_style=False,
                      needs_strict_format=False, high_volume=False))
print(should_finetune(needs_facts=False, needs_style=True,
                      needs_strict_format=True,  high_volume=True))

The usual answer is "no, not yet": prompt engineering is free and instant; reach for tuning only when it plateaus on style/format/cost — never to inject knowledge.

Exercise 2 · Estimate the true effort — data prep dominates (offline)Intermediate

Context: Teams underestimate tuning because they picture the GPU run, not the labeling. In reality data prep dominates the effort — the training run is the small part.

Your task: Reproduce finetune_effort(...) and report the total hours and the percentage that is data prep for 1000 examples at 3 hours per 100.

Requirements:

  • Compute data-prep hours as examples/100 × hours-per-100
  • Total = data-prep + GPU hours + eval hours
  • Return data-prep hours, total hours, and data-prep as a percentage of total
  • Stdlib only; round the reported numbers
  • Show that for the given inputs data prep is the large majority of the effort

💡 Hint: The interesting output is the percentage — it makes the point that labeling, not the GPU run, is where the time goes.

Show solution

Runnable, stdlib only:

def finetune_effort(examples_needed, hours_per_100_examples, gpu_hours, eval_hours):
    data_hours = examples_needed / 100 * hours_per_100_examples
    total = data_hours + gpu_hours + eval_hours
    return {"data_prep_hours": round(data_hours),
            "total_hours": round(total),
            "data_is_pct": round(data_hours / total * 100)}

print(finetune_effort(examples_needed=1000, hours_per_100_examples=3,
                      gpu_hours=4, eval_hours=8))
# {'data_prep_hours': 30, 'total_hours': 42, 'data_is_pct': 71}

71% of the effort is building the dataset; the GPU run is the small part. Teams underestimate tuning because they picture the training run, not the labeling.

Exercise 3 · Why LoRA is cheap: count trainable parameters (offline)Advanced

Context: LoRA is cheap because it freezes the base weights and trains small low-rank adapters instead of the full matrix. Counting the trainable parameters shows just how small that is.

Your task: Write lora_params(d_out, d_in, r) comparing full fine-tuning (d_out×d_in) against LoRA (r×(d_out+d_in)) and compute the ratio for a 4096×4096 layer at r=8.

Requirements:

  • Full fine-tuning parameter count is d_out × d_in
  • LoRA parameter count is r × (d_out + d_in) (the two adapter matrices)
  • Return full count, LoRA count, and LoRA as a percentage of full
  • Stdlib only; format the counts readably
  • Show LoRA is well under 1% of the layer's parameters at r=8
  • Connect the tiny count to fitting on modest GPUs and producing swappable adapter files

💡 Hint: The ratio collapses to roughly r×(d_out+d_in) over d_out×d_in — a fraction of a percent for a square layer at small rank.

Show solution

Runnable, stdlib only:

def lora_params(d_out, d_in, r):
    full = d_out * d_in
    lora = r * (d_out + d_in)          # A: (r x d_in) + B: (d_out x r)
    return full, lora, round(lora / full * 100, 3)

full, lora, pct = lora_params(4096, 4096, r=8)
print(f"full={full:,}  lora={lora:,}  lora is {pct}% of full")
# full=16,777,216  lora=65,536  lora is 0.391% of full

LoRA trains <0.5% of the parameters of that layer, which is why it fits on modest GPUs and produces tiny, swappable adapter files instead of a full model copy.

Exercise 4 · Measure tuned vs baseline before you trust it (offline)Expert

Context: Never ship a tuned model on vibes. A tuned model must beat the prompted baseline by a real margin to justify its ongoing retraining, maintenance, and serving cost.

Your task: Given a labeled eval set and predictions from a baseline and a tuned model, compute each model's accuracy and only recommend the tuned model if it clears the baseline by a set margin. Runnable offline.

Requirements:

  • Write an accuracy(preds, gold) over paired predictions and labels
  • Compute accuracy for both the baseline and the tuned predictions
  • Compare against a required margin (e.g. 5 points) before recommending the tuned model
  • Stdlib only; print both accuracies and the decision
  • Recommend keeping the cheaper baseline when the gain doesn't clear the margin

💡 Hint: Gate the recommendation on tuned - baseline >= MARGIN, not on the tuned model merely being higher.

Show solution

Runnable, stdlib only:

def accuracy(preds, gold):
    return sum(p == g for p, g in zip(preds, gold)) / len(gold)

gold     = ["refund", "refund", "cancel", "cancel", "other", "refund"]
baseline = ["refund", "cancel", "cancel", "cancel", "refund", "refund"]
tuned    = ["refund", "refund", "cancel", "cancel", "other", "refund"]

a_base = accuracy(baseline, gold)     # 0.666...
a_tuned = accuracy(tuned, gold)       # 1.0
MARGIN = 0.05
print(round(a_base, 3), round(a_tuned, 3))
if a_tuned - a_base >= MARGIN:
    print("ship tuned: +{:.0%} over baseline".format(a_tuned - a_base))
else:
    print("keep baseline: gain not worth the maintenance cost")

A tuned model must clear a real margin (here 5 points) to justify its ongoing maintenance, retraining, and serving cost — otherwise the cheaper prompted baseline wins.

Exercise 5 · Format a supervised fine-tuning dataset + validate it (offline)Professional

Context: Most of fine-tuning is the data. Trainers consume clean chat-formatted JSONL, and duplicate or empty examples quietly leak into eval and inflate accuracy.

Your task: Write a validator to_sft_records(pairs) that turns raw (prompt, completion) pairs into chat-style JSONL records, rejecting empty examples and dropping duplicates. Runnable offline with stdlib json.

Requirements:

  • Raise on any pair with an empty prompt or completion
  • Deduplicate on the (stripped prompt, stripped completion) key
  • Emit each record as a chat message list with a user and an assistant turn
  • Strip whitespace on the stored content
  • Show a duplicate being dropped (three pairs in, two records out) and print one record as a JSONL line
  • Explain why dedupe and non-empty checks matter for honest eval metrics

💡 Hint: Track a seen set of the stripped pair so the second copy is skipped before it becomes a record.

Show solution

Runnable, stdlib only:

import json

def to_sft_records(pairs):
    records, seen = [], set()
    for prompt, completion in pairs:
        if not prompt.strip() or not completion.strip():
            raise ValueError("empty prompt or completion")
        key = (prompt.strip(), completion.strip())
        if key in seen:                      # dedupe: leaked dupes inflate metrics
            continue
        seen.add(key)
        records.append({"messages": [
            {"role": "user", "content": prompt.strip()},
            {"role": "assistant", "content": completion.strip()},
        ]})
    return records

pairs = [("Classify: love it", "positive"),
         ("Classify: love it", "positive"),   # duplicate -> dropped
         ("Classify: hate it", "negative")]
recs = to_sft_records(pairs)
print(len(recs))                              # 2
print(json.dumps(recs[0]))                    # one JSONL line

Deduping and non-empty checks matter: duplicate examples leak into eval and inflate accuracy; clean chat-formatted JSONL is exactly what SFT/LoRA trainers consume.

Exercise 6 · Run a real LoRA fine-tune (needs GPU + libs)Industry scenario

Context: Once you've decided tuning is worth it and prepared the data, the actual LoRA run is a standard PEFT skeleton. It needs a GPU and the libraries; the decision, data, and eval rungs above are the runnable offline part.

Your task: Write the training setup with transformers + peft to LoRA-tune a base model. Needs a GPU and the libraries (transformers, peft, datasets).

Requirements:

  • Load a base causal-LM model and tokenizer
  • Build a LoraConfig (rank, alpha, dropout, target modules, causal task type) and wrap the model with get_peft_model
  • Print trainable parameters to confirm well under 1% — matching the LoRA math
  • Load the prepared JSONL dataset and train with an SFT trainer and standard TrainingArguments
  • Save only the adapter (megabytes), not a full model copy
  • Frame it as: decide with the offline rungs first, then spend the GPU hours

💡 Hint: The saved artifact is just the small adapter, which is exactly why LoRA is cheap to store, swap, and serve.

Show solution

Needs a GPU and libraries (transformers, peft, trl/datasets). Standard PEFT-LoRA skeleton — no invented APIs:

from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
from datasets import load_dataset

base = "meta-llama/Llama-3.2-1B"
tok = AutoTokenizer.from_pretrained(base)
model = AutoModelForCausalLM.from_pretrained(base, device_map="auto")

lora = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.05,
                  target_modules=["q_proj", "v_proj"], task_type="CAUSAL_LM")
model = get_peft_model(model, lora)
model.print_trainable_parameters()      # <1% trainable -- matches the LoRA math above

ds = load_dataset("json", data_files="train.jsonl", split="train")
trainer = SFTTrainer(
    model=model, tokenizer=tok, train_dataset=ds,
    args=TrainingArguments(output_dir="out", num_train_epochs=1,
                           per_device_train_batch_size=4, learning_rate=2e-4),
)
trainer.train()
model.save_pretrained("adapter")        # tiny adapter, not a full model copy

The saved artifact is just the LoRA adapter (megabytes), which is why it's cheap to store, swap, and serve. Decide with the offline rungs above; only then spend the GPU hours here.

✓ Checkpoint — you can move on when you can…

  • State the fine-tune vs RAG vs prompt decision.
  • Recognize the four cases tuning wins.
  • Estimate fine-tuning's true cost (data dominates).
  • Require a measured win; own the build decision.

Knowledge check check yourself

✓ Knowledge check

The lesson's decision framework climbs prompting → RAG → fine-tuning. Why is fine-tuning usually the wrong answer when you need to add knowledge?

Show answer
Fine-tuning changes the model's behavior (style, format, a narrow skill), not what it knows — it's a poor way to add facts. Missing knowledge (your docs, product details, fresh data) is a RAG problem: you retrieve the right text into the prompt. Prompting and RAG are cheaper and solve most problems, so fine-tuning is the last-resort rung you reach only when you need consistent behavior.
✓ Knowledge check

The cost lab shows data prep as ~71% of fine-tuning effort. Why does the lesson stress this, and what does it imply about when you can fine-tune well?

Show answer
Because fine-tuning feels like "just run a training command," but the training run is minutes while building a clean, labeled dataset is days — data prep is the dominant, easily-underestimated cost, and it climbs further as example count grows. The implication: if you can't commit to producing quality data, you can't fine-tune well, so do prompt+RAG instead.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in