LoRA (Low-Rank Adaptation of Large Language Models) 是一种用于微调大型预训练模型的方法,尤其适合在计算资源有限的情况下进行微调。通过限制参数更新的范围,并巧妙利用矩阵分解,LoRA 大幅减少了微调过程中的参数量,从而提高了效率并降低了显存占用。
1. LoRA为什么会出现?
LoRA 的出现是为了解决大型语言模型(如 GPT、BERT 等)在微调时面临的计算和内存瓶颈。随着模型参数规模的急剧增大,传统的全参数微调(fine-tuning)需要大量的计算资源和存储空间,限制了模型在资源有限环境中的应用。
LoRA 的核心思想是:在不改变原始模型权重的情况下,通过引入低秩矩阵的增量来进行微调。也就是说,LoRA 不直接更新原模型的参数,而是为其特定的层添加额外的可训练参数,从而实现对模型的适应调整。一方面知识对部分层进行微调,另一方面还对需要更新的矩阵层ΔW
进行了分解,进一步减少了参数量和计算量。
2. 基本原理
LoRA 假设神经网络的权重更新矩阵 ΔW
是低秩的。利用这一假设,它通过低秩矩阵分解,将矩阵更新分解为两个小矩阵的乘积,从而减少了参数的数量。
具体来说,在标准的神经网络层中,例如一个线性层,其计算可以表示为:
y
=
W
x
y=Wx
y=Wx
其中 W
是权重矩阵,x
是输入,y
是输出。在传统微调中,我们需要直接更新 W
这个大矩阵。而 LoRA 的做法是保持 W
不变,增加一个可训练的增量 ΔW
,使得微调后的输出为:
y
=
(
W
+
Δ
W
)
x
y=(W+ΔW)x
y=(W+ΔW)x
为了降低 ΔW
的参数量,LoRA 通过将 ΔW
分解为两个低秩矩阵 A
和 B
,即:
Δ
W
=
A
B
ΔW=AB
ΔW=AB
其中 A
和 B
的维度远小于 W
,从而大大减少了微调时需要更新的参数。
3. 微调过程
LoRA 微调的具体步骤如下:
3.1 预训练模型加载
首先加载一个大型预训练模型,例如 GPT 或 Qwen。此时,模型的所有参数 W
都是固定的(微调过程中冻结模型参数),不会在 LoRA 微调过程中直接更新。
3.2 插入 LoRA 层
在模型的某些层(例如 Transformer 的注意力层)中插入 LoRA 层。具体地,在这些层的线性变换(Linear Layers)上引入低秩分解。通常,LoRA 会选择在特定的层(如自注意力层的 Query 和 Key 投影矩阵)应用低秩矩阵分解。
3.3 定义低秩矩阵
在插入的 LoRA 层中,定义两个小矩阵 A
和 B
,其中 A
的尺寸是 r×dr
, B
的尺寸是 d×kd
,这里 d
是输入维度,k
是输出维度,r
是分解的秩,通常远小于 d
和k
。
3.4 训练过程
- 在训练过程中,只有 LoRA 引入的
A
和B
矩阵是可训练的,原始模型的权重W
不会被更新。 - 模型的前向传播仍然会计算完整的
Wx
,同时也会计算增量ΔWx=(AB)x
,然后将它们相加。 - 通过这种方式,LoRA 微调可以适应新的任务数据,但所需更新的参数量大大减少。
3.5 损失函数与优化器
LoRA 微调的损失函数和标准的微调一样,通常是交叉熵损失或者其他适合任务的损失函数。优化器也可以是常用的如 Adam、SGD 等。唯一的区别是,只有 A
和 B
参与梯度更新,而模型的其他部分保持冻结状态。
在 LoRA 微调过程中,损失函数的计算方式与传统的全参数微调相似,核心思想是在保持原模型权重不变的前提下,通过引入的低秩矩阵来调整模型的输出。具体来说,LoRA 引入了两个小矩阵 A
和 B
来生成增量矩阵 ΔW
,并将其加到模型的某些层上进行训练。
前向传播中的输出:
在微调过程中,模型的输出是基于原始权重和加入的低秩矩阵共同生成的:
-
原始模型的输出:模型加载了预训练好的权重矩阵
W
,例如线性层权重矩阵。在前向传播中,原模型的输出为:
y pre-trained = W x y_{\text{pre-trained}} = W x ypre-trained=Wx
这里W
是原始的权重矩阵,x
是输入。 -
加入 LoRA 的输出:LoRA 方法通过低秩矩阵
A
和B
来调整权重矩阵,形成一个增量矩阵ΔW
,其计算如下:
Δ W = A B ΔW=AB ΔW=AB
因此,经过微调后的线性层输出为:
y LoRA = ( W + Δ W ) x = W x + ( A B ) x y_{\text{LoRA}} = (W + \Delta W) x = W x + (A B) x yLoRA=(W+ΔW)x=Wx+(AB)x
这里ΔW
反映了 LoRA 层的贡献,它在前向传播中加入了微调产生的变化。、
损失函数的计算:
与常规的全参数微调类似,LoRA 微调的损失函数通常是基于特定任务的目标来定义的,比如交叉熵损失(分类任务)或均方误差(回归任务)。LoRA 的关键在于损失函数的输入是微调后的模型输出 yLoRA
,而不是只使用原始权重的输出。
假设模型在给定输入 xxx 后的目标输出为 ytrue
,那么典型的损失函数可以是交叉熵损失:
L
=
CrossEntropy
(
y
LoRA
,
y
true
)
\mathcal{L} = \text{CrossEntropy}(y_{\text{LoRA}}, y_{\text{true}})
L=CrossEntropy(yLoRA,ytrue)
或者是均方误差损失(MSE):
L
=
MSE
(
y
LoRA
,
y
true
)
\mathcal{L} = \text{MSE}(y_{\text{LoRA}}, y_{\text{true}})
L=MSE(yLoRA,ytrue)
其中
y
LoRA
=
(
W
+
Δ
W
)
x
y_{\text{LoRA}} = (W + \Delta W) x
yLoRA=(W+ΔW)x
注意,这里的损失函数仅仅是基于模型的输出计算的,和传统微调的方式没有区别。LoRA 的创新之处在于它通过调整参数的方式减少了可训练参数的数量,但损失计算还是基于模型的输出和目标输出的差异。
参数更新:
在损失函数计算完毕后,优化器(如 Adam 或 SGD)会基于损失函数的梯度来更新参数。在 LoRA 微调中,模型的大部分权重(原始的 W
)是冻结的,不会更新。只有新增的低秩矩阵 A
和 B
参与参数更新。
具体来说,基于损失函数的反向传播过程会计算:
-
对低秩矩阵
A
的梯度
∂ L ∂ A \frac{\partial \mathcal{L}}{\partial A} ∂A∂L -
对低秩矩阵
B
的梯度
∂ L ∂ B \frac{\partial \mathcal{L}}{\partial B} ∂B∂L
而原始权重矩阵 W
的梯度则为零,因为它是冻结的。
因此,优化器仅更新低秩矩阵 A
和 B
的参数,模型的其他部分保持不变。这种方式使得 LoRA 在显著减少训练参数的同时,仍能有效调整模型的输出,从而适应新的任务。
最后,将微调完的 A
和 B
矩阵相乘得到 ΔW
,然后与原模型参数 W
相加即可得到微调后的模型参数。
4. 优势与特点
LoRA 相比传统的微调方法有以下几个显著优势:
- 参数效率:只需更新非常少量的参数,大大降低了显存的占用。
- 加速训练:由于参与训练的参数量减少,训练速度可以显著提高。
- 节省存储:微调后,只需存储两个低秩矩阵
A
和B
,而不需要存储整个大型模型的参数。 - 性能保留:在很多任务中,LoRA 的微调效果可以接近甚至达到全参数微调的效果。
5. 应用场景
LoRA 尤其适用于以下场景:
- 资源受限环境:如边缘设备或低计算资源的服务器,LoRA 可以使得在有限资源下进行大模型的微调成为可能。
- 多任务微调:由于 LoRA 只引入了极少量的参数,可以对同一模型进行多任务微调,而不需要每次保存完整的模型权重。
6. 实践中的应用
在实际应用中,LoRA 已被广泛应用于各类语言模型的微调,特别是在自然语言处理(NLP)任务中。例如,LoRA 可以用于将 GPT-3 微调到特定的下游任务(如文本生成、问答等)上,而无需更新整个 GPT-3 模型。
此外,LoRA 也逐渐应用于其他领域,如计算机视觉中的图像分类和目标检测任务中,作为一种通用的低秩适应方法。
总结
LoRA 微调通过将大型模型的权重更新限制在低秩矩阵上,实现了高效的参数更新和资源节约。它在保持预训练模型参数不变的情况下,仅通过少量的参数调整适应新的任务,从而极大降低了微调的成本,适用于需要频繁微调或者资源有限的应用场景。