接着对$\Delta W = BAx$作零填充使其与$W_0x$shape一致,并进行缩放(由于不一定会对整个预训练权重矩阵做低秩分解,所以 $\Delta W = BAx$的shape不一定等于 $W_0x$,要对前者进行padding,使其与后者的shape一致,才能让两者element-wise add)
将这部分结果加回 $W_0x$中
前向传播代码:
1 2 3 4 5 6 7 8
defforward(self,x:torch.Tensor): result = F.linear(x,transpose(self.weight,self.fan_in_fan_out),bias=self.bias) ifself.r > 0: after_A = self.lora_A(self.lora_dropout(x)) after_B = self.lora_B(after_A.transpose(-2,-1)).transpose(-2,-1) result += self.zero_pad(after_B)*self.scaling return result
填充部分代码:
1 2 3 4 5 6 7
defzero_pad(self,x): #创建一个形状与x的形状相同,但最后一个维度的大小为self.out_features的全零张量 result = x.new_zeros((*x.shape[:-1],self.out_features)) result = result.view(-1,self.out_features) result[:,self.lora_ind] = x.reshape(-1,self.out_features//len(self.enable_lora)*sum(self.enable_lora)) return result.view((*x.shape[:-1],self.out_features))
# Compute the indices # 记录权重矩阵中,做了低秩分解的是哪些“子矩阵” self.lora_ind = self.weight.new_zeros((out_features,), dtype=torch.bool).view(len(enable_lora), -1) self.lora_ind[enable_lora, :] = True self.lora_ind = self.lora_ind.view(-1)
self.reset_parameters() if fan_in_fan_out: # fan_in_fan_out 是针对 GPT-2 的 Conv1D 模块的, # 该模块和 Linear 的区别就是维度互为转置 self.weight.data = self.weight.data.T
defreset_parameters(self): nn.Linear.reset_parameters(self) ifhasattr(self, "lora_A"): # initialize A the same way as the default for nn.Linear and B to zero nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5)) nn.init.zeros_(self.lora_B.weight)
# 注:当调用 model.eval() 时就会调用 train(mode=False) # 将低秩矩阵 A, B 合并至原权重矩阵 W ifnot mode andself.merge_weights andnotself.merged: # Merge the weights and mark it ifself.r > 0andany(self.enable_lora): # \delta_W = BA delta_w = ( # 这里使用1维卷积将低秩矩阵 A, B 进行“融合”: # A(r * k) 作为输入,r 看作是其 channel,k 看作是空间维度上的大小; # B(d * r * 1) 作为卷积权重,d 是 output channel, r 是 input channel, 1 是 kernel size(注意B本身就是用1维分组卷积实现的)。 # 由于是卷积,因此二维的 A 需要增加一维给 mini-batch:r * k -> 1 * r * k。 # 卷积后,输入(1 * r * k) -> 输出(1 * d * k) F.conv1d( self.lora_A.weight.data.unsqueeze(0), self.lora_B.weight.data, groups=sum(self.enable_lora), ) .squeeze(0) # 1 * d * k -> d * k .transpose(-2, -1) # d * k -> k * d ) # zero_pad() 是对低秩分解矩阵 \delta_W 进行0填充,因为原权重矩阵 W 中可能有些部分没有进行低秩分解, # 从而得到一个和原权重矩阵 W 的 shape 对齐的结果,以便进行加和。k * d -> k * D(假设 D 是原权重矩阵 W 的 out features) # 对于原权重矩阵 W 是 Linear 层的情况,fan_in_fan_out = False,于是这里会进行 transpose: k * D -> D * k; # 而对于原权重矩阵 W 是 GPT-2 的 Conv1D 的情况,fan_in_fan_out=True,于是不需要 transpose,它的 out features 就是放在第二维的 # W = W + # \delta_W self.weight.data += transpose(self.zero_pad(delta_w * self.scaling), notself.fan_in_fan_out) elif xxx: ...
if xxx: ... # 前一个分支是代表 mode=False,进入该分支说明 mode=True,即调用了 model.train(), # 那么当低秩矩阵 A, B 已经合并至原权重矩阵 W 中时,就需要将它们分解出来,以便进行训练(预训练权重 W 无需训练)。 elifself.merge_weights andself.merged: # Make sure that the weights are not merged ifself.r > 0andany(self.enable_lora): # \delta_W = BA delta_w = ( F.conv1d( self.lora_A.weight.data.unsqueeze(0), self.lora_B.weight.data, groups=sum(self.enable_lora), ) .squeeze(0) .transpose(-2, -1) ) # W = W - \delta_W self.weight.data -= transpose(self.zero_pad(delta_w * self.scaling), notself.fan_in_fan_out) self.merged = False