最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

基于ChatGLM2-INT4 + LoRA訓(xùn)練一個(gè)屬于自己的微信聊天機(jī)器人(Kaggle + Colab)

2023-09-15 21:17 作者:豚骨拉面--  | 我要投稿

(注:本專欄只討論相關(guān)技術(shù)細(xì)節(jié),不涉及任何其他目的,如侵刪)?

提醒:本專欄全文較長(zhǎng),部分是選做內(nèi)容,可自行取舍。

摘要

2023年3月,清華大學(xué)NLP團(tuán)隊(duì)開源了對(duì)話模型ChatGLM-6B,該模型具有對(duì)話流暢、部署門檻較低等優(yōu)點(diǎn)。同年6月,該模型的升級(jí)版本ChatGLM2-6B發(fā)布,二代模型具有更強(qiáng)大的性能和推理能力。本項(xiàng)目利用大模型的低秩適配(Low-Rank Adaptation,LoRA)技術(shù)對(duì)INT4量化ChatGLM-6B系列模型進(jìn)行微調(diào),在包含~7k段對(duì)話的微信聊天對(duì)話數(shù)據(jù)集上進(jìn)行訓(xùn)練,以實(shí)現(xiàn)一個(gè)微信聊天機(jī)器人。

本專欄討論了微調(diào)相關(guān)的實(shí)現(xiàn)細(xì)節(jié),包括數(shù)據(jù)集預(yù)處理、量化模型加載、并行計(jì)算(受限于Kaggle CPU的顯存,實(shí)際上并未實(shí)現(xiàn),但正文中有相關(guān)的討論)、LoRA實(shí)現(xiàn)和訓(xùn)練等,同時(shí)包含部分ChatGLM的源碼解析。需要指出的是,由于微信聊天對(duì)話數(shù)據(jù)的處理比較粗糙,不少對(duì)話上下文不具有明顯的邏輯性,因此模型最后的推理性能并不理想。想要提高模型的推理回答質(zhì)量,需要進(jìn)行更嚴(yán)格的數(shù)據(jù)清洗工作。

本專欄是對(duì)我上一期視頻的補(bǔ)充:

相應(yīng)的Kaggle Notebook鏈接為https://www.kaggle.com/code/littleweakweak/finetune-lora-chatglm2,可以與本專欄結(jié)合起來(lái)看。

目錄

  • 前言(廢話)

  • 數(shù)據(jù)集預(yù)處理

  • 模型量化與加載

  • 并行計(jì)算

  • Monkey Patch(選做)

  • LoRA

  • 模型訓(xùn)練(Kaggle + Colab)

  • 模型推理

  • 鏈接匯總

前言(廢話)

首先感謝ChatGLM團(tuán)隊(duì)提供的開源模型和官方微調(diào)教程:

https://huggingface.co/THUDM/chatglm-6b

https://huggingface.co/THUDM/chatglm2-6b

(PS:前兩天ChatGLM官方賬號(hào)私聊我,問(wèn)我能不能出一期“智譜清言”的使用體驗(yàn),我打算下一期視頻就出。作為一個(gè)AI初學(xué)者?& B站小透明新人,能被官方翻牌子還是很受寵若驚的。)

訓(xùn)練一個(gè)微信聊天機(jī)器人的想法實(shí)際上去年11月就有了,當(dāng)時(shí)為了幫女朋友寫計(jì)算語(yǔ)言學(xué)的大作業(yè),提取了幾千段微信對(duì)話一通分析,想不到現(xiàn)在就已經(jīng)變成前女友了,真是感慨萬(wàn)千(小丑見大丑)

今年2月份,我嘗試?yán)弥形腉PT2訓(xùn)練了一個(gè)微信聊天機(jī)器人,最后的效果還挺出乎我意料的。后來(lái)ChatGLM-6B和ChatGLM2-6B相繼發(fā)布,我很好奇更大的模型在同樣的數(shù)據(jù)集上能表現(xiàn)出怎樣的性能。然而Kaggle GPU的顯存過(guò)小,全參數(shù)微調(diào)不現(xiàn)實(shí),而LoRA可以實(shí)現(xiàn)較低的微調(diào)顯存消耗和較好的模型推理性能,因此我基于LoRA對(duì)模型進(jìn)行微調(diào)。

這個(gè)項(xiàng)目做了差不多有一個(gè)月,最后的實(shí)現(xiàn)難度大大超出了我原來(lái)的設(shè)想。在Kaggle那兩塊破GPU上做微調(diào)真就是“戴著鐐銬跳舞”,好在最后也算是做完了。在整個(gè)過(guò)程中,我也是從零開始學(xué)習(xí)LoRA、模型量化、并行計(jì)算(Accelerate庫(kù))等相關(guān)知識(shí),時(shí)間有限,能力不足,項(xiàng)目還是有不少缺陷。有任何問(wèn)題或者意見都?xì)g迎評(píng)論區(qū)留言或者私聊我,大家一起學(xué)習(xí)、一起進(jìn)步。

(以下將ChatGLM-6B和ChatGLM2-6B分別簡(jiǎn)稱為GLM1和GLM2

數(shù)據(jù)集預(yù)處理

微信聊天記錄的提取可以看我之前的專欄:

這里我們只需要把微信文本提取到txt文件(data.txt)中,格式如下(每段對(duì)話之間由\n分隔):

圖中對(duì)話來(lái)自貼吧和知乎,僅供參考

然后利用下面的代碼劃分?jǐn)?shù)據(jù)集,指定訓(xùn)練集和驗(yàn)證集的存儲(chǔ)路徑(train.txt / val.txt):

然后把train.txt和val.txt上傳到Kaggle的Dataset中,具體操作見視頻。

下一步需要構(gòu)建訓(xùn)練和測(cè)試用的Dataset和定義collate_fn,這里我主要參考了下面兩個(gè)鏈接:

https://github.com/yuanzhoulvpi2017/zero_nlp

https://github.com/liucongg/ChatGLM-Finetuning

同時(shí),根據(jù)GLM2源碼中的chat函數(shù)以及tokenizer中的build_prompt函數(shù):

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/modeling_chatglm.py#L1019-L1035
https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/tokenization_chatglm.py#L162-L169

模型推理生成回答時(shí),需要在問(wèn)答前后加上“[Round 0]\n問(wèn)”或“\n答”。因此我們需要轉(zhuǎn)換一下輸入數(shù)據(jù)的格式,加上相應(yīng)的prompt,即convert_txt函數(shù):

根據(jù)一些實(shí)驗(yàn)測(cè)試,最后我選用的prompt方式是,在所有對(duì)話前后加了“Round”和“問(wèn)” 或“答”,同時(shí)在最開始加了一段對(duì)話:“\n問(wèn):你現(xiàn)在是一個(gè)微信聊天機(jī)器人,接下來(lái)你要根據(jù)我的提問(wèn),作出相應(yīng)的回答。\n答:好的,請(qǐng)開始你的提問(wèn)?!?/p>

convert_txt函數(shù)會(huì)對(duì)輸入文本進(jìn)行問(wèn)答劃分,最后將處理后的數(shù)據(jù)集保存在一個(gè)字典中。

然后構(gòu)建Dataset,這里我用的是Hugging Face的datasets庫(kù),可以方便地構(gòu)建Dataset對(duì)象。具體用法見https://huggingface.co/docs/datasets/index。

我們需要定義preprocess函數(shù)來(lái)處理數(shù)據(jù)集,其功能是對(duì)數(shù)據(jù)集中的文本做tokenize,同時(shí)保存每段對(duì)話中提問(wèn)的起始位置,以便后續(xù)進(jìn)行mask操作

這里我用的是GLM2的tokenizer,tokenizer會(huì)在每段文本前加上[gMASK]和sop兩個(gè)標(biāo)記

其中64790和64792分別對(duì)應(yīng)[gMASK]和sop:

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/tokenization_chatglm.py#L158-L160

注意:GLM1和GLM2用的tokenizer的詞表大小不一樣,如果后續(xù)訓(xùn)練中出現(xiàn)tokenizer報(bào)錯(cuò)數(shù)組越界或是下面這種ValueError,可能是tokenizer用錯(cuò)了)

下一步是定義collate_fn,目的是把所有提問(wèn)的部分都mask掉(-100表示不對(duì)該位置的元素計(jì)算loss):

在官方推薦的LoRA-GLM1的微調(diào)代碼中(也就是上面出現(xiàn)的zero_nlp),collate_fn還返回了處理后的attention_mask:

具體來(lái)說(shuō),模型在生成提問(wèn)部分對(duì)應(yīng)的輸出時(shí),可以看到整個(gè)提問(wèn)部分;在生成回答部分對(duì)應(yīng)的輸出時(shí),才和原始的GPT模型一致,只看到當(dāng)前已經(jīng)生成的內(nèi)容。

GLM1微調(diào)時(shí)的attention mask(藍(lán)色為看到的部分)

而在GLM2的微調(diào)代碼中,collate_fn沒(méi)有返回預(yù)先定義的attention_mask,而是由模型在計(jì)算Attention時(shí)自動(dòng)創(chuàng)建一個(gè)上三角形的mask(與原始GPT一致)

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/modeling_chatglm.py#L677-L693

按照我個(gè)人的理解,預(yù)定義atttention_mask似乎有些多此一舉了,因?yàn)榉祷氐膌abel已經(jīng)把提問(wèn)部分給mask掉了,即loss不會(huì)傳給提問(wèn)部分的元素,所以模型如何預(yù)測(cè)提問(wèn)部分并不重要。

Anyway,最終的collate_fn只返回了input和label,這里可以驗(yàn)證一下collate_fn的結(jié)果是否正確(主要看label中mask的位置):

然后設(shè)置Batch size,定義dataloader:

模型量化與加載

(這一部分由于我也是初學(xué)者,下面的理解可能會(huì)有不少錯(cuò)誤,歡迎大家指正)

接下來(lái)是模型量化與加載,這是最麻煩的一步。Kaggle的CPU只有~13GB的內(nèi)存,用AutoModel.from_pretrained加載模型時(shí)需要先加載到CPU上,而GLM1的模型大小為~14GB,GLM2的模型大小為~13GB,因此GLM1不能直接加載,GLM2有時(shí)可以直接加載(取決于之前占用的內(nèi)存大?。?。此外,如果像我之前部署GLM1推理一樣,使用load_checkpoint_and_dispatch函數(shù),之后可能會(huì)報(bào)錯(cuò)tensor的device不一樣??紤]到GPU也只有~15GB的顯存,最終我使用的是INT4量化的GLM2進(jìn)行微調(diào)。

加載量化模型主要有兩種方式:

1. 基于Accelerate和bitsandbytes庫(kù),在AutoModel.from_pretrained中指定load_in_4bit=True,即

模型量化的原理可以參考https://huggingface.co/blog/hf-bitsandbytes-integration,我覺(jué)得寫的很清楚。這種方式似乎需要先加載一遍完整的模型到CPU中,然后在移動(dòng)到目標(biāo)device的過(guò)程中完成量化,因此只能用這種方式加載相對(duì)小一些的GLM2,加載完成后大約占用顯存5GB,不過(guò)很多時(shí)候會(huì)加載失敗。

量化完成后,模型中的nn.Linear層會(huì)被替換為bitsandbytes中的Linear4bit層,為了數(shù)值穩(wěn)定性,輸出層沒(méi)有進(jìn)行量化:

這種加載方式的好處是Peft庫(kù)可以支持基于bitsandbytes的量化模型,后續(xù)在LoRA微調(diào)時(shí),代碼比較簡(jiǎn)潔。Peft庫(kù)官方提供的INT8微調(diào)demo都用的是這種方式。

2. 直接加載官方的量化模型,即

ChatGLM官方提供了模型量化方式,見https://huggingface.co/THUDM/chatglm2-6b/blob/main/quantization.py,其中定義了量化線性層類QuantizedLinear:

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/quantization.py#L124-L149

然而,Peft庫(kù)不知道這個(gè)QuantizedLinear是什么東西,所以如果要使用Peft庫(kù),需要把QuantizedLinear換成Peft支持的Linear4bit層或LinearNF4層,之后在LoRA部分會(huì)詳細(xì)說(shuō)明。

加載時(shí)最好不要指定device_map='auto',后續(xù)可能會(huì)報(bào)錯(cuò)。

(PS:我還嘗試過(guò)使用low_cpu_mem_usage和tensor_parallel來(lái)加載,都失敗了,感興趣的朋友可以參考這兩個(gè)鏈接試一下:

https://huggingface.co/docs/transformers/main_classes/model

https://www.kaggle.com/code/blacksamorez/tensor-parallel-int4-llm

并行計(jì)算

Kaggle免費(fèi)提供的GPU比Colab的更好:1塊P100或者2塊T4。然而P100不支持量化模型(見https://github.com/THUDM/ChatGLM-6B/issues/1322),因此我們很自然地想用2塊T4做并行計(jì)算。

Hugging Face提供了用于并行計(jì)算的Accelerate庫(kù),下面是一些介紹:

https://huggingface.co/docs/accelerate/index

https://blog.csdn.net/cxx654/article/details/131817042

具體來(lái)說(shuō),我們需要修改訓(xùn)練代碼,去掉所有的.to(device)或者.cuda()操作,而是讓accelerate來(lái)分配device。然后把訓(xùn)練代碼寫在一個(gè)train函數(shù)里,調(diào)用notebook_launcher函數(shù)進(jìn)行分布式訓(xùn)練:

可參考下面的鏈接:

https://huggingface.co/learn/nlp-course/chapter3/4#supercharge-your-training-loop-with-accelerate

https://huggingface.co/docs/accelerate/basic_tutorials/notebook

然而,notebook_launcher似乎是要在CPU上加載兩次完整的模型,而加載一次INT4模型最多需要~9GB的內(nèi)存消耗,因此CPU根本吃不消。之后我又換了launch py腳本的方式(用Accelerate或DeepSpeed),也出現(xiàn)了同樣的問(wèn)題,所以并行計(jì)算的嘗試宣告失敗。之后我可能會(huì)嘗試學(xué)一下DDP,看看能不能做。

這里有兩個(gè)小坑點(diǎn),用Accelerate的xd可以注意一下,以免出現(xiàn)nan梯度。(本來(lái)是在一篇CSDN上看到的,后面找不到原文鏈接了):

1. 量化模型加載時(shí),需要選擇參數(shù)類型為float32

2. 混合精度訓(xùn)練時(shí),不要使用accelerate.autocast,而是用accelerate.accumulate,訓(xùn)練for loop大概長(zhǎng)下面這樣:

調(diào)用Accelerate庫(kù)的小demo:https://www.kaggle.com/code/littleweakweak/test-accelerate-chatglm

Monkey Patch(選做)

(這一部分是選做,有興趣的xd可以自行嘗試,不感興趣的可以直接跳到LoRA部分)

Monkey Patch(猴子補(bǔ)?。┦且环N在程序運(yùn)行時(shí)動(dòng)態(tài)修改對(duì)象屬性的方法,像這樣:

可參考下面兩個(gè)鏈接:

https://zhuanlan.zhihu.com/p/71181926/

https://stackoverflow.com/questions/5626193/what-is-monkey-patching

由于模型文件是直接從Hugging Face加載,想要修改內(nèi)部代碼并不容易,此時(shí)就可以運(yùn)用Monkey Patch來(lái)魔改內(nèi)部實(shí)現(xiàn)。我這里使用Monkey Patch主要用于簡(jiǎn)單并行,即把模型的不同層放在兩個(gè)GPU上,以節(jié)約顯存。我在嘗試的時(shí)候?yàn)榱撕?jiǎn)便,只把embedding和output_layer(均為65024 * 4096)放在了cuda 0,其它層放在cuda 1。

GLM2的model是一個(gè)ChatGLMForConditionalGeneration對(duì)象,而model的transformer屬性是一個(gè)ChatGLMModel對(duì)象

在GLM2的源碼中,ChatGLMForConditionalGeneration的forward函數(shù)(簡(jiǎn)稱為外層forward)會(huì)進(jìn)一步調(diào)用transformer(ChatGLMModel)的forward函數(shù)(簡(jiǎn)稱為內(nèi)層forward),其中embedding層在內(nèi)層forward中被調(diào)用,output_layer在外層forward中被調(diào)用。

ChatGLMForConditionalGeneration的forward函數(shù)(外層forward):

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/modeling_chatglm.py#L917-L975

ChatGLMModel的forward函數(shù)(內(nèi)層forward):

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/modeling_chatglm.py#L786-L843

所以,為了實(shí)現(xiàn)簡(jiǎn)易版“模型并行”,我們需要利用Monkey Patch重寫內(nèi)外層的forward函數(shù),即對(duì)embedding和output_layer的相關(guān)tensor做device轉(zhuǎn)換。

內(nèi)層forward改寫:

改寫embed層的操作

外層forward改寫:

改寫output_layer層的操作

Monkey Patch和模型加載的代碼:

(這里提醒一下,想要移動(dòng)nn.Module對(duì)象到特定device,需要直接對(duì)nn.Module對(duì)象調(diào)用.cuda()或.to(),移動(dòng)它的parameters是沒(méi)用的。參考

https://discuss.pytorch.org/t/moving-a-module-to-a-device/142175

https://github.com/pytorch/pytorch/issues/7460

經(jīng)過(guò)測(cè)試,移動(dòng)embedding和output_layer可以節(jié)省~1GB的顯存(INT4量化操作會(huì)消耗cuda 0的~1GB顯存):

綜上,移動(dòng)模型的部分層確實(shí)可以顯著減小顯存占用,提高GPU的利用效率。只是后面訓(xùn)練的時(shí)候我懶得加了,大家可以按照這個(gè)模式自行魔改內(nèi)部函數(shù),以實(shí)現(xiàn)更高效的模型并行(比如前一半layer在cuda 0,后一半layer在cuda 1)。上面的代碼匯總在https://www.kaggle.com/littleweakweak/test-multi-device-glm。

LoRA

低秩適配(Low-Rank Adaption,LoRA)是一種最近常用的大模型微調(diào)技術(shù),其原理非常簡(jiǎn)單巧妙。它認(rèn)為權(quán)重矩陣的更新本質(zhì)上是低秩的,所以可以用兩個(gè)低秩矩陣的乘積來(lái)近似。按我個(gè)人的理解,LoRA假設(shè)權(quán)重矩陣中只有極少元素的更新是獨(dú)立有效的,而大部分元素的更新是冗余的,那么就可以用低秩矩陣去近似其中的“有效更新”。原理可見:

https://arxiv.org/pdf/2106.09685.pdf
https://arxiv.org/pdf/2106.09685.pdf

https://baijiahao.baidu.com/s?id=1771735489327080446&wfr=spider&for=pc

運(yùn)用LoRA微調(diào)時(shí),需要凍結(jié)原始權(quán)重的梯度,然后對(duì)目標(biāo)線性層添加低秩適配器(矩陣A、B),在訓(xùn)練過(guò)程中只更新低秩適配器的權(quán)重,從而大大降低了訓(xùn)練參數(shù)量。

實(shí)現(xiàn)LoRA可以通過(guò)Peft庫(kù),也可以自己手寫(LoRA from scratch)。

1. 通過(guò)Peft庫(kù)

Peft庫(kù)實(shí)現(xiàn)了多種大模型的adapter微調(diào)方式,同時(shí)支持對(duì)bitsandbytes量化模型的微調(diào)(量化層目前還不支持梯度更新,但LoRA等方法只需要更新adapter層的參數(shù)),可參考:

https://huggingface.co/docs/peft/conceptual_guides/lora

https://github.com/huggingface/peft

注意安裝的Peft庫(kù)的版本應(yīng)該為0.4.0,否則在import的時(shí)候會(huì)報(bào)錯(cuò)。

前面提到Peft庫(kù)不支持對(duì)QuantizedLinear層添加adapter,因此需要把QuantizedLinear層轉(zhuǎn)換為L(zhǎng)inearNF4層:

需要注意的是,LinearNF4的實(shí)際量化發(fā)生在.cuda()或.to()調(diào)用,模型權(quán)重會(huì)從float類型轉(zhuǎn)為int8類型。在量化完成后,不能再對(duì)量化層進(jìn)行任何.cuda()或.to()操作,即使不改變device。因?yàn)?strong>每一次.cuda()或.to()操作似乎都會(huì)執(zhí)行一次量化,而多次量化后在矩陣相乘時(shí)會(huì)報(bào)錯(cuò):

下一步利用Peft庫(kù)配置LoRA,這里我們選擇adapter的秩 r = 8,比例系數(shù) α = 32,對(duì)每個(gè)Attention模塊的QKV變換層(LinearNF4)和輸出層(Linear)添加adapter:

其中prepare_model_for_kbit_training函數(shù)的作用是開啟輸入梯度(有利于微調(diào)adapter)和梯度檢查點(diǎn)(節(jié)省顯存,但是訓(xùn)練耗時(shí)更長(zhǎng)),具體見https://blog.csdn.net/BIT_666/article/details/131675165;target_modules指定了需要添加adapter的module,注意在GLM中最后一層輸出層名為lm_head,GLM2中則為output_layer。最后調(diào)用get_peft_model完成adapter的添加:

輸出層的LoRA模塊:

2. LoRA from scratch

我自己手寫的LoRA參考了BERT-LoRA-TensorRT項(xiàng)目,項(xiàng)目鏈接:

https://medium.com/@alexmriggio/lora-low-rank-adaptation-from-scratch-code-and-theory-f31509106650

https://github.com/alexriggio/BERT-LoRA-TensorRT

具體地,我們需要將所有要添加adapter的線性層替換為線性LoRA模塊,共包含3個(gè)線性層(pretrained線性層,矩陣A、B),然后將原線性層的權(quán)重復(fù)制到pretrained線性層中。由于原始QuantizedLinear是對(duì)原始權(quán)重進(jìn)行INT4量化,因此量化權(quán)重的輸入維度應(yīng)該是原權(quán)重輸入維度的一半(猜測(cè):一個(gè)byte表示兩個(gè)“INT8”,這里weight_bit_width=4):

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/quantization.py#L124-L149

這里我們直接復(fù)制INT4權(quán)重,所以權(quán)重維度不需要除2,為了方便后續(xù)構(gòu)造和復(fù)制權(quán)重,我稍微修改了一下QuantizedLinear的定義:(其實(shí)不改貌似也行,可以直接把weight_bit_width設(shè)為8)。

接下來(lái)調(diào)用qlinear_conversion函數(shù),把模型原有的QuantizedLinear層換成自定義的QuantizedLinear;開啟梯度檢查點(diǎn)和輸入梯度(相當(dāng)于peft中的prepare_model_for_kbit_training)。最后可以自行決定是否要把最后一層參數(shù)變成float32:

接下來(lái)定義針對(duì)量化線性層的QLinearLoRA模塊:

類似地,我們也要定義針對(duì)普通量化層的LinearLoRA模塊。然后定義添加LoRA模塊的函數(shù)create_qlora:

調(diào)用add_lora_layers對(duì)模型添加LoRA模塊:

最后,凍結(jié)除LoRA的adapter以外所有層的梯度,至此模型部分的修改已經(jīng)全部完成。

模型訓(xùn)練(Kaggle + Colab)

經(jīng)過(guò)了前面繁瑣的模型修改,接下來(lái)是模型訓(xùn)練,這一部分的邏輯基本上沒(méi)什么大的改動(dòng),所以代碼部分我就不細(xì)講了。前面我們通過(guò)兩種方式實(shí)現(xiàn)了LoRA,基于Peft庫(kù)的和LoRA from scratch。在實(shí)際訓(xùn)練的時(shí)候,我發(fā)現(xiàn)基于Peft庫(kù)的訓(xùn)練微調(diào)要遠(yuǎn)遠(yuǎn)慢于LoRA from scratch?;赑eft庫(kù)的微調(diào)耗時(shí)大概是3h / epoch(不開梯度檢查點(diǎn)~2h):

基于Peft庫(kù)的微調(diào)耗時(shí)(不開梯度檢查點(diǎn))

而LoRA from scratch的耗時(shí)大概是50min ~ 1h?/ epoch(參考原視頻,耗時(shí)似乎與網(wǎng)速有關(guān),最快可以30min / epoch,平均~1h),訓(xùn)練耗時(shí)受batch size影響不大。這里我還不太清楚訓(xùn)練耗時(shí)差異的原因,有知道的大佬可以在評(píng)論區(qū)說(shuō)一下。我猜測(cè)是Kaggle的T4?GPU太辣雞了,可能對(duì)持Peft庫(kù)內(nèi)部的一些優(yōu)化兼容性較差??傊?,由于基于Peft庫(kù)的微調(diào)實(shí)在太久了,我最后只用了LoRA from scratch來(lái)微調(diào)。

另外,由于使用AutoModel.from_pretrained加載的模型參數(shù)均為float16類型,而后續(xù)添加的LoRA矩陣A、B均為float32,因此直接訓(xùn)練會(huì)報(bào)錯(cuò):

所以我們需要利用torch.autocast開啟混合精度訓(xùn)練,讓torch幫我們自動(dòng)做類型轉(zhuǎn)換(autocast只用wrap forward部分):

關(guān)于自動(dòng)混合精度(Automatic Mixed Precision,AMP)的介紹可進(jìn)一步參考:

https://pytorch.org/docs/stable/amp.html

https://www.cs.toronto.edu/ecosystem/documents/AMP-Tutorial.pdf

然而,如果在加載模型的時(shí)候指定參數(shù)類型為float32,則在量化層的backward過(guò)程會(huì)報(bào)類型錯(cuò)誤:

原因可能是W8A16Linear是自定義的torch.autograd.Function子類,其中的forward和backward都是自定義的,torch.autocast對(duì)其不起作用。

https://huggingface.co/THUDM/chatglm2-6b/blob/8fd7fba285f7171d3ae7ea3b35c53b6340501ed1/quantization.py#L44-L64

其中,forward函數(shù)調(diào)用了extract_weight_to_half函數(shù),其作用是把INT4量化的權(quán)重(int8)恢復(fù)為float16類型,然后保存輸入張量inp供backward過(guò)程使用。而此時(shí)模型的非量化層(如embedding)的參數(shù)類型是float32,所以經(jīng)過(guò)前面一系列模型層的inp的類型也是float32,也就出現(xiàn)了類型沖突。

關(guān)于自定義torch.autograd.Function子類可以參考:

https://pytorch.org/docs/stable/autograd.html

https://pytorch.org/tutorials/beginner/examples_autograd/two_layer_net_custom_function.html

https://zhuanlan.zhihu.com/p/574119247

因此,我們需要稍微修改W8A16Linear的定義,將inp的類型轉(zhuǎn)換為float16(其實(shí)也可以把weight轉(zhuǎn)換為float32,但是這么做訓(xùn)練會(huì)非常耗時(shí)):

我自己只嘗試了float16加載,感興趣的xd可以試一下float32加載,看看訓(xùn)練之后的效果。

確保訓(xùn)練循環(huán)無(wú)誤后,就可以用Save version在后臺(tái)訓(xùn)練了。我自己訓(xùn)練了30個(gè)epoch,最終的訓(xùn)練loss大概收斂在2.6左右(很早就收斂了,大概是數(shù)據(jù)集的緣故)。

Colab的代碼基本和Kaggle一樣,鏈接如下:

https://colab.research.google.com/drive/1jHvQXG_SH4aY1fZo2H7degxmszBs2rfN?usp=sharing

注意修改相關(guān)路徑:

修改完成后,可以直接選擇“全部運(yùn)行”。

模型推理

訓(xùn)練完成后,可以進(jìn)行模型推理。一般的LoRA微調(diào)在推理時(shí)為了提高速度,在模型加載后會(huì)先將adapter的權(quán)重merge到原權(quán)重上

h%3DW_0x%20%2B%20%5CDelta%20Wx%3D(W_0%2BBA)x

但在量化模型中,W0是量化后的權(quán)重,需要先恢復(fù)為float16才可以merge,而恢復(fù)后的權(quán)重會(huì)占用大量顯存,因此我們這里不預(yù)先進(jìn)行權(quán)重的merge,只把模型轉(zhuǎn)為eval模式。

推理的Kaggle Notebook為:https://www.kaggle.com/code/littleweakweak/infer-chatglm-lora。

首先需要上傳checkpoint文件,然后打開推理Notebook,指定checkpoint路徑,設(shè)置推理參數(shù)(top_p和temperature),具體可以參考原視頻里的操作。

關(guān)于top_p和temperature可以參考https://zhuanlan.zhihu.com/p/613428710。在GLM2的chat函數(shù)中,top_p和temperature的默認(rèn)值均為0.8,以使模型的回答具有足夠的多樣性。然而微調(diào)之后,模型的回答在一定程度上會(huì)被局限在訓(xùn)練語(yǔ)料的分布中。根據(jù)我的測(cè)試,應(yīng)該選取一個(gè)較低的top_p值和較高的temperature值,這樣模型的回答才“相對(duì)”合理一些。最終我選取的參數(shù)為top_p=0.15,temperature=20.0。(如果temperature過(guò)低,模型根本不能生成正常的回答)

而在對(duì)話過(guò)程中,我將prompt中的“\n問(wèn):你現(xiàn)在是一個(gè)微信聊天機(jī)器人,接下來(lái)你要根據(jù)我的提問(wèn),作出相應(yīng)的回答。\n答:好的,請(qǐng)開始你的提問(wèn)?!弊鳛?strong>history傳入模型,同時(shí)限制模型每次生成回答的長(zhǎng)度為20,以免模型會(huì)生成過(guò)長(zhǎng)的回答。(似乎模型在微調(diào)后忘記了如何終止回答)

視頻中也呈現(xiàn)了推理的效果,由于訓(xùn)練語(yǔ)料的清洗比較粗糙,所以很多時(shí)候模型生成的回答根本沒(méi)法看,我只是選取了幾段稍微合理的回答。想要提高模型的推理回答質(zhì)量,需要進(jìn)行更嚴(yán)格的數(shù)據(jù)清洗工作。

鏈接匯總

1. ChatGLM項(xiàng)目鏈接:

https://huggingface.co/THUDM/chatglm-6b

https://huggingface.co/THUDM/chatglm2-6b

2. 模型訓(xùn)練Notebook:

https://www.kaggle.com/code/littleweakweak/finetune-lora-chatglm2

3. 調(diào)用Accelerate庫(kù)的demo:

https://www.kaggle.com/code/littleweakweak/test-accelerate-chatglm

4. 手動(dòng)模型并行demo:

https://www.kaggle.com/littleweakweak/test-multi-device-glm

5. 模型推理Notebook:

https://www.kaggle.com/code/littleweakweak/infer-chatglm-lora

結(jié)語(yǔ)

這個(gè)項(xiàng)目斷斷續(xù)續(xù)做了有小一個(gè)月,最后的工作量遠(yuǎn)遠(yuǎn)超出我當(dāng)初的設(shè)想。一開始的模型并行就弄了差不多兩個(gè)星期,最終宣告失??;后面的LoRA實(shí)現(xiàn)、模型訓(xùn)練和推理又弄了差不多兩個(gè)星期,好在最后的效果我個(gè)人還能接受,算是努力沒(méi)有白費(fèi)吧。

這篇專欄也寫了差不多一個(gè)星期,中間白天還要上課,也有各種各樣的事情,所以有些地方可能觀感上不是很連貫,望大家包涵。以后我打算延續(xù)這種“頻展示效果、專欄解釋細(xì)節(jié)”的模式,一個(gè)原因是我覺(jué)得視頻時(shí)間有限,能傳達(dá)的信息量也有限;另一個(gè)原因是寫專欄可以比較系統(tǒng)地整理我的思路,同時(shí)便于大家理解其中的細(xì)節(jié),我自己以后也會(huì)時(shí)不時(shí)翻看這篇專欄。大家如果覺(jué)得有更好的形式,或是有任何的疑問(wèn)、意見或建議,都?xì)g迎在評(píng)論區(qū)留言或是私信我。我也是初學(xué)者,大家一起進(jìn)步。

基于ChatGLM2-INT4 + LoRA訓(xùn)練一個(gè)屬于自己的微信聊天機(jī)器人(Kaggle + Colab)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
百色市| 奉贤区| 霍州市| 永济市| 京山县| 富宁县| 加查县| 玉门市| 丰城市| 寿宁县| 郎溪县| 昌宁县| 靖州| 庆元县| 陕西省| 东兰县| 涞水县| 南京市| 武鸣县| 张掖市| 西峡县| 明光市| 巴塘县| 江北区| 吴川市| 平罗县| 淮安市| 彭阳县| 辽宁省| 泗水县| 永定县| 略阳县| 庐江县| 南城县| 华蓥市| 丹江口市| 绥芬河市| 乡宁县| 永昌县| 锡林浩特市| 金阳县|