Fine-Tuning Techniques#

Fine-Tuning vs. RAG Explained

The important question is not “which fine-tuning buzzword should I use?” It is “what minimum change to the model, with what evidence, improves my held-out task?” Start with an adapter. Earn the right to do anything more expensive.

⏱ ~14 min read · ~35 min hands-on 🔗 needs: Fine-Tuning Strategy · Hugging Face Ecosystem · Quantization

The techniques, from least to most invasive#

TechniqueWhat changesBest first useMain risk
Prompt / few-shotno weightsestablish a baselineprompt becomes long/fragile
SFT (supervised fine-tuning)weights learn input → desired output examplesstable formatting, classification, extraction, task behaviourcopies mistakes in labels
LoRAtrains small low-rank adapter matrices; base model stays frozenalmost every first open-model adaptationwrong base model/template still fails
QLoRALoRA while the frozen base model is loaded in 4-bitGPU/RAM-constrained first runhardware/software compatibility and quality trade-off
Preference tuning (DPO etc.)learns chosen output over rejected outputyou can rank alternatives more easily than write a perfect oneunclear/inconsistent preferences
Full fine-tuningupdates all/most base weightsspecialised, well-funded work with strong data/evalscost, catastrophic forgetting, hard rollback
Continued pretraininglearns from raw domain text before task tuninga large, licensed domain corpus materially differs from base knowledgecostly; raw text is often a bad substitute for RAG

For this course, the practical default is instruction-tuned base model + supervised fine-tuning + LoRA, usually with 4-bit QLoRA. It is cheap enough to learn, produces a small adapter to share, and is reversible: remove the adapter to return to the base model.

SFT: teach the output you want to see#

Supervised fine-tuning demonstrates a repeated mapping:

input:  a messy support request
output: a strict JSON triage record with no invented facts

The model is rewarded for the target response tokens. Therefore, the answer column is your most important code. If examples contain unsupported claims, overly long explanations, or inconsistent formatting, you are explicitly training those behaviours.

A small custom task: support-ticket triage#

Use one narrow task rather than “make the model know our company.” Here the model must label a ticket and ask one safe next question.

{"messages":[{"role":"system","content":"Classify the support ticket. Return JSON only with keys priority, category, and next_question. Never invent account details."},{"role":"user","content":"I was charged twice for order 2918 and need help today."},{"role":"assistant","content":"{\"priority\":\"high\",\"category\":\"billing\",\"next_question\":\"What is the email address used for order 2918?\"}"}]}
{"messages":[{"role":"system","content":"Classify the support ticket. Return JSON only with keys priority, category, and next_question. Never invent account details."},{"role":"user","content":"The dashboard font is difficult to read on my phone."},{"role":"assistant","content":"{\"priority\":\"low\",\"category\":\"usability\",\"next_question\":\"Which phone model, browser, and dashboard page are you using?\"}"}]}
{"messages":[{"role":"system","content":"Classify the support ticket. Return JSON only with keys priority, category, and next_question. Never invent account details."},{"role":"user","content":"I cannot log in after changing my password."},{"role":"assistant","content":"{\"priority\":\"medium\",\"category\":\"authentication\",\"next_question\":\"What error message appears after you submit the new password?\"}"}]}

This is a format specimen, not enough training data. Make at least dozens of original, reviewed examples before expecting an adapter to generalise; guided-tool documentation often suggests 100+ for a meaningful first run. Include normal cases, messy wording, missing facts, and cases where the correct answer is to escalate rather than guess.

LoRA without the algebra headache#

Large transformers have huge learned weight matrices. LoRA leaves those base matrices frozen and learns a small update that is added during inference:

original weight W (frozen)
          + low-rank update A × B (trained)
          = adapted behaviour

The rank (r) is the size/capacity of that update. Larger ranks can capture more change but consume more memory and make overfitting easier. lora_alpha scales the update; dropout is a small regulariser. Do not tune all three randomly before you have a baseline.

Useful first-run defaults are intentionally boring:

SettingBeginner starting pointChange it when…
Base modelsmall, instruction-tuned model you can runyour task/eval proves it is too weak
MethodLoRA / QLoRAyou have evidence an adapter cannot meet the goal
Rank r8 or 16quality plateaus with clean, sufficient data
Epochs1–2validation improves without divergence
Learning ratetool’s conservative defaultyou understand loss/validation behaviour
Sequence lengthjust above the longest useful exampleinputs are being truncated
Evaluationheld-out set + task rubricnever omit it to save time

The exact option names vary between Unsloth, TRL, PEFT, and hosted providers. Record the actual values in MLflow rather than relying on memory.

QLoRA: use a quantized base, train an adapter#

QLoRA loads the frozen base model at 4-bit precision and trains LoRA adapters in higher precision. This dramatically lowers memory pressure compared with changing every base weight. It does not mean “train a model using only four bits”; the adapter and computation still need memory.

4-bit frozen base model + trainable LoRA adapter → QLoRA adapter

Read Quantization before using this as a magic “make any model fit” switch. It changes resource needs and can affect quality; it does not fix a bad dataset.

The response-only rule#

For chat SFT, the usual goal is to train on the assistant response, not to train the model to repeat the system prompt and user message. Make sure your trainer’s “train on responses only” / label-masking setting matches your chat template.

Check an actual formatted example before a long run:

<system> Classify the support ticket…
<user> I was charged twice…
<assistant> {"priority":"high", ...}
                         ^ these response tokens should be the training target

If a tuned model parrots the prompt, answers as the user, or writes role labels, suspect template/label masking before changing epochs or rank.

A beginner experiment plan#

Use the custom ticket task above. The Gemma 4 lesson gives the click-by-click Unsloth route; here is the experimental plan that makes any tool useful.

  1. Write 100+ permitted, reviewed ticket → JSON examples. Use one schema and one policy.
  2. Reserve 20% by ticket source/time, not random duplicates, as validation. Keep a final 10–20 case eval unseen.
  3. Test the base model with the exact system prompt. Save outputs and score JSON validity, correct category, safety, cost, and latency.
  4. Fine-tune one LoRA/QLoRA adapter with conservative defaults; train for one epoch first.
  5. Run the adapter on the same final eval. Compare outputs side by side with the base model.
  6. Improve one thing at a time: first labels/coverage, then prompt/template, then data size; only then a hyperparameter.
  7. Keep the best adapter only if it improves the chosen release metric without creating new safety failures.

Improvement levers in the right order#

If you observe…Improve this firstDo not jump straight to…
Invalid JSONprompt/schema examples; include malformed-input casesmore epochs
Wrong category on slang/typosdata coverage and labelslarger base model
Made-up account/order factsexplicit unknown/escalation examples; tool/RAG designtuning more aggressively
Good train results, bad unseen resultsdeduplicate and diversify data; lower epochshigher LoRA rank
Long inputs are cut offsample/sequence length and input designblindly increasing GPU size
No meaningful gain over base promptstop; revisit the task decisionfull fine-tuning

Do not collect or train on private reasoning traces merely to make an answer look intelligent. Train verifiable outputs, concise explanations when needed, and task-specific checks. Evaluate the result, not hidden thought text.

When it fails#

SymptomLikely causeFix
Loss falls but quality does not improveObjective/data does not match the real taskUse task evals and inspect examples, not loss alone.
Validation quality drops after an epochOverfittingStop early; improve/diversify data; lower epochs.
Adapter makes outputs worseWrong chat template, base model, or label maskingVerify one formatted example and test base/adapted model with the same prompt.
CUDA out-of-memoryBase model/context/batch is too largereduce batch/sequence length, use gradient accumulation, QLoRA, or a smaller model.
Model is fluent but unsafeTraining/eval set lacks refusals, uncertainty, or abuse casesadd reviewed negative/edge cases and system safeguards.

Your turn (≈35 min)#

  1. Pick one narrow task and define one machine-checkable property of success (for example, valid JSON with exactly three keys).
  2. Create 20 original examples in the message format. Mark each with a reviewer and source/permission note.
  3. Write five held-out evaluation cases: a normal case, typo/slang, missing information, ambiguous case, and unsafe/escalation case.
  4. Run your base-model prompt on the five cases and save the outputs.
  5. Decide whether LoRA/QLoRA is justified. If yes, follow the next Gemma/Unsloth lesson; if no, improve the prompt/RAG/tool design instead.

Checklist#

  • I can distinguish SFT, LoRA, QLoRA, preference tuning, and full fine-tuning.
  • I know why LoRA is the first adaptation method to try.
  • My custom examples demonstrate the exact behaviour I need.
  • I have response-only training and the right chat template.
  • I use held-out task evaluation, not training loss alone.
  • I improve data and task design before hyperparameter hunting.

Go deeper#