Step 16 · LoRA, the path most people take
Train 0.1% of the parameters, get most of the benefit, on one consumer GPU. This is the module with the highest practical value in the whole track.
In 60 seconds
Step 16 · LoRA, the path most people take
Train 0.1% of the parameters, get most of the benefit, on one consumer GPU. This is the module with the highest practical value in the whole track.
class LoRALinear(nn.Module):
def __init__(self, base: nn.Linear, r=16, alpha=32, dropout=0.05):
super().__init__()
self.base = base
for p in self.base.parameters():
p.requires_grad = False # freeze the original
self.A = nn.Parameter(torch.zeros(r, base.in_features))
self.B = nn.Parameter(torch.zeros(base.out_features, r))
nn.init.kaiming_uniform_(self.A, a=math.sqrt(5))
# B starts at zero, so the adapter is a no-op at step 0
self.scale = alpha / r
self.drop = nn.Dropout(dropout)
def forward(self, x):
return self.base(x) + self.drop(x) @ self.A.T @ self.B.T * self.scale
def merge(self):
"""Fold the adapter into the base weight for zero-overhead serving."""
self.base.weight.data += (self.B @ self.A) * self.scale
return self.baseB is initialised to zero. That means the adapted model starts exactly equal to the original — no random perturbation, no warmup shock. It is a small detail that makes LoRA remarkably well behaved.QLoRA: even the frozen weights get smaller
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # normal-float 4, best for weights
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # quantise the quantisation constants
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B", quantization_config=bnb, device_map="auto")
model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05, bias="none",
task_type="CAUSAL_LM",
# adapt attention AND the feed-forward layers -- ignoring the FFN
# is the single most common reason a LoRA underperforms
target_modules=["q_proj","k_proj","v_proj","o_proj",
"gate_proj","up_proj","down_proj"],
))
model.print_trainable_parameters() # ~ 42M of 8B = 0.5%Choosing rank
| Rank | Trainable share | Right for |
|---|---|---|
| 4–8 | ~0.1% | Style, tone, output format |
| 16–32 | ~0.5% | Most tasks — start here |
| 64–128 | ~2% | Big behaviour shifts, new domains |
| 256+ | ~5% | Approaching full fine-tuning; consider whether you need it |
alpha to roughly twice the rank and change one of them at a time. Tuning both at once is how people convince themselves LoRA does not work.Why this is the highest-leverage module here
- Cheap. A useful fine-tune costs a few dollars of GPU time.
- Portable. An adapter is tens of megabytes. Ship dozens, swap at runtime, serve many customers from one base model.
- Reversible. Unhappy with it? Remove the adapter. The base model was never touched.
- Mergeable. Fold it into the weights for serving and pay zero inference overhead.
- Composable. Adapters can be stacked or blended, with mixed results but real utility.
Watch and read more
Lab
A LoRA fine-tune that beats a full one on your task, for a hundredth of the cost.
The problem
target_modules=["q_proj","k_proj","v_proj","o_proj",
"gate_proj","up_proj","down_proj"] # drop the last three and compareYou are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1LoRA r=16 matches full fine-tuning on your task. Give a task where it would not, and explain why.Reveal
Questions people ask
Does LoRA match full fine-tuning?
For most task adaptation, close enough that the difference is hard to measure. Full fine-tuning pulls ahead when you are teaching a genuinely large amount of new behaviour or training on very large datasets.
Which modules should I adapt?
Attention projections plus the feed-forward layers. Adapting attention only is the most common configuration mistake and leaves substantial quality on the table.
Can I merge several adapters?
Yes, by weighted averaging, and results vary. Adapters trained for conflicting behaviours interfere. Serving them separately and routing is more predictable.
Does quantising to 4 bits hurt?
Measurably but modestly with NF4 double quantisation, and the ability to fine-tune a model you otherwise could not touch usually outweighs it. For serving, 8-bit or 4-bit quantisation of the final model is standard practice.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress