GenAI Course · Module 10 · Session 40

PEFT Hands-On

⏱ 15 min read🎯 Advanced🖼 3 diagrams🧪 2 live demos🐍 Python code

This is the final session of the course, and the most hands-on. Let us understand exactly what LoRA changes inside the model, how QLoRA squeezes fine-tuning onto a single GPU, and then run a complete fine-tune and prove it beats the base model.

In this session

  • How LoRA works: the low-rank update, rank and alpha
  • A live calculator: how many parameters LoRA really trains
  • QLoRA: 4-bit base model + LoRA adapters
  • A complete fine-tune with PEFT + TRL
  • Evaluating against the base model, then merging and serving

01LoRA: how it works

A layer in an LLM multiplies its input by a big weight matrix W (for example 4096 × 4096 ≈ 16.8 million numbers). LoRA freezes W and learns a small correction made of two thin matrices, B and A:

W′ = W + (α / r) · B · A   where B is 4096 × r and A is r × 4096
shapeparameterstrained?W (frozen)4096 × 409616.8 Mno ❄B (trained)4096 × 832,768yes 🔥A (trained)8 × 409632,768yes 🔥
With rank r = 8, LoRA trains 65,536 numbers instead of 16.8 million for this matrix: about 0.4%.
SettingMeaningTypical value
r (rank)size of the thin matrices; higher = more capacity8 – 64
lora_alphascales the update (effective scale α/r)often 2 × r
target_moduleswhich layers get adaptersq_proj, v_proj (or all linear layers)
lora_dropoutregularisation0.05 – 0.1
📝

Like sticky notes on a textbook. You don’t rewrite the whole book (full fine-tuning). You add small sticky notes with corrections (LoRA). Remove the notes and the original book is untouched; swap notes for a different exam.

02How many parameters does LoRA train?

For a Llama-2-7B-style model (32 layers, hidden size 4096) with adapters on q_proj and v_proj:

Demo · LoRA rank vs trainable parameters

Formula: 32 layers × 2 matrices × r × (4096 + 4096). Even at r = 64 you train under 0.5% of the weights.

03QLoRA: fine-tuning on one GPU

QLoRA (2023) loads the frozen base model in 4-bit precision and trains LoRA adapters on top in higher precision. Its key tricks:

Frozen base model4-bit NF4 · ~4–5 GB for 7BLoRA adaptersBF16 · trainable · a few MBYour fine-tuned modelbase + adapter
QLoRA keeps the big model tiny in memory and trains only the small adapters.
The QLoRA paper fine-tuned a 65B model on a single 48 GB GPU. For 7–8B models, a free or low-cost 16 GB Colab GPU is often enough with small batches.

04A complete fine-tune with PEFT + TRL

This is the standard recipe with Hugging Face libraries. Run it on a GPU (for example Google Colab); replace the model and data with your own.

qlora_finetune.py
# pip install transformers peft trl bitsandbytes datasets accelerate
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig

base = "meta-llama/Llama-3.1-8B-Instruct"                 # any causal LM you have access to
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
                         bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.bfloat16)
model = AutoModelForCausalLM.from_pretrained(base, quantization_config=bnb, device_map="auto")
tok = AutoTokenizer.from_pretrained(base)

lora = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05, task_type="CAUSAL_LM",
                  target_modules=["q_proj", "k_proj", "v_proj", "o_proj"])

data = load_dataset("json", data_files={"train": "train.jsonl", "test": "test.jsonl"})   # chat "messages" format

args = SFTConfig(output_dir="ticket-classifier-lora", num_train_epochs=3, learning_rate=2e-4,
                 per_device_train_batch_size=4, gradient_accumulation_steps=4,
                 logging_steps=20, eval_strategy="epoch", bf16=True)

trainer = SFTTrainer(model=model, args=args, train_dataset=data["train"], eval_dataset=data["test"],
                     peft_config=lora, processing_class=tok)
trainer.train()
trainer.save_model("ticket-classifier-lora")             # saves only the small adapter
Library versions change fast. Argument names in TRL (for example processing_class vs tokenizer, eval_strategy vs evaluation_strategy) differ between releases. If you get an argument error, check the docs for your installed version.

05Evaluate against the base model

A fine-tune is only worth it if it beats the base model on data it has never seen. Use the held-out test set from Session 39:

Test set116 held-out ticketsBase modelsame promptFine-tunedsame promptScore bothaccuracy, valid JSONDecideship, iterate or stop
Same prompts, same test set, two models. Only then decide.
Demo · Example comparison (illustrative)

Merge and serve

merge_adapter.py
import torch
from peft import AutoPeftModelForCausalLM

model = AutoPeftModelForCausalLM.from_pretrained("ticket-classifier-lora", torch_dtype=torch.bfloat16)
merged = model.merge_and_unload()                         # fold B·A into W: no extra latency
merged.save_pretrained("ticket-classifier-merged")

Or keep adapters separate and load the right one per request. Serving engines like vLLM can serve many LoRA adapters on one base model (recall the “swap adapters” idea from the GenAI lessons).

06Summary

07Quick quiz

1. What does LoRA keep frozen?

2. With r = 8 on q_proj and v_proj of a 7B model, LoRA trains about…

3. QLoRA stores the base model in…

4. The fine-tuned model scores 95% vs base 74% on training data only. What’s missing?

5. What does merge_and_unload() do?

08References

Created by Ajeetkumar