Linear algebra for LLMsPart 7: Decompositions and low rank

LoRA

7 min

Freeze the weight, learn a low-rank update

W′=W+αr BA\displaystyle W' = W + \frac{\alpha}{r}\,BA
B ⁣:out×r,A ⁣:r×in\displaystyle B\!: \text{out} \times r, \qquad A\!: r \times \text{in}

LoRA (low-rank adaptation) keeps the pretrained WW fixed and trains two thin matrices. Their product BABA has WW’s shape but rank at most rr (Part 3), with rr small: 8, 16 or 64.

Write

W′=W+αrBAW' = W + \frac{\alpha}{r}BA, B ⁣:(out,r)B\!: (\text{out}, r), A ⁣:(r,in)A\!: (r, \text{in}).

In the forward pass

y=xW⊤+αr (xA⊤) B⊤\displaystyle y = xW^\top + \frac{\alpha}{r}\,(xA^\top)\,B^\top

In nn.Linear layout: AA maps the input down to rr numbers, BB maps them back up. The out×in\text{out} \times \text{in} product is never formed during training (Part 2’s cheap ordering).

BB starts at zero

B=0  ⇒  BA=0  ⇒  W′=W at step 0\displaystyle B = 0 \;\Rightarrow\; BA = 0 \;\Rightarrow\; W' = W \text{ at step } 0

AA starts random and BB starts at zero, so training begins from exactly the pretrained model and the update grows from nothing. α/r\alpha/r scales the update so that changing rr needs less retuning of the learning rate.

Write

Init: AA random, B=0B = 0, so W′=WW' = W at the start.

How many parameters

r (in+out)\displaystyle r\,(\text{in} + \text{out})
4096×4096, r=8 ⁣:8×8192=65,536 vs 16,777,216\displaystyle 4096 \times 4096,\ r = 8\!: \quad 8 \times 8192 = 65{,}536 \text{ vs } 16{,}777{,}216

0.39% of the matrix. Gradients and optimizer state are only needed for these, which is where most of LoRA’s memory saving comes from.

Write

LoRA params per matrix: r(in+out)r(\text{in} + \text{out}).

✏️ Your turn

Adapt a real model

Llama 3 8B: WQ (4096→4096), WV (4096→1024), 32 layers, r=16\displaystyle \text{Llama 3 8B: } W_Q\ (4096 \to 4096),\ W_V\ (4096 \to 1024),\ 32 \text{ layers},\ r = 16

How many LoRA parameters if you adapt WQW_Q and WVW_V in every layer? Roughly what fraction of 8.03B is that?

Work it out on paper, then go to the next card to check.

Answer: about 6.8 million

WQ ⁣:16(4096+4096)=131,072\displaystyle W_Q\!: 16(4096 + 4096) = 131{,}072
WV ⁣:16(4096+1024)=81,920\displaystyle W_V\!: 16(4096 + 1024) = 81{,}920
32×212,992=6,815,744≈0.085%\displaystyle 32 \times 212{,}992 = 6{,}815{,}744 \approx 0.085\%

Under a tenth of a percent of the model. The original LoRA paper found that adapting these two projections alone worked well.

Merge for free inference

Wmerged=W+αrBA\displaystyle W_{\text{merged}} = W + \frac{\alpha}{r}BA

After training, add the update into the weight once. The merged layer is an ordinary matrix of the same shape: no extra parameters or FLOPs at inference. (Or keep it separate, to swap many adapters on one base model.)

Write

Merge: W←W+αrBAW \leftarrow W + \frac{\alpha}{r}BA. Zero inference overhead.

In code

class LoRALinear(nn.Module):
    def __init__(self, base: nn.Linear, r=8, alpha=16):
        super().__init__()
        self.base, self.scale = base, alpha / r
        self.A = nn.Parameter(torch.randn(r, base.in_features) / r)
        self.B = nn.Parameter(torch.zeros(base.out_features, r))
        base.weight.requires_grad_(False)

    def forward(self, x):
        return self.base(x) + self.scale * (x @ self.A.T) @ self.B.T

A sketch of the idea; libraries such as PEFT add dropout, initialization choices and merging.

📌 Recap

LoRA in one card

  • W′=W+αrBAW' = W + \frac{\alpha}{r}BA, B ⁣:(out,r)B\!: (\text{out}, r), A ⁣:(r,in)A\!: (r, \text{in}), WW frozen.
  • B=0B = 0 at the start, so training begins at the pretrained model.
  • r(in+out)r(\text{in} + \text{out}) parameters per adapted matrix.
  • Merge after training: no inference cost.

Write

The four lines above.