JJJYmmm Blog - PEFT
type: channel
link: http://jjjymmm.cn/index.php/tag/PEFT/
items: LoRA源码阅读

LoRA源码阅读
type: item
date: 2023-12-20T13:38:00+00:00
description: 简单介绍LoRA，并对LoRA官方源码进行解读，修复了一个bug(可能)Github: https://github.com/microsoft/LoRAArxiv: https://arxiv.org/abs/2106.09685简单介绍在LLM微调领域常常听到LoRA及其各种变体，一直以来的印象就是参数量少，不影响推理。今天彻底看看相应的论文和实现。总结LoRA其实很容易，整篇论文除去实验，只需要一张图和一个公式。$$ h = W_0x + \Delta Wx = W_0x + B A x \label{eq1} \tag{1} $$简单来说，LoRA的灵感来自Aghajanyan等人1，他们发现预训练LLM具有较低的“instrisic dimension”，尽管随机投影到较小的子空间，但仍然可以有效地学习。也就意味着式$\eqref{eq1}$中的$W$可能是低秩的，那么合理假设：在下游任务的微调过程中，模型权重改变量$\Delta W$也可能是低秩的，那么$\Delta W$就可以使用矩阵$A,B$进行建模。例如$A = matrix(d,r), B = matrix(r, h)$，两者的秩都不会超过$r$,且$r<<min(d,h)$。这样的好处在于，由于$r$可以非常小(论文里甚至在训GPT-2时取$r=1$)，$A,B$的参数量相较于LLM也很小，微调友好。并且对于不同的下游任务，可以通过切换不同的$\Delta W$来实现task的切换(相当于复用预训练LLM的权重)。部署时，也可以将$\Delta W$合并到$W$中，从而不影响推理性能(与其他微调方法相比，如Adapter)。源码解读接下来直接看loralib的官方实现，整体代码还是十分简洁的。库里实现了nn.Embedding/nn.Linear/nn.Conv的LoRA封装。有些实现细节与论文有所出入，以下主要讨论出入部分。LoRALayerLoRALayer类的作用主要是定义LoRA中需要用到的一些参数，如rank，scale因子$\alpha$，dropout rate，以及参数是否合并的标志merge_weights等。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_weightsLinear先从Linear层说起，init函数中，定义了$A,B$两个矩阵。这里A矩阵(B矩阵同理)定义成(r, in_features)的shape，而不是(in_features, r) , 应该是为了和nn.Linear.weight.data的shape: (out_features, in_features)保持一致# Actual trainable parameters if r > 0: self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features))) self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))值得注意的是这里A矩阵使用的是kaiming_uniform_。nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5)) nn.init.zeros_(self.lora_B)训练时，如果是train mode，会将$\Delta W$与$W$分离；如果是eval mode，则会将其合并，提高推理速度。这里的$\Delta W$就是$B @ A$。def train(self, mode: bool = True): def T(w): return w.transpose(0, 1) if self.fan_in_fan_out else w nn.Linear.train(self, mode) if mode: if self.merge_weights and self.merged: # Make …