论文阅读笔记——LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

LoRA 论文
传统全面微调,对每个任务学习的参数与原始模型相同:
m a x Φ ∑ ( x , y ) ∈ Z ∑ t = 1 ∣ y ∣ l o g ( P Φ ( y t ∣ x , y < t ) ) 式(1) max_{\Phi}\sum_{(x,y)\in Z}\sum^{|y|}_{t=1}log(P_{\Phi}(y_t|x,y<t)) \qquad \text{式(1)} maxΦ(x,y)Zt=1ylog(PΦ(ytx,y<t))(1)
LoRA 提出对模型中权重更新部分低秩分解,编码任务特定的参数,大幅减少所需参数规模,同时优化 Θ \Theta Θ 来寻找 Δ Θ \Delta \Theta ΔΘ 。对于 175B 的 GPT-3 参数量只有原来的 0.01%。
m a x Θ ∑ ( x , y ) ∈ Z ∑ t = 1 ∣ y ∣ l o g ( p Φ 0 + Δ Φ ( Θ ) ( y t ∣ x , y < t ) ) max_{\Theta}\sum_{(x,y)\in Z}\sum^{|y|}_{t=1}log(p_{\Phi_0+\Delta \Phi(\Theta})(y_t|x,y<t)) maxΘ(x,y)Zt=1ylog(pΦ0+ΔΦ(Θ)(ytx,y<t))

传统方法不足

  • 添加 adapters 的策略虽然参数少,但会在推理阶段引入延迟——增加了模型深度。并且有额外参数和计算,在模型中这些会被放大。
  • 直接优化输入层(prefix)在训练参数方面并非单调变化且保留一部分长度进行调整降低了下游任务的序列长度——占用了一部分序列长度,减少了可用的输入序列长度。并且他的优化难度也大。

LoRA

在这里插入图片描述
核心思想:对于一个预训练的 W 0 ∈ R d × k W_0 \in R^{d×k} W0Rd×k ,训练低秩矩阵 B A BA BA 来替代权重更新部分 Δ W \Delta W ΔW d i m ( A ) = r × k , d i m ( B ) = d × r r < < m i n ( d , k ) dim(A) = r×k, \quad dim(B)=d×r \quad r << min(d,k) dim(A)=r×k,dim(B)=d×rr<<min(d,k)
h = W 0 x + Δ W x = W 0 x + B A x h=W_0x+\Delta Wx=W_0x+BAx h=W0x+ΔWx=W0x+BAx
其中,A 采取随机高斯初始化,B 为 0。

LoRA 在适应期间不需要满足满秩的条件,只需要将 r 设置为预训练权重矩阵的秩,大致可恢复完全微调的能力,可以维持原来架构。

LoRA 优势:

  1. 参数高效:训练参数减少了数千倍,例如在GPT-3中,训练参数从1750亿减少到数百万甚至更少。
  2. 计算资源节省:由于需要计算梯度的参数大大减少,显存占用降低,训练速度加快。
  3. 无额外推理延迟:训练完成后,可以将低秩更新融合到预训练权重中,推理时无需额外计算。
  4. 任务切换灵活:不同任务只需存储和加载小的低秩矩阵,实现快速切换,减少存储需求。
    将 LoRA 应用于 transformer 架构中,只需要对自注意力模块 ( W q , W k , W v , W 0 ) (W_q,W_k,W_v,W_0) (Wq,Wk,Wv,W0) 中的 W q , W v W_q,W_v Wq,Wv 进行适应,保持 MLP 不变。
    在下游部署时,只需要减去 B A BA BA 即可恢复 W 0 W_0 W0,再根据任务需求加上对应 B ′ A ′ B^{'}A^{'} BA 。最明显的好处在于内存和存储使用量减少。

实验结果

在这里插入图片描述

代码实现

class LoRALayer():
    def __init__(
        self, 
        r: int, 
        lora_alpha: int, 
        lora_dropout: float,
        merge_weights: bool,
    ):
        self.r = r
        self.lora_alpha = lora_alpha
        # Optional dropout
        if lora_dropout > 0.:
            self.lora_dropout = nn.Dropout(p=lora_dropout)
        else:
            self.lora_dropout = lambda x: x
        # Mark the weight as unmerged
        self.merged = False
        self.merge_weights = merge_weights
class Embedding(nn.Embedding, LoRALayer):
    def __init__(
        self,
        num_embeddings: int,
        embedding_dim: int,
        r: int = 0,
        lora_alpha: int = 1,
        merge_weights: bool = True,
        **kwargs
    ):
        nn.Embedding.__init__(self, num_embeddings, embedding_dim, **kwargs)
        LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=0,
                           merge_weights=merge_weights)

        if r > 0:
            self.lora_A = nn.Parameter(self.weight.new_zeros((r, num_embeddings)))
            self.lora_B = nn.Parameter(self.weight.new_zeros((embedding_dim, r)))
            self.scaling = self.lora_alpha / self.r
            # 冻结预训练权重
            self.weight.requires_grad = False
        self.reset_parameters()

    def reset_parameters(self):
        nn.Embedding.reset_parameters(self)
        if hasattr(self, 'lora_A'):
            nn.init.zeros_(self.lora_A)
            nn.init.normal_(self.lora_B)

    def train(self, mode: bool = True):
        nn.Embedding.train(self, mode)
        if mode:
            if self.merge_weights and self.merged:
                if self.r > 0:
                    self.weight.data -= (self.lora_B @ self.lora_A).transpose(0, 1) * self.scaling
                self.merged = False
        else:
            if self.merge_weights and not self.merged:
                # Merge the weights and mark it
                if self.r > 0:
                    self.weight.data += (self.lora_B @ self.lora_A).transpose(0, 1) * self.scaling
                self.merged = True
                
	    def forward(self, x: torch.Tensor):
	        if self.r > 0 and not self.merged:
	            result = nn.Embedding.forward(self, x)
	            after_A = F.embedding(
	                x, self.lora_A.transpose(0, 1), self.padding_idx, self.max_norm,
	                self.norm_type, self.scale_grad_by_freq, self.sparse
	            )
	            result += (after_A @ self.lora_B.transpose(0, 1)) * self.scaling
	            return result
	        else:
	            return nn.Embedding.forward(self, x)
### 如何实现LoRA(低秩适应) #### 准备环境 为了实现LoRA,首先需要准备合适的开发环境。这通常涉及安装必要的库和依赖项。由于LoRA的官方GitHub仓库提供了详细的设置指南[^1],建议按照该指南配置环境。 #### 修改模型结构 在Transformer架构中实施LoRA的关键在于向选定的权重矩阵添加可训练的低秩分解矩阵对。具体来说: - 对于每一个目标权重矩阵 \( W \),创建两个新的小规模矩阵 \( A \in \mathbb{R}^{r\times d} \) 和 \( B\in \mathbb{R}^{d\times r} \),其中\( r \ll d \)[^3]。 ```python import torch.nn as nn class LoRAModule(nn.Module): def __init__(self, original_layer, rank=4): super().__init__() self.original_layer = original_layer # Initialize low-rank matrices A and B with Xavier uniform initialization. self.A = nn.Parameter(torch.empty(rank, original_layer.in_features)) self.B = nn.Parameter(torch.empty(original_layer.out_features, rank)) nn.init.xavier_uniform_(self.A) nn.init.zeros_(self.B) def forward(self, input_tensor): # Compute the output using both the original weights and the added low-rank adaptation terms. adapted_output = self.original_layer(input_tensor) + (input_tensor @ self.A.T @ self.B.T) return adapted_output ``` 此代码片段展示了如何为单个线性层定义一个带有LoRA机制的新模块。这里`original_layer`代表原有的预训练模型中的某一层,而`rank`则指定了要使用的低秩近似维度大小。 #### 训练过程调整 当采用LoRA时,只需更新这些额外加入的小型矩阵而不是整个网络的所有参数。因此,在实际训练过程中只需要关注这部分新增加的部分即可[^2]。 ```python from transformers import AdamW optimizer = AdamW([ {'params': model.lora_modules.parameters()}, # Only optimize parameters of LoRA modules ], lr=learning_rate) ``` 上述Python代码说明了怎样配置优化器来专门针对那些被注入到原有模型里的低秩矩阵进行梯度下降操作。 #### 部署阶段处理 一旦完成训练之后,在推断期间可以将所有已学得的低秩矩阵与对应的固定部分相乘合并成单一的整体表示形式,以此消除任何可能存在的性能瓶颈。 ```python def merge_lora_weights(model): for name, module in model.named_children(): if isinstance(module, LoRAModule): merged_weight = module.original_layer.weight.data.clone() merged_weight += (module.A @ module.B).T delattr(module, 'A') delattr(module, 'B') setattr(module, 'merged', True) module.original_layer.weight.data.copy_(merged_weight) ``` 这段函数遍历给定的PyTorch模型实例,并对于每个属于`LoRAModule`类型的子组件执行融合动作——即将之前分离出来的两组小型矩阵重新组合回原位,同时删除不再需要的对象属性以节省内存空间。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值