Skip to content

【AI】大模型LoRA微调的原理和实验(未开始) ​

前置数学基础 ​

矩阵的秩 ​

首先介绍一下矩阵的初等变换。矩阵的初等变换有行变换和列变换,规则是一致的,这里以行变换说明。以下几种变换是初等行变换:

  1. 交换任意两行的位置
  2. 非0的常数乘任意一行
  3. 把任意一行乘以非0常数,加到另一行上面

如果一个矩阵经过任意数量的初等变换之后,尽量将矩阵中的行(或者列)变为全0。剩下的无法变化的行数(或列数)就是矩阵的秩。如何可以变为全0?如果一行是其它行的倍数,或者可以被其它多个行经过倍数和加减表示,那么这一行就可以变为0。

例如下面的例子,第二行是第一行的两倍,那么第二行=第一行*2,第四行=第一行+第三行,那么第二行和第四行都可以经过初等行变换处理为全0行。

[A1A2A3A4]=[1234246810102244]=[12341∗22∗23∗24∗210101+12+03+14+0]=[A1A1∗2A3A1+A3]

剩下无法被其它向量表示的向量,都是线性无关的,这些线性无关的向量个数即是矩阵的秩。从信息论的角度来说,这些可以被其它向量表示的向量,是不增加信息量的,它自身没有存在价值,属于冗余参数。

LoRA方法原理 ​

LoRA简介 ​

众所周知,大模型之所以有“大”这个字,是因为模型参数量非常大,训练和部署都需要较高的算力和内存(显存)。尤其是训练对于硬件的要求更高,在之前的文章中我们也描过:【AI】一文读懂大模型生态:分类/参数/结构/训练/GPU/评测/排行/社区。当我们想微调模型使其更好的适应某些任务或者知识时,如果将所有的参数一起调整,那么如此高的硬件要求会使得大部分开发者望而却步,无法实现。深度神经网络中的参数近似一个黑盒,我们无法区分出哪些参数对应哪些知识,精确的对某一部分参数做针对性调整。

那么有没有一种方法可以对大模型进行微调,但是不要求如此高的硬件性能,且不用精确挑选参数呢?有的,这就是LoRA方法。LoRA英文全称叫做Low-Rank Adaptation,即“低秩适应”方法,是由微软在2021年提出的,论文原文:[LoRA: Low-Rank Adaptation of Large Language Models(https://arxiv.org/abs/2106.09685)。

在大模型的每层中,有Q/K/V/O几个矩阵,这些矩阵的行数和列数都和向量维度有关,例如在Qwen3-0.6B中是1024‌×1024和1024‌×2048,每个矩阵的参数量都非常大。实际使用LoRA方法时,可以对这些矩阵全部微调,或者只选择部分微调。我们来看一下公式,这里以1024‌×1024为例说明。

设:W1024×1024为原始矩阵X1024为输入向量H1024为输出向量模型本身执行:H1024=W1024×1024X1024

如果对所有参数都进行调整,这叫做“全量微调”,相当于在W旁边挂一个和W一样参数量的矩阵,它对于硬件的要求非常高。

设:V1024×1024为全量微调的参数全量微调执行:H1024=W1024×1024X1024+V1024×1024X1024=(W1024×1024+V1024×1024)X1024

但LoRA方法,将些矩阵冻结不修改,而是在旁边挂A和B两个小矩阵,通过调整两个小矩阵的参数值来影响最终结果。

设:Ar×1024和B1024×r为LoRA微调的参数LoRA微调执行:H1024=W1024×1024X1024+(ar)B1024×rAr×1024X1024=(W1024×1024+(ar)B1024×rAr×1024)X1024=(W1024×1024+△W1024×1024)X1024

其中a和r为超参数。超参数的含义是我们在训练之前就提前确定好的参数,训练过程中不会变化。虽然训练中不会变化,但参数值对于模型表现还是有较大影响的,甚至有时需要尝试针对不同的超参数进行训练。

r表示AB两个矩阵的行数和列数,一般可以取8,16,64等值。这里我们以8来举例。对于一个矩阵,全量微调需要调整的参数量为1024×1024=1048576。而LoRA方法仅需调整AB两个矩阵,参数量为2×1024×8=16384。16384/1048576=0.015%,也就是说通过LoRA使用r=8的参数,调整的参数量为全量微调的0.015%。

同时矩阵乘法的性质,B1024×rAr×1024=△W1024×1024,AB两个矩阵无论r值取多少,相乘之后的矩阵形状都和模型原有的参数矩阵W一致,因此虽然LoRA方法参数量小,但它可以影响到模型原矩阵的每一个参数值。且当我们训练完成,希望将LoRA方法的调整混合回原模型时,也是简单的相加即可:

W1024×1024′=W1024×1024+B1024×rAr×1024=W1024×1024+△W1024×1024H1024=(W1024×1024+△W1024×1024)X1024=W1024×1024′X1024

a表示缩放比例。这个超参数如果固定不变,当r增大时,可以抵消由于矩阵维度增加带来数字增加,使得调节幅度突然增大的问题。但也有很多人与r搭配使用,使得a/r之后的数字相同,代表BA矩阵在不同的r下采用相同的缩放系数。

在实际应用中,BA矩阵可以合并进模型中,永久修改参数值,也可以保留外挂形式,甚至可以针对不同的任务微调不同的参数,使用时再切换不同的外挂矩阵。

简化计算 ​

合并进原模型矩阵时需要计算BA相乘,组成1024×1024的矩阵。但在LoRA训练时,却不需要这么处理,而是走一条计算量更少的路,这里我们对比两种计算方式。其中每个操作计算方式为结果矩阵元素个数*单个元素的计算量。第一种是计算BA相乘的方式:

△W1024×1024=B1024×8A8×1024乘法:1024∗1024∗8=8388608加法:1024∗1024∗7=7340032W1024×1024′=W1024×1024+△W1024×1024加法:1024∗1024=1048576H1024=W1024×1024′X1024乘法:1024∗1024=1048576加法:1024∗1023=1047552合计乘法:9437184加法:9436160

可以看到,第一种方式计算量最大的就是BA相乘。第二种是先另A与X相乘,这样会得到一个8维的向量,再用这个向量与B相乘,这种方式可以大幅缩减原有BA相乘的计算消耗:

C8=A8×1024X1024乘法:8∗1024=8192加法:8∗1023=8184H1024″=B1024×8C8乘法:1024∗8=8192加法:1024∗7=7168H1024′=W1024×1024X1024乘法:1024∗1024=1048576加法:1024∗1023=1047552H1024=H1024′+H1024″加法:1024合计乘法:1064960加法:1063928

通过计算结果可以看到,不管是乘法还是加法都明显大幅下降,这里我们再给出下降的比例,这里以乘法为例:

  • 9437184/1064960 = 8.86 第一种的计算量是第二种的接近9倍
  • 不使用LoRA方法的场景:只有WX这一个矩阵乘法计算,计算量为1048576
  • 9437184/1048576 = 9 以第一种方式使用LoRA,计算量为不使用的9倍
  • 1064960/1048576 = 1.0156 以第二种方式使用LoRA,计算量仅增加了1.56%

仅通过改变计算的先后顺序,能做到计算量相比不使用LoRA仅有微小的上涨,实际使用时可以几乎忽略不计。

低秩证明 ​

前面说到LoRA的中文名叫做“低秩适应”,这里的秩表示的就是矩阵的秩,低秩意思是它用一个秩较低的矩阵取适应这个大的W矩阵,这里秩的值就是超参数r。

前面说过r值是矩阵A和B的列数和行数,那么A和B的矩阵的秩是小于等于r的。那么BA相乘的组合矩阵,虽然维度是1024×1024,但是秩依然是小于等于r的。事实上它不超过A的秩,也不超过B的秩。这里我们证明一下这个结论:

B1024×rAr×1024=[b11a11+b12a21+...b11a12+b12a22+...b11a13+b12a23+......b21a11+b22a21+...b21a12+b22a22+...b21a13+b22a23+......b31a11+b32a21+...b31a12+b32a22+...b31a13+b32a23+..................]=[b11[a11a12a13...]+b12[a21a22a23...]+...b21[a11a12a13...]+b22[a21a22a23...]+...b31[a11a12a13...]+b32[a21a22a23...]+......]

可以看到,通过将矩阵乘法后的每一行拆分,可以将b看作系数,结果矩阵中的每一行都是由不同的B的系数乘以A矩阵中的每一行组成的(公式中为了方便说明以列向量表示,实际都是A的行向量)。通过这种方式,结果矩阵的每一行都是A矩阵的组合,根据矩阵的秩的定义,那么结果矩阵的秩肯定小于等于矩阵A的秩。

同理,我们查看结果矩阵的每一列,发现可以表示A矩阵作为系数,B的列向量作为结果矩阵每一列的组成部分。这里公式未列出,可以自行推导。因此结果矩阵的秩肯定小于等于矩阵B的秩。这里就证明了前面的结论:BA相乘的结果矩阵的秩,不超过A的秩,也不超过B的秩。

我们一般把r叫做LoRA训练后矩阵的秩。理论上训练结果的秩最大是r,小于r也是可能的。在实际的模型训练中,由于r的取值一般比较小,实际上都能让AB矩阵的秩达到r的状态。

使用LoRA来修改模型参数,好处是模型参数量小使得存储空间占用小,且训练容易。但低秩特性也有一些劣势,与全量微调的效果是有差距的。例如可以用LoRA做到调整输出风格,适配简单任务,但在学习有些新知识方面却较难做到。

初始化和均匀分布 ​

在训练之前,首先将AB两个矩阵初始化。其中A矩阵的每个元素初始化为随机数,B矩阵初始化为全0矩阵。这样在开始训练时得到的结果与原模型一致。

原模型:H1024=W1024×1024X1024LoRA初始化时:H1024=W1024×1024X1024+B1024×rAr×1024X1024=W1024×1024X1024+[00...00............]Ar×1024X1024=W1024×1024X1024

在这个初始化基础上,每次输入训练数据得到输出token,再根据输出token是否正确来反向更新AB矩阵的每个参数,每次只更新一个很小的值。最后训练完毕后得到AB矩阵最终的参数值。

那么应该如何A矩阵的初始化方式呢?理论上任意的随机数都可以,因为模型训练时会逐步调整参数值。但部分不合理的初始化方式会使得模型初始化时偏差较大,致使参数调整过程更长也更难。因此,合理的初始化方式可以让训练过程更轻松,更容易达到想要的结果。

微软在当年提出的论文中,使用的是高斯分布来生成随机数。但目前最常用的是采用Kaiming Uniform分布(均匀分布)来实现,这也是各大框架的默认方式。这是由中国人何恺明发明的,他还发明了深度残差网络(ResNet),现在几乎所有大模型都在使用。

首先介绍下均匀分布。均分分布指的是设定一个范围,在这个范围内生成随机数,且随机数的在这个范围内每个位置出现的可能性都是一样的。例如范围为(0,10),则随机数在(0,5)之间的概率和(5,10)一致,都是50%;且和(3,8)的概率也一致。

然后再介绍一下Kaiming Uniform分布,这个分布是为了ReLU激活函数设计的权重初始化方法‌。它的均匀分布中点为0,分布的范围是这样:(-bound, bound)。其中bound的计算公式如下:

bound=6(1+a2)fan_in

其中a与RELU有关,fan_in指的是输入维度的的个数。但本文的LoRA与激活函数是两回事,因此这里就不介绍公式和ReLU了。在PyTorch中为了和之前的分布公式做兼容,LoRA中的a值默认取5,计算结果如下:

bound=6(1+a2)fan_in=6(1+52)fan_in(带入a=5)=66fan_in=1fan_in

这就是LoRA中A矩阵初始化随机数的分布范围计算公式。带入fan_in的值为1024,最后计算出bound的结果为1/32 = 0.03125。因此均匀分布的范围就是 (-0.03125,0.03125)。

总结 ​

  1. 还有很多在LoRA基础上改进的方法

参考 ​