Part IV · Ch. 7 — LoRAs and Adapters

Part IV · Chapter 7 of 8

LoRAs and Adapters

Teaching a frozen model one new thing with a very small file


The apple the prompt cannot name

Chapter 6 ended on a hard edge. Prompts steer the model within what it already knows, and teaching it something new is a different job. That is this chapter.

Say there is a particular apple you care about: an heirloom variety with a russet shoulder and a squat, lopsided profile. You type it into the prompt and you get an apple, a good one, and not that one. No arrangement of words reaches it, because the words the noise predictor was trained against never pointed at your apple.

The obvious fix is to keep training. Take the finished model, feed it thirty photographs of your apple, and let Chapter 3's gradient steps keep rolling. That is full fine-tuning, and it works. It also hands you a bill. A model of this kind holds billions of learned numbers, the parameters $\theta$ from Chapter 3, counting the hundreds-of-millions U-Net together with the other trained pieces packed into the same checkpoint. Full fine-tuning nudges every one of them, each needing its own gradient and its own bookkeeping while training runs, and what you get back is a second copy of the model: gigabytes, for one apple.

There is a much smaller way to write down a change, and it is the rest of this chapter: a LoRA, short for Low-Rank Adaptation. It leaves the model's own billions of numbers frozen, in Chapter 5's sense of the word: the training process is forbidden to change those numbers at all. They are read and multiplied by, and never touched. That is what makes everything else cheap. The same idea, with the same numbers, appears in the sister book's LoRA chapter, so the two books agree digit for digit. This chapter builds it from scratch anyway, then does the part that belongs to images.

Two panels side by side. On the left, headed 'full fine-tuning', a large square grid of cells is drawn entirely in violet with every cell shaded, labeled 'every weight moves' and beneath it '1,000,000 numbers trained'. On the right, headed 'LoRA', the same size grid is drawn in muted gray with a small padlock in its corner and the label 'frozen — untouched'; beside it stand two narrow mint strips, a tall thin one labeled B and a short wide one labeled A, with the caption '16,000 numbers trained'. A mint plus sign sits between the gray grid and the mint strips.
Two ways to change a model. Full fine-tuning moves every violet cell and hands you a whole new model. A LoRA leaves the grid frozen and learns the two mint strips beside it, and the frozen grid still does nearly all of the work.

Learning only the change

Inside the noise predictor, the numbers that do the work are arranged in rectangular grids called weight matrices. Pick one and call it $\mathbf{W}$, "the matrix W", a block of numbers the network multiplies things by. After a fine-tune it has become some new matrix $\mathbf{W}'$, "W-prime", and whatever fine-tuning did to it, the result can be written as the original plus a grid of changes. Write $\Delta\mathbf{W}$ for that grid of changes, using the Greek letter delta, the traditional symbol for "a change":

$$\mathbf{W}' = \mathbf{W} + \Delta\mathbf{W}$$

In words: the adapted matrix is the original matrix plus a matrix of changes, added cell by matching cell.

On its own that rewrite saves nothing. $\Delta\mathbf{W}$ has as many cells as $\mathbf{W}$, so a million-cell matrix still needs a million-cell correction. It only pays if the change can be described more cheaply than the thing it changes. So how complicated is the change a narrow fine-tune needs?

The LoRA bet is that it is very simple, and there is a precise way to say "simple" about a matrix. A matrix built by multiplying one tall skinny column by one flat wide row can only push its outputs along a single direction. It has rank one, the simplest non-zero change there is. Take a column $\mathbf{B}$ and a row $\mathbf{A}$:

$$\mathbf{B} = \begin{bmatrix} 2 \\ 1 \end{bmatrix}, \qquad \mathbf{A} = \begin{bmatrix} 3 & 1 \end{bmatrix}, \qquad \Delta\mathbf{W} = \mathbf{B}\mathbf{A}$$

In words: the whole grid of changes is one column of numbers standing up, multiplied by one row of numbers lying flat.

Worked example — every entry of the product

Multiplying a column by a row fills a grid: the entry in row $i$, column $j$ is the $i$-th number of $\mathbf{B}$ times the $j$-th number of $\mathbf{A}$. All four, one at a time:

  • Row 1, column 1: $2 \times 3 = 6$.
  • Row 1, column 2: $2 \times 1 = 2$.
  • Row 2, column 1: $1 \times 3 = 3$.
  • Row 2, column 2: $1 \times 1 = 1$.
$$\mathbf{B}\mathbf{A} = \begin{bmatrix} 2 \\ 1 \end{bmatrix}\begin{bmatrix} 3 & 1 \end{bmatrix} = \begin{bmatrix} 6 & 2 \\ 3 & 1 \end{bmatrix}$$

In words: two numbers and two numbers, four numbers stored, reconstruct all four cells of the change grid.

Count that: four numbers stored to rebuild four cells. On a toy this small the technique breaks even. The saving arrives when the grids get big, and the end of the chapter counts it.

Now the property that makes it "low rank". Feed this change grid a few different inputs and look at where the answers land. Feeding a grid an input means taking each row of the grid and dotting it with the input, multiplying matching slots and adding the results, and those answers, top row first, are the output list. That is all multiplying by a matrix is.

  • Input $\begin{bmatrix} 1 \\ 0 \end{bmatrix}$: top $= 6 \times 1 + 2 \times 0 = 6$, bottom $= 3 \times 1 + 1 \times 0 = 3$, so the output is $\begin{bmatrix} 6 \\ 3 \end{bmatrix}$.
  • Input $\begin{bmatrix} 0 \\ 1 \end{bmatrix}$: top $= 6 \times 0 + 2 \times 1 = 2$, bottom $= 3 \times 0 + 1 \times 1 = 1$, so the output is $\begin{bmatrix} 2 \\ 1 \end{bmatrix}$.
  • Input $\begin{bmatrix} 1 \\ 1 \end{bmatrix}$: top $= 6 + 2 = 8$, bottom $= 3 + 1 = 4$, so the output is $\begin{bmatrix} 8 \\ 4 \end{bmatrix}$.

Three different inputs, and every answer is some number of copies of $[2, 1]$: three copies, one copy, four copies. That holds for every possible input. The row $\mathbf{A}$ crushes whatever comes in down to a single number, and then the column $\mathbf{B}$ stretches that one number back out along one fixed direction. Everything squeezes through a one-number bottleneck.

That bottleneck width is the knob. Let $r$ numbers through instead of one, where $r$ is the rank, and $\mathbf{B}$ becomes a matrix with $r$ columns, $\mathbf{A}$ a matrix with $r$ rows, and the change can span up to $r$ independent directions. In practice $r$ is small: 4, 8, 16 or 32, against matrices a thousand numbers wide. Small works because the change a narrow fine-tune needs is simple. You are not teaching the model to see. You are teaching it one apple. Everything else about training is unchanged. The loss is Chapter 3's, in the latent, conditioned form Chapters 4 and 6 left it in, $L(\theta) = \|\boldsymbol{\epsilon} - \boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t, \mathbf{c})\|^2$, with the gradient allowed to move only $\mathbf{B}$ and $\mathbf{A}$.

Left to right. A mint column of two boxed numbers, 2 above 1, labeled B, the column. A multiplication sign. A mint row of two boxed numbers, 3 and 1, labeled A, the row. An equals sign. A two-by-two grid of violet-outlined boxes holding 6 and 2 on the top row and 3 and 1 on the bottom, labeled BA, the change grid. To the right, a panel lists all four entries computed one at a time: row 1 column 1, 2 times 3 equals 6; row 1 column 2, 2 times 1 equals 2; row 2 column 1, 1 times 3 equals 3; row 2 column 2, 1 times 1 equals 1. A mint line across the bottom reads: 4 numbers stored — 4 cells reconstructed — every output on one line: rank 1.
A column times a row. Each cell of the change grid is one number from $\mathbf{B}$ times one number from $\mathbf{A}$. No cell is free to be anything else, which is what "low rank" costs you and what makes the file small.

The strength dial, as arithmetic

A LoRA ships with one more number: a scale that says how loudly the learned change should speak. In the update it is the fraction $\frac{\alpha}{r}$:

$$\mathbf{W}' = \mathbf{W} + \frac{\alpha}{r}\,\mathbf{B}\mathbf{A}$$

In words: the working matrix is the frozen matrix plus alpha-over-r times the learned low-rank change.

$r$ is the rank, the bottleneck width we just chose, and $\alpha$ is "alpha, the LoRA scale", a volume knob. Dividing by $r$ is a courtesy to your future self: double the rank and the product $\mathbf{B}\mathbf{A}$ picks up roughly twice as many contributions, so dividing by $r$ keeps the overall loudness comparable when you change rank.

Worked example — the frozen matrix, adapted

Let the frozen matrix be a plain doubling machine, $\mathbf{W} = \begin{bmatrix} 2 & 0 \\ 0 & 2 \end{bmatrix}$, chosen so you can see at a glance what the base model contributes and anything else in the answer must have come from the adapter. Keep $\mathbf{B}\mathbf{A} = \begin{bmatrix} 6 & 2 \\ 3 & 1 \end{bmatrix}$ from the last section, and set $\alpha = 2$ with $r = 1$, so $\frac{\alpha}{r} = \frac{2}{1} = 2$. First scale the change, cell by cell: $2 \times 6 = 12$, $2 \times 2 = 4$, $2 \times 3 = 6$, $2 \times 1 = 2$. Then add it on, cell by matching cell: $2 + 12 = 14$, $0 + 4 = 4$, $0 + 6 = 6$, $2 + 2 = 4$.

$$\mathbf{W}' = \begin{bmatrix} 2 & 0 \\ 0 & 2 \end{bmatrix} + 2\begin{bmatrix} 6 & 2 \\ 3 & 1 \end{bmatrix} = \begin{bmatrix} 14 & 4 \\ 6 & 4 \end{bmatrix}$$

In words: the adapted matrix is the frozen doubler with the scaled change folded into it, one cell at a time.

Now push the input $\begin{bmatrix} 1 \\ 1 \end{bmatrix}$ through both. The frozen matrix gives top $= 2 \times 1 + 0 \times 1 = 2$ and bottom $= 0 \times 1 + 2 \times 1 = 2$. The adapted matrix gives top $= 14 \times 1 + 4 \times 1 = 18$ and bottom $= 6 \times 1 + 4 \times 1 = 10$.

$$\mathbf{W}\begin{bmatrix} 1 \\ 1 \end{bmatrix} = \begin{bmatrix} 2 \\ 2 \end{bmatrix}, \qquad \mathbf{W}'\begin{bmatrix} 1 \\ 1 \end{bmatrix} = \begin{bmatrix} 18 \\ 10 \end{bmatrix}$$

In words: the same input that the base model turned into $[2, 2]$ now comes out as $[18, 10]$. The adapter has bent this one matrix's behavior hard.

Our toy shouts on purpose, so that every digit stays visible. Real adapters nudge. Notice the folding too: once you have computed $\mathbf{W}'$ you can throw the two strips away and run the adapted matrix directly, which is why a merged LoRA costs nothing extra when you generate.

Now the number you turn. In a workflow it is labeled LoRA strength, and it is the multiplier in front of $\mathbf{B}\mathbf{A}$. Sweep it across the same input $[1, 1]$, remembering that $\mathbf{B}\mathbf{A}\begin{bmatrix} 1 \\ 1 \end{bmatrix} = \begin{bmatrix} 8 \\ 4 \end{bmatrix}$ from the worked example above, and the base path contributes $[2, 2]$ every time:

Worked example — the slider is pure arithmetic

$\frac{\alpha}{r}$change contributedoutputwhat the picture does
0$0 \times [8, 4] = [0, 0]$$[2 + 0,\; 2 + 0] = [2, 2]$the concept is absent: this is the base model
0.5$0.5 \times [8, 4] = [4, 2]$$[2 + 4,\; 2 + 2] = [6, 4]$the concept shows up, faintly
1.0$1.0 \times [8, 4] = [8, 4]$$[2 + 8,\; 2 + 4] = [10, 6]$the concept is clearly present
1.5$1.5 \times [8, 4] = [12, 6]$$[2 + 12,\; 2 + 6] = [14, 8]$the change swamps the base model's contribution

Read the middle column against the constant $[2, 2]$ the base path supplies. At 0 the adapter is silent. At 1.0 it contributes four times what the base matrix does on this input, and by 1.5 the base model's opinion is a rounding error. That is what an over-strength LoRA looks like on screen: the concept everywhere, at the cost of composition, background and lighting.

A horizontal strip of four square pictures sharing one prompt and one seed, labeled underneath strength 0, strength 0.5, strength 1.0 and strength 1.5, and beneath those the words absent, faint, correct and swamped. The first is a photograph of a shiny red apple on a wooden table against a gray wall. The second is nearly the same photograph, the apple a little smaller and glossier. The third keeps the wooden table and the gray wall, but the apple has become the book's flat orange apple drawing with dark speckles and a mint leaf. The fourth is the same drawing in pale yellow, and the table, the wall and the lighting have flattened into dull gray-brown bands. A mint bracket under the middle two pictures reads 'the useful band', and a line above the strip reads 'same prompt, same seed: only the multiplier in front of BA changes'.
The same prompt, the same seed, four strengths. Only the multiplier in front of $\mathbf{B}\mathbf{A}$ changes across the strip. At 0.5 the adapter barely registers; at 1.0 its apple is in the picture and the table and wall are still the base model's; at 1.5 it has overwritten the background and the lighting as well, which is the arithmetic above with the adapter's contribution swamping everything the frozen model wanted to say. Stable Diffusion 1.5 with a rank-8 adapter trained on sixteen drawings of this book's speckled apple under the trigger word "zkw apple"; the prompt "a photo of a zkw apple on a wooden table", seed 7, 25 DDIM sampler steps at guidance scale 7.5.

Where the adapter attaches

Which matrices get the treatment? Almost always the ones inside the cross-attention blocks of the U-Net, from Chapter 6, where each patch of the latent asks the conditioning $\mathbf{c}$ what it should be. Those are the matrices that decide how strongly a word in your prompt pulls on a region of the image, so a small correction there is a large change in how the model reads language.

That explains trigger words. Whoever trains an adapter captions the training images with a rare token, "sks apple" or something else the base model has no strong opinion about, and the adapter learns to attach a large correction to that token's pull, because there was nothing there before to interfere with. Leave the trigger word out of your prompt and such an adapter can look inert. Put it in and the whole thing switches on. You are choosing whether the corrected path ever gets a strong query to answer.

Everything else is as you left it. Picture Chapter 4's latent-diffusion-pipeline map again: encoder, loop in latent space, decoder. A LoRA changes nothing about it, not the VAE, not the sampler, and no extra stage. It edits a handful of matrices inside the noise predictor, and Chapter 5's loop runs as before, calling a slightly different function at every sampler step.

Why varied apples matter

Back in Chapter 1 we laid out nine deliberately different apple thumbnails, red and green and golden, spotted, large, small, photographed from two angles, and the caption promised that the difference would matter when we got to LoRAs. This is where that gets paid.

Training an adapter is Chapter 3's procedure on a very small dataset. Encode one of your photographs to a latent, pick a random timestep $t$, add the matching noise using Chapter 2's closed form, ask the noise predictor for that noise, compare, and push $\mathbf{B}$ and $\mathbf{A}$ downhill. Repeat over your twenty or thirty images. The learned change is whatever consistently reduces that loss across your set.

That last sentence contains the trap. The adapter learns whatever is consistent, and it has no idea which parts of your photographs are the concept and which are the circumstances. Say all thirty photographs of your heirloom apple sit on the same wooden table, in the same warm afternoon light, shot from the same height. "Russet shoulder" is consistent across your set, and so is "wooden table", and "warm light", and "shot from above". The gradient cannot tell them apart, so the adapter learns the whole bundle as one concept. Load it, prompt for your apple in a snowfield, and the wooden table comes to the snowfield with it. That is the method working as specified on data that specified the wrong thing.

The fix is variety, and you apply it with a camera rather than a setting. Photograph the apple on a table, on grass, in a hand, against a white wall; morning light, lamp light, flat overcast; close, far, above, side. Now the only thing consistent across the set is the apple, so the only thing the adapter can learn is the apple. Variety isolates the concept from its accidents, and twenty varied photographs beat two hundred from one sitting.

Two rows, each running training data on the left to a generated result on the right. The top row is outlined in red and labeled 'all shot on the same wooden table': five near-identical gray thumbnails, each holding the same amber apple sitting on an amber slab, captioned 'same table, same light, same angle'. A muted arrow leads to a snowfield panel in which the apple appears with the amber slab still under it, labeled in red 'the table came along'. The bottom row is outlined in mint and labeled 'varied backgrounds, light and angles': five thumbnails of five visibly different apples — amber and mint-green, varying in size, shape, lean and speckling — against five different background tints, captioned 'grass, wall, hand, lamplight, overhead'. Its arrow leads to a snowfield panel holding the apple alone with no slab, labeled in mint 'only the apple came along'.
The direct sequel to Chapter 1's varied apples. The adapter learns whatever is consistent across the training set. If the background is consistent, the background is part of the concept, and it gets dragged into every image you generate.

Two neighboring failures deserve names. Overfitting is training for too many passes over too few images: the adapter stops learning "this apple" and starts memorizing "these thirty pictures", and the giveaway is a generation that reproduces a training photo's composition whatever else you ask for. And data quality beats quantity: five sharp, well-lit, correctly captioned photographs teach a cleaner concept than fifty blurry ones, because every blurry image quietly teaches "blurry" as part of the target.

Counting the savings

Now the arithmetic that made the technique famous. Take a weight matrix that is $1{,}000$ numbers wide and $1{,}000$ numbers tall, a typical size inside a model like this. A full change $\Delta\mathbf{W}$ to it has one entry per cell:

$$1{,}000 \times 1{,}000 = 1{,}000{,}000$$

In words: changing that matrix the ordinary way means writing down and training one million numbers.

Now the low-rank version at a typical rank of $r = 8$. The column block $\mathbf{B}$ is $1{,}000$ tall and $8$ wide; the row block $\mathbf{A}$ is $8$ tall and $1{,}000$ wide:

$$\underbrace{(1{,}000 \times 8)}_{\mathbf{B}} + \underbrace{(8 \times 1{,}000)}_{\mathbf{A}} = 8{,}000 + 8{,}000 = 16{,}000$$

In words: sixteen thousand trained numbers stand in for a million.

The ratio, worked: $16{,}000 \div 1{,}000{,}000 = 0.016$, which is $1.6\%$. Multiply that across the handful of cross-attention matrices a LoRA touches and the file lands in the low tens of megabytes, beside a base model of several gigabytes. Those figures are typical of models of this kind rather than laws. It is why a shelf of adapters is practical to own: one frozen base model and a stack of small files, each teaching it one thing.

Because the corrections are additions, they compose. Load two adapters and the corrections add up on each shared matrix, each with its own strength:

$$\mathbf{W}' = \mathbf{W} + s_1\mathbf{B}_1\mathbf{A}_1 + s_2\mathbf{B}_2\mathbf{A}_2$$

In words: the adapted matrix is the frozen matrix plus the first adapter's change at its own strength, plus the second adapter's change at its own strength. These strengths $s_1$ and $s_2$ are LoRA strengths, not Chapter 6's guidance scale $s$, which lives in a different equation.

Composition works right up until it does not. Each adapter was trained separately, assuming the frozen matrix underneath it was untouched and knowing nothing of the other. If they push the same matrices in conflicting directions, say a style adapter that flattens everything against a subject adapter that insists on photographic texture, the sum is a fight rather than a compromise, and the usual repair is turning both strengths down.

See it move

Watch (19:33): the moment the million-cell violet grid freezes and the two mint strips appear beside it, and then the strength dial turning the same apple from absent to correct to scorched, with nothing else in the pipeline moving.

Where you'll meet this

In a workflow graph, this chapter is one node, usually called Load LoRA, sitting after the checkpoint loader, taking the base model in on one wire and handing a modified model out on another. That wiring is the picture of the chapter: the adapter never stands alone in the graph, because it has nothing to do without a model flowing through it.

The number in that node's box is $\frac{\alpha}{r}$ from the sweep table, the same multiplier with the same arithmetic behind it. Some interfaces give you two of them, one for the correction applied inside the U-Net and one for the correction applied to the text encoder, because a LoRA can carry strips for both. And when a model page tells you to include a strange token like "sks apple" in your prompt, you know what you are doing: handing the corrected cross-attention path the one query it was trained to answer loudly.

Three habits fall out of the math. If a generation looks untouched, check the trigger word first. If it looks scorched, drop the strength before you rewrite the prompt, because you are turning down the multiplier in front of $\mathbf{B}\mathbf{A}$ and nothing more. And if a LoRA does nothing at all, check which base model it was trained against. LoRA is the famous member of a larger family of adaptation methods, and the others are a topic for another day.

What you now know

  • A fine-tune never has to rewrite a weight matrix. It can be written as the original plus a change, $\mathbf{W}' = \mathbf{W} + \Delta\mathbf{W}$, and the whole game is describing that change cheaply.
  • A LoRA describes the change as a column times a row: $\mathbf{B} = [2, 1]$ times $\mathbf{A} = [3, 1]$ fills the grid $\begin{bmatrix} 6 & 2 \\ 3 & 1 \end{bmatrix}$, and every output it can produce lies along the single direction $[2, 1]$, which is rank 1.
  • The strength you turn in a workflow is the multiplier $\frac{\alpha}{r}$ in $\mathbf{W}' = \mathbf{W} + \frac{\alpha}{r}\mathbf{B}\mathbf{A}$, and it is pure arithmetic: on our toy it moves the output from $[2, 2]$ at strength 0 to $[18, 10]$ at $\frac{\alpha}{r} = 2$. This $\alpha$ is not the noise schedule's $\alpha_t$.
  • Adapters usually attach to the cross-attention matrices from Chapter 6, which is why trigger words switch them on so sharply, and a rank-8 adapter on a $1{,}000 \times 1{,}000$ matrix trains $16{,}000$ numbers instead of $1{,}000{,}000$, which is $1.6\%$: megabytes riding on gigabytes.
  • The adapter learns whatever is consistent across your training images, so varied backgrounds, light and angles are what separate your apple from the wooden table it sat on. That is the promise Chapter 1's varied apples made.
  • A LoRA is not a model: it holds a correction to specific matrices, it is inert on its own, and it runs only on top of the base model it was trained against.

Where we're headed. You can now teach a frozen model one new thing with a file small enough to email, and read every number on the node that loads it. What you cannot do is make it last longer than an instant. The adapter bends the noise predictor, the sampler walks its loop, the decoder hands back a picture, and that is the end of it. Chapter 8 adds the missing axis, treating a whole sequence of frames as one object and using temporal attention to keep the apple the same apple from frame to frame.