LoRA 与 QLoRA
LoRA 与 QLoRA
1. 使用背景
大模型越来越大之后,一个很现实的问题就是:全量微调越来越贵。
传统full fine-tuning的做法是,把模型里几乎所有参数都设成可训练,然后在下游任务上继续训练。这样当然直接,但代价也很明显:
- 需要更新的参数太多;
- 优化器状态很占显存;
- 每个任务都保存一整套新权重,存储和部署都很重。
所以后面就有一个很自然的想法:
能不能尽量不动原模型,只额外训练一小部分参数,让模型适配新任务。这就是参数高效微调(PEFT)这条线的核心思路。
而LoRA可以说是这条线里最有代表性的方法之一。
LoRA之后,QLoRA又进一步往前走了一步:
不仅只训练小规模增量参数,还把冻结的基座模型做低比特量化,从而把显存进一步压低。
2. 理论基础
(1)为什么全量微调代价大
设预训练模型某一层参数为:
W∈Rd×k W\in\mathbb{R}^{d\times k}W∈Rd×k如果做全量微调,那么训练时我们实际上是在学习:
W′=W+ΔW W' = W + \Delta WW′=W+ΔW
其中ΔW\Delta WΔW和WWW同形状。这意味着:
- 参数本身要存;
- 梯度要存;
- 优化器状态也要存。
- 当模型很大时,这三部分叠起来,显存开销会非常夸张。
(2)一个关键观察
LoRA论文里的一个很核心出发点是:
很多下游适配其实不一定需要在完整高维空间里自由更新全部参数。换句话说,任务适配带来的参数变化ΔW\Delta WΔW,很可能本身就带有某种低秩结构。
如果这个观察成立,那么我们就不一定要直接学习整个ΔW\Delta WΔW,而可以把它写成更小的低秩分解。
3. LoRA:Low-Rank Adaptation
(1)核心思想
LoRA最核心的做法可以直接写成:
W′=W+ΔW=W+BA W' = W + \Delta W = W + BAW′=W+ΔW=W+BA
其中:
B∈Rd×r,A∈Rr×k,r≪min(d,k) B\in\mathbb{R}^{d\times r},\qquad A\in\mathbb{R}^{r\times k},\qquad r\ll \min(d,k)B∈Rd×r,A∈Rr×k,r≪min(d,k)也就是说,原来要学习一个d×kd\times kd×k的完整增量矩阵ΔW\Delta WΔW,现在只学两个更小的矩阵AAA和BBB。
因为rrr很小,所以新增可训练参数量会明显下降。
(2)冻结原模型
LoRA里一个非常关键的点是:
原始预训练权重WWW冻结,不更新。真正训练的只有低秩增量部分A,BA,BA,B。
所以LoRA不是“把模型缩小”,而是:
保留大模型原有能力,只给它外挂一个小规模、可训练的低秩修正项。
(3)前向怎么写
对输入xxx,原来的线性层可以写成:
h=Wx h = Wxh=Wx加上LoRA之后,就变成:
h=Wx+BAx h = Wx + BAxh=Wx+BAx如果写得更直观一点,就是:
h=Wx+ΔWx h = Wx + \Delta Wxh=Wx+ΔWx其中ΔW=BA\Delta W=BAΔW=BA就是LoRA学出来的任务增量。
(4)为什么这样能省参数
原来完整的ΔW\Delta WΔW需要d×kd\times kd×k个参数;
现在只需要:
d×r+r×k d\times r + r\times kd×r+r×k
个参数。当rrr远小于d,kd,kd,k时,参数量就会大幅下降。
所以LoRA最本质的压缩来自一句话:
把高维增量矩阵,约束到一个低秩子空间里学习。
4. LoRA一般加在哪里
(1)最常见的位置
在Transformer里,LoRA最常被加在线性变换层上,尤其是attention里的投影矩阵,比如:
- WqW_qWq
- WkW_kWk
- WvW_vWv
- WoW_oWo
有时候也会加在FFN里的线性层上。
但最经典、最常见的做法,还是优先加在attention相关投影上。
(2)为什么这些位置有效
因为这些矩阵本身就承担着特征变换和信息路由的作用。
在很多任务适配里,稍微改一下这些投影方向,就足以让模型表现出明显不同的行为。
所以LoRA不是随便往哪儿塞都一样,它之所以常加在这些位置,是因为这些地方本来就对模型行为很敏感。
5. LoRA里的几个关键超参数
(1)rank (r)
LoRA里最核心的超参数就是秩rrr。
rrr越小,参数越省;
rrr越大,适配空间越大。
所以rrr本质上控制的是:
你允许模型在多大程度上偏离原始权重。
(2)scaling
在很多实现里,LoRA增量项还会乘一个缩放系数,比如写成:
W′=W+αrBA W' = W + \frac{\alpha}{r}BAW′=W+rαBA其中α\alphaα是LoRA scaling。
这个系数的作用就是控制LoRA增量项在前向里的影响强度,使训练更稳定。
(3)dropout
- 实际训练时,也常给LoRA分支加dropout。
- 这主要是为了防止小规模适配参数过拟合,尤其是在数据量不大时更常见。
6. LoRA为什么有效
(1)它不是重新学一个模型
LoRA并没有试图重新训练一个完整模型。
它更像是在已有大模型周围,加了一个低维的“任务偏移层”。
所以它能有效,一个很重要的原因就在于:
预训练模型本身已经很强了,下游适配很多时候只需要小幅修正,而不需要推倒重来。
(2)低秩约束其实是一种归纳偏置
LoRA把增量限制成低秩,本质上是在告诉模型:
任务适配不需要无限自由度,只需要在一个较低维的方向子空间里调整。
这其实是一种很强的结构假设。
它之所以有效,很大程度上说明很多任务适配本身就确实没必要更新整块大矩阵。
(3)和adapter的区别
Adapter也是参数高效微调的一条经典路线。
它通常是在原网络层之间插入一个小模块。
LoRA和adapter都能少训很多参数,但LoRA的一个典型优势是:
在推理阶段,LoRA增量可以和原权重合并,因此不会像额外插层那样天然引入额外推理路径。
这也是LoRA后来迅速流行起来的一个重要原因。
7. LoRA的优点和问题
(1)优点
- LoRA的优点很直接:
- 可训练参数少;
- 显存占用低;
- 任务切换方便;
- 推理时可合并权重;
- 对现有Transformer结构侵入性小。
(2)问题
但LoRA也不是没有代价。
最典型的问题有几个:
- rank太小时,表达能力可能不够;
- 不同任务对LoRA插入位置和超参数很敏感;
- 只训练LoRA分支有时会损失一部分full fine-tuning的上限;
- 当基座模型本身很大时,即使冻结不训,光把它完整加载进显存也仍然很贵。
- 最后这一点其实非常关键,因为它直接引出了QLoRA。
8. 为什么会有QLoRA
(1)LoRA已经省训练参数了,但基座模型还是大
LoRA虽然只训练很少参数,但通常还是需要把整个预训练模型以较高精度加载到显存里。
这就意味着:
训练参数省了,不代表底座模型本身的显存占用就小了。尤其当模型到了30B、65B这种规模时,就算完全冻结不更新,光加载进去也很费显存。
(2)于是一个很自然的问题出现了
能不能把冻结的基座模型量化得更低,同时仍然保留LoRA式微调能力?
QLoRA就是在解决这个问题。
9. QLoRA:Quantized LoRA
(1)核心思想
QLoRA的核心可以概括成一句话:
把预训练基座模型量化到4-bit并冻结,只在LoRA适配器上训练。
所以它保留了LoRA“只训小量参数”的优点,又进一步把基座模型本身的显存占用压下去了。
如果写得更直观一点:
- 基座模型:4-bit量化,冻结;
- LoRA分支:正常可训练;
- 梯度通过量化基座反传到LoRA参数。
(2)最关键的一点
QLoRA不是把LoRA本身量化训练,而是:
量化的是冻结的预训练主干,真正更新的仍然是LoRA适配器。
这一点很重要,因为它决定了QLoRA的训练逻辑仍然建立在LoRA上,只不过把“底座怎么存”这件事做得更省了。
10. QLoRA里的三个关键技术
(1)NF4
QLoRA提出了NormalFloat 4(NF4)这种4-bit数据类型。
其核心出发点是:预训练权重通常近似服从正态分布,那么量化时就应该用更适合这种分布的表示方式。
所以NF4不是普通随便压成4-bit,而是更针对权重统计特性设计的4-bit表示。
(2)Double Quantization
除了量化权重本身,QLoRA还进一步去量化量化过程中的常数项。
这就是所谓的double quantization。
这一步的作用是继续压缩显存,因为量化参数本身如果都按高精度存,累积起来也是一笔开销。
(3)Paged Optimizers
QLoRA还引入了paged optimizers来处理训练中可能出现的显存峰值问题。
因为大模型训练时,某些时刻内存使用会突然飙高,如果没有额外处理,很容易OOM。
paged optimizer的作用,简单理解就是让内存管理更平滑,减少这些峰值冲击。
11. LoRA和QLoRA的关系
(1)共同点
- LoRA和QLoRA的共同点很清楚:
- 都冻结基座模型;
- 都只训练小规模LoRA参数;
- 都属于PEFT路线。
(2)不同点
真正的差别在于基座模型怎么存:
LoRA通常默认基座模型还是常规精度加载;
QLoRA则把基座模型做4-bit量化再加载。
所以可以粗略理解成:
LoRA解决“训练谁”的问题;
QLoRA进一步解决“底座怎么放进显存”的问题。
12. 为什么LoRA / QLoRA会这么流行
(1)因为它们抓住了一个现实矛盾
大模型微调里最现实的矛盾就是:
大家想用更大的模型,但算力和显存又没法无限涨。
LoRA和QLoRA之所以这么火,就是因为它们没有试图暴力解决这个矛盾,而是很聪明地绕了一步:
不去训全部参数,只训最必要的一小部分;
不去高精度存全部底座,而是尽量压低冻结部分的存储成本。
(2)因为它们真的容易落地
这也是很关键的一点。
很多方法理论上很好,但很难接进现有系统。
LoRA / QLoRA则非常容易工程化:
- 改动小;
- 适配现有Transformer方便;
- 训练脚本好接;
- 多任务切换也方便。
- 所以它们不只是论文里好看,而是真的非常适合实际训练流程。
(3)因为它们给了“中小资源微调”一条现实路径
对很多团队来说,真正的问题不是“能不能训到SOTA”,而是“能不能在有限GPU条件下把模型训起来”。
LoRA和QLoRA给的就是这样一条现实路径。
这也是为什么后来很多开源社区、指令微调、领域适配基本都会优先碰到它们。
13. 从更高一层看,LoRA到底是什么
(1)它不是“模型压缩”
LoRA经常会被误解成一种模型压缩方法。
其实不太准确。
它更准确的定位应该是:
参数高效适配。
也就是说,它不是把原模型变小,而是让你在不大动原模型的前提下,用更少可训练参数完成任务迁移。
(2)它本质上是在学习一个低维任务偏移
我觉得这是理解LoRA最关键的一句话:
LoRA学的不是整个模型,而是“从预训练能力到下游任务能力”的低维偏移量。
一旦这样理解,很多事情就顺了:
- 为什么冻结基座还能有效;
- 为什么低秩就足够;
- 为什么不同任务可以挂不同LoRA分支。
(3)QLoRA则是在这个基础上进一步压缩底座成本
所以如果把两者合在一起看:
LoRA解决的是“微调不必全量更新”;
QLoRA解决的是“冻结底座也别占太多显存”。
14. 一点理解
(1)LoRA最漂亮的地方
我觉得LoRA最漂亮的地方就在于,它没有去和全量微调正面对抗,而是抓住了一个更本质的问题:
任务适配真的需要更新全部参数吗?
然后它给出的答案非常干脆:
很多时候,不需要。
只要学一个低秩增量就够了。
(2)QLoRA最漂亮的地方
QLoRA最漂亮的地方则在于,它把LoRA这条线又往现实硬件条件那边推了一大步。
也就是说,它不只是从“算法上省参数”,而是进一步做到:
在真实显存约束下,也能把大模型微调跑起来。
(3)怎么记LoRA与QLoRA
- 如果只是为了学习,我觉得可以把这条线记成四句话:
- 全量微调很贵,因为所有参数都要更新;
- LoRA把权重增量写成低秩分解,只训练小规模适配器;
- QLoRA进一步把冻结底座量化到4-bit,继续压低显存;
- 所以它们共同代表的是“大模型参数高效微调”这条非常现实的路线。
15. 参考鸣谢
LoRA: Low-Rank Adaptation of Large Language Models
https://arxiv.org/abs/2106.09685QLoRA: Efficient Finetuning of Quantized LLMs
https://arxiv.org/abs/2305.14314Parameter-Efficient Fine-Tuning for Large Models: A Comprehensive Survey
https://arxiv.org/abs/2403.14608
16. 注
- 这篇主要是个人学习整理,重点放在主线理解;
- 文中主要写的是LoRA和QLoRA最核心的思路,很多实现细节如target modules选择、merge/unmerge、bitsandbytes细节、LoRA变体(DoRA、AdaLoRA等)没有展开;
- 才疏学浅,欢迎批评、指导和交流;
- 有错误望大家及时指正!
