Key ideas
- Full fine-tuning updates all weights of a pretrained model on task data. The model is
not shrunk: it keeps every parameter, plus at most a small new task head (distillation, not
fine-tuning, makes a smaller model).
- BERT-style encoder: feed the
<cls>output, which depends on all tokens, into a new fully connected layer trained from scratch; the pretrained layers are updated too1. - T5: every task is text-to-text with a task description in the input ("Summarize: …"), the decoder generates outputs of any length and no extra layers are needed, so one minibatch can mix tasks1.
- Decoder LLMs (SFT): train on prompt–response text with the usual next-token loss;
positions to ignore (padding, often the prompt) get label , the default
ignore_indexthat PyTorch's cross-entropy loss skips7.
- BERT-style encoder: feed the
- Batching. Dynamic padding pads each batch only to its own longest sequence (done by a
collate function such as
DataCollatorWithPadding), which saves computation compared with padding everything to the global maximum2. - Parameter-efficient fine-tuning (PEFT) freezes the pretrained weights and trains a few new ones, which saves memory and lets one base model serve many tasks by swapping the small task-specific weights3.
- LoRA3. For a frozen learn with , , . starts random Gaussian and at zero, so training starts exactly at the pretrained model; the update is scaled by , which spares retuning the learning rate when changes. The memory saving comes mainly from not storing gradients and Adam states for frozen weights (up to 2/3 less VRAM; GPT-3 175B: 1.2 TB → 350 GB). Because the update is linear and parallel to , it can be merged into : no extra inference latency. The original paper adapts only attention matrices (e.g. , ).
- QLoRA4 stores the frozen base model in 4-bit NormalFloat (NF4, levels at quantiles of a normal distribution), dequantizes to BFloat16 for each matmul, and backpropagates through it into 16-bit LoRA adapters. Double quantization also quantizes the per-block scaling constants; paged optimizers move optimizer states to CPU RAM during memory spikes. It found LoRA on all linear layers necessary to match 16-bit full fine-tuning.
- Adapters5 insert a small bottleneck MLP (down-projection, non-linearity, up-projection, skip connection) twice per Transformer layer. They run in sequence with the other layers, so unlike merged LoRA they add inference latency3.
- Prefix vs. prompt tuning6. Prefix tuning prepends trainable vectors to the activations of every layer; prompt tuning prepends a few trainable "soft prompt" embeddings to the input only. Soft prompts must be learned by gradient descent and match no real words, but prompt tuning closes the gap to full model tuning as models grow, matching it at T5-XXL (11B).
Formulas
- LoRA forward3: ; trainable parameters per matrix instead of (equal only at , i.e. 2048 for ).
- Gradients at initialization (): but ; starts learning after the first update of .
- Houlsby adapter with bottleneck 5: parameters (weights and biases of the two projections).
- Quantized storage: bits per parameter with one fp32 constant per block of 64, or with double quantization4.
Worked example
A 7B model with 32 layers and ; LoRA with on and :
- per matrix ; total trainable parameters, about of 7B.
- Base weights: bytes GB in bfloat16; GB as 4-bit codes; with double-quantized constants GB (3.94 GB with fp32 constants).
In code
import torch
import torch.nn as nn
class LoRALinear(nn.Module):
def __init__(self, base, r=8, alpha=16):
super().__init__()
self.base = base.requires_grad_(False) # frozen W0 and bias
self.A = nn.Parameter(torch.randn(r, base.in_features) * 0.01)
self.B = nn.Parameter(torch.zeros(base.out_features, r)) # B = 0, so BA = 0 at start
self.s = alpha / r
def forward(self, x):
return self.base(x) + self.s * (x @ self.A.T @ self.B.T)
layer, x = LoRALinear(nn.Linear(512, 512)), torch.randn(4, 512)
print(torch.equal(layer(x), layer.base(x))) # True: starts at W0
print(sum(p.numel() for p in layer.parameters() if p.requires_grad)) # 8192 = 8 * (512 + 512)
Common pitfalls
- Plain LoRA does not quantize the base model; 4-bit storage is what QLoRA adds.
- Gradients still flow through frozen layers to reach adapters below them; only the weight gradients and optimizer states of frozen matrices are skipped.
- The factor does not cap the rank (the shapes do) and does not make at the start (the zero init of does).
- Unmerged, the LoRA branch costs extra compute; zero added latency requires merging.
- Prefix tuning ≠ prompt tuning: every layer vs. input embeddings only; neither updates the pretrained weights.
- Fine-tuning a narrow task can overwrite earlier abilities (catastrophic forgetting); mixing in original data (rehearsal) or an EWC penalty reduces it8, and adapters leave the frozen base model intact5.
- Fine-tuning with a different tokenizer than the pretrained one breaks the embedding lookup.
References
- Dive into Deep Learning — Sec. 11.9.1.2 Fine-Tuning BERT, 11.9.2.2 Fine-Tuning T5
- Hugging Face LLM Course — Ch. 3 Fine-tuning a pretrained model, Processing the data (Dynamic padding)
- Hu et al. (2021), LoRA: Low-Rank Adaptation of Large Language Models — Sec. 3 Aren't Existing Solutions Good Enough? (adapter latency), 4.1 Low-Rank-Parametrized Update Matrices, 4.2 Applying LoRA to Transformer
- Dettmers et al. (2023), QLoRA: Efficient Finetuning of Quantized LLMs — Sec. 3 QLoRA Finetuning (4-bit NormalFloat, Double Quantization, Paged Optimizers); Sec. 4 QLoRA vs. Standard Finetuning
- Houlsby et al. (2019), Parameter-Efficient Transfer Learning for NLP — Abstract and Sec. 1 Introduction (frozen base, no forgetting); Sec. 2.1 Instantiation for Transformer Networks (Figure 2)
- Lester et al. (2021), The Power of Scale for Parameter-Efficient Prompt Tuning — Sec. 2 Prompt Tuning, 3.1 Closing the Gap, 4 Comparison to Similar Approaches
- PyTorch documentation: torch.nn.CrossEntropyLoss — Parameters, ignore_index (default -100)
- Wikipedia: Catastrophic interference — Introduction; Proposed solutions (Rehearsal, Elastic weight consolidation)