Visual walkthrough — LoRA and QLoRA
A visual walkthrough of why a giant weight update can be squeezed into two skinny matrices — built from absolute zero, one picture per step.
This is the picture-first companion to LoRA and QLoRA. We assume you have seen a grid of numbers (a matrix) and nothing else. Every symbol is earned before it is used.
If you want the neighbouring ideas: Matrix Rank and Low-Rank Approximation, Full Fine-Tuning vs PEFT, Adam Optimizer memory cost, Quantization of Neural Networks, Attention Q K V Projections.
Step 1 — What a linear layer actually is
Call the input list . It has numbers, so we say — read that as " is a point in -dimensional space", i.e. a list of real numbers.
The recipe grid is . It has rows and columns, written . The output is:
- ::: the incoming list of numbers.
- ::: the frozen, already-trained recipe grid ( rows columns).
- ::: the outgoing list of numbers (one per row of ).
WHY this matters: every number inside is a "weight" we could in principle change during fine-tuning. There are of them — for a real layer that is millions.
PICTURE: the grid on the left, the input column on its right, the output column below — each output cell is one row of the grid combined with .

Step 2 — Fine-tuning wants to add a correction grid
- ::: the original grid, kept exactly.
- ::: the correction grid — same shape — this is what fine-tuning learns.
- ::: the new, adapted grid.
The Greek letter ("delta") is the standard maths symbol for "the change in". So literally reads "the change in ".
WHY this is a problem: is also . Learning it fully costs as much memory as itself — and training memory (via Adam) is several times worse. We have bought nothing.
PICTURE: two same-size grids stacked, a "+" between them, giving a same-size result. Notice how fat is — that fatness is the enemy.

Step 3 — The rank insight: many grids are secretly thin
Look at the picture: a grid where every column is a multiple of . Sixteen numbers, but you could rebuild all of them from one column and four multipliers. That grid has rank 1.
WHY we care: experiments show the correction during fine-tuning behaves like a low-rank grid — it only needs a handful of independent directions, not of them. The task-specific change is genuinely thin. (Deeper background: Matrix Rank and Low-Rank Approximation.)
PICTURE: on the left a full "rank-many" grid of scattered colours; on the right a rank-1 grid whose columns are obviously the same pattern rescaled.

Step 4 — A thin grid factors into two skinny grids
- ::: the rank we allow — a small number we choose, like . The symbol means "much smaller than", so .
- ::: tall and skinny — rows, only columns.
- ::: short and wide — only rows, columns.
- ::: multiply them back and you get a full grid whose rank is at most .
Why this factorization and not something fancier? Because a product can produce any rank- grid and nothing wider. It is the smallest honest description of a rank- correction — exactly the restriction we wanted.
WHY the win is huge: we now train numbers instead of .
For : full , LoRA — a 256× shrink.
PICTURE: the fat correction grid on the left; an arrow to a tall skinny times a short wide on the right, with the tiny shaded areas showing how little we now store.

Step 5 — Plugging the factors into the forward pass
Read it left to right:
- ::: the original frozen layer, untouched.
- ::: the low-rank correction applied to the same input. Compute it right-to-left: first (a tiny -length list), then turns it back into a -length list. Cheap.
- ::: a scaling knob (a plain number, e.g. ).
- ::: divides by the rank so the update's strength doesn't balloon when you pick a bigger .
WHY divide by : as grows, sums more terms and naturally gets larger. Dividing by keeps the effective push roughly constant, so you don't re-tune the learning rate every time you change rank.
PICTURE: input flows into two parallel paths — the wide frozen path and the narrow bottleneck path — and their outputs add.

Step 6 — The zero-init edge case (why starts empty)
At step 0:
- ::: the all-zeros grid; zero times anything is zero.
WHY this exact choice: if both and were random, the very first output would be — we'd inject noise into a carefully trained model and see a sudden loss spike. Starting from means the model at step 0 is identical to the pretrained one, and the first gradient step is a pure improvement direction.
PICTURE: two loss-vs-step curves — the zero-init curve starts at the pretrained loss and descends smoothly; the both-random curve leaps up first, then struggles back down.

Step 7 — The merge: paying zero at inference
- ::: "becomes" — overwrite with the merged grid.
- After merging, is one plain grid again. No bottleneck path, no extra work at inference.
WHY this beats adapter layers: adapters bolt on extra sub-networks that always run, adding latency. LoRA's correction can be absorbed into the weight it corrects, so a served model runs exactly as fast as the original. (The one exception: if you swap many different adapters on the fly, you keep them separate and skip merging.)
PICTURE: the two-path diagram collapses via an arrow into a single merged grid identical in shape to the original.

The one-picture summary
Everything above in a single flow: the fat forbidden , its factoring into skinny and , the parallel forward pass, and the final merge back to one grid.

Recall Feynman: the whole walkthrough in plain words
A layer is a grid of numbers that mixes your inputs. To adapt it we want to add a correction grid, but a full correction is just as huge as the original — no savings. The rescue: the correction you actually need is "thin" (low rank), meaning it hides only a few real directions. A thin grid always splits into a tall-skinny grid times a short-wide grid , and those two together are tiny. We run the frozen original and the little bottleneck side by side and add their outputs. We start at all zeros so the correction is invisible on step one and we never shock the trained model. When training finishes we fold the correction back into the original grid, so serving the model costs nothing extra. That is LoRA: two small decorations you can bolt on, learn cheaply, and melt back in.
Recall
Rank of a grid whose every column is a multiple of one column? ::: 1. Shapes of and in ? ::: is , is . Parameters trained per matrix vs full? ::: instead of . Why is initialised to zero? ::: So at step 0 and the model starts identical to pretrained. Why divide by ? ::: Keeps update strength stable as rank changes, so the learning rate need not be retuned. Inference cost after merging ? ::: Zero extra — it's one ordinary grid again.