VerifiedarXiv:2106.0968520 min
Training · LLMs

LoRA: Low-Rank Adaptation of Large Language Models

Fine-tune a large model by freezing its weights and training a low-rank update beside them.

On GPT-3 175B, LoRA trains about 10,000 times fewer parameters than full fine-tuning, needs about a third of the GPU memory, matches its accuracy, and folds into the weights after training so the model runs at its original speed.

Explaining the paperLoRA: Low-Rank Adaptation of Large Language ModelsHu, Shen, Wallis, Allen-Zhu, Li, Wang, Wang, Chen · ICLR 2022 · arXiv:2106.09685 ↗

The paper's hypothesis is that the change fine-tuning makes to a weight matrix spans only a few directions. It builds the method on that hypothesis and tests it on models up to GPT-3 175B.

LoRA (Low-Rank Adaptation) is a way to specialize a pretrained language model to a task, such as SQL generation or dialogue summarization, without retraining all of its weights. It came out of Microsoft in 2021 and is now one of the most widely used ways to fine-tune large models on modest hardware. The page builds it from a few ideas, in this order: what full fine-tuning keeps in memory, the fact that fine-tuning learns a change to each weight matrix, what the rank of a matrix measures, the hypothesis that the change has low rank, the layer that follows from it, how that layer is initialized and scaled, why it costs nothing at inference, and what the experiments show.

What full fine-tuning costs

Full fine-tuning continues training every weight of the pretrained model on task data. For GPT-3, that is 175 billion weights. Stored as 16-bit floats (2 bytes each), the weights alone take 350 GB. Training needs more than the weights. Every trained weight gets a gradient of the same size, and the Adam optimizer the paper uses keeps two running averages per weight, a mean of the gradient and a mean of its square. So a trained weight costs roughly four times its own storage before a single activation is counted.

The paper measures this on GPT-3 175B: full fine-tuning with Adam needs 1.2 TB of GPU memory; LoRA needs 350 GB. The difference comes almost entirely from optimizer state and gradients that LoRA never creates for the frozen weights. Each fine-tuned copy is also another 350 GB checkpoint on disk, so serving 100 tasks as 100 full fine-tunes means about 35 TB of weights. With LoRA the paper puts the same 100 tasks at about 354 GB: one shared base model plus 100 adapters of about 35 MB each.

Figure 1 draws the training-memory bill in units of the weight memory. Slide the model size and watch the ratio of trainable parameters in the corner.

Figure 1 · what fine-tuning keeps in memory
175B params
Memory in multiples of the weight memory (activations not shown). Full fine-tuning holds the weights, a same-sized gradient, and Adam's two running moments for every parameter. LoRA holds the same weights frozen, with no gradient or optimizer state, and trains only a small adapter, drawn taller than its true size so it is visible. The ratio uses the paper's GPT-3 setting, rank-4 adapters on Wq and Wv in every layer.

At 175B the readout says about 9,300× fewer trainable parameters: 175 billion against 18.9 million. The paper rounds this to 10,000×. At 125M parameters the same recipe gives about 850×: the adapter shrinks with the width d, but the full matrices shrink with d², so the ratio grows with the width.

Fine-tuning learns an update

A linear layer multiplies its input by a weight matrix. Write the pretrained matrix as W0∈Rd×kW_0 \in \mathbb{R}^{d\times k}: it takes a kk-dimensional input vector xx and returns a dd-dimensional output h=W0xh = W_0 x. Fine-tuning changes the matrix to W0+ΔWW_0 + \Delta W, so the layer computes

h=(W0+ΔW) x=W0x+ΔWx,ΔW∈Rd×kh = (W_0 + \Delta W)\,x = W_0 x + \Delta W x, \qquad \Delta W \in \mathbb{R}^{d\times k}
(1)

Everything fine-tuning learns is in ΔW\Delta W. It has the same shape as W0W_0, so a model with 175 billion weights has an update with 175 billion entries, each with its own gradient and optimizer state. The question LoRA asks is whether ΔW\Delta W needs that many independent numbers, or whether a much smaller set of numbers can generate it.

Rank and low-rank factors

The rank of a matrix is the number of linearly independent columns it has; equivalently, the number of independent rows. A small example shows what that means for storage. This 3×4 matrix has 12 entries:

[2468123436912]=[213][1234]\begin{bmatrix} 2 & 4 & 6 & 8\\ 1 & 2 & 3 & 4\\ 3 & 6 & 9 & 12 \end{bmatrix} = \begin{bmatrix} 2\\ 1\\ 3 \end{bmatrix} \begin{bmatrix} 1 & 2 & 3 & 4 \end{bmatrix}

Every row is a multiple of [1,2,3,4][1,2,3,4], so the rank is 1 and the matrix is the product of a 3×1 column and a 1×4 row: 7 numbers instead of 12. A rank-2 matrix is a sum of two such products, which is the product of a 3×2 matrix and a 2×4 matrix. In general, any d×kd\times k matrix of rank rr can be written as

ΔW=BA,B∈Rd×r,  A∈Rr×k,  r≪min⁡(d,k)\Delta W = B A, \qquad B \in \mathbb{R}^{d\times r},\ \ A \in \mathbb{R}^{r\times k},\ \ r \ll \min(d,k)
(2)

and storing BB and AA costs r(d+k)r(d+k) numbers instead of dkdk. For one GPT-3 attention matrix, d=k=12288d = k = 12288, so the full matrix has 150,994,944 entries. At r=8r = 8 the factors hold 8×(12288+12288)=196,6088 \times (12288 + 12288) = 196{,}608 numbers, 768 times fewer. That ratio is for a single matrix; the whole-model 10,000× figure is larger because LoRA leaves most matrices without an adapter at all.

Real matrices are rarely exactly low-rank, but many are close to it. The singular value decomposition (SVD) writes any matrix as a sum of rank-1 pieces, sorted by size:

M=∑i=1min⁡(d,k)σi uivi⊤,σ1≥σ2≥⋯≥0M = \sum_{i=1}^{\min(d,k)} \sigma_i\, u_i v_i^{\top}, \qquad \sigma_1 \ge \sigma_2 \ge \dots \ge 0

where the uiu_i are orthonormal output directions, the viv_i orthonormal input directions, and the singular value σi\sigma_i says how much the matrix stretches along that pair. Keeping the first rr terms gives the closest rank-rr matrix in the Frobenius norm (the square root of the sum of squared entries), a result known as the Eckart–Young theorem, and the squared error is the sum of the dropped σi2\sigma_i^2. When the singular values fall off fast, a few terms carry almost all of the matrix. Figure 2 rebuilds a 28×28 matrix whose singular values shrink by a factor of 0.62 per term; drag the rank and read the energy kept (the fraction of the total ∑σi2\textstyle\sum \sigma_i^2) under the panels.

Figure 2 · low-rank reconstruction
r = 2
params: r(d+k) = 112 vs d·k = 784 (14% · 7.0× fewer) · energy kept 85.2%
Left, the full matrix. Right, its rank-r reconstruction from the first r SVD terms, stored as r(d+k) numbers instead of d·k. At r = 2 the reconstruction already keeps 85% of the energy from 14% of the numbers.

Figure 3 plots the two costs of the choice against each other for the same matrix. The number of stored values grows linearly with rr, one row of AA and one column of BB per step. The reconstruction error falls quickly at first and then flattens, because each added term carries less than the one before it.

Figure 3 · the rank tradeoff
14% params · 38% err
Stored values (as a percentage of d·k) rise linearly with r; the relative reconstruction error falls steeply and then flattens. Past the bend, each extra rank costs as much as the first one and buys little.

The low-rank hypothesis

The pretrained W0W_0 is typically full-rank, and LoRA does not compress it. The paper's hypothesis concerns only the update: that ΔW\Delta W has a low “intrinsic rank”, meaning a rank-rr matrix with small rr can do the job of the full update during adaptation. It is a hypothesis, not a theorem, and the paper tests it empirically in its Section 7.

It comes from earlier work on intrinsic dimension. Li et al. (2018) trained networks inside a random low-dimensional subspace of their parameters and measured how small the subspace could be before accuracy dropped. Aghajanyan et al. (2020) did the same for fine-tuning pretrained language models: optimizing only 200 parameters, projected back into the full parameter space by a fixed random matrix, brought RoBERTa to 90% of its full fine-tuning performance on the MRPC paraphrase task. If a few hundred numbers can steer a whole model, the per-matrix update plausibly lives in a few directions too.

A reading of why this could hold: pretraining builds general features and uses the full width of each matrix to do it. Adapting to one task mostly changes how strongly some of those existing features are used, and a change concentrated in a handful of input and output directions is, by definition, a low-rank matrix. The paper's Section 7.3 measures something close to this on GPT-3, reported at the end of the page.

Why not use the obvious alternatives? Training only a subset of existing weights (the last layers, or the biases) restricts which weights may change, but LoRA restricts the shape of the change and still lets every adapted matrix move. Random-subspace training, the Aghajanyan method, needs a projection from the subspace to all parameters on every step. Adapters and prefix tuning, the two parameter-efficient methods the paper compares against, each carry a cost at use time: adapters add sequential layers, and prefix tuning reserves part of the input sequence for learned tokens and is hard to optimize.

The LoRA layer

LoRA builds the hypothesis into the layer. It freezes W0W_0, adds a second path next to it that computes BAxBAx, and sums the two outputs:

h=W0x+αr BA x,A random,  B=0  (at init)h = W_0 x + \frac{\alpha}{r}\,B A\,x, \qquad A\ \text{random},\ \ B = \mathbf{0}\ \ (\text{at init})
(3)

AA projects the kk-dimensional input down to rr numbers, and BB projects those back up to dd. The paper's own Eq. (3) omits the gain α/r\alpha/r and introduces it in the next paragraph; (3) above includes it, as the code does. Only AA and BB are trained. The next section covers the initialization and the gain.

The listing below runs one training step through GPT-3's query projection with r=4r = 4, for a batch of nn token vectors. The frozen path is an ordinary matmul. The LoRA path first shrinks each token to 4 numbers, then expands it back to 12,288, and adds that to the frozen output. The extra compute is r(d+k)/(dk)r(d+k)/(dk), about 0.07% of the layer's matmul.

# one training step through one adapted layer (GPT-3 W_q, r = 4)
# x: (n, 12288), n token vectors entering the layer
h = x @ W0.T                  # (n, 12288)  W0 frozen: no grad, no Adam state
z = dropout(x) @ A.T          # (n, 4)      A: (4, 12288), trainable
h = h + (alpha / r) * (z @ B.T)   # (n, 12288)  B: (12288, 4), trainable
loss.backward()   # gradients for A and B only: 4 * (12288 + 12288) = 98,304
opt.step()        # Adam keeps m and v for those 98,304 numbers, nothing else

Figure 4 lays the product BABA out like a multiplication table. Every entry of ΔW\Delta W is the dot product of one row of BB (rr numbers) with one column of AA (rr numbers). Hover a cell to see which row and column produce it, and set r=1r = 1 to see a single outer product: every row of ΔW\Delta W is then a scaled copy of the same pattern, as in the 3×4 example above.

Figure 4 · two thin factors make a full matrix
r = 2 · 80 of 400 numbers
A above, B to the left, their product ΔW = BA below. Drag the rank from 1 to 8: the product gains structure while the trained count stays 2·d·r against the d² entries it fills (80 of 400 at r = 2 here; 196,608 of 150,994,944 for a GPT-3 matrix at r = 8).

Figure 5 shows where the path sits in the layer. The same input xx goes through the frozen W0W_0 and through AA then BB, and the two outputs are added. Its other two buttons belong to the merging section below.

Figure 5 · where LoRA plugs in
LoRA, training: the frozen W₀ and a trainable rank-r path in parallel, summed. LoRA, merged: after training the path is folded into one matrix. adapter (2019): the earlier alternative, a bottleneck module in series after the layer.

The corresponding lines of the authors' loralib, abridged:

# loralib/layers.py, class Linear (abridged; line numbers from main)
self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features)))
self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))
self.scaling = self.lora_alpha / self.r                      # line 112
self.weight.requires_grad = False                            # line 114

nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))        # line 124
nn.init.zeros_(self.lora_B)                                  # line 125

def forward(self, x):                      # while not merged
    result = F.linear(x, T(self.weight), bias=self.bias)
    result += (self.lora_dropout(x) @ self.lora_A.transpose(0, 1)
               @ self.lora_B.transpose(0, 1)) * self.scaling  # line 149
    return result

# model.eval() merges once:                                  # line 141
#   self.weight.data += T(self.lora_B @ self.lora_A) * self.scaling
# model.train() subtracts the same product again (line 135).

The code fixes three details the paper text leaves open. AA is initialized with Kaiming-uniform values (uniform in ±1/k\pm 1/\sqrt{k} for this setting), where the paper says Gaussian. The gain is exactly lora_alpha / r. And dropout is applied to the input of the LoRA path only, never to the frozen path; the paper text does not mention it.

Initialization and the α/r gain

BB starts at zero, so BA=0BA = 0 and the model at step 0 is exactly the pretrained model. The first step still changes the model, because only one of the two factors is zero. With gg the gradient of the loss with respect to the layer output hh, and s=α/rs = \alpha/r, the gradients of the two factors are

∂L∂B=s g (Ax)⊤,∂L∂A=s B⊤g x⊤\frac{\partial L}{\partial B} = s\, g\,(A x)^{\top}, \qquad \frac{\partial L}{\partial A} = s\, B^{\top} g\, x^{\top}

At step 0, AA is random, so the gradient on BB is nonzero and BB moves. The gradient on AA contains BB and is exactly zero, so AA starts moving on the second step. If both factors started at zero, both gradients would be zero forever and nothing would train. Zeroing AA and randomizing BB would also work, by the same argument with the roles swapped.

The gain α/r\alpha/r handles a different problem: how the size of each step depends on rr. Adam moves every parameter by roughly the learning rate per step, whatever the size of its gradient. Each entry of BABA is a sum of rr products, so with more columns in BB the same learning rate changes BABA by more per step. Dividing by rr cancels that growth. The paper sets α\alpha to the first rr it tries and never tunes it, and says the scaling reduces the need to retune hyperparameters when rr changes. It also notes that, under Adam, tuning α\alpha is roughly the same as tuning the learning rate.

Figure 6 trains a LoRA adapter on a toy problem to show both effects. The target ΔW∗\Delta W^* is a 16×16 matrix with eight directions of decreasing size, and each run fits (α/r)BA(\alpha/r)BA to it with Adam (learning rate 0.02), B=0B = 0, AA uniform as in loralib, and α=4\alpha = 4, so the gain is 1 at r=4r = 4. Press Play, then compare ranks and switch the gain off.

Figure 6 · training from B = 0, with and without α/r
step 40
Left, the target update and the adapter's current product, blank at step 0. Right, the remaining error for r = 1, 2, 4, 8, 16, with the selected rank highlighted. Scrub the steps, pick a rank, and toggle the gain.

Every run starts at error 1.000, because the adapter's product is zero. The rank-1 run stalls at 0.313, the rank-2 run at 0.094 and the rank-4 run at 0.009. Those are the Eckart–Young floors for this target: the fraction of its squared norm outside its best 1, 2 or 4 directions, so no matrix of that rank can do better. Ranks 8 and 16 reach zero, because the target has eight directions; the extra capacity at r=16r = 16 buys nothing. With the gain on, ranks 2 through 16 sit between 0.49 and 0.56 at step 10. With it off, the same learning rate leaves rank 1 at 0.92 and takes rank 16 to 0.13 by step 10: raising rr acts like raising the learning rate, and a learning rate tuned at one rank would be wrong at another.

Merging: no inference latency

The LoRA path is linear and runs in parallel with W0W_0, so after training it can be added into the weight matrix once:

W=W0+αrBA⟹h=WxW = W_0 + \frac{\alpha}{r} B A \quad\Longrightarrow\quad h = W x
(4)

WW has the same shape as W0W_0. The deployed model is an ordinary model with ordinary weights, and it runs one matmul per layer, the same as the base model, so LoRA adds no inference latency by construction. In loralib this happens when you call model.eval(), and model.train() subtracts the product again. Switching a deployed model to another task works the same way: subtract BABA, add a different B′A′B'A'. Click LoRA, merged in Figure 5 to see the single path.

Adapters (Houlsby et al., 2019), the method LoRA is mainly compared against, insert small bottleneck modules after existing layers: project down to a few dimensions, apply a nonlinearity, project back up, add a skip connection. They also train few parameters, but the nonlinearity means they cannot be folded into the weights, and each one is an extra step that must run after the layer before it. Click adapter (2019) in Figure 5 to see the serial path. The paper measures GPT-2 medium on one GPU (its Table 1). At batch size 32 and sequence length 512, adapters add 2.2% to 3.0% to a 1,449 ms forward pass. At batch size 1 and sequence length 128, the setting of online serving, they add 20.7% to 30.3% to a 19.8 ms pass. The paper attributes this to the adapter layers running sequentially, which the GPU's parallelism cannot hide when the batch is small.

A merged model serves one task. The paper notes that once the adapters are absorbed into the weights, batching requests for different tasks (each with its own AA and BB) through one forward pass is no longer straightforward. A multi-task deployment keeps the adapters unmerged and pays for the small extra path on every request.

Which matrices, which rank

A Transformer layer has four weight matrices in self-attention, the query, key, value and output projections Wq,Wk,Wv,WoW_q, W_k, W_v, W_o, each dmodel×dmodeld_{\text{model}}\times d_{\text{model}}, and two in the MLP. The paper adapts only attention matrices and freezes the MLP, for simplicity and parameter efficiency; it says LoRA could go on any subset of weight matrices. GPT-3 175B has 96 layers and dmodel=12288d_{\text{model}} = 12288. The standard setting of the paper, r=4r = 4 on WqW_q and WvW_v in every layer, trains

96×2×4×(12288+12288)=18,874,368 parameters96 \times 2 \times 4 \times (12288 + 12288) = 18{,}874{,}368 \ \text{parameters}

which is about 36 MiB at 2 bytes each. The paper calls it roughly 35 MB and compares it with the 350 GB checkpoint of a full fine-tune: the roughly 10,000× smaller checkpoint in the abstract.

With a fixed budget of about 18M parameters, the paper tried seven ways to spend it (Table 5): all of it on one matrix at r=8r = 8, on two matrices at r=4r = 4, or on all four at r=2r = 2. Putting it all on WqW_q or WkW_k scores clearly lower on WikiSQL, a benchmark of turning English questions into SQL queries (70.4 and 70.0), than splitting it over WqW_q and WvW_v (73.7). The paper concludes that a budget is better spread over more matrices at a lower rank, though WoW_o alone at r=8r = 8 (73.2) does beat WqW_q and WkW_k together at r=4r = 4 (71.4). Table 6 then sweeps the rank for three choices of matrices. For {Wq,Wv}\{W_q, W_v\}, WikiSQL accuracy runs 73.4, 73.3, 73.7, 73.8 and 73.5 at rr = 1, 2, 4, 8 and 64: flat within the ±0.5 spread the paper reports between runs. WqW_q alone needs a larger rank and still scores lower. Figure 7 plots both tables.

Figure 7 · rank and matrix choice on GPT-3 175B
The paper's Tables 5 and 6. rank sweep: accuracy against r for Wq alone, Wq, Wv, and all four attention matrices. 18M budget: seven ways to spend the same parameter count. Whiskers show the typical run-to-run spread; the dashed line is full fine-tuning. Hover a point for its value.

Rank 1 on WqW_q and WvW_v already reaches 73.4 on WikiSQL and 91.3 on MultiNLI, a benchmark of deciding whether one sentence implies, contradicts, or is neutral to another. The paper checks why with a subspace comparison: it trains adapters at r=8r = 8 and r=64r = 64, takes the singular vectors of each learned AA, and measures how much the top directions of one span the top directions of the other, on a scale from 0 (no overlap) to 1 (the same subspace). The top direction is shared, with similarity above 0.5; the remaining directions of the r=64r = 64 adapter overlap little with the r=8r = 8 one, and the paper reads them as mostly noise accumulated during training. That result is specific to these tasks. The paper warns that a task far from the pretraining data, such as one in a different language, may need a much larger rr, up to retraining the whole matrix.

Results and limits

On GPT-3 175B (Table 4), full fine-tuning trains 175,255.8M parameters and scores 73.8 on WikiSQL, 89.5 on MultiNLI-matched, and 52.0/28.0/44.5 ROUGE-1/2/L on SAMSum dialogue summarization (ROUGE counts word and word-pair overlap with reference summaries). LoRA with 4.7M trainable parameters scores 73.4, 91.7 and 53.8/29.8/45.9; LoRA with 37.7M scores 74.0, 91.6 and 53.4/29.2/45.1. The 4.7M configuration is rq=rv=1r_q = r_v = 1 (or rv=2r_v = 2 alone), and the 37.7M one is rq=rv=8r_q = r_v = 8 or r=4r = 4 on all four attention matrices. LoRA matches or beats full fine-tuning on these three tasks, with the 4.7M version 0.4 points behind on WikiSQL, inside the ±0.5 run-to-run spread.

On RoBERTa, DeBERTa and GPT-2 the comparison comes out the same way as on GPT-3: LoRA on par with or better than full fine-tuning. On GLUE, a suite of eight language-understanding tasks, RoBERTa-base with 0.3M LoRA parameters averages 87.2 against 86.4 for full fine-tuning of all 125M; DeBERTa XXL with 4.7M averages 91.3 against 91.1 for all 1.5B. On E2E, which asks for a sentence describing a restaurant from a list of its attributes, GPT-2 medium with 0.35M LoRA parameters reaches 70.4 BLEU (n-gram overlap with reference sentences) against 68.2 for full fine-tuning.

Training is also faster. On GPT-3 175B the paper measures 43.1 tokens per second per V100 GPU with LoRA against 32.5 for full fine-tuning, which it reports as a 25% speedup: the backward pass skips the weight gradients of every frozen matrix. Memory drops from 1.2 TB to 350 GB, the “3×” of the abstract, and the paper puts the general saving at up to two-thirds of training VRAM when r≪dmodelr \ll d_{\text{model}}.

Section 7.3 of the paper looks at what the learned update does. For WqW_q at r=4r = 4, the update has Frobenius norm 6.91, against 61.95 for WqW_q itself. Projecting WqW_q onto the four directions of the update gives a norm of only 0.32, compared with 21.67 on WqW_q's own top four directions and 0.02 on four random directions. So the update points at directions that already exist in the pretrained matrix (0.32 is well above random) but that the matrix barely uses (0.32 is far below 21.67), and it scales them up by about 6.91 / 0.32 ≈ 21.5. The paper's reading is that LoRA amplifies features learned in pretraining that matter for the task but were not emphasized.

You still choose which matrices to adapt and at what rank, and the paper's guidance comes from a few tasks on a few models. A merged model serves one task. The frozen base stays fully in memory, so LoRA reduces the training overhead and the per-task storage, never the 350 GB of base weights. Activations for the backward pass are still stored, because the gradient has to pass through every frozen layer to reach the adapters below it. The most-used follow-up, QLoRA, shrinks the base: it stores the frozen weights in 4 bits and trains LoRA adapters on every linear layer on top of them.

Provenance Verified against primary literatureHow we verify
LoRA (Hu et al., 2021)The method, the α/r scaling, B = 0 at initialization, the adapter-latency measurements (Table 1), and the GPT-3 175B results (Tables 4 to 7). Numbers on this page are from arXiv v2.
loralib (official code)microsoft/LoRA, loralib/layers.py, class Linear (lines 90 to 150): scaling = lora_alpha / r (line 112); the pretrained weight frozen with requires_grad = False (114); A Kaiming-uniform and B zero (124 to 125); merge on eval() and un-merge on train() (135, 141); dropout on the LoRA input only (149).
Li et al. (2018); Aghajanyan et al. (2020)Intrinsic dimension: a model can be fine-tuned inside a small random subspace of its parameters (200 parameters reach 90% of full performance for RoBERTa on MRPC). LoRA takes its hypothesis from these results; the hypothesis itself is tested only empirically.
Houlsby et al. (2019)Bottleneck adapters, the parameter-efficient method LoRA is measured against for inference latency.
Eckart and Young (1936)The truncated SVD is the best rank-r approximation of a matrix in Frobenius norm; the squared error is the sum of the discarded squared singular values.
Yang and Hu (2021)The width-scaling argument the paper cites for the α/r gain.
This pageThe 768× per-matrix count, the 18.9M and 9,300× figures, the forward-pass shapes, and the toy training runs in Figure 6 are this page’s arithmetic and simulation on the paper’s settings, not numbers the paper prints.
correctionInitialization: the paper (Section 4.1 and its Figure 1) says A is drawn from a random Gaussian; the released code initializes A with Kaiming-uniform (nn.init.kaiming_uniform_, a = √5) and B with zeros (layers.py lines 124 to 125). The comment directly above those lines says the opposite ("initialize B the same way as the default for nn.Linear and A to zero"); the executed lines are what we teach. The comment adds that the change "should not affect performance". Second, Appendix D.4 lists the 37.7M GPT-3 configuration as r = 2 on all four attention matrices. That count is wrong: r = 2 on four matrices is 18.9M parameters, and the paper’s own Table 15 gives 37.7M for r = 4 on four matrices (or r = 8 on W_q and W_v). Minor: Section 7.2 names the fourth matrix W_c; it is W_o.

Questions you might still have

?

If W₀ is full-rank, how can a low-rank ΔW be enough?
LoRA never approximates W₀. It keeps every one of W₀’s directions, frozen, and approximates only the change that adapts it to a task. That the change is low-rank is a hypothesis; the evidence is that r = 1 on W_q and W_v already matches larger ranks on GPT-3 (73.4 on WikiSQL, 91.3 on MultiNLI) and that the top learned direction is shared between r = 8 and r = 64 runs.

?

Why initialize B to zero and A randomly, and not both to zero?
B = 0 makes BA = 0, so training starts exactly at the pretrained model. If both were zero, the gradient on B (which is proportional to A) and the gradient on A (proportional to B) would both be zero, and nothing would ever move. With A random, the first step moves B, and A starts moving from the second step on.

?

How should I set α and r?
The paper sets α to the first r it tries and never tunes it, then treats the rank as the only knob. With Adam, α acts roughly like the learning rate, so a changed α needs a re-tuned learning rate. For r, the GPT-3 experiments put r = 1 to 8 on W_q and W_v within 0.5 points of each other on both tasks; a task far from the pretraining data may need more.

?

Does LoRA make the model smaller or faster to run?
No. The frozen base model is still fully resident in memory (350 GB for GPT-3 175B in 16-bit), and a merged model runs at exactly the base model’s speed. What shrinks is the training footprint (no gradients or optimizer state for W₀) and the per-task checkpoint (about 35 MB instead of 350 GB). QLoRA shrinks the base itself by storing it in 4 bits.

?

Does LoRA reduce activation memory?
Mostly no. To reach an adapter in layer ℓ, the backward pass has to run through every layer above it, which needs their stored activations, just as in full fine-tuning. The saving is that no weight gradient and no optimizer state are ever created for W₀; activations are a separate cost that gradient checkpointing addresses.

?

Can LoRA go on the MLP layers too?
In principle it applies to any weight matrix, and the paper says so. Its own experiments adapt only attention matrices and freeze the MLP, leaving MLP, LayerNorm and bias adaptation to future work. Later work, QLoRA among it, puts adapters on every linear layer.

Footnotes & further reading

  1. The paper: Hu, Shen, Wallis, Allen-Zhu, Li, Wang, Wang, Chen, LoRA: Low-Rank Adaptation of Large Language Models (Microsoft, 2021; ICLR 2022). Code (loralib/layers.py).
  2. Intrinsic dimension of fine-tuning: Aghajanyan, Zettlemoyer, Gupta, Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning (2020). The original measurement: Li, Farkhoor, Liu, Yosinski, Measuring the Intrinsic Dimension of Objective Landscapes (2018).
  3. The adapter baseline: Houlsby et al., Parameter-Efficient Transfer Learning for NLP (2019).
  4. Best low-rank approximation by truncated SVD: the Eckart–Young–Mirsky theorem; a summary is in Low-rank approximation.
  5. The width-scaling work cited for the α/r gain: Yang and Hu, Feature Learning in Infinite-Width Neural Networks (2021).
  6. The optimizer whose state dominates the memory bill: Adam, and its weight-decay variant AdamW, which the paper's appendix uses for training.
  7. The most-used follow-up: Dettmers et al., QLoRA (2023), LoRA on a 4-bit quantized frozen base; explained on its own page.