外观
代码实现
Implementation
虽然已经有一些开源的 LoRA 实现可用,但我们还是打算用 PyTorch 从零实现一遍,这样能更好地理解其中的实践细节。
如前所述,一个典型的 LoRA 层由两个矩阵 A 和 B 组成。下面的 LoRAWeights 类实现了这两个矩阵,以及对应的前向传播(forward pass):

如上所示,LoRAWeights 类的目标是把一个维度为 d×k 的矩阵分解成 A、B 两个矩阵。因此,它接受四个参数:
- d:矩阵 W 的行数。
- k:矩阵 W 的列数。
- r:秩(rank)超参数。
- alpha:一个缩放参数,用于控制适配(adaptation)的强度。
此外,self.A 和 self.B 都是该模块中可学习的参数,分别对应分解中所使用的两个矩阵。
- 矩阵 A 是从高斯分布中初始化得到的。
- 注:如有需要,我们也可以对矩阵 A 做缩放,避免初始值过大。
- 矩阵 B 是一个零矩阵。
如前所述,这样可以确保在微调开始时,A 和 B 的乘积为零。这种初始化方式也印证了这样一个事实:只要还没有进行任何微调,原始模型的权重就会被完整保留:

在 forward 方法中,输入 x 依次与矩阵 A、B 相乘,再乘以 alpha 进行缩放,得到的结果作为该模块的输出返回。
参数 alpha 是另一个超参数,起到缩放因子的作用,它决定了新增层对当前模型的影响程度。

alpha 值越高,意味着 LoRA 层带来的改动越显著,可能会让模型行为出现更明显的调整。
反之,alpha 值越低,改动就越细微,因为变换所带来的影响被削弱了。
如前所述,LoRA 是应用在神经网络中的大型矩阵上的。举例来说,假设我们有下面这个神经网络类:

由于 LoRA 是在训练完成之后才应用的,所以我们手头应该已经有一个训练好的模型。假设可以通过 model 这个对象来访问它。
现在,我们的主要目标是把 LoRAWeights 类中的矩阵,附加到上述网络各层的矩阵上。当然,每一层(fc1、fc2、fc3、fc4)都会有各自对应的 LoRAWeights 层。
注:当然,并不是每一层都必须配一个对应的 LoRAWeights 微调层。事实上,原始论文中提到,他们在研究中只针对下游任务对注意力权重做适配,出于参数效率的考虑,冻结了 Transformer 中的多层感知机(前馈网络)单元。
比如在我们这个例子里,我们可以冻结 fc4 层,因为相比网络中的其他层,它的体量并不算特别大。
此外,我们要记住,训练这个网络的方式和训练其他任何神经网络一样,只不过这次只训练权重矩阵 A 和 B,也就是说,预训练模型(model)是被冻结的。具体做法如下:

搞定!
接下来,我们用 LoRAWeights 类来定义下面这个微调网络:

如上所示:
- LoRA 层被应用在现有模型的全连接层(fc1、fc2、fc3)之上。具体来说,我们根据模型中全连接层(fc1、fc2、fc3)的维度,创建了三个 LoRAWeights 层(loralayer1、loralayer2、loralayer3)。
- 在 forward 方法中,我们先把输入传入原始模型的第一个全连接层(fc1),再把输出与同一输入经过 LoRA 层(self.loralayer1(x))得到的结果相加,然后对这个和应用 ReLU 激活函数。第二个
和第三个全连接层(fc2、fc3)也重复同样的处理,最后返回预训练模型最后一个全连接层(fc4)的输出。
搞定!
至此,这个 MyNeuralNetworkwithLoRA 模型就可以像训练其他任何神经网络一样进行训练了。
我们已经确保了预训练模型(model)在微调过程中不会更新,只有 LoRAWeights 类中的权重会被学习。
到目前为止,我们探讨了如何高效地更新模型权重(也就是 LoRA 及其各种变体)。
但微调的效果,同样也取决于你用什么数据来更新模型。
这就引出了指令微调(IFT, Instruction Fine-Tuning)——通过在精心构建的「指令—回复」数据对上进行训练,教会 LLM 如何遵循人类指令的过程。
IFT 是有监督微调(SFT, Supervised Fine-Tuning)的基础,如今大多数现代 LLM 在对齐(alignment)过程中,都会依赖某种形式的 IFT。是不是相当简单?