Frontend Labs
⋯

Chapter 42 — Project 2: fine-tune a real model with LoRA

Why fine-tune instead of pretrain

Pretraining taught the model language. Fine-tuning teaches it your task, format, or style — and needs a few thousand examples instead of trillions.

Fine-tuning is good for: output format, tone/persona, a domain's vocabulary and conventions, task-specific behaviour, and making a small model do one job as well as a big general model.

Fine-tuning is bad for: injecting facts (unreliable; use RAG — Chapter 47) and anything that changes weekly.

What LoRA is

Full fine-tuning updates all 8 billion parameters — needing many times the model size in GPU memory. LoRA (Low-Rank Adaptation) freezes the original weights and trains a small "patch" beside them:

   original weight matrix W  (frozen, e.g. 4096 x 4096 = 16.7M numbers)
                +
   a small update:  B x A    where A is 8 x 4096 and B is 4096 x 8
                             = only 65,536 numbers  (0.4% as many!)

   effective weight = W + (B x A)

Consequences:

  • Trains on far less memory. With QLoRA, short sequence lengths, and the right runtime, many 7B-class models can be fine-tuned on a single consumer or free Colab-style GPU; plain LoRA for 7B still depends heavily on GPU memory.
  • The saved adapter is ~10–100 MB instead of 14 GB.
  • You can keep many adapters for one base model and swap them per task.
  • Quality is close to full fine-tuning for most instruction/style tasks.

QLoRA goes further: it loads the frozen base model in 4-bit precision, letting you fine-tune much larger models on one consumer GPU.

Step 1 — Prepare your data

The format is a list of conversations. Aim for 500–5,000 high-quality examples — quality and diversity matter far more than volume.

# make_data.py
import json

examples = [
    {"messages": [
        {"role": "user", "content": "How many holiday days do I get?"},
        {"role": "assistant", "content": "You receive 25 days of paid holiday per calendar year, plus public holidays."}]},
    {"messages": [
        {"role": "user", "content": "What's the parental leave policy?"},
        {"role": "assistant", "content": "Primary carers receive 26 weeks at full pay. Secondary carers receive 6 weeks at full pay."}]},
    # ... hundreds more, covering the variety of real questions
]

with open("train.jsonl", "w") as f:
    for ex in examples:
        f.write(json.dumps(ex) + "\n")

Data quality checklist:

  • Cover the variety of phrasings real users produce (remember your Chapter 4 list).
  • Include the edge cases and the "I don't know" responses you want.
  • Be consistent in format — the model learns your format precisely, including your mistakes.
  • Hold out ~10% as a validation set.
  • Deduplicate.

Step 2 — Fine-tune

Run this on a free Google Colab GPU (Runtime → Change runtime type → T4 GPU).

# pip install -U transformers peft trl datasets accelerate bitsandbytes

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

BASE = "Qwen/Qwen2.5-0.5B-Instruct"       # start small; scale up once it works

tok = AutoTokenizer.from_pretrained(BASE)
model = AutoModelForCausalLM.from_pretrained(BASE, device_map="auto")

dataset = load_dataset("json", data_files="train.jsonl", split="train")

peft_config = LoraConfig(
    r=16,                  # rank: how big the patch is (8-64 typical)
    lora_alpha=32,         # scaling, usually 2 x r
    lora_dropout=0.05,
    task_type="CAUSAL_LM",
    target_modules=["q_proj","k_proj","v_proj","o_proj",
                    "gate_proj","up_proj","down_proj"],
)

trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    peft_config=peft_config,
    args=SFTConfig(
        output_dir="./out",
        num_train_epochs=3,             # 1-3 for fine-tuning; more overfits
        per_device_train_batch_size=4,
        gradient_accumulation_steps=4,  # effective batch = 16
        learning_rate=2e-4,             # LoRA likes higher LR than full FT
        logging_steps=10,
        save_strategy="epoch",
        fp16=True,                      # T4-class Colab GPUs usually support fp16, not bf16
        max_seq_length=1024,
    ),
)

trainer.train()
trainer.save_model("./my-adapter")      # only ~20 MB!

Step 3 — Use it

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch

BASE = "Qwen/Qwen2.5-0.5B-Instruct"
tok = AutoTokenizer.from_pretrained(BASE)
base = AutoModelForCausalLM.from_pretrained(BASE, device_map="auto")
model = PeftModel.from_pretrained(base, "./my-adapter")     # attach the patch
model.eval()

def ask(q):
    text = tok.apply_chat_template([{"role":"user","content":q}],
                                   tokenize=False, add_generation_prompt=True)
    ids = tok(text, return_tensors="pt").to(model.device)
    with torch.no_grad():
        out = model.generate(**ids, max_new_tokens=120, do_sample=False,
                             pad_token_id=tok.eos_token_id)
    return tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True)

print(ask("How much annual leave do I get?"))

Always compare against the base model on the same questions. If fine-tuning didn't clearly help, you need better data, not more epochs.

Key settings and what they do

Setting Meaning Typical
r (rank) size of the LoRA patch — capacity 8–64 (start 16)
lora_alpha scaling of the patch 2 x r
learning_rate LoRA tolerates higher LR than full fine-tuning 1e-4 – 3e-4
num_train_epochs passes over your data 1–3; more overfits fast
target_modules which layers get patched attention + MLP projections
max_seq_length truncation length fit your longest example

Common problems

Symptom Cause Fix
Model repeats training examples verbatim overfitting fewer epochs, more/varied data, lower r
No visible change LR too low, too few steps, or target_modules wrong raise LR, check the adapter actually loaded
It got worse at everything else catastrophic forgetting fewer epochs, lower LR, mix in general examples
Weird formatting in outputs wrong chat template or inconsistent training data use apply_chat_template; audit your data
Out of memory model/batch too big smaller model, batch_size=1, more grad accumulation, QLoRA 4-bit

Check yourself

  1. What does LoRA freeze and what does it train?
  2. Why is fine-tuning bad at injecting facts?
  3. What are two signs you've overfitted a fine-tune?

Further reading

  • Docs: huggingface.co/docs/peft — the PEFT library (LoRA and friends).
  • Docs: huggingface.co/docs/trl — SFTTrainer and DPOTrainer.
  • Paper: Hu et al., "LoRA: Low-Rank Adaptation of Large Language Models" (2021, arXiv:2106.09685).
  • Paper: Dettmers et al., "QLoRA" (2023, arXiv:2305.14314).
  • Course: Hugging Face LLM Course, the fine-tuning chapters — free, hands-on.
  • Tool: github.com/unslothai/unsloth — a popular library that makes LoRA fine-tuning 2x faster with less memory; has ready-to-run Colab notebooks.

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36