论文地址:arxiv.org/pdf/2106.09685

论文代码: https://github.com/microsoft/LoRA.

转载自:图解大模型微调系列之:大模型低秩适配器LoRA(原理篇) - 知乎

图解大模型微调系列之:大模型低秩适配器LoRA(源码解读与实操篇) - 知乎

大模型系列(一)- LoRA

1. 全参数微调

全量微调指的是,在下游任务的训练中,对预训练模型的每一个参数都做更新。

缺点:训练代价昂贵,在微调中发现有bug时覆水难收。

启发:由于模型在预训练阶段已经吃了足够多的数据,收获了足够的经验,因此我只要想办法给模型增加一个额外知识模块,让这个小模块去适配我的下游任务,模型主体保持不变(freeze)即可。

2. Adapter Tuning与Prefix Tuning

Adapter Tuning与Prefix Tuning是Lora出现之前两种主流的微调方法

2.1 Adapter Tuning

Adapter Tuning的方法有很多种,以arxiv.org/pdf/1902.00751为例,该篇文章是LoRA论文中提及这项技术时所引用的第一篇文章。

在微调时,除了Adapter的部分,其余的参数都是被冻住的(freeze)

显著劣势添加了Adapter后,模型整体的层数变深,会减缓训练速度和推理速度

原因是:

  • 需要耗费额外的运算量在Adapter上
  • 当我们采用并行训练时(例如Transformer架构常用的张量模型并行),Adapter层会产生额外的通讯量,增加通讯时间

2.2 Prefix Tuning

Prefix Tuning的方法也有很多种,在这篇文章中2101.00190通过对输入数据增加前缀(prefix)来做微调。当然,prefix也可以不止加在输入层,还可以加在Transformer Layer输出的中间层。

如上图所示:

  • 第一个示例对于GPT这样的生成式模型,在输入序列的最前面加入prefix token,图例中加入2个prefix token,在实际应用中,prefix token的个数是个超参,可以根据模型实际微调效果进行调整。
  • 第二个示例对于BART这样的Encoder-Decoder架构模型,则在x和y的前面同时添加prefix token。

在后续微调中,我们只需要冻住模型其余部分,单独训练prefix token相关的参数即可,每个下游任务都可以单独训练一套prefix token。

prefix的作用

引导模型提取x相关的信息,进而更好地生成y。例如,我们要做一个summarization的任务,那么经过微调后,prefix就能领悟到当前要做的是个“总结形式”的任务,然后引导模型去x中提炼关键信息;如果我们要做一个情感分类的任务,prefix就能引导模型去提炼出x中和情感相关的语义信息,以此类推。

缺点

  • 较难训练,且模型的效果并不严格随prefix参数量的增加而上升,这点在原始论文中也有指出
  • 会使得输入层有效信息长度减少。为了节省计算量和显存,我们一般会固定输入数据长度。增加了prefix之后,留给原始文字数据的空间就少了,因此可能会降低原始文字中prompt的表达能力。

3. LoRA

LoRA(Low-Rank Adaptation,低秩适配器)

LoRA的原理见第四节

image-20241024195919971

全参数微调与Lora微调的区别

全参数微调

  • WRddW \in \mathbb R^{d*d}:预训练权重,在微调的过程中被冻住
  • ΔWRdd\Delta W \in \mathbb R^{d*d}:微调增量权重
  • 参数微调量:d2d^2

输出h=Wx+ΔWxh = Wx + \Delta W x

Lora微调

  • ARrdA \in \mathbb R^{r*d}:低秩矩阵A,其中r被称为秩,对A使用高斯初始化
  • BRdrB \in \mathbb R^{d*r}:低秩矩阵B,采用零初始化
  • 参数微调量:2rd2*r*d

输出h=Wx+BAxh = Wx + BAx

因此减少了Lora微调在r远小于d的情况下大大降低了参数微调量。

原论文中提到过对于两个低秩矩阵,会用超参 α (一个常数)来做调整

h=Wx+αrBAxh = Wx + \frac{\alpha}{r}BAx

在实操中,一般取αr\alpha \ge r

为什么对A采用高斯初始化,对B采用零初始化

让训练刚开始时B的值为0,这样不会给模型带来额外的噪声。根据Lora一作在github issue上的回答,A采用零初始化,B采用高斯初始化也可以。当前还没有发现转换 A,B 初始化方式产生的显著区别,只要这两者中任意一者为0,另一者不为0即可。

3.1 Lora整体架构

image-20241024195508119

3.2 LoRA的训练和推理过程

在实际操作中,可以在任何你想要的模型层上做LoRA操作,比如Transformer中的Wq,Wk,Wv,WoW_{q}, W_{k}, W_{v}, W_{o}、MLP层的权重、甚至是Embedding部分的权重。

3.2.1 训练

在训练过程中,固定住预训练权重 W ,只对低秩矩阵 A 和 B 进行训练。在保存权重时,我们只需保存低秩矩阵的部分即可。LoRA原论文统计得到,这样可以大幅度减少显存的消耗和模型的大小。

训练的每一时刻,LoRA都能做到节省显存吗?

考虑backward时对BB计算梯度,由公式h=Wx+BAx=Wsumxh = Wx + BAx = W_{sum}x可以推出:

LB=LhhWsumWsumB=LhxTWsumB\begin{aligned} \frac{\partial L}{\partial B} &= \frac{\partial L}{\partial h}\frac{\partial h}{\partial W_{sum}}\frac{\partial W_{sum}}{\partial B}\\ &=\frac{\partial L}{\partial h}x^{T}\frac{\partial W_{sum}}{\partial B} \end{aligned}

对比全量微调公式h=Wx+ΔWxh = Wx + \Delta W x

LB=Lhh(W+ΔW)(W+ΔW)ΔW=LhxT(W+ΔW)ΔW=LhxT\begin{aligned} \frac{\partial L}{\partial B} &= \frac{\partial L}{\partial h}\frac{\partial h}{\partial (W+\Delta W)}\frac{\partial (W+\Delta W)}{\partial \Delta W}\\ &=\frac{\partial L}{\partial h}x^{T}\frac{\partial (W+\Delta W)}{\partial \Delta W}\\ &=\frac{\partial L}{\partial h}x^{T} \end{aligned}

会发现公共项LhxT\frac{\partial L}{\partial h}x^{T},这意味着为了计算BB,的梯度,我们需要用到和全参数微调过程中一样大小的中间值结果。因此对LoRA来说,这一层的峰值显存,和全量微调基本是一致的,算上WsumB\frac{\partial W_{sum}}{\partial B}一项的话则高于全量微调。

那么为什么LoRA能从整体上降低显存使用?

  • LoRA并不是作用在模型的每一层,例如论文里的LoRA只作用在attention部分
  • LoRA虽然会导致某一层的峰值显存高于全量微调,但计算完梯度后,这个中间结果就可以被清掉了,不会一致保存。
  • 当待训练权重从d*d降为2*r*d时,需要保存的optimizer states也减少了。

3.2.2 推理

按照W=W+αrBAW = W + \frac{\alpha}{r}BA,合并低秩矩阵和预训练权重,然后正常做forward推理。这样我们完全不会更改模型的架构,因此不会像Adapter Tuning一样产生推理上的延时。

同时可以分开保存预训练权重W低秩权重BA,对于不同的下游任务只利用保存的预训练权重W来训练不同的低秩权重BA。这意味着不同的下游任务所保存的低秩权重BA是不同的,在做相应的下游任务推理时只需要把相应任务对应的低秩权重BA合并到预训练权重上即可。(在源码中就算合并了预训练权重W低秩权重BA,在微调期间也可以把它们区分开来。)

4. LoRA低秩适配的原理

4.1 秩

原理还介绍就不用学了,大学白上了。

秩表示的是矩阵的信息量。如果矩阵中的某一维,总可以通过其余维度线性推导而来,那么对模型来说,这一维的信息是冗余的,是重复表达的。

全参数微调中的增量权重 ΔW 可能也存在冗余的信息,因此我们并不需要用完整ddd*d尺寸来表示它。可以用SVD分解奇异值分解)来找出ΔW中真正有用的特征维度。

4.2 奇异值分解

对任意m行n列的矩阵M,M可以被分解为如下所示:

M=UΣVTM = U\Sigma V^{T}

其中:

  • U是m行m列的正交矩阵,该矩阵的每一个列向量都是MMTMM^T的特征向量
  • V是n行n列的正交矩阵,该矩阵的每一个列向量都是MTMM^TM的特征向量
  • Σ\Sigma是m行n列对角阵,将MMTMM^TMTMM^TM的特征值得到的就是该矩阵主对角线上的元素,也可以看成矩阵A的奇异值。

以n>m的矩阵M举例

当M的秩为a(a<m)时,M可以被表示为M=U^ΣV^TM = \hat{U}\Sigma \hat{V}^{T} ,其中U^\hat{U}UU的前a列,大小为(m,a),ΣV^T\Sigma \hat{V}^{T}的大小为(a,m),因此用U^ΣV^T\hat{U}\Sigma \hat{V}^{T}计算M的参数量变为了2am2*a*m,在a比较小时明显小于M自身mmm*m的参数量。

当n=m或者n<m的状况同理

4.3 LoRA低秩适配

问题在于能做奇异值分解的前提是已知ΔW\Delta W,而实践中ΔW\Delta W作为全参数微调中的权重增量,如果你不全参数微调一遍,又怎么能知道ΔW\Delta W长什么样呢?而如果你做了全量微调,那还要低秩适配做什么呢?

数学方法不通,就让模型自己去学习怎么做奇异值分解。因此LoRA最终的低秩适配策略是:我把秩rr当成一个超参,再让模型自己去学低秩矩阵A,BA,B

4.4 超参α\alpha

原论文中对超参α\alpha的解释

大致意思是说,在我们采用Adam做优化器时,调整α\alpha的作用就相当于调整learning rate。一般而言,我们把α\alpha设置为我们第一次做实验时设置的 rr,然后就把α固定下来,之后只调整 r 即可,这样做的好处是当我们尝试不同的rr时,我们不需要再去调整别的超参了。

回顾一下我们的输出计算方法为:

h=Wx+αrBAxh = Wx + \frac{\alpha}{r}BAx

其中, WW表示预训练权重(旧知识), αrBA\frac{\alpha}{r}BA表示增量权重 ΔW\Delta W的近似(新知识)。理论上说,rr较小时,我们提取的是 ΔW\Delta W中信息含量最丰富的维度,此时信息精炼,但不全面;当 rr 较大时,我们的低秩近似越逼近ΔW\Delta W,此时信息更加全面,但带来的噪声也越多(含有很多冗余无效的信息)。

基于以上的猜想,在第一次实验时要尽量把r调的比较大,期望把全部的信息都包括进来,尽管会有很多无效信息,这时我们认为αrBA\frac{\alpha}{r}BA所包含的信息量已经近似于ΔW\Delta W,但是这时候并没有实现参数量的减少。这时我们设置α=r\alpha = r,意味着我们假定LoRA低秩微调的效果和全参数微调持平,之后再调整r的大小以期待减少参数量的同时不减少包含的信息量,即依旧与ΔW\Delta W近似。

需要注意的是每一次r的调整意味着BA都重新学习了,不是一开始的BA

以4.2节的例子为例,尽管我们没采用全量微调,但如果采用全量微调,ΔW\Delta W是客观存在的,它可以奇异值分解:ΔW=U^ΣV^T\Delta W = \hat{U}\Sigma \hat{V}^{T}

我们一开始选取一个很大的r,因此它大概率大于ΔW\Delta W的秩,在减小r的过程中,学习到的BA越来越接近真正的低秩分解的U^ΣV^T\hat{U}\Sigma \hat{V}^{T}

  • 若选取的r大于ΔW\Delta W的秩,则B的秩大于U^\hat{U}的秩,代表选取了更多的列,多出来的列是冗余的信息。
  • 若选取的r小于ΔW\Delta W的秩,则B的秩小于U^\hat{U}的秩,代表选取了更少的列,丢失了一部分信息。

问题:为什么要有参数αr\frac{\alpha}{r},直接调整r的大小,使得学习到的B近似于U^\hat{U},A近似于ΣV^T\Sigma \hat{V}^{T}就行了

我个人认为提出来一个参数αr\frac{\alpha}{r},也无非是使得BA的元素都除以参数αr\frac{\alpha}{r}而已,与直接使用h=Wx+BAxh = Wx + BAx没有本质上的区别。这里多一个α\alpha参数用以设定为第一次实验r的值,感觉是起记录的作用。