【学习】手写一个lora微调算法~


手写一个LoRA算法

参考视频: https://www.bilibili.com/video/BV1ZZ421T7XJ/?share_source=copy_web&vd_source=2ff04f0cc70237133af9bcd6be27a652


初始化LoRA Linear类

首先 LoRA 的原理就是在预训练模型的权重矩阵上添加两个 LoRA_A 和 LoRA_B 矩阵组合输出嘛,由于 LoRA_A 和 LoRA_B 矩阵可以通过秩 $r$ 来调整规模,因此训练起来参数量就比 FT 小了很多。

所以实际上在训练的时候,我们是通过冻结原模型参数矩阵,来学 LoRA_A 和 LoRA_B 矩阵的参数来实现的。那么就需要将原模型里的 Linear 层给替换成封装了 LoRA 模块的 LoRA Linear 层。

LoraLinear 类的初始化如下:

class LoraLinear(nn.Module):
    def _init_(self, in_features, out_features, merge, rank=16, alpha=16, dropout=0.5):
        super(LoraLinear, self)._init_()
        self.in_features = in_features
        self.out_features = out_features
        self.merge = merge
        self.rank = rank
        self.alpha = alpha
        self.dropout = dropout

in_features:输入维度,比如词嵌入维度是768,这里就是768;

out_features:输出维度;

merge:控制是否合并 LoRA 和原始权重;

rank:LoRA 的秩,越小压缩程度越大,控制 LoRA 矩阵参数量的主要因素;

alpha:缩放系数,用于调节 LoRA 矩阵参数的影响程度;

dropout:防止过拟合的 dropout 率;

        self.linear = nn.Linear(in_features, out_features)
        if rank > 0:
            # 构建权重参数
            self.lora_b = nn.Parameter(torch.zeros(out_features, rank))
            self.lora_a = nn.Parameter(torch.zeros(rank, in_features))
            self.scale = self.alpha / rank
            self.linear.weight.requires_grad = False

        if dropout > 0:
            self.dropout = nn.Dropout(self.dropout)
        else:
            self.dropout = nn.Identity()
        
        self.initial_weights()
        
    def initial_weights(self):
        nn.init.kaiming_uniform_(self.lora_a, a=math.sqrt(5))

创建 LoRA 的 A 和 B 矩阵:A (rank × in_features) 和 B (out_features × rank);

计算缩放因子 scale = alpha / rank;

使用 Kaiming 初始化来初始化 LoRA 矩阵 A,这里只需要初始化一个权重矩阵,另一个 B 矩阵初始化为零矩阵。这样训练开始时 A @ B = zeros,随着训练进行会慢慢调整 B 矩阵的参数值;

前向过程

    def forward(self, x):
        if self.rank > 0 and self.merge:
            # 可以看到这边lora_b是out_features * rank的矩阵,lora_a是rank * in_features的矩阵,乘完后是out_features * in_features的矩阵
            # 那么weight就是out_features * in_features的矩阵,刚好和linear.weight的形状一样
            # 而wx+b,因此x就是in_features * batch_size的矩阵
            output = F.linear(x, weight=self.linear.weight + self.lora_b @ self.lora_a * self.scale, bias=self.linear.bias)
            output = self.dropout(output)
            return output
        else:
            return self.linear(x)

这里就是用 LoRA 的 A 和 B 矩阵乘上原始权重矩阵的计算过程来在前向中参与进计算过程。在训练的时候就会反向更新 LoRA 两个矩阵的参数。

最简单的实现过程就是这样,假若要将 LoRA 模块包装到具体的一个模型进行微调,那么可以把 base_model 里的 module 里面的线性层替换成 LoRA Linear 层,然后再冻结预训练权重在下游任务上进行训练。可以直接利用 hugging face 的 Trainer 类开一个 Trainer 直接训。


文章作者: Cyan.
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cyan. !
 上一篇
【学习】在DeepSeek-R1-1.5b模型上做一个简单的lora微调~ 【学习】在DeepSeek-R1-1.5b模型上做一个简单的lora微调~
最近开始上手微调模型做些小项目,所以先简单写了个lora微调DeepSeek-R1-Distill-Qwen-1.5B的脚本,熟悉熟悉一些基本的库和微调操作。记录一下~
2025-02-18
下一篇 
【学习】DeepSeekMoE:我们需要更多Experts! 【学习】DeepSeekMoE:我们需要更多Experts!
将MOE的专家数进一步分割细化,增设共享专家,预训练模型的工作。话说今年DeepSeek真火哇
2025-01-05
  目录