LoRA+ is LoRA with one change: the two adapter matrices get different learning rates. In LoRA's update , the matrix reads from the layer input and writes to the output. Hayou, Ghosh and Yu (2024) showed with an infinite-width scaling analysis that training both at the same learning rate cannot make both matrices contribute efficiently as models get wider, and that the fix is to set with . Nothing else about LoRA changes: the same adapters, the same memory, the same merge, the same serving. In practice it is a two-group optimiser configuration and one extra hyperparameter, the ratio . It applies wherever LoRA does, under SFT, DPO or RL objectives, and combines with initialisations such as PiSSA or with QLoRA's quantised base.
The problem and where it came from#
Hu et al.'s original LoRA used one learning rate for both adapter matrices, a choice inherited by most libraries. The scaling-theory literature (the line of work behind P) had already shown that, as network width grows, different parameter tensors need learning rates that scale differently with width, otherwise some layers learn too slowly or blow up. Hayou, Ghosh and Yu (UC Berkeley, 2024, ICML) applied that reasoning to LoRA. Their result: with a shared learning rate, either or is effectively frozen in the large-width limit, so LoRA operates in a regime where only one side of the adapter learns. Setting and in width restores "efficient feature learning" where both sides contribute. Empirically they reported 1% to 2% improvements and up to about 2 times faster convergence at the same compute, with larger gains on harder tasks: on Llama-7B fine-tuned on a 100K subset of Flan-v2, MMLU improved by about 1.3% over the best equal-rate setting, while on MNLI (easy for Llama) the ratio made no difference.
The update rule and the scaling argument#
For a frozen , LoRA trains and with scale . LoRA+ runs the same optimiser (typically AdamW) with two parameter groups:
where and are the optimiser's processed gradients (for Adam, the bias-corrected first moment divided by the root of the second). You tune on a one-dimensional grid as before, with fixed.
Why one learning rate is wrong
Follow the change in the adapter's output for an input over one step. It splits into three pieces:
Efficient learning, in the paper's definition, means both and are as width grows: both matrices move the features by a non-vanishing amount. Now count orders. multiplies an -dimensional input, so an Adam update of per entry, aligned with , changes by : the update sums over coordinates. multiplies an -dimensional vector whose entries are , so an update of per entry changes by . Keeping at scale, the two terms are
With , making order one requires , which makes and the training unstable; keeping stable requires , which makes , so is effectively frozen. Only with satisfies both, which means . The paper proves this for a linear toy model with gradient descent (Proposition 2) and states it for Adam-type optimisers in general networks (Theorem 1, under an alignment assumption). The constant inside is not given by the theory, so the ratio must be set empirically.
Initialisation changes the right ratio
LoRA needs at step 0, so one matrix starts at zero. The paper studies both choices: Init[1] is the standard one (, random with variance ), used by PEFT and the original LoRA; Init[2] is the reverse (, random with variance ). Their main experiments found best with Init[2] on RoBERTa and GPT-2; with Init[1] the best ratio was smaller, of order to ; and for Llama experiments a ratio of order to appeared best. Because the commonly quoted default of 16 comes from the Init[2] setting, it is worth knowing which initialisation your library uses before copying it.
Interaction with alpha, rank and the base learning rate
LoRA's scale multiplies both factors' contributions, so with Adam it acts like a common multiplier on both and and does not change their balance. The ratio is therefore a separate axis from , and changing or switching to rsLoRA's should be followed by retuning , not . Rank enters the counting only through the size of , so to first order the best ratio does not depend on ; width does the work. Two consequences are practical. A ratio tuned on a 1B model should be raised, not copied, for a model several times wider, since scales with in the theory. And because is now the larger rate, it is the one that sets the stability limit: when you scale up , expect the best to come down, which is exactly what our toy showed (the best fell tenfold for each fourfold increase in width while stayed roughly constant).
Next to its siblings
| Method | What changes relative to LoRA | Effect | Cost |
|---|---|---|---|
| LoRA+ | Both factors learn at large width | None | |
| rsLoRA | Scale instead of | High ranks stop being under-scaled | None |
| PiSSA | Initialise from top singular components | Faster early convergence | One SVD per matrix |
| DoRA | Separate magnitude vector | Closer to full fine-tuning's update pattern | Extra norm computation |
| GaLore | No adapter; low-rank optimiser state | Full-rank updates | Dense weights and gradients |
LoRA+ and rsLoRA address different axes (width and rank) and can be combined. LoRA+ also composes with PiSSA, DoRA and QLoRA, since it only touches the optimiser's parameter groups.
Worked example: balancing the two terms at width 4096#
Use the counting above with Adam's sign-like updates, which move every entry by about the learning rate. Take width , an input with entries of size about 1, and of order 1 (as it is with initialised at variance ). Early in training under the standard initialisation, starts at zero and after one step has entries of order . Then , because 's change is summed over input coordinates and then read out through , and :
| Reading | ||||
|---|---|---|---|---|
| 1e-3 | 1e-3 | 0.0041 | 0.0010 | Both tiny: slow learning |
| 3e-2 | 3e-2 | 3.69 | 0.030 | 's term explodes before 's matters |
| 2.4e-4 () | 1 | 1.0 | 1.0 | Balanced: |
The last row is the theory's prescription taken literally, . Real networks put constants in front of every , which is why measured best ratios are in the range 2 to 16 rather than 4,096, but the direction is the same: as width grows, needs a larger step relative to . The worked numbers also explain the most common LoRA symptom this fixes. If you raise a shared learning rate until training moves, the side becomes unstable first; if you keep it safe, the side barely moves.
A toy run: the best ratio grows with width#
We reproduced the paper's style of toy in PyTorch (seed 0). A frozen random two-layer ReLU network maps 8 inputs through a width- hidden layer to one output; the target network is the same with shifted by a fixed rank-4 matrix. A rank-4 LoRA on is trained for 200 Adam steps (batch 128, 1,024 training points), and we report the training loss relative to the starting loss. For each width and ratio we took the best over a log grid ( to ).
With Init[2] (, random) the effect was large and followed the theory closely. The best ratio was 64 at width 64, 256 at width 256 and 1,024 at width 1,024 (the top of our grid), growing in proportion to width. The best equal-rate loss was 0.155, 0.240 and 0.167 for the three widths; the best LoRA+ loss was 0.046, 0.051 and 0.052, three to five times lower. The best fell from 3.2e-3 to 3.2e-4 to 3.2e-5 as width grew, while stayed between 0.03 and 0.2, which is the , pattern.
With Init[1] (, random, the PEFT default) the picture was different. The best ratio was 1 at widths 64 and 256 and 4 at width 1,024, and the gain at 1,024 was small (0.0339 against 0.0361 with equal rates). Equal-rate training with Init[1] already reached about the loss that Init[2] needed LoRA+ to reach. That matches the paper's report that the optimal ratio is smaller under Init[1], and it is a useful warning: on a standard LoRA setup, expect LoRA+ to be a modest, task-dependent improvement, and tune over something like rather than assuming 16.
The algorithm and training loop#
model = add_lora(base_model, r, alpha) # freeze base, add A (random), B (zero)
groups = [
{"params": [all lora_A matrices], "lr": lr},
{"params": [all lora_B matrices and 1-D extras], "lr": lr * ratio}, # ratio = lambda
{"params": [lora embeddings, if any], "lr": lr_embedding},
]
optimizer = AdamW(groups, weight_decay=0.0)
scheduler = cosine_with_warmup(optimizer) # scales every group's lr together
for step in range(total_steps): # unchanged LoRA training loop
loss = objective(model(batch)); loss.backward()
clip_grad_norm_(trainable, 1.0)
optimizer.step(); scheduler.step(); optimizer.zero_grad()
That is the whole method. The schedule must apply multiplicatively to both groups so the ratio is preserved; standard schedulers do this. Gradient clipping by global norm is fine, because the gradients are unchanged; only the step sizes differ. Data requirements are those of the objective and of LoRA.
Hyperparameters that matter#
| Hyperparameter | Starting value | Notes |
|---|---|---|
| Ratio | 4 to 16 | Paper: about 16 with init on RoBERTa/GPT-2; 4 to 8 with the standard init; 2 to 4 for Llama experiments. Larger for harder tasks and wider models |
| Base learning rate | About half your usual LoRA rate | PEFT's documentation suggests a slightly smaller rate (for example by a factor of 2) than vanilla LoRA when using a large ratio |
| Embedding adapters | Separate, small rate (PEFT default 1e-6) | LoRA on embeddings has different scaling |
| Rank, alpha, targets | As for LoRA | Unchanged |
| Weight decay on | 0 | With a large , decoupled decay acts more strongly on |
Tune in two passes. First fix and find the best LoRA learning rate. Then try with at that rate and at half of it. If the best is at the top of your grid, extend it; if it is 1 or 2, your setup is not width-limited and LoRA+ will not help much.
Compute and memory cost#
None beyond LoRA's. The trainable parameters, optimiser state, activations and FLOPs are identical: memory is about for base and adapter parameters in bf16 with AdamW. The only real cost is the extra hyperparameter, which at most doubles or triples a small learning-rate sweep. When the paper reports "up to 2 times speedup", it means reaching the same accuracy in about half the steps (RoBERTa-base on MNLI with ), not a faster step.
Measuring whether one side is frozen#
You can check the theory's diagnosis directly instead of inferring it from loss curves. On a fixed probe batch, every few hundred steps, record for a handful of layers the two contributions to the change in adapter output since the previous checkpoint: for the side and for the side. If one is consistently one or two orders of magnitude smaller than the other, that matrix is effectively frozen and the ratio is wrong for your width and initialisation. Under the standard initialisation with equal rates the usual pattern is the opposite of what people expect early on: moves first, because has zero gradient at step 0, and the side catches up only once is non-zero. A healthy LoRA+ run shows the two contributions within a factor of a few of each other after warmup. The measurement costs two extra adapter forward passes per probe and needs only the saved adapter weights, so it can be run offline on checkpoints.
Failure modes and how to detect them#
Instability from a large
Loss spikes or diverges early, especially with high . The effective step on the output side has grown by . Lower (and so ) together, or reduce . Watch per layer in the first few hundred steps; it should rise smoothly.
No gain at all
The task is easy for the model (the paper's Llama on MNLI case), or the standard initialisation already balances the two sides at your width, as in our toy. Not a failure, just a signal to stop tuning .
Parameter-group mistakes
The higher rate lands on the wrong matrix. In PEFT naming, lora_B is the output-side matrix that is initialised to zero and should get the higher rate; lora_A is the input side. Print the learning rate of each group and a parameter name from it before training. Custom LoRA code that names the matrices the other way round inverts the method.
Ratio lost to a scheduler or wrapper
Some training wrappers rebuild the optimiser from a single learning rate, silently erasing the groups. Log the actual learning rates of both groups at step 0 and mid-run.
When to use LoRA+, and what to use instead#
LoRA+ is nearly free, so the question is less whether to use it than how much tuning effort to spend on it. Treat as one more axis of the learning-rate sweep you already run. It pays most on wide models, hard tasks that need the adapter to learn new features, and setups that use the initialisation; it pays least on easy tasks, short alignment stages and the standard initialisation at moderate width. If a sweep says , believe it.
| Situation | Use |
|---|---|
| Any LoRA run where you will do a learning-rate sweep anyway | LoRA+ with in the sweep |
| Hard SFT tasks on wide models where LoRA trails full fine-tuning | LoRA+ with 8 to 16, then higher rank or DoRA |
| High-rank adapters that learn slowly | rsLoRA scaling, optionally with LoRA+ |
| Short DPO or GRPO stages with small learning rates | Plain LoRA is usually fine; try 2 to 4 if updates look one-sided |
| The update itself needs high rank | GaLore or full fine-tuning |
Minimal implementation#
Two parameter groups are all you need. PEFT ships a helper that builds them for you.
import torch
def loraplus_param_groups(model, lr, ratio=8.0, lr_embedding=1e-6, weight_decay=0.0):
a, b, emb = [], [], []
for name, p in model.named_parameters():
if not p.requires_grad:
continue
if "lora_embedding" in name:
emb.append(p)
elif "lora_B" in name or p.ndim == 1: # output side (zero-initialised) and 1-D extras
b.append(p)
else: # lora_A: input side
a.append(p)
return [{"params": a, "lr": lr, "weight_decay": weight_decay},
{"params": b, "lr": lr * ratio, "weight_decay": 0.0},
{"params": emb, "lr": lr_embedding, "weight_decay": weight_decay}]
# opt = torch.optim.AdamW(loraplus_param_groups(model, lr=1e-4, ratio=8.0))
# with PEFT:
# from peft.optimizers import create_loraplus_optimizer
# opt = create_loraplus_optimizer(model=peft_model, optimizer_cls=torch.optim.AdamW,
# lr=1e-4, loraplus_lr_ratio=8.0)
# then pass optimizers=(opt, None) to a Hugging Face Trainer
In POFE every LoRA recipe exposes the ratio as a single field next to the learning rate, defaulting to 1, so that a sweep over is one extra axis rather than custom optimiser code.
Further reading#
Hayou, Ghosh and Yu (2024) is the paper; Section 3 has the intuitive toy-model argument and Section 4 the general theorem, with ratio studies in Section 5 and Appendix C. Hu et al. (2021) is LoRA itself. Yang and Hu (2020) on feature learning in infinite-width networks is the theory LoRA+ builds on. Kalajdzievski (2023) is rsLoRA, the companion fix for the rank axis. Meng et al. (2024, PiSSA) is a different answer to slow early LoRA training, through initialisation rather than step sizes.
Frequently asked questions#
What does LoRA+ change?
Only the optimizer: the B matrices (output side, zero-initialised in standard LoRA) get learning rate lambda times that of the A matrices. Adapters, memory, merging and inference are identical to LoRA.
What ratio should I use?
The paper found about 16 best with an A = 0 initialisation on RoBERTa and GPT-2, 4 to 8 with the standard B = 0 initialisation, and 2 to 4 in Llama experiments. Sweep 1, 2, 4, 8 and 16 and extend the grid if the best value is at the top.
Why should B learn faster than A?
A change to A is summed over the n input coordinates, so its effect grows with width n, while a change to B acts on an r-dimensional vector of order one. Keeping both contributions of order one as n grows requires eta_A of order 1/n and eta_B of order 1, a ratio that grows with width.
How much does LoRA+ help?
The paper reports 1% to 2% better results and up to about 2 times faster convergence, with larger gains on harder tasks (about 1.3% MMLU for Llama-7B on Flan-v2) and none on easy ones (Llama-7B on MNLI). In our toy the gain was large with A = 0 init and small with the standard init.
How do I use LoRA+ with PEFT?
Use peft.optimizers.create_loraplus_optimizer(model, optimizer_cls=torch.optim.AdamW, lr=..., loraplus_lr_ratio=...) and pass the optimizer to your Trainer. PEFT puts lora_B and 1-D parameters in the high-rate group and LoRA embeddings at a separate small rate.
References#
- Soufiane Hayou, Nikhil Ghosh, Bin Yu (2024). LoRA+: Efficient Low Rank Adaptation of Large Models. arxiv.org/abs/2402.12354
- Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, Weizhu Chen (2021). LoRA: Low-Rank Adaptation of Large Language Models. arxiv.org/abs/2106.09685
- Greg Yang, Edward J. Hu (2020). Feature Learning in Infinite-Width Neural Networks. arxiv.org/abs/2011.14522
- Damjan Kalajdzievski (2023). A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA. arxiv.org/abs/2312.03732
- Fanxu Meng, Zhaohui Wang, Muhan Zhang (2024). PiSSA: Principal Singular Values and Singular Vectors Adaptation of Large Language Models. arxiv.org/abs/2404.02948