大模型的引數量都在100B級別,由於算力的吃緊,在這個基礎上進行所有引數的微調變得不可能。LoRA正是在這個背景下提出的解決方案。
雖然模型的引數眾多,但其實模型主要依賴低秩維度的內容(low intrinsic dimension
),由此引出低秩自適應方法lora,通過低秩分解來模擬引數的改變數,從而以極小的引數量來實現大模型的間接訓練。
LoRA的思想也很簡單,在原始PLM旁邊增加一個旁路,做一個降維再升維的操作,來模擬所謂的 intrinsic rank
。
訓練的時候固定PLM的引數,只訓練降維矩陣A與升維矩陣B。而模型的輸入輸出維度不變,輸出時將BA與PLM的引數疊加。
用隨機高斯分佈初始化A,用0矩陣初始化B,保證訓練的開始此旁路矩陣依然是0矩陣。
這種思想有點類似於殘差連線,同時使用這個旁路的更新來模擬full finetuning的過程。並且,full finetuning可以被看做是LoRA的特例(當r等於k時)
總的來說,lora就是凍結預先訓練的模型權重,並將可訓練的秩分解矩陣注入Transformer架構的每一層。
目前對於大多數實驗只在 Wq 和 Wv使用LoRA,可訓練引數的數量由秩r和原始權值的形狀決定。
原始碼:https://github.com/microsoft/LoRA
LoRALayer層
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
Linear層
class Linear(nn.Linear, LoRALayer):
# LoRA implemented in a dense layer
def __init__(
self,
in_features: int,
out_features: int,
r: int = 0,
lora_alpha: int = 1,
lora_dropout: float = 0.,
fan_in_fan_out: bool = False, # Set this to True if the layer to replace stores weight like (fan_in, fan_out)
merge_weights: bool = True,
**kwargs
):
nn.Linear.__init__(self, in_features, out_features, **kwargs)
LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout,
merge_weights=merge_weights)
self.fan_in_fan_out = fan_in_fan_out
# 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)))
self.scaling = self.lora_alpha / self.r
# Freezing the pre-trained weight matrix
self.weight.requires_grad = False
self.reset_parameters()
if fan_in_fan_out:
self.weight.data = self.weight.data.transpose(0, 1)
def reset_parameters(self):
nn.Linear.reset_parameters(self)
if hasattr(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, a=math.sqrt(5))
nn.init.zeros_(self.lora_B)
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 sure that the weights are not merged
if self.r > 0:
self.weight.data -= T(self.lora_B @ self.lora_A) * 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 += T(self.lora_B @ self.lora_A) * self.scaling
self.merged = True
def forward(self, x: torch.Tensor):
def T(w):
return w.transpose(0, 1) if self.fan_in_fan_out else w
if self.r > 0 and not self.merged:
result = F.linear(x, T(self.weight), bias=self.bias)
if self.r > 0:
result += (self.lora_dropout(x) @ self.lora_A.transpose(0, 1) @ self.lora_B.transpose(0, 1)) * self.scaling
return result
else:
return F.linear(x, T(self.weight), bias=self.bias)
Peft實現
from peft import LoraConfig, get_peft_model, prepare_model_for_int8_training, TaskType
# Define LoRA Config
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q", "v"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.SEQ_2_SEQ_LM
)
# prepare int-8 model for training
model = prepare_model_for_int8_training(model)
# add LoRA adaptor
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 18874368 || all params: 11154206720 || trainable%: 0.16921300163961817
參考連結:
https://zhuanlan.zhihu.com/p/631077870
https://zhuanlan.zhihu.com/p/636759194
https://zhuanlan.zhihu.com/p/514033873
微調非常大的模型的成本過高;對650億引數的LLaMA模型進行進行16位元微調需要超過780GB的GPU記憶體,QLORA使用一種新的高精度技術將預訓練模型量化為int4,然後新增一小組可學習的低秩介面卡權重。它是通過量化權重反向傳播梯度來調整的。QLORA將65B引數模型進行微調的平均記憶體需求從 >780GB 的 GPU 記憶體減少到 <48GB,而不會降低執行時間或預測效能。這標誌著LLM微調可存取性的顯著轉變:現在最大的公開可用的模型,迄今為止在單個GPU上進行微調。
首先分析下LoRA微調中的痛點:
引數空間小:LoRA中參與訓練的引數量較少,解空間較小,效果相比全量微調有一定的差距。
微調大模型成本高:對於上百億引數量的模型,LoRA微調的成本還是很高。
精度損失:針對第二點,可以採用int8或int4量化,進一步對模型基座的引數進行壓縮。但是又會引發精度損失的問題,降低模型效能。
今天的主角QLoRA優點:
4-bit NormalFloat:提出一種理論最優的4-bit的量化資料型別,優於當前普遍使用的FP4與Int4。對於正態分佈權重而言,一種資訊理論上最優的新資料型別,該資料型別對正態分佈資料產生比 4 bit整數和 4bit 浮點數更好的實證結果。QLORA包含一種低精度儲存資料型別(通常為4-bit)和一種計算資料型別(通常為BFloat16)。在實踐中,QLORA權重張量使用時,需要將將張量去量化為BFloat16,然後在16位元計算精度下進行矩陣乘法運算。模型本身用4bit載入,訓練時把數值反量化到bf16後進行訓練。
Double Quantization:對第一次量化後的那些常數再進行一次量化,減少儲存空間。相比於當前的模型量化方法,更加節省視訊記憶體空間。每個引數平均節省0.37bit,對於65B的LLaMA模型,大約能節省3GB視訊記憶體空間。
Paged Optimizers:使用NVIDIA統一記憶體特性,該特性可以在在GPU偶爾OOM的情況下,進行CPU和GPU之間自動分頁到分頁的傳輸,以實現無錯誤的 GPU 處理。該功能的工作方式類似於 CPU 記憶體和磁碟之間的常規記憶體分頁。使用此功能為優化器狀態(Optimizer)分配分頁記憶體,然後在 GPU 記憶體不足時將其自動解除安裝到 CPU 記憶體,並在優化器更新步驟需要時將其載入回 GPU 記憶體。
增加Adapter:4-bit的NormalFloat與Double Quantization,節省了很多空間,但帶來了效能損失,作者通過插入更多adapter來彌補這種效能損失。在LoRA中,一般會選擇在query和value的全連線層處插入adapter。而QLoRA則在所有全連線層處都插入了adapter,增加了訓練引數,彌補精度帶來的效能損失。
參考:
https://zhuanlan.zhihu.com/p/632164305
https://zhuanlan.zhihu.com/p/636215898
https://zhuanlan.zhihu.com/p/634256206
https://zhuanlan.zhihu.com/p/632229856
https://blog.csdn.net/qq_39970492/article/details/131048994
QLORA 可以使用 4 位基礎模型和低秩介面卡 (LoRA) 複製 16 位完全微調效能。QLORA將微調65B引數模型的平均記憶體需求從>780GB的GPU記憶體降低到<48GB,與完全微調的16位元基準相比,既不降低執行時間也不降低預測效能,這意味著可以在單個GPU上微調迄今為止最大的公開可用模型。