LLM Mastery
37. LoRA and Parameter-Efficient Fine-Tuning
You don't need to touch every weight to change model behavior — a rank-8 detour around the matrix is often enough.
Level: Advanced · Article 37/50 · Karpathy-style LLM course
Full fine-tuning of a 7B model means updating 7 billion numbers, storing optimizer state for each, and checkpointing a full new copy of the model for every task you care about. LoRA (Low-Rank Adaptation) gets you most of the benefit while touching a fraction of a percent of the parameters — and its central bet, that task adaptation lives in a low-dimensional subspace, turned out to be right often enough to become the default.
Idea
Full fine-tune: W → W + ΔW, where ΔW is the same shape as W — a dense update over every entry. LoRA instead freezes W and adds an adapter constrained to low rank: ΔW ≈ B @ A, where A is r × d_in, B is d_out × r, and r is small (often 4–64) relative to d_in, d_out.
import torch, torch.nn as nn
class LoRALinear(nn.Module):
def __init__(self, base: nn.Linear, r: int = 8, alpha: int = 16):
super().__init__()
self.base = base
for p in self.base.parameters():
p.requires_grad = False
d_out, d_in = base.weight.shape
self.A = nn.Parameter(torch.randn(r, d_in) * 0.01)
self.B = nn.Parameter(torch.zeros(d_out, r))
self.scale = alpha / r
def forward(self, x):
return self.base(x) + self.scale * (x @ self.A.T @ self.B.T)
B starts at zero, so x @ A.T @ B.T is exactly zero on step one — the adapter is a no-op until gradients pull it somewhere useful. That’s a deliberate design choice, not an accident: it guarantees fine-tuning starts from a model that behaves identically to the base checkpoint, then edits from there.
The parameter math
For a 4096×4096 attention projection with r=8:
- Full fine-tune: 4096 × 4096 ≈ 16.8M trainable params, for that one matrix.
- LoRA:
Ais 8×4096 = 32,768,Bis 4096×8 = 32,768. Total ≈ 65,536.
That’s 256× fewer trainable parameters for that matrix, and the ratio only improves as the base matrix gets bigger — LoRA cost grows linearly with d, full fine-tuning grows quadratically. Apply it to attention Q/K/V/O projections across a 32-layer model and you’re typically fine-tuning under 1% of total weights, with an Adam optimizer state (two moment buffers) that’s correspondingly under 1% the size. That’s the difference between fine-tuning on a single consumer GPU and needing a cluster.
Failure mode
LoRA fails quietly, not loudly — you get a model that trains without errors and is just… mediocre. Three usual suspects:
- Rank too small for the task.
r=4might be plenty for a narrow style adapter, hopeless for teaching a genuinely new skill across many domains. If validation loss plateaus higher than a full fine-tune’s, doublerbefore touching anything else. - Adapting the wrong modules. Bolting LoRA only onto attention projections and skipping the MLP layers misses where a lot of factual and associative knowledge actually lives (see article 25). If your task is knowledge-heavy rather than style-heavy, adapt the MLP too.
- Mistuned alpha/r ratio.
scale = alpha / rsets the adapter’s effective learning-rate multiplier. Changerwithout adjustingalphaand you silently change how strongly the adapter can perturb the base model — a classic “tuned one hyperparameter, forgot it was coupled to another” bug.
Full fine-tuning still wins when you have the compute and data and need to move the model further than a low-rank detour can reach — think base-capability shifts, not persona or format shifts.
There’s a second, less obvious payoff: because the base weights never change, you can keep dozens of task-specific adapters — each a few tens of megabytes — and swap them into the same loaded base model at request time. That’s the difference between hosting one fine-tune per customer on separate GPUs and hosting one base model with a folder of adapters, which is why LoRA shows up as much in serving architecture diagrams as it does in training scripts.
Exercise
A model has 32 transformer layers. Each layer applies LoRA (r=8, alpha=16) to four projections — Q, K, V, O — each of shape 4096×4096. Compute the total number of trainable LoRA parameters across the whole model, and compare it to the model’s total parameter count (roughly 7B for a model this shape). What percentage of the model is trainable? Then answer: if you doubled r to 16 without changing alpha, what happens to the adapter’s effective contribution at initialization, and why doesn’t it matter that B starts at zero either way?
← 36. Supervised Fine-Tuning (SFT)
38. Preferences: RLHF and DPO →