如何花費較少的算力成本來進行微調(diào)訓(xùn)練,十分重要,當(dāng)前關(guān)于LLaMA、Alpaca、Instruct微調(diào)、LoRa微調(diào)等多個概念大家講的很多,最近也在學(xué)習(xí),也看到幾個有趣的話題。
首先,來看關(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)練過程中調(diào)整模型參數(shù)的過程,以優(yōu)化模型的性能。在微調(diào)過程中,使用一個預(yù)先訓(xùn)練好的模型作為基礎(chǔ)模型,然后在新的數(shù)據(jù)集上對該模型進行微調(diào)。Instruct微調(diào)是一種通過更新預(yù)訓(xùn)練模型的所有參數(shù)來完成的微調(diào)方法,通過微調(diào)使其適用于多個下游應(yīng)用。
LoRa微調(diào)則是指對低功耗廣域網(wǎng)(LoRaWAN)中的LoRa節(jié)點參數(shù)進行微調(diào)的過程,以提高節(jié)點的傳輸效率。在LoRa微調(diào)中,需要了解節(jié)點的硬件和網(wǎng)絡(luò)部署情況,并通過對節(jié)點參數(shù)進行微小調(diào)整來優(yōu)化傳輸效率。
與Instruct微調(diào)相比,LoRA在每個Transformer塊中注入可訓(xùn)練層,因為不需要為大多數(shù)模型權(quán)重計算梯度,大大減少了需要訓(xùn)練參數(shù)的數(shù)量并且降低了GPU內(nèi)存的要求。研究發(fā)現(xiàn),使用LoRA進行的微調(diào)質(zhì)量與全模型微調(diào)相當(dāng),速度更快并且需要更少的計算。因此,如果有低延遲和低內(nèi)存需求的情況,建議使用LoRA微調(diào)。
其次,我們再來看看為什么會有LLaMA模型和LoRA兩種模型
如上所述,模型的微調(diào)方式有很多種,基于LoRA的微調(diào)產(chǎn)生保存了新的權(quán)重,可以將生成的LoRA權(quán)重認為是一個原來LLaMA模型的補丁權(quán)重 。至于LLaMA 權(quán)重,它則是由Mean公司開源的大模型預(yù)訓(xùn)練權(quán)重。
最后,我們來看看關(guān)于詞表擴充,為什么要擴充詞表,直接在原版LLaMA上用中文預(yù)訓(xùn)練不行?
本身LLaMA對中文支持不是很好,大多數(shù)相關(guān)衍生工作是直接在原版上進行pretrain/finetune的,從而采取了更大膽的策略——增加中文詞表,可能進一步加劇中文訓(xùn)練不充分的問題,但從長遠看是否有利于后續(xù)進一步預(yù)訓(xùn)練就得靠時間檢驗了,加入詞表是有一定破壞性的,一是破壞原有分詞體系,二是增加了未訓(xùn)練的權(quán)重。所以如果不能進行充分訓(xùn)練的話,可能會有比較大的問題。如果不是特別專的領(lǐng)域(比如生物醫(yī)學(xué)等涉及很多專業(yè)詞匯的領(lǐng)域)沒有太大必要去擴充英文詞表。
原版LLaMA模型的詞表大小是32K,其主要針對英語進行訓(xùn)練(具體詳見LLaMA論文),對多語種支持不是特別理想(可以對比一下多語言經(jīng)典模型XLM-R的詞表大小為250K)。通過初步統(tǒng)計發(fā)現(xiàn),LLaMA詞表中僅包含很少的中文字符,所以在切詞時會把中文切地更碎,需要多個byte token才能拼成一個完整的漢字,進而導(dǎo)致信息密度降低。
比如,在擴展詞表后的模型中,單個漢字傾向于被切成1個token,而在原版LLaMA中可能就需要2-3個才能組合成一個漢字,顯著降低編解碼的效率。
由于原版LLaMA對中文的支持非常有限,Chinese-LLaMA-Alpaca項目在原版LLaMA的基礎(chǔ)上進一步擴充了中文詞表。在通用中文語料上訓(xùn)練了基于sentencepiece的20K中文詞表并與原版LLaMA模型的32K詞表進行合并,排除重復(fù)的token后,得到的最終中文LLaMA詞表大小為49953。需要注意的是,在fine-tune階段Alpaca比LLaMA多一個pad token,所以中文Alpaca的詞表大小為49954。
為了進一步加深對lora的理解,本文主要從LoRA基本原理及PEFT中的實現(xiàn)、基于mt0-large+lora的完整實踐兩方面進行介紹,供大家一起參考。
一、LoRA基本原理及PEFT中的實現(xiàn)
當(dāng)前,已經(jīng)出現(xiàn)了很多l(xiāng)ora作為adapter的微調(diào)模型,如Alpaca LoRA,Chinese-LLaMA-Alpaca等,其在公開時會注明:中文LLaMA/Alpaca LoRA模型無法單獨使用,需要搭配原版LLaMA模型,發(fā)布的是LoRA權(quán)重,可以理解為原LLaMA模型上的一個“補丁”,兩者進行合并即可獲得完整版權(quán)重。
LoRA的實現(xiàn)原理在于,凍結(jié)預(yù)訓(xùn)練模型權(quán)重,并將可訓(xùn)練的秩分解矩陣注入到Transformer層的每個權(quán)重中,大大減少了下游任務(wù)的可訓(xùn)練參數(shù)數(shù)量。直白的來說,實際上是增加了右側(cè)的“旁支”,也就是先用一個Linear層A,將數(shù)據(jù)從 d維降到r,再用第二個Linear層B,將數(shù)據(jù)從r變回d維。最后再將左右兩部分的結(jié)果相加融合,得到輸出的hidden_state。
如上圖所示,左邊是預(yù)訓(xùn)練模型的權(quán)重,輸入輸出維度都是d,在訓(xùn)練期間被凍結(jié),不接受梯度更新。右邊部分對A使用隨機的高斯初始化,B在訓(xùn)練開始時為零,r是秩,會對△Wx做縮放 α/r。
幸運的是,HuggingFace的PEFT(Parameter-Efficient Fine-Tuning,地址:https://github.com/huggingface/peft)中提供了模型微調(diào)加速的方法,參數(shù)高效微調(diào)(PEFT)方法能夠使預(yù)先訓(xùn)練好的語言模型(PLMs)有效地適應(yīng)各種下游應(yīng)用,而不需要對模型的所有參數(shù)進行微調(diào)。
對大規(guī)模的PLM進行微調(diào)往往成本過高,在這方面,PEFT方法只對少數(shù)(額外的)模型參數(shù)進行微調(diào),基本思想在于僅微調(diào)少量 (額外) 模型參數(shù),同時凍結(jié)預(yù)訓(xùn)練 LLM 的大部分參數(shù),從而大大降低了計算和存儲成本,這也克服了災(zāi)難性遺忘的問題,這是在 LLM 的全參數(shù)微調(diào)期間觀察到的一種現(xiàn)象PEFT 方法也顯示出在低數(shù)據(jù)狀態(tài)下比微調(diào)更好,可以更好地泛化到域外場景。
例如,使用PEFT-lora進行加速微調(diào)的效果如下,從中我們可以看到該方案的優(yōu)勢:
例如,其對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)勢:
1)一個預(yù)先訓(xùn)練好的模型可以被共享,并用于為不同的任務(wù)建立許多小的LoRA模塊。可以凍結(jié)共享模型,并通過替換圖中的矩陣A和B來有效地切換任務(wù),大大降低了存儲需求和任務(wù)切換的難度。
2)在使用自適應(yīng)優(yōu)化器時,LoRA使訓(xùn)練更加有效,并將硬件進入門檻降低了3倍,因為我們不需要計算梯度或維護大多數(shù)參數(shù)的優(yōu)化器狀態(tài)。相反,我們只優(yōu)化注入的、小得多的低秩矩陣。
3)簡單的線性設(shè)計允許在部署時將可訓(xùn)練矩陣與凍結(jié)權(quán)重合并,與完全微調(diào)的模型相比,在結(jié)構(gòu)上沒有引入推理延遲。
4)LoRA與許多先前的方法是正交的,可以與許多方法結(jié)合,如前綴調(diào)整。我們在附錄E中提供了一個例子。
1、引入開源組件
”+”表示增加代碼:
??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、進行推理
??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的完整實踐
接下來,我們來使用huggingface-peft庫來進行一個lora的實踐。
首先,在模型方面,我們選用mt0-large模型為例(只有1.2b),進行實驗,模型地址:https://huggingface.co/bigscience/mt0-large。
模型權(quán)重地址:https://huggingface.co/bigscience/mt0-large/tree/main
先看看mt0-large是什么。多任務(wù)提示微調(diào)(MTF)已被證明可以幫助大型語言模型在zero-shot的環(huán)境下生成新的任務(wù),但到目前為止,MTF的探索主要集中在英語數(shù)據(jù)和模型上,將MTF應(yīng)用于預(yù)訓(xùn)練的多語言BLOOM和mT5模型系列,就產(chǎn)生稱為BLOOMZ和mT0的微調(diào)變體。
具體的,總共生產(chǎn)了三種不同尺寸的核心型號:
BLOOMZ-P3 / mT0-P3:在純英語的P3上進行微調(diào)的模型。
BLOOMZ / mT0: 在xP3上進行微調(diào)的模型,xP3由帶有英語提示的多語言數(shù)據(jù)集組成。
BLOOMZ-MT / mT0-MT: 在xP3mt上進行模型微調(diào),xP3mt由多語言數(shù)據(jù)集和機器翻譯的提示語組成。
其次,在任務(wù)方面,我們選用金融領(lǐng)域情感分析任務(wù)financial_sentiment_analysis,給定一個句子,要求識別出該句子是negative、positive還是neutral三個中的哪一個,其中的數(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'}
我們可以通過datasests組件進行調(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)化器和正則項
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=}")
執(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ù)測
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)) ????
運行實例,例如輸入:
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中的實現(xiàn)、基于mt0-large+lora的完整實踐兩方面進行了介紹。關(guān)于進一步的細節(jié),我們可以熟悉原理后,可以進行動手實踐,加深理解。
編輯:黃飛
?
評論
查看更多