Lesson 10 of 13

Fine-tuning and LoRA

Fine-tuning adapts a pretrained model to a new task. LoRA freezes the original weights and learns a small low-rank change instead, cutting the numbers you train by orders of magnitude.

Advanced18 min

Builds on How LLMs are trained and Layers are matrix multiplications (Neural networks)

In this lesson you will

  • Contrast pretraining, full fine-tuning, and instruction tuning, and estimate the memory full fine-tuning needs
  • Explain how LoRA represents a weight change as B times A, and why B starts at zero
  • Count LoRA's trainable parameters for real model shapes
  • Use singular values to judge how much of a change a given rank can capture

You have a base model that writes fluent text, and you want it to do something more specific: answer in your support team’s style, follow instructions, or write SQL. Training a new model from scratch would cost millions and throw away everything the base model learned. Instead you keep training the model you have, on a much smaller set of examples. This lesson is about how to do that cheaply.

From pretraining to fine-tuning

The lesson on training described the stages. PretrainingThe first and most expensive stage of training a language model, in which it learns to predict the next token across a very large amount of text. The result is a base model.Taught in How LLMs are trainedOpen in glossary on trillions of tokens produces a base model. Fine-tuningContinuing to train a pretrained model on a smaller, targeted dataset, such as example conversations, to change its behavior. Supervised fine-tuning uses examples of good responses.Taught in How LLMs are trainedOpen in glossary continues training on far less data chosen for a purpose, with the same next-token loss.

One especially important kind is Instruction tuningFine-tuning a pretrained model on many tasks written as instructions paired with good responses, so it learns to follow instructions it has not seen before.Open in glossary: fine-tuning on many tasks written as instructions paired with good responses. Wei et al. (2021) instruction-tuned a 137-billion-parameter model on more than 60 datasets phrased as instructions, and it got better at following instructions for kinds of tasks it had never seen. The supervised stage of InstructGPT (Ouyang et al., 2022) is the same idea with responses written by people.

The simplest approach, full fine-tuning, updates every weight. That is expensive in two ways:

  • Memory while training. Training with Adam in mixed precision keeps, for each parameter, a 16-bit weight, a 16-bit gradient, and 12 bytes of optimizer state (a 32-bit copy of the weight plus two running averages), 16 bytes in all (Rajbhandari et al., 2020). For Llama 2 7B, with 6.74 billion parameters, that is about 108 GB before counting activations, more than a single 80 GB GPU holds.
  • Storage afterwards. Every fine-tuned version is a complete copy of the model: 13.5 GB at 16 bits for each task or customer.

Learn the change, not the model

Write the fine-tuned weights of one layer as the pretrained weights plus a change: W=W0+ΔWW = W_0 + \Delta W. Full fine-tuning learns ΔW\Delta W with as many numbers as W0W_0 has. Low-rank adaptationA way to fine-tune a model by freezing its weights and learning each change as the product of two thin matrices, B times A, of a small rank r. It trains a tiny fraction of the parameters.Open in glossary (LoRA, Hu et al., 2021) freezes W0W_0 and learns ΔW\Delta W as the product of two thin matrices:

h=W0x+αrBAx,B∈Rd×r,A∈Rr×kh = W_0 x + \frac{\alpha}{r} B A x, \qquad B \in \mathbb{R}^{d \times r}, \quad A \in \mathbb{R}^{r \times k}

where W0W_0 is d×kd \times k and the rank rr is small, often between 1 and 64. Only BB and AA are trained, so one adapted matrix trains r(d+k)r(d + k) numbers instead of dkdk. For a 4096×40964096 \times 4096 matrix at r=8r = 8, that is 65,536 instead of 16.8 million.

Three details matter:

  • BB starts at zero. AA starts with small random numbers and BB with zeros, so BA=0BA = 0 and training begins exactly at the pretrained model. (AA must be random: if both started at zero, neither would ever get a gradient.)
  • The scale α/r\alpha / r. The update is multiplied by a constant α\alpha divided by the rank. Hu et al. set α\alpha once and did not tune it; dividing by rr keeps the size of the update from changing much when you try a different rank, so other settings such as the learning rate need less retuning.
  • No extra cost when serving. After training, αrBA\frac{\alpha}{r}BA can be added into W0W_0, giving an ordinary weight matrix. The model runs exactly as fast as before. Or you can keep one base model and swap small adapters per task.

The product BABA can never have Rank (of a matrix)The number of independent directions a matrix can produce. A matrix of rank r can be written as a sum of r pieces, each one column times one row.Open in glossary higher than rr: every change it can express is built from at most rr independent directions. Hu et al. hypothesized that the changes fine-tuning makes have a low “intrinsic rank”, building on earlier evidence that fine-tuning works even when restricted to a small random subspace of the parameters (Aghajanyan, Zettlemoyer, and Gupta, 2020).

How much can rank r capture?

The Singular value decompositionWriting a matrix as a sum of rank-one pieces, each with a weight called a singular value, largest first. Keeping the largest r pieces gives the best rank-r approximation.Open in glossary answers that exactly. It writes any matrix as a sum of rank-one pieces, each with a weight called a singular value, largest first. The best rank-rr approximation keeps the rr largest pieces and drops the rest (the Eckart-Young theorem), so the singular values tell you, before any training, how much of a change each rank can capture.

The demo uses a 32×3232 \times 32 target change. The middle panel shows the best BABA for your rank, the right panel what it misses. Train B and A runs gradient descent from B=0B = 0, as LoRA does, on the squared difference from the target.

How low can the rank go?

LoRA learns a change to a weight matrix as B times A. Choose the rank and see how much of a target change it can capture.

Target change
B A, rank 2 (best)
What it misses
  • negative
  • positive (paler is closer to zero)

B is 32 x 2 and A is 2 x 32: 64 + 64 = 128 numbers, against 1,024 for the full 32 x 32 change.

Singular values of the target, largest first kept by rank 2 dropped
Target update
2
Numbers LoRA trains here128 of 1,024
Missed at best13.3%
Best possible at this rank86.7% captured
Same rank on a 4,096 x 4,096 matrix16,384 numbers, 0.10% of the full matrix

Challenges

0 of 3 complete

  1. On the low-rank update, find the smallest rank that captures at least 99% of it. (not yet)
  2. Find the smallest rank that captures 90% of the random noise. (not yet)
  3. Train B and A from B = 0 until they miss at most one percentage point more of the change than the best that rank allows. (not yet)

Try this

  • On Low-rank update, step the rank from 1 to 5 and watch the singular values chart. Each new rank picks up the next bar. Predict where the error stops falling before you get there.
  • Switch to Random noise at the same rank. The bars fall off slowly, with no few large ones standing out, so every rank adds only a little.
  • Press Train B and A at rank 2. The share missed starts at 100%, because B=0B = 0 means BA=0BA = 0, and then falls to the dashed line.
  • Watch the last readout as you move the rank: at the size of a real attention matrix, even rank 32 trains under 2% of the numbers.
Ranks, parameter counts, and a zero startPython with NumPy
import numpy as np

rng = np.random.default_rng(0)
d = 32

# A target change built like the demo's: four strong directions plus faint noise.
U, _ = np.linalg.qr(rng.normal(size=(d, d)))
V, _ = np.linalg.qr(rng.normal(size=(d, d)))
s = np.full(d, 0.15)
s[:4] = [8, 5, 3, 2]
delta = U @ np.diag(s) @ V.T

sv = np.linalg.svd(delta, compute_uv=False)
for r in (1, 2, 4, 8):
    kept = (sv[:r] ** 2).sum() / (sv ** 2).sum()  # the best rank-r share
    print(f"rank {r}: B and A hold {r * (d + d):4d} numbers (full: {d * d}), "
          f"best possible captures {kept:.1%}")

# LoRA starts exactly at the base model, because B starts at zero.
W = rng.normal(size=(d, d))
r, alpha = 4, 8
A = rng.normal(0, 1 / np.sqrt(d), size=(r, d))
B = np.zeros((d, r))
x = rng.normal(size=d)
print("starts at the base model:", np.allclose(W @ x + (alpha / r) * B @ (A @ x), W @ x))

# Real shapes: GPT-3 175B adapts W_q and W_v (12,288 x 12,288) in 96 layers.
gpt3 = 96 * 2 * 4 * (12288 + 12288)
print(f"GPT-3, r = 4 on W_q and W_v: {gpt3:,} numbers, {2 * gpt3 / 1e6:.1f} MB at 16 bits")
rank 1: B and A hold   64 numbers (full: 1024), best possible captures 62.4%
rank 2: B and A hold  128 numbers (full: 1024), best possible captures 86.7%
rank 4: B and A hold  256 numbers (full: 1024), best possible captures 99.4%
rank 8: B and A hold  512 numbers (full: 1024), best possible captures 99.5%
starts at the base model: True
GPT-3, r = 4 on W_q and W_v: 18,874,368 numbers, 37.7 MB at 16 bits

What you saw is the whole bet behind LoRA. When a change has a few dominant directions, as Low-rank update does, a small rank captures nearly all of it: rank 4 keeps 99.4% with a quarter of the numbers. When it has none, as Random noise does, rank 17 is needed for 90%, and at that rank BB and AA hold more numbers than the matrix itself.

The demo simplifies in one important way. It fits a known target change directly. Real LoRA never sees the change it is aiming for; it follows the gradient of the model’s loss on training examples, through every layer. The rank still limits what it can learn in exactly the way the singular values show.

Real numbers

Hu et al. applied LoRA to GPT-3 175B, adapting only the query and value projections, WqW_q and WvW_v, in all 96 layers. Each is 12,288×12,28812{,}288 \times 12{,}288, so at r=4r = 4 the adapter trains 96×2×4×(12,288+12,288)=18,874,36896 \times 2 \times 4 \times (12{,}288 + 12{,}288) = 18{,}874{,}368 numbers, about one ten-thousandth of the model’s 174.6 billion. They report that this cut the checkpoint from 350 GB to about 35 MB and training memory from 1.2 TB to 350 GB, and that LoRA matched or beat full fine-tuning on the benchmarks they tested.

Llama 2 7B (6.74 billion parameters)Numbers trainedShare of the model
Full fine-tuning6,738,415,616100%
LoRA, r=16r = 16 on all seven matrices of every layer39,976,9600.59%
LoRA, r=8r = 8 on WqW_q and WvW_v4,194,3040.062%

The frozen weights still have to sit in memory, 13.5 GB at 16 bits for Llama 2 7B, but they need no gradients and no optimizer state. Storing the frozen weights in 4 bits shrinks that too; that combination, QLoRA, comes up in the lesson on quantization.

Why the update is scaled by alpha over rOptional

With Adam, each parameter’s step is roughly the learning rate in size, whatever the size of its gradient. Early in training each entry of BB moves away from zero by about the learning rate per step, and each output of BAxBAx adds up rr such contributions. Without a correction, a larger rr would tend to give a larger change at the same learning rate. Dividing by rr counteracts this, so Hu et al. could pick α\alpha once and keep it when trying other ranks; they note that tuning α\alpha is roughly the same as tuning the learning rate. Later work has proposed other scalings, such as α/r\alpha / \sqrt{r} (Kalajdzievski, 2023), but α/r\alpha / r is the original and still the common default.

Key ideas

  • Fine-tuning continues training a pretrained model on a small, targeted dataset; instruction tuning uses tasks written as instructions with good responses.
  • Full fine-tuning with Adam needs about 16 bytes per parameter of memory while training, and a full copy of the model per task.
  • LoRA freezes W0W_0 and learns ΔW=αrBA\Delta W = \frac{\alpha}{r} B A, training r(d+k)r(d + k) numbers per matrix. BB starts at zero, so training starts at the base model, and the product can be merged into W0W_0 afterwards.
  • The singular values of a change show how much of it any rank can capture. LoRA saves the most when the change it needs is close to low rank.

Check yourself

Pick an answer to see why it is right or wrong. Nothing is graded. Your first answer is saved in this browser so the question can come back for review.

1A LoRA adapter of rank 8 is added to one 4,096 x 4,096 weight matrix. How many numbers does it train?
2Why does LoRA initialize B to zero?
3In the lab, the random noise target needs rank 17 to capture 90% of it. What does that say about using LoRA for such a change?

Progress is saved in this browser only.

Up nextGenerating fast: the KV cache
Next
Transformers and LLMs
  1. 1Predicting the next token
  2. 2Before transformers: recurrent networks
  3. 3Attention, step by step
  4. 4Many heads and the causal mask
  5. 5Where words are
  6. 6The transformer block
  7. 7Inside a real language model
  8. 8How LLMs are trained
  9. 9Where training data comes from
  10. 10Fine-tuning and LoRA
  11. 11Generating fast: the KV cache
  12. 12Quantization: smaller numbers
  13. 13How a chat assistant works

Try "embedding", "softmax", "overfitting", or "backpropagation".