如何花費(fèi)較少的算力成本來(lái)進(jìn)行微調(diào)訓(xùn)練,十分重要,當(dāng)前關(guān)于LLaMA、Alpaca、Instruct微調(diào)、LoRa微調(diào)等多個(gè)概念大家講的很多,最近也在學(xué)習(xí),也看到幾個(gè)有趣的話題。
首先,來(lái)看關(guān)于Instruct微調(diào)和LoRa微調(diào)
Instruct微調(diào)和LoRa微調(diào)是兩種不同的技術(shù)。Instruct微調(diào)是指在深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練過(guò)程中調(diào)整模型參數(shù)的過(guò)程,以?xún)?yōu)化模型的性能。在微調(diào)過(guò)程中,使用一個(gè)預(yù)先訓(xùn)練好的模型作為基礎(chǔ)模型,然后在新的數(shù)據(jù)集上對(duì)該模型進(jìn)行微調(diào)。Instruct微調(diào)是一種通過(guò)更新預(yù)訓(xùn)練模型的所有參數(shù)來(lái)完成的微調(diào)方法,通過(guò)微調(diào)使其適用于多個(gè)下游應(yīng)用。
LoRa微調(diào)則是指對(duì)低功耗廣域網(wǎng)(LoRaWAN)中的LoRa節(jié)點(diǎn)參數(shù)進(jìn)行微調(diào)的過(guò)程,以提高節(jié)點(diǎn)的傳輸效率。在LoRa微調(diào)中,需要了解節(jié)點(diǎn)的硬件和網(wǎng)絡(luò)部署情況,并通過(guò)對(duì)節(jié)點(diǎn)參數(shù)進(jìn)行微小調(diào)整來(lái)優(yōu)化傳輸效率。
與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ì)算。因此,如果有低延遲和低內(nèi)存需求的情況,建議使用LoRA微調(diào)。
其次,我們?cè)賮?lái)看看為什么會(huì)有LLaMA模型和LoRA兩種模型
如上所述,模型的微調(diào)方式有很多種,基于LoRA的微調(diào)產(chǎn)生保存了新的權(quán)重,可以將生成的LoRA權(quán)重認(rèn)為是一個(gè)原來(lái)LLaMA模型的補(bǔ)丁權(quán)重 。至于LLaMA 權(quán)重,它則是由Mean公司開(kāi)源的大模型預(yù)訓(xùn)練權(quán)重。
最后,我們來(lái)看看關(guān)于詞表擴(kuò)充,為什么要擴(kuò)充詞表,直接在原版LLaMA上用中文預(yù)訓(xùn)練不行?
本身LLaMA對(duì)中文支持不是很好,大多數(shù)相關(guān)衍生工作是直接在原版上進(jìn)行pretrain/finetune的,從而采取了更大膽的策略——增加中文詞表,可能進(jìn)一步加劇中文訓(xùn)練不充分的問(wèn)題,但從長(zhǎng)遠(yuǎn)看是否有利于后續(xù)進(jìn)一步預(yù)訓(xùn)練就得靠時(shí)間檢驗(yàn)了,加入詞表是有一定破壞性的,一是破壞原有分詞體系,二是增加了未訓(xùn)練的權(quán)重。所以如果不能進(jìn)行充分訓(xùn)練的話,可能會(huì)有比較大的問(wèn)題。如果不是特別專(zhuān)的領(lǐng)域(比如生物醫(yī)學(xué)等涉及很多專(zhuān)業(yè)詞匯的領(lǐng)域)沒(méi)有太大必要去擴(kuò)充英文詞表。
原版LLaMA模型的詞表大小是32K,其主要針對(duì)英語(yǔ)進(jìn)行訓(xùn)練(具體詳見(jiàn)LLaMA論文),對(duì)多語(yǔ)種支持不是特別理想(可以對(duì)比一下多語(yǔ)言經(jīng)典模型XLM-R的詞表大小為250K)。通過(guò)初步統(tǒng)計(jì)發(fā)現(xiàn),LLaMA詞表中僅包含很少的中文字符,所以在切詞時(shí)會(huì)把中文切地更碎,需要多個(gè)byte token才能拼成一個(gè)完整的漢字,進(jìn)而導(dǎo)致信息密度降低。
比如,在擴(kuò)展詞表后的模型中,單個(gè)漢字傾向于被切成1個(gè)token,而在原版LLaMA中可能就需要2-3個(gè)才能組合成一個(gè)漢字,顯著降低編解碼的效率。
由于原版LLaMA對(duì)中文的支持非常有限,Chinese-LLaMA-Alpaca項(xiàng)目在原版LLaMA的基礎(chǔ)上進(jìn)一步擴(kuò)充了中文詞表。在通用中文語(yǔ)料上訓(xùn)練了基于sentencepiece的20K中文詞表并與原版LLaMA模型的32K詞表進(jìn)行合并,排除重復(fù)的token后,得到的最終中文LLaMA詞表大小為49953。需要注意的是,在fine-tune階段Alpaca比LLaMA多一個(gè)pad token,所以中文Alpaca的詞表大小為49954。
為了進(jìn)一步加深對(duì)lora的理解,本文主要從LoRA基本原理及PEFT中的實(shí)現(xiàn)、基于mt0-large+lora的完整實(shí)踐兩方面進(jìn)行介紹,供大家一起參考。
一、LoRA基本原理及PEFT中的實(shí)現(xiàn)
當(dāng)前,已經(jīng)出現(xiàn)了很多l(xiāng)ora作為adapter的微調(diào)模型,如Alpaca LoRA,Chinese-LLaMA-Alpaca等,其在公開(kāi)時(shí)會(huì)注明:中文LLaMA/Alpaca LoRA模型無(wú)法單獨(dú)使用,需要搭配原版LLaMA模型,發(fā)布的是LoRA權(quán)重,可以理解為原LLaMA模型上的一個(gè)“補(bǔ)丁”,兩者進(jìn)行合并即可獲得完整版權(quán)重。
LoRA的實(shí)現(xiàn)原理在于,凍結(jié)預(yù)訓(xùn)練模型權(quán)重,并將可訓(xùn)練的秩分解矩陣注入到Transformer層的每個(gè)權(quán)重中,大大減少了下游任務(wù)的可訓(xùn)練參數(shù)數(shù)量。直白的來(lái)說(shuō),實(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é),不接受梯度更新。右邊部分對(duì)A使用隨機(jī)的高斯初始化,B在訓(xùn)練開(kāi)始時(shí)為零,r是秩,會(huì)對(duì)△Wx做縮放 α/r。
幸運(yùn)的是,HuggingFace的PEFT(Parameter-Efficient Fine-Tuning,地址:https://github.com/huggingface/peft)中提供了模型微調(diào)加速的方法,參數(shù)高效微調(diào)(PEFT)方法能夠使預(yù)先訓(xùn)練好的語(yǔ)言模型(PLMs)有效地適應(yīng)各種下游應(yīng)用,而不需要對(duì)模型的所有參數(shù)進(jìn)行微調(diào)。
對(duì)大規(guī)模的PLM進(jìn)行微調(diào)往往成本過(guò)高,在這方面,PEFT方法只對(duì)少數(shù)(額外的)模型參數(shù)進(jìn)行微調(diào),基本思想在于僅微調(diào)少量 (額外) 模型參數(shù),同時(shí)凍結(jié)預(yù)訓(xùn)練 LLM 的大部分參數(shù),從而大大降低了計(jì)算和存儲(chǔ)成本,這也克服了災(zāi)難性遺忘的問(wèn)題,這是在 LLM 的全參數(shù)微調(diào)期間觀察到的一種現(xiàn)象PEFT 方法也顯示出在低數(shù)據(jù)狀態(tài)下比微調(diào)更好,可以更好地泛化到域外場(chǎng)景。
例如,使用PEFT-lora進(jìn)行加速微調(diào)的效果如下,從中我們可以看到該方案的優(yōu)勢(shì):
例如,其對(duì)LoRA做了封裝支持,幾步即可使用:
from?peft?import?get_peft_model,?LoraConfig,?TaskType peft_config?=?LoraConfig( ????task_type=TaskType.CAUSAL_LM,? ????inference_mode=False,? ????r=8,? ????lora_alpha=32,? ????lora_dropout=0.1, ????target_modules=['query_key_value'] ) model?=?"加載的模型" model?=?get_peft_model(model,?peft_config) model.print_trainable_parameters()
論文中提到了LoRA的一些優(yōu)勢(shì):
1)一個(gè)預(yù)先訓(xùn)練好的模型可以被共享,并用于為不同的任務(wù)建立許多小的LoRA模塊??梢?xún)鼋Y(jié)共享模型,并通過(guò)替換圖中的矩陣A和B來(lái)有效地切換任務(wù),大大降低了存儲(chǔ)需求和任務(wù)切換的難度。
2)在使用自適應(yīng)優(yōu)化器時(shí),LoRA使訓(xùn)練更加有效,并將硬件進(jìn)入門(mén)檻降低了3倍,因?yàn)槲覀儾恍枰?jì)算梯度或維護(hù)大多數(shù)參數(shù)的優(yōu)化器狀態(tài)。相反,我們只優(yōu)化注入的、小得多的低秩矩陣。
3)簡(jiǎn)單的線性設(shè)計(jì)允許在部署時(shí)將可訓(xùn)練矩陣與凍結(jié)權(quán)重合并,與完全微調(diào)的模型相比,在結(jié)構(gòu)上沒(méi)有引入推理延遲。
4)LoRA與許多先前的方法是正交的,可以與許多方法結(jié)合,如前綴調(diào)整。我們?cè)诟戒汦中提供了一個(gè)例子。
1、引入開(kāi)源組件
”+”表示增加代碼:
??from?transformers?import?AutoModelForSeq2SeqLM +?from?peft?import?get_peft_model,?LoraConfig,?TaskType? ??model_name_or_path?=?"bigscience/mt0-large" ??tokenizer_name_or_path?=?"bigscience/mt0-large"
2、引入lora配置信息
peft_config?=?LoraConfig( ????task_type=TaskType.SEQ_2_SEQ_LM,? ????inference_mode=False,? ????r=8,? ????lora_alpha=32,? ????lora_dropout=0.1 )
3、進(jìn)行推理
??from?transformers?import?AutoModelForSeq2SeqLM +?from?peft?import?PeftModel,?PeftConfig ??peft_model_id?=?"smangrul/twitter_complaints_bigscience_T0_3B_LORA_SEQ_2_SEQ_LM" ??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) ??tokenizer?=?AutoTokenizer.from_pretrained(config.base_model_name_or_path) ??model?=?model.to(device) ??model.eval() ??inputs?=?tokenizer("Tweet?text?:?@HondaCustSvc?Your?customer?service?has?been?horrible?during?the?recall?process.?I?will?never?purchase?a?Honda?again.?Label?:",?return_tensors="pt") ??with?torch.no_grad(): ??????outputs?=?model.generate(input_ids=inputs["input_ids"].to("cuda"),?max_new_tokens=10) ??????print(tokenizer.batch_decode(outputs.detach().cpu().numpy(),?skip_special_tokens=True)[0]) #?'complaint'
二、基于mt0-large+lora的完整實(shí)踐
接下來(lái),我們來(lái)使用huggingface-peft庫(kù)來(lái)進(jìn)行一個(gè)lora的實(shí)踐。
首先,在模型方面,我們選用mt0-large模型為例(只有1.2b),進(jìn)行實(shí)驗(yàn),模型地址:https://huggingface.co/bigscience/mt0-large。
模型權(quán)重地址:https://huggingface.co/bigscience/mt0-large/tree/main
先看看mt0-large是什么。多任務(wù)提示微調(diào)(MTF)已被證明可以幫助大型語(yǔ)言模型在zero-shot的環(huán)境下生成新的任務(wù),但到目前為止,MTF的探索主要集中在英語(yǔ)數(shù)據(jù)和模型上,將MTF應(yīng)用于預(yù)訓(xùn)練的多語(yǔ)言BLOOM和mT5模型系列,就產(chǎn)生稱(chēng)為BLOOMZ和mT0的微調(diào)變體。
具體的,總共生產(chǎn)了三種不同尺寸的核心型號(hào):
BLOOMZ-P3 / mT0-P3:在純英語(yǔ)的P3上進(jìn)行微調(diào)的模型。
BLOOMZ / mT0: 在xP3上進(jìn)行微調(diào)的模型,xP3由帶有英語(yǔ)提示的多語(yǔ)言數(shù)據(jù)集組成。
BLOOMZ-MT / mT0-MT: 在xP3mt上進(jìn)行模型微調(diào),xP3mt由多語(yǔ)言數(shù)據(jù)集和機(jī)器翻譯的提示語(yǔ)組成。
其次,在任務(wù)方面,我們選用金融領(lǐng)域情感分析任務(wù)financial_sentiment_analysis,給定一個(gè)句子,要求識(shí)別出該句子是negative、positive還是neutral三個(gè)中的哪一個(gè),其中的數(shù)據(jù)樣式如下:
{'sentence':?"The?10,000-odd?square?metre?plot?that?Stockmann?has?bought?for? the?Nevsky?Center?shopping?center?is?located?on?Nevsky?Prospect?,? St?Petersburg?'s?high?street?,?next?to?the?Vosstaniya?Square?underground ?station?,?in?the?immediate?vicinity?of?Moscow?Station?.", ?'label':?1, ?'text_label':?'neutral'}
我們可以通過(guò)datasests組件進(jìn)行調(diào)用。
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 from?datasets?import?load_dataset device?=?"cuda" model_name_or_path?=?"bigscience/mt0-large" tokenizer_name_or_path?=?"bigscience/mt0-large" checkpoint_name?=?"financial_sentiment_analysis_lora_v1.pt" text_column?=?"sentence" label_column?=?"text_label" max_length?=?128 lr?=?1e-3 num_epochs?=?3 batch_size?=?8
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) model?=?get_peft_model(model,?peft_config) model.print_trainable_parameters()
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)練與評(píng)估
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=}")
執(zhí)行訓(xùn)練日志輸出如下:
100%|████████████████████████████████████████████████████████████████████████████████████████|?255/255?[02:21<00:00,??1.81it/s] 100%|██████████████████████████████████████████████████████████████████████████████████████████|?29/29?[00:07<00:00,??4.13it/s] epoch=0:?train_ppl=tensor(14.6341,?device='cuda:0')?train_epoch_loss=tensor(2.6834,?device='cuda:0')?eval_ppl=tensor(1.0057,?device='cuda:0')?eval_epoch_loss=tensor(0.0057,?device='cuda:0') 100%|████████████████████████████████████████████████████████████████████████████████████████|?255/255?[02:00<00:00,??2.11it/s] 100%|██████████████████████████████████████████████████████████████████████████████████████████|?29/29?[00:05<00:00,??5.66it/s] epoch=1:?train_ppl=tensor(1.7576,?device='cuda:0')?train_epoch_loss=tensor(0.5640,?device='cuda:0')?eval_ppl=tensor(1.0052,?device='cuda:0')?eval_epoch_loss=tensor(0.0052,?device='cuda:0') 100%|████████████████████████████████████████████████████████████████████████████████████████|?255/255?[01:33<00:00,??2.74it/s] 100%|██████████████████████████████████████████████████████████████████████████████████████████|?29/29?[00:04<00:00,??6.23it/s] epoch=2:?train_ppl=tensor(1.3830,?device='cuda:0')?train_epoch_loss=tensor(0.3243,?device='cuda:0')?eval_ppl=tensor(1.0035,?device='cuda:0')?eval_epoch_loss=tensor(0.0035,?device='cuda:0')
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ù)測(cè)
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)) ????
運(yùn)行實(shí)例,例如輸入:
Demand?for?fireplace?products?was?lower?than?expected?,?especially?in?Germany?.
輸出:
{'input_ids':?tensor([[??259,???264,???259,?82903,???332,??1090,?10040,?10371,???639,???259, ?????????19540,??2421,???259,?25505,???259,???261,???259,?21230,???281,?17052, ???????????259,???260,?????1]]),?'attention_mask':?tensor([[1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1]])} tensor([[????0,???259,?32588,?????1]]) ['negative']
總結(jié)
本文主要從LoRA基本原理及PEFT中的實(shí)現(xiàn)、基于mt0-large+lora的完整實(shí)踐兩方面進(jìn)行了介紹。關(guān)于進(jìn)一步的細(xì)節(jié),我們可以熟悉原理后,可以進(jìn)行動(dòng)手實(shí)踐,加深理解。
編輯:黃飛
?
評(píng)論
查看更多