一.微調(diào)方法
Instruct微調(diào)和LoRA微調(diào)是兩種不同的技術(shù)。
1.1 Instruct微調(diào)
Instruct微調(diào)是指在深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中調(diào)整模型參數(shù)的過程,以優(yōu)化模型的性能。在微調(diào)過程中,使用一個(gè)預(yù)先訓(xùn)練好的模型作為基礎(chǔ)模型,然后在新的數(shù)據(jù)集上對該模型進(jìn)行微調(diào)。Instruct微調(diào)是一種通過更新預(yù)訓(xùn)練模型的所有參數(shù)來完成的微調(diào)方法,通過微調(diào)使其適用于多個(gè)下游應(yīng)用。
1.2 LoRA微調(diào)
LoRA(Low-Rank Adaptation)微調(diào)凍結(jié)了預(yù)訓(xùn)練的模型權(quán)重,并將可訓(xùn)練的秩分解矩陣注入到 Transformer 架構(gòu)的每一層,極大地減少了下游任務(wù)的可訓(xùn)練參數(shù)的數(shù)量。與Instruct微調(diào)相比,LoRA在每個(gè)Transformer塊中注入可訓(xùn)練層,因?yàn)椴恍枰獮榇蠖鄶?shù)模型權(quán)重計(jì)算梯度,大大減少了需要訓(xùn)練參數(shù)的數(shù)量并且降低了GPU內(nèi)存的要求。研究發(fā)現(xiàn),使用LoRA進(jìn)行的微調(diào)質(zhì)量與全模型微調(diào)相當(dāng),速度更快并且需要更少的計(jì)算。
基于LoRA的微調(diào)產(chǎn)生保存了新的權(quán)重,可以將生成的LoRA權(quán)重認(rèn)為是一個(gè)原來預(yù)訓(xùn)練模型的補(bǔ)丁權(quán)重 。所以LoRA模型無法單獨(dú)使用,需要搭配原模型,兩者進(jìn)行合并即可獲得完整版權(quán)重。
二.LoRA原理
神經(jīng)網(wǎng)絡(luò)包含許多密集的層,這些層執(zhí)行矩陣乘法。這些層中的權(quán)重矩陣通常具有全秩。當(dāng)適應(yīng)特定的任務(wù)時(shí),預(yù)訓(xùn)練的語言模型往往具有較低的“instrisic dimension”,盡管隨機(jī)投影到較小的子空間,但仍然可以有效地學(xué)習(xí)。
LoRA的實(shí)現(xiàn)原理在于,凍結(jié)預(yù)訓(xùn)練模型權(quán)重,并將可訓(xùn)練的秩分解矩陣注入到Transformer層的每個(gè)權(quán)重中,大大減少了下游任務(wù)的可訓(xùn)練參數(shù)數(shù)量。直白的來說,實(shí)際上是增加了右側(cè)的“旁支”,也就是先用一個(gè)Linear層A,將數(shù)據(jù)從 d維降到r,再用第二個(gè)Linear層B,將數(shù)據(jù)從r變回d維。最后再將左右兩部分的結(jié)果相加融合,得到輸出的hidden_state。
如上圖所示,左邊是預(yù)訓(xùn)練模型的權(quán)重,輸入輸出維度都是d,在訓(xùn)練期間被凍結(jié),不接受梯度更新。右邊部分對A使用隨機(jī)的高斯初始化,B在訓(xùn)練開始時(shí)為零,r是秩,會(huì)對△Wx做縮放 α/r。
Lora 性質(zhì) 1:全面微調(diào)的推廣
通過將 LoRA 秩 r 設(shè)置為預(yù)訓(xùn)練的權(quán)重矩陣的秩,大致恢復(fù)了完整微調(diào)的表達(dá)性。換句話說,當(dāng)增加可訓(xùn)練參數(shù)的數(shù)量時(shí),訓(xùn)練LoRA大致收斂于訓(xùn)練原始模型。
Lora 性質(zhì) 2:沒有額外的推斷延遲
在生產(chǎn)中部署時(shí),可以顯式地計(jì)算和存儲(chǔ)
W
=
W
0
+
B
A
W = W_0 + BA
W=W0?+BA,并像往常一樣執(zhí)行推理,也就是將 LoRA 權(quán)重和原始模型權(quán)重合并,不增加任何的推斷耗時(shí)
W
0
W_0
W0? 和
B
A
BA
BA 都是
R
d
×
k
R^{d×k}
Rd×k。當(dāng)需要切換到另一個(gè)下游任務(wù)時(shí),可以通過減去 BA 然后添加不同的
B
′
A
′
B'A'
B′A′ 來恢復(fù)
W
0
W_0
W0?,這是一個(gè)內(nèi)存開銷很小的快速操作。
LoRA的優(yōu)勢
1)一個(gè)預(yù)先訓(xùn)練好的模型可以被共享,并用于為不同的任務(wù)建立許多小的LoRA模塊??梢詢鼋Y(jié)共享模型,并通過替換圖中的矩陣A和B來有效地切換任務(wù),大大降低了存儲(chǔ)需求和任務(wù)切換的難度。
2)在使用自適應(yīng)優(yōu)化器時(shí),LoRA使訓(xùn)練更加有效,并將硬件進(jìn)入門檻降低了3倍,因?yàn)槲覀儾恍枰?jì)算梯度或維護(hù)大多數(shù)參數(shù)的優(yōu)化器狀態(tài)。相反,我們只優(yōu)化注入的、小得多的低秩矩陣。
3)簡單的線性設(shè)計(jì)允許在部署時(shí)將可訓(xùn)練矩陣與凍結(jié)權(quán)重合并,與完全微調(diào)的模型相比,在結(jié)構(gòu)上沒有引入推理延遲。
4)LoRA與許多先前的方法是正交的,可以與許多方法結(jié)合,如前綴調(diào)整。
三.LoRA使用
HuggingFace的PEFT(Parameter-Efficient Fine-Tuning)中提供了模型微調(diào)加速的方法,參數(shù)高效微調(diào)(PEFT)方法能夠使預(yù)先訓(xùn)練好的語言模型(PLMs)有效地適應(yīng)各種下游應(yīng)用,而不需要對模型的所有參數(shù)進(jìn)行微調(diào)。
對大規(guī)模的PLM進(jìn)行微調(diào)往往成本過高,在這方面,PEFT方法只對少數(shù)(額外的)模型參數(shù)進(jìn)行微調(diào),基本思想在于僅微調(diào)少量 (額外) 模型參數(shù),同時(shí)凍結(jié)預(yù)訓(xùn)練 LLM 的大部分參數(shù),從而大大降低了計(jì)算和存儲(chǔ)成本,這也克服了災(zāi)難性遺忘的問題,這是在 LLM 的全參數(shù)微調(diào)期間觀察到的一種現(xiàn)象PEFT 方法也顯示出在低數(shù)據(jù)狀態(tài)下比微調(diào)更好,可以更好地泛化到域外場景。文章來源:http://www.zghlxwxcb.cn/news/detail-446602.html
例如,使用PEFT-lora進(jìn)行加速微調(diào)的效果如下,從中我們可以看到該方案的優(yōu)勢:文章來源地址http://www.zghlxwxcb.cn/news/detail-446602.html
## 1、引入組件并設(shè)置參數(shù)
from transformers import AutoModelForSeq2SeqLM
from peft import get_peft_config, get_peft_model, get_peft_model_state_dict, LoraConfig, TaskType
import torch
from datasets import load_dataset
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
from transformers import AutoTokenizer
from torch.utils.data import DataLoader
from transformers import default_data_collator, get_linear_schedule_with_warmup
from tqdm import tqdm
## 2、搭建模型
peft_config = LoraConfig(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
## 3、加載數(shù)據(jù)
dataset = load_dataset("financial_phrasebank", "sentences_allagree")
dataset = dataset["train"].train_test_split(test_size=0.1)
dataset["validation"] = dataset["test"]
del dataset["test"]
classes = dataset["train"].features["label"].names
dataset = dataset.map(
lambda x: {"text_label": [classes[label] for label in x["label"]]},
batched=True,
num_proc=1,
)
## 4、訓(xùn)練數(shù)據(jù)預(yù)處理
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
def preprocess_function(examples):
inputs = examples[text_column]
targets = examples[label_column]
model_inputs = tokenizer(inputs, max_length=max_length, padding="max_length", truncation=True, return_tensors="pt")
labels = tokenizer(targets, max_length=3, padding="max_length", truncation=True, return_tensors="pt")
labels = labels["input_ids"]
labels[labels == tokenizer.pad_token_id] = -100
model_inputs["labels"] = labels
return model_inputs
processed_datasets = dataset.map(
preprocess_function,
batched=True,
num_proc=1,
remove_columns=dataset["train"].column_names,
load_from_cache_file=False,
desc="Running tokenizer on dataset",
)
train_dataset = processed_datasets["train"]
eval_dataset = processed_datasets["validation"]
train_dataloader = DataLoader(
train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=True
)
eval_dataloader = DataLoader(eval_dataset, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=True)
## 5、設(shè)定優(yōu)化器和正則項(xiàng)
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
lr_scheduler = get_linear_schedule_with_warmup(
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=(len(train_dataloader) * num_epochs),
)
## 6、訓(xùn)練與評估
model = model.to(device)
for epoch in range(num_epochs):
model.train()
total_loss = 0
for step, batch in enumerate(tqdm(train_dataloader)):
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
total_loss += loss.detach().float()
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
model.eval()
eval_loss = 0
eval_preds = []
for step, batch in enumerate(tqdm(eval_dataloader)):
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
loss = outputs.loss
eval_loss += loss.detach().float()
eval_preds.extend(
tokenizer.batch_decode(torch.argmax(outputs.logits, -1).detach().cpu().numpy(), skip_special_tokens=True)
)
eval_epoch_loss = eval_loss / len(eval_dataloader)
eval_ppl = torch.exp(eval_epoch_loss)
train_epoch_loss = total_loss / len(train_dataloader)
train_ppl = torch.exp(train_epoch_loss)
print(f"{epoch=}: {train_ppl=} {train_epoch_loss=} {eval_ppl=} {eval_epoch_loss=}")
## 7、模型保存
peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
model.save_pretrained(peft_model_id)
## 8、模型推理預(yù)測
from peft import PeftModel, PeftConfig
peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
config = PeftConfig.from_pretrained(peft_model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(config.base_model_name_or_path)
model = PeftModel.from_pretrained(model, peft_model_id)
model.eval()
inputs = tokenizer(dataset["validation"][text_column][i], return_tensors="pt")
print(dataset["validation"][text_column][i])
print(inputs)
with torch.no_grad():
outputs = model.generate(input_ids=inputs["input_ids"], max_new_tokens=10)
print(outputs)
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
到了這里,關(guān)于AIGC:【LLM(一)】——LoRA微調(diào)加速技術(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!