LoRA
Suppose a hundred customers each want their own fine-tuned version of a 175B model. Full fine-tuning gives you a hundred checkpoints of 350 GB each, 35 TB of weights, and a hundred copies of the model to keep in GPU memory if you want to serve them all. Hu et al. (2021) showed you could do it differently. Freeze the model, and for each customer train a pair of skinny matrices beside some of its weight matrices. On GPT-3 175B, with rank 4 adapters on only the query and value projections, each customer's checkpoint shrank from 350 GB to about 35 MB, a factor of about 10,000, and a hundred of them fit in 354 GB alongside one shared copy of the base. Training memory fell from 1.2 TB to 350 GB, and quality matched full fine-tuning on their benchmarks.
That method is LoRA, low-rank adaptation, and it is now the default way to fine-tune when memory or the number of variants matters. This section explains why a tiny update can be enough, derives the forward and backward passes, explains the two details everyone asks about in interviews (why starts at zero and why the update is scaled by ), and covers which layers to adapt, how to merge adapters, and how to build LoRA from scratch.
Why a small update can be enough
Start with an observation about fine-tuning, not about LoRA. Li et al. (2018) measured the intrinsic dimension of a training problem by freezing the weights at their initial values and training only a vector that is mapped into the full space by a fixed random matrix ,
and finding the smallest , called , that reaches 90% of the performance of training all parameters of the model. Aghajanyan et al. (2021) applied this to fine-tuning pretrained language models and found the numbers startlingly small. With RoBERTa-Large, about 200 parameters projected into the full space reached 90% of full fine-tuning on the MRPC paraphrase task. Larger pretrained models had lower intrinsic dimension, and pretraining longer lowered it further. In plain words, pretraining leaves the model in a place from which a task needs only a few independent adjustments.
Why could that be? A random -dimensional subspace can contain a good solution only if success depends on about constraints. Here is a linear model with 1,000 weights whose fine-tuning data lives in a 20-dimensional subspace of its inputs, a cartoon of how narrow fine-tuning data is compared with everything a pretrained model can represent. We fit the best in random subspaces of growing dimension.
At the random subspace matches full fine-tuning exactly, and adding more dimensions gains nothing, because only 20 combinations of weights affect the task's predictions at all. Below 20 the subspace is too small to reach the target, and how close it gets depends on the luck of the draw (here happens to do worse than ). The other 980 directions are free. Real networks are not linear and their "task subspace" is not exact, but the experiments say the effective number is small. LoRA turns this observation into a structured, efficient parameterization.
The low-rank update
Take one weight matrix of the model, in PyTorch's convention , so that a layer computes for a column input (and for a batch of row vectors). Full fine-tuning learns an arbitrary update . LoRA freezes and restricts the update to a product of two thin matrices,
with rank and a constant . In plain words, compresses the input to numbers, expands those back to the output width, and the result is added to what the frozen layer computes. The update has rank at most . (This is not the attention-weight matrix of Chapter 4, and is not the batch size . The names come from the LoRA paper.)
Three things follow from the shapes.
- Parameters. LoRA trains numbers instead of . For a projection with that is 65,536 instead of 16.8 million, 0.39%.
- Compute. Never form during training. Computing costs multiply-adds per token instead of the of a full matrix, the contraction-order argument of §0.1. The frozen matmul still runs, so LoRA saves memory far more than it saves compute. Count it per token for a model with parameters. Full fine-tuning costs about FLOPs, forward, for the gradient with respect to each layer's input, and for the gradient with respect to its weights (the accounting of §9.2). A frozen matrix still needs the input gradient, so backpropagation can reach earlier adapters, but not the weight gradient, so LoRA costs about , a third less. Hu et al. measured a 25% training speedup on GPT-3 175B.
- Memory. The frozen needs no gradient, no master copy, and no optimizer state, so its cost drops from 16 bytes per parameter to 2 (§14.4). Only the adapter pays the full 16.
How good can a rank- update be? If you knew the update that full fine-tuning would make, the best rank- approximation in Frobenius norm is its truncated SVD, and the error is set by the singular values left out (the Eckart-Young theorem, proved in §0.10 and used on word counts in §1.4),
So the question "is LoRA enough" is partly the question "how fast do the singular values of real updates fall". Here are real ones, from full fine-tuning of the small Shakespeare GPT used in §14.4.
The updates are far from random. A rank of 12 to 14 captures 80% of the squared norm of the attention updates, where a random matrix of the same shape needs 50 to 74. But they are not truly low rank either. Their spectra have long tails, and the MLP up-projection of layer 3 needs about 40 directions for 80%. Biderman et al. (2024) found the same at scale. On code and math, full fine-tuning learned perturbations with ranks 10 to 100 times higher than typical LoRA configurations, and LoRA learned less of the target domain (and forgot less). LoRA does not reproduce the full fine-tuning update. It finds its own low-rank solution, which is as good when the task needs little capacity and falls short when it needs a lot, a point Schulman et al. (2025) made quantitatively by comparing LoRA's parameter count with the information content of the training data.
The same study adds two empirical findings that interviewers like to probe. LoRA was less tolerant of large batches than full fine-tuning, with a loss penalty that grew with the batch size and did not shrink at higher rank, so a comparison run at a batch size tuned for full fine-tuning can make LoRA look worse than it is. And in reinforcement learning, LoRA matched full fine-tuning even at rank 1. Their explanation is the capacity argument again. A policy-gradient update learns from one scalar reward per sampled episode, on the order of one bit of information, while a supervised example supplies a whole target sequence, so reinforcement learning needs far less room in the adapter (§15.7 sets up that view of training).
Initialization, and why B starts at zero
LoRA initializes randomly and to zero (Hu et al. used a Gaussian for , and the PEFT library uses a Kaiming-uniform draw). Three reasons, each of which an interviewer will probe.
The model starts exactly at the base. With , , so step 0 of fine-tuning is the pretrained model, with its quality intact. Random initialization of both would add a random perturbation to every adapted layer before training even begins.
Both cannot be zero. To see why, we need the gradients. Write the batched layer as and let be the upstream gradient. Using the matrix-calculus rule that for the gradients are and (§0.2), with the compressed input,
In plain words, 's gradient is proportional to , and 's gradient is proportional to . If both are zero, both gradients are zero, and training never moves. That point is a stationary point (in general a saddle), and gradient descent started exactly there cannot leave it.
Only B moves on the first step. With the gradient of is zero, so the first update changes only, by under plain gradient descent. The resulting weight update is
In plain words, LoRA's first step is the full fine-tuning gradient multiplied on the right by , a matrix of rank that keeps only the part of each row lying in the -dimensional row space of the random matrix (it is exactly a projection when the rows of are orthonormal, and close to a scaled projection for a random with , whose rows are nearly orthogonal). That is exactly the random-subspace training of the intrinsic dimension experiments, applied matrix by matrix. After the first step starts to move too, and the subspace rotates toward useful directions, which is what makes LoRA better than a fixed random projection.
LoRA forward pass, merge, and unmerge
Implement lora_forward(X, W0, A, B, alpha), merge(W0, A, B, alpha), and unmerge(W, A, B, alpha). Shapes follow PyTorch, W0 is (d_out, d_in), A is (r, d_in), B is (d_out, r), and X is (n, d_in) (rows are tokens). The rank is read from A, and the scale is .
lora_forwardreturns , without forming .mergereturns the single matrix that a deployed model would use.unmergerecovers from a merged matrix.
LoRA backward pass, checked numerically
Implement lora_backward(X, W0, A, B, alpha, G) returning (dA, dB, dX), the gradients of a loss with respect to A, B, and X, given G of shape (n, d_out) for the layer . W0 is frozen, so it gets no gradient. Each gradient has the shape of the thing it is the gradient of.
The scale alpha over r, and rsLoRA
Why multiply by at all, when could absorb any constant? Because the constant changes how fast training moves as you change , and you want to change without retuning everything else. Hu et al. set to the first rank they tried and never tuned it, noting that with Adam, tuning is roughly the same as tuning the learning rate. Kalajdzievski (2023) argued instead for , which he called rank-stabilized LoRA (rsLoRA), proving for gradient descent that only this choice keeps the adapter's learning neither exploding nor collapsing as , and showing that with larger ranks learned no faster than small ones. Both are right, for different optimizers, and one step of algebra shows why.
∂ How much one step reduces the loss, as a function of r
Start from , where only has a gradient, with . A step changes the output by and, to first order, the loss by
Plain gradient descent, . Then . Each of the rows of contributes an independent, identically distributed term to this squared norm, so it grows like , and . With that is , which shrinks as grows, the collapse rsLoRA describes. With it is , independent of .
Adam, whose first steps are close to sign descent because (§0.6), takes . Then , a sum of absolute values whose typical size does not depend on , so . With that is , independent of . With it is , which grows with the rank, so a larger rank acts like a larger learning rate.
Let's check both predictions numerically on one layer.
Under plain gradient descent, gives the same progress at every rank and gives 64 times less at rank 256 than at rank 4. Under Adam the roles swap. is the rank-independent choice, and makes rank 256 move 8 times faster than rank 4. That reconciles the two papers and matches large-scale practice. Schulman et al. (2025), training with Adam, found that with the standard the optimal learning rate changed by less than a factor of two between ranks 4 and 512, and that early learning curves at the same learning rate were nearly identical across ranks. If you use rsLoRA (PEFT's use_rslora=True), remember that raising also raises the effective step size, so retune the learning rate when you change the rank.
Two practical consequences. Since and the learning rate are entangled, fix (common choices are , , or ) and tune the learning rate, which is what the QLoRA authors did. And LoRA's best learning rate is higher than full fine-tuning's, by about an order of magnitude (Biderman et al. 2024 and Schulman et al. 2025 both found roughly 10 times), which is why QLoRA used for 7B and 13B models where full fine-tuning would use . LoRA+ (Hayou et al. 2024) goes one step further and gives a larger learning rate than , recommending a ratio of , motivated by the same asymmetry between the two factors that the derivation exposes.
Which layers to adapt
The original paper, constrained to a fixed budget of trainable parameters on GPT-3, found adapting both and at rank 4 better than putting all of the budget into or alone at rank 8, and adapting all four attention matrices at rank 2 as good or slightly better (Hu et al. 2021). Spreading rank across matrices beats concentrating it. Later work pushed this to its conclusion.
- QLoRA's authors could not match full fine-tuning on LLaMA when adapting only the query and value projections, the paper's default. Adapting all linear layers of every block was the critical choice, and once they did, the rank hardly mattered (Dettmers et al. 2023).
- Schulman et al. (2025) found attention-only LoRA significantly worse than MLP-only LoRA, even at matched parameter counts (attention-only at rank 256 underperformed MLP-only at rank 128), while adding attention to the MLP adapters gave no further gain.
So the modern default is every linear layer of every block, the query, key, value, and output projections and the MLP's gate, up, and down projections, which PEFT spells target_modules="all-linear". Embeddings and the output head are usually left frozen. The exception is when you add tokens, such as new chat-template markers. Their embedding rows start as an untrained mean (§2.5), and an adapter on the linear layers cannot give them distinct meanings, so those rows (or the whole embedding and head, via PEFT's modules_to_save) must be trained too. The small GPT behind this chapter's widgets did exactly that, training the three new token rows alongside its LoRA adapters.
Count LoRA's trainable parameters for a LLaMA config
Implement lora_param_count(cfg, r, modules) for a LLaMA-style decoder. cfg has d_model, d_ff, n_layers, n_heads, and n_kv_heads, with head size d_model // n_heads. The per-block linear layers and their (input, output) widths are
qando, ,kandv, (grouped-query attention, §4.5),gateandup, , anddown, .
LoRA on a layer adds parameters. Return the total for the listed modules across all layers, as a Python int.
Merging, swapping, and serving adapters
Because has the same shape as , you can fold it in after training, , and the deployed model has exactly the base architecture and zero added latency, an advantage over adapters that insert new layers (§14.6). You can also keep adapters separate and swap them. Unmerging subtracts the same product, and many adapters can share one copy of the base in memory, which is how a single GPU serves many fine-tuned variants, a technique serving systems for multi-adapter traffic build on by batching requests for different adapters together.
Three cautions. Merging in bfloat16 rounds the sum, and since is small relative to , part of it can round away (the same 8-bit-mantissa issue that forces master weights in mixed precision, §10.1), so merge in float32 and cast once. Merging into a 4-bit quantized base is lossy, because the merged matrix must be requantized and the adapter was trained against the quantized weights, not the merged ones. And adding several adapters together (task arithmetic) works sometimes and interferes other times. Treat merged combinations as new models that need evaluation.
LoRA from scratch
Here is a LoRA layer in NumPy, with the forward and backward passes derived above, trained with Adam toward a target update that is rank 4 plus a little full-rank noise. The inputs are whitened, so minimizing the regression loss is the same as minimizing , and we can compare the result with the Eckart-Young optimum for each rank.
At every rank, gradient descent on and finds essentially the best rank- update the SVD allows. The error drops sharply up to rank 4, where the true update's structure lives, and slowly after, as the extra ranks fit the noise. This is no accident. For this objective every local minimum of the factored problem is a global one, a classical theorem for linear networks (Baldi and Hornik 1989), and that benign geometry is part of why LoRA trains so reliably.
The real way
A LoRA layer in PyTorch is a wrapper around nn.Linear. Here it is, applied to every linear layer of a small model, with the parameter count, a few training steps, and a merge that must leave the outputs unchanged.
With Hugging Face PEFT the same thing is a configuration.
Read more
- Hu et al. (2021). LoRA: Low-Rank Adaptation of Large Language Models. The method, the GPT-3 results, and the analysis of which matrices to adapt and how the update relates to the pretrained weight.
- Aghajanyan et al. (2021). Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning. Random-subspace fine-tuning and why bigger, longer-pretrained models need fewer dimensions.
- Kalajdzievski (2023). A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA. The case for alpha over the square root of r, with a proof.
- Biderman et al. (2024). LoRA Learns Less and Forgets Less. LoRA versus full fine-tuning on code and math, and the rank of full fine-tuning updates.
- Schulman et al. (2025). LoRA Without Regret. When LoRA matches full fine-tuning, the importance of MLP layers, and learning rates across ranks.
- Hayou et al. (2024). LoRA+: Efficient Low Rank Adaptation of Large Models. Different learning rates for A and B.
- Li et al. (2018). Measuring the Intrinsic Dimension of Objective Landscapes. The original random-subspace method.