在本文中,我們將展示如何使用 大語言模型低秩適配 (Low-Rank Adaptation of Large Language Models,LoRA) 技術(shù)在單 GPU 上微調(diào) 110 億參數(shù)的 FLAN-T5 XXL 模型。在此過程中,我們會(huì)使用到 Hugging Face 的 Transformers、Accelerate 和 PEFT 庫。
快速入門: 輕量化微調(diào) (Parameter Efficient Fine-Tuning,PEFT)
PEFT 是 Hugging Face 的一個(gè)新的開源庫。使用 PEFT 庫,無需微調(diào)模型的全部參數(shù),即可高效地將預(yù)訓(xùn)練語言模型 (Pre-trained Language Model,PLM) 適配到各種下游應(yīng)用。
注意: 本教程是在 g5.2xlarge AWS EC2 實(shí)例上創(chuàng)建和運(yùn)行的,該實(shí)例包含 1 個(gè) NVIDIA A10G。
1. 搭建開發(fā)環(huán)境
在本例中,我們使用 AWS 預(yù)置的 PyTorch 深度學(xué)習(xí) AMI,其已安裝了正確的 CUDA 驅(qū)動(dòng)程序和 PyTorch。在此基礎(chǔ)上,我們還需要安裝一些 Hugging Face 庫,包括 transformers 和 datasets。運(yùn)行下面的代碼就可安裝所有需要的包。
#installHuggingFaceLibraries !pipinstallgit+https://github.com/huggingface/peft.git !pipinstall"transformers==4.27.1""datasets==2.9.0""accelerate==0.17.1""evaluate==0.4.0""bitsandbytes==0.37.1"loralib--upgrade--quiet #installadditionaldependenciesneededfortraining !pipinstallrouge-scoretensorboardpy7zr
2. 加載并準(zhǔn)備數(shù)據(jù)集
這里,我們使用 samsum 數(shù)據(jù)集,該數(shù)據(jù)集包含大約 16k 個(gè)含摘要的聊天類對(duì)話數(shù)據(jù)。這些對(duì)話由精通英語的語言學(xué)家制作。
{ "id":"13818513", "summary":"AmandabakedcookiesandwillbringJerrysometomorrow.", "dialogue":"Amanda:Ibakedcookies.Doyouwantsome? Jerry:Sure! Amanda:I'llbringyoutomorrow:-)" }
我們使用 Datasets 庫中的 load_dataset() 方法來加載 samsum 數(shù)據(jù)集。
fromdatasetsimportload_dataset #Loaddatasetfromthehub dataset=load_dataset("samsum") print(f"Traindatasetsize:{len(dataset['train'])}") print(f"Testdatasetsize:{len(dataset['test'])}") #Traindatasetsize:14732 #Testdatasetsize:819
為了訓(xùn)練模型,我們要用 Transformers Tokenizer 將輸入文本轉(zhuǎn)換為詞元 ID。
fromtransformersimportAutoTokenizer,AutoModelForSeq2SeqLM model_id="google/flan-t5-xxl" #LoadtokenizerofFLAN-t5-XL tokenizer=AutoTokenizer.from_pretrained(model_id)
在開始訓(xùn)練之前,我們還需要對(duì)數(shù)據(jù)進(jìn)行預(yù)處理。生成式文本摘要屬于文本生成任務(wù)。我們將文本輸入給模型,模型會(huì)輸出摘要。我們需要了解輸入和輸出文本的長(zhǎng)度信息,以利于我們高效地批量處理這些數(shù)據(jù)。
fromdatasetsimportconcatenate_datasets importnumpyasnp #Themaximumtotalinputsequencelengthaftertokenization. #Sequenceslongerthanthiswillbetruncated,sequencesshorterwillbepadded. tokenized_inputs=concatenate_datasets([dataset["train"],dataset["test"]]).map(lambdax:tokenizer(x["dialogue"],truncation=True),batched=True,remove_columns=["dialogue","summary"]) input_lenghts=[len(x)forxintokenized_inputs["input_ids"]] #take85percentileofmaxlengthforbetterutilization max_source_length=int(np.percentile(input_lenghts,85)) print(f"Maxsourcelength:{max_source_length}") #Themaximumtotalsequencelengthfortargettextaftertokenization. #Sequenceslongerthanthiswillbetruncated,sequencesshorterwillbepadded." tokenized_targets=concatenate_datasets([dataset["train"],dataset["test"]]).map(lambdax:tokenizer(x["summary"],truncation=True),batched=True,remove_columns=["dialogue","summary"]) target_lenghts=[len(x)forxintokenized_targets["input_ids"]] #take90percentileofmaxlengthforbetterutilization max_target_length=int(np.percentile(target_lenghts,90)) print(f"Maxtargetlength:{max_target_length}")
我們將在訓(xùn)練前統(tǒng)一對(duì)數(shù)據(jù)集進(jìn)行預(yù)處理并將預(yù)處理后的數(shù)據(jù)集保存到磁盤。你可以在本地機(jī)器或 CPU 上運(yùn)行此步驟并將其上傳到 Hugging Face Hub。
defpreprocess_function(sample,padding="max_length"): #addprefixtotheinputfort5 inputs=["summarize:"+itemforiteminsample["dialogue"]] #tokenizeinputs model_inputs=tokenizer(inputs,max_length=max_source_length,padding=padding,truncation=True) #Tokenizetargetswiththe`text_target`keywordargument labels=tokenizer(text_target=sample["summary"],max_length=max_target_length,padding=padding,truncation=True) #Ifwearepaddinghere,replacealltokenizer.pad_token_idinthelabelsby-100whenwewanttoignore #paddingintheloss. ifpadding=="max_length": labels["input_ids"]=[ [(lifl!=tokenizer.pad_token_idelse-100)forlinlabel]forlabelinlabels["input_ids"] ] model_inputs["labels"]=labels["input_ids"] returnmodel_inputs tokenized_dataset=dataset.map(preprocess_function,batched=True,remove_columns=["dialogue","summary","id"]) print(f"Keysoftokenizeddataset:{list(tokenized_dataset['train'].features)}") #savedatasetstodiskforlatereasyloading tokenized_dataset["train"].save_to_disk("data/train") tokenized_dataset["test"].save_to_disk("data/eval")
3. 使用 LoRA 和 bnb int-8 微調(diào) T5
除了 LoRA 技術(shù),我們還使用 bitsanbytes LLM.int8() 把凍結(jié)的 LLM 量化為 int8。這使我們能夠?qū)?FLAN-T5 XXL 所需的內(nèi)存降低到約四分之一。
訓(xùn)練的第一步是加載模型。我們使用 philschmid/flan-t5-xxl-sharded-fp16 模型,它是 google/flan-t5-xxl 的分片版。分片可以讓我們?cè)诩虞d模型時(shí)不耗盡內(nèi)存。
fromtransformersimportAutoModelForSeq2SeqLM #huggingfacehubmodelid model_id="philschmid/flan-t5-xxl-sharded-fp16" #loadmodelfromthehub model=AutoModelForSeq2SeqLM.from_pretrained(model_id,load_in_8bit=True,device_map="auto")
現(xiàn)在,我們可以使用 peft 為 LoRA int-8 訓(xùn)練作準(zhǔn)備了。
frompeftimportLoraConfig,get_peft_model,prepare_model_for_int8_training,TaskType #DefineLoRAConfig 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 ) #prepareint-8modelfortraining model=prepare_model_for_int8_training(model) #addLoRAadaptor model=get_peft_model(model,lora_config) model.print_trainable_parameters() #trainableparams:18874368||allparams:11154206720||trainable%:0.16921300163961817
如你所見,這里我們只訓(xùn)練了模型參數(shù)的 0.16%!這個(gè)巨大的內(nèi)存增益讓我們安心地微調(diào)模型,而不用擔(dān)心內(nèi)存問題。
接下來需要?jiǎng)?chuàng)建一個(gè) DataCollator,負(fù)責(zé)對(duì)輸入和標(biāo)簽進(jìn)行填充,我們使用 Transformers 庫中的 DataCollatorForSeq2Seq 來完成這一環(huán)節(jié)。
fromtransformersimportDataCollatorForSeq2Seq #wewanttoignoretokenizerpadtokenintheloss label_pad_token_id=-100 #Datacollator data_collator=DataCollatorForSeq2Seq( tokenizer, model=model, label_pad_token_id=label_pad_token_id, pad_to_multiple_of=8 )
最后一步是定義訓(xùn)練超參 ( TrainingArguments)。
fromtransformersimportSeq2SeqTrainer,Seq2SeqTrainingArguments output_dir="lora-flan-t5-xxl" #Definetrainingargs training_args=Seq2SeqTrainingArguments( output_dir=output_dir, auto_find_batch_size=True, learning_rate=1e-3,#higherlearningrate num_train_epochs=5, logging_dir=f"{output_dir}/logs", logging_strategy="steps", logging_steps=500, save_strategy="no", report_to="tensorboard", ) #CreateTrainerinstance trainer=Seq2SeqTrainer( model=model, args=training_args, data_collator=data_collator, train_dataset=tokenized_dataset["train"], ) model.config.use_cache=False#silencethewarnings.Pleasere-enableforinference!
運(yùn)行下面的代碼,開始訓(xùn)練模型。請(qǐng)注意,對(duì)于 T5,出于收斂穩(wěn)定性考量,某些層我們?nèi)员3?float32 精度。
#trainmodel trainer.train()
訓(xùn)練耗時(shí)約 10 小時(shí) 36 分鐘,訓(xùn)練 10 小時(shí)的成本約為 13.22 美元。相比之下,如果 在 FLAN-T5-XXL 上進(jìn)行全模型微調(diào) 10 個(gè)小時(shí),我們需要 8 個(gè) A100 40GB,成本約為 322 美元。
我們可以將模型保存下來以用于后面的推理和評(píng)估。我們暫時(shí)將其保存到磁盤,但你也可以使用 model.push_to_hub 方法將其上傳到 Hugging Face Hub。
#SaveourLoRAmodel&tokenizerresults peft_model_id="results" trainer.model.save_pretrained(peft_model_id) tokenizer.save_pretrained(peft_model_id) #ifyouwanttosavethebasemodeltocall #trainer.model.base_model.save_pretrained(peft_model_id)
最后生成的 LoRA checkpoint 文件很小,僅需 84MB 就包含了從 samsum 數(shù)據(jù)集上學(xué)到的所有知識(shí)。
4. 使用 LoRA FLAN-T5 進(jìn)行評(píng)估和推理
我們將使用 evaluate 庫來評(píng)估 rogue 分?jǐn)?shù)。我們可以使用 PEFT 和 transformers 來對(duì) FLAN-T5 XXL 模型進(jìn)行推理。對(duì) FLAN-T5 XXL 模型,我們至少需要 18GB 的 GPU 顯存。
importtorch frompeftimportPeftModel,PeftConfig fromtransformersimportAutoModelForSeq2SeqLM,AutoTokenizer #Loadpeftconfigforpre-trainedcheckpointetc. peft_model_id="results" config=PeftConfig.from_pretrained(peft_model_id) #loadbaseLLMmodelandtokenizer model=AutoModelForSeq2SeqLM.from_pretrained(config.base_model_name_or_path,load_in_8bit=True,device_map={"":0}) tokenizer=AutoTokenizer.from_pretrained(config.base_model_name_or_path) #LoadtheLoramodel model=PeftModel.from_pretrained(model,peft_model_id,device_map={"":0}) model.eval() print("Peftmodelloaded")
我們用測(cè)試數(shù)據(jù)集中的一個(gè)隨機(jī)樣本來試試摘要效果。
fromdatasetsimportload_dataset fromrandomimportrandrange #Loaddatasetfromthehubandgetasample dataset=load_dataset("samsum") sample=dataset['test'][randrange(len(dataset["test"]))] input_ids=tokenizer(sample["dialogue"],return_tensors="pt",truncation=True).input_ids.cuda() #withtorch.inference_mode(): outputs=model.generate(input_ids=input_ids,max_new_tokens=10,do_sample=True,top_p=0.9) print(f"inputsentence:{sample['dialogue']} {'---'*20}") print(f"summary: {tokenizer.batch_decode(outputs.detach().cpu().numpy(),skip_special_tokens=True)[0]}")
不錯(cuò)!我們的模型有效!現(xiàn)在,讓我們仔細(xì)看看,并使用 test 集中的全部數(shù)據(jù)對(duì)其進(jìn)行評(píng)估。為此,我們需要實(shí)現(xiàn)一些工具函數(shù)來幫助生成摘要并將其與相應(yīng)的參考摘要組合到一起。評(píng)估摘要任務(wù)最常用的指標(biāo)是 rogue_score,它的全稱是 Recall-Oriented Understudy for Gisting Evaluation。與常用的準(zhǔn)確率指標(biāo)不同,它將生成的摘要與一組參考摘要進(jìn)行比較。
importevaluate importnumpyasnp fromdatasetsimportload_from_disk fromtqdmimporttqdm #Metric metric=evaluate.load("rouge") defevaluate_peft_model(sample,max_target_length=50): #generatesummary outputs=model.generate(input_ids=sample["input_ids"].unsqueeze(0).cuda(),do_sample=True,top_p=0.9,max_new_tokens=max_target_length) prediction=tokenizer.decode(outputs[0].detach().cpu().numpy(),skip_special_tokens=True) #decodeevalsample #Replace-100inthelabelsaswecan'tdecodethem. labels=np.where(sample['labels']!=-100,sample['labels'],tokenizer.pad_token_id) labels=tokenizer.decode(labels,skip_special_tokens=True) #Somesimplepost-processing returnprediction,labels #loadtestdatasetfromdistk test_dataset=load_from_disk("data/eval/").with_format("torch") #runpredictions #thiscantake~45minutes predictions,references=[],[] forsampleintqdm(test_dataset): p,l=evaluate_peft_model(sample) predictions.append(p) references.append(l) #computemetric rogue=metric.compute(predictions=predictions,references=references,use_stemmer=True) #printresults print(f"Rogue1:{rogue['rouge1']*100:2f}%") print(f"rouge2:{rogue['rouge2']*100:2f}%") print(f"rougeL:{rogue['rougeL']*100:2f}%") print(f"rougeLsum:{rogue['rougeLsum']*100:2f}%") #Rogue1:50.386161% #rouge2:24.842412% #rougeL:41.370130% #rougeLsum:41.394230%
我們 PEFT 微調(diào)后的 FLAN-T5-XXL 在測(cè)試集上取得了 50.38% 的 rogue1 分?jǐn)?shù)。相比之下,flan-t5-base 的全模型微調(diào)獲得了 47.23 的 rouge1 分?jǐn)?shù)。rouge1 分?jǐn)?shù)提高了 3%。
令人難以置信的是,我們的 LoRA checkpoint 只有 84MB,而且性能比對(duì)更小的模型進(jìn)行全模型微調(diào)后的 checkpoint 更好。
審核編輯:劉清
-
PLM
+關(guān)注
關(guān)注
2文章
91瀏覽量
20781 -
AWS
+關(guān)注
關(guān)注
0文章
418瀏覽量
24182 -
LoRa模塊
+關(guān)注
關(guān)注
5文章
114瀏覽量
13790 -
pytorch
+關(guān)注
關(guān)注
2文章
794瀏覽量
13010
原文標(biāo)題:使用 LoRA 和 Hugging Face 高效訓(xùn)練大語言模型
文章出處:【微信號(hào):zenRRan,微信公眾號(hào):深度學(xué)習(xí)自然語言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論