實(shí)戰(zhàn):從環(huán)境搭建到推理部署全流程指南)
簡介大語言模型微調(diào)是行業(yè)落地的核心技術(shù)環(huán)節(jié)。LoRA作為一種高效參數(shù)微調(diào)方法通過凍結(jié)基座模型權(quán)重并訓(xùn)練低秩增量矩陣顯著降低顯存占用使6B級別模型在普通顯卡上也能完成業(yè)務(wù)定制。ChatGLM3-6B作為中文場景中表現(xiàn)優(yōu)秀的基礎(chǔ)模型配合LoRA可在知識問答、領(lǐng)域?qū)υ挼热蝿?wù)上快速適配。本文完整梳理了從環(huán)境搭建、數(shù)據(jù)清洗、訓(xùn)練參數(shù)配置到推理驗(yàn)證的工程流程并針對顯存溢出、過擬合、loss異常等常見問題給出可操作的排查方案為開發(fā)者提供一套可直接復(fù)現(xiàn)的微調(diào)實(shí)踐路徑。 做AI應(yīng)用落地這幾年我最大的感受是模型底座要選對但真正決定業(yè)務(wù)體驗(yàn)的往往是最后那一步定向調(diào)教。ChatGLM3-6B是我常用的底座之一通用對話、知識問答都能打可一旦牽扯到具體的行業(yè)術(shù)語、輸出格式、角色人設(shè)直接拿原版模型上線就會顯得官方而空洞。這篇就把我的完整跑通方案記錄下來——基于ChatGLM3-6B模型用LoRA方法做微調(diào)從環(huán)境搭建、數(shù)據(jù)處理到訓(xùn)練、推理每一步都給出可復(fù)現(xiàn)的源碼和參數(shù)并且把我在實(shí)操中踩過的坑一并講清楚。先說這套方案適合誰。如果你準(zhǔn)備在自己的顯卡上把一個大模型拉向垂直場景手頭有一張24G顯存左右的卡或者幾張消費(fèi)級卡做數(shù)據(jù)并行想把模型從什么都能聊變成你的領(lǐng)域?qū)<夷沁@篇文章就是給你準(zhǔn)備的。LoRA的核心價值在于不跟那60億參數(shù)硬碰硬而是訓(xùn)練一組規(guī)模極小的增量矩陣讓模型在保持原有能力的同時學(xué)會你要的說話方式。我的實(shí)測結(jié)論是在1千到1萬條高質(zhì)量業(yè)務(wù)數(shù)據(jù)下LoRA微調(diào)的效果跟全參微調(diào)已經(jīng)非常接近但訓(xùn)練資源需求卻低了一個數(shù)量級。1. 項(xiàng)目背景與整體設(shè)計思路1.1 為什么選ChatGLM3-6B當(dāng)?shù)鬃疫xChatGLM3-6B有幾個非常實(shí)際的考量。首先是參數(shù)規(guī)??ㄎ缓芎线m6B這個量級既不像70B那樣需要多卡集群伺候也不會像幾百M(fèi)的小模型那樣微調(diào)完還是顯得笨。它在中文上的表現(xiàn)尤其是在指令遵循和上下文理解方面明顯好于同體量的多數(shù)開源模型這對我做的業(yè)務(wù)問答、文本潤色、結(jié)構(gòu)化抽取這類任務(wù)來說特別重要。其次ChatGLM3的對話協(xié)議是固定的一套模版格式。官方微調(diào)腳本里已經(jīng)對這種格式做了完整支持包括system prompt、多輪歷史、工具調(diào)用這些字段。這意味著你在微調(diào)時不需要自己發(fā)明輪子只要把數(shù)據(jù)整理成對應(yīng)的對話結(jié)構(gòu)剩下的交給模型。最后社區(qū)的生態(tài)成熟度也是個隱性優(yōu)勢。transformers、peft、datasets這些主流庫對ChatGLM3支持得很到位踩坑時隨便一搜就能找到答案這一點(diǎn)對新手來說價值很大。說到生態(tài)我還要多提一句很多人以為選底座只看榜單分?jǐn)?shù)但實(shí)際開發(fā)里庫的兼容性和社區(qū)活躍度往往是決定能否按時交付的關(guān)鍵。我早期用過一些小眾模型文檔不全、加載報錯、社區(qū)沒人回答折騰一周還在環(huán)境階段。后來統(tǒng)一收斂到ChatGLM3這類有官方微調(diào)倉庫、有大量第三方教程的模型上交付效率明顯上來了。1.2 選LoRA而不選全參微調(diào)核心邏輯在哪這里我要把方案選型講透因?yàn)楹芏喑鯇W(xué)者上來就糾結(jié)我到底該用LoRA還是全參微調(diào)。我自己的判斷標(biāo)準(zhǔn)很簡單先看你的硬件再看你的數(shù)據(jù)量。全參微調(diào)意味著6B模型的所有參數(shù)都要參與梯度更新。以AdamW優(yōu)化器為例光優(yōu)化器狀態(tài)就要占掉兩倍模型參數(shù)量的內(nèi)存加上模型本身、梯度、激活值一張24G的卡根本塞不下通常需要A100 80G級別的設(shè)備。LoRA的做法是凍結(jié)所有原始參數(shù)只訓(xùn)練插入在attention層里的低秩矩陣。以r8為例ChatGLM3-6B可訓(xùn)練參數(shù)大概只有800萬到1000萬級別只占全部參數(shù)的1%左右。訓(xùn)練時的顯存大頭是模型本身的權(quán)重和推理帶來的激活值所以24G單卡就能很舒服地跑起來。再從數(shù)據(jù)量的角度說全參微調(diào)在數(shù)據(jù)量不足時特別容易災(zāi)難性遺忘——模型把原來的通用能力全忘了只知道你的業(yè)務(wù)話術(shù)。LoRA因?yàn)樵紮?quán)重不動相當(dāng)于在保持常識基礎(chǔ)的同時疊加一個業(yè)務(wù)適配層一千條高質(zhì)量數(shù)據(jù)就能看到效果而且訓(xùn)練完的LoRA權(quán)重只有幾十MB切換任務(wù)時只要把a(bǔ)dapter換掉就行不需要維護(hù)多個完整模型副本。P-Tuning v2也是官方推薦的一種輕量方案它通過在模型輸入側(cè)加連續(xù)型prompt來實(shí)現(xiàn)微調(diào)。但它對長文本、復(fù)雜推理場景的適配不如LoRA靈活而且每次推理都要多走一段連續(xù)prompt的學(xué)習(xí)路徑。我綜合對比后LoRA在大多數(shù)業(yè)務(wù)場景下是性價比最優(yōu)的選擇這也是這篇文章為什么用LoRA來實(shí)戰(zhàn)的原因。2. 環(huán)境準(zhǔn)備與依賴安裝2.1 硬件評估顯存到底怎么算在你開始敲pip install之前先把機(jī)器的情況摸清楚。我的運(yùn)行環(huán)境是這樣的單張RTX 4090 24G系統(tǒng)是Ubuntu 22.04CUDA 12.1PyTorch 2.1。這個配置可以很輕松地跑ChatGLM3-6B的LoRA微調(diào)實(shí)際峰值顯存約21GB左右。如果你用的是16G顯存的卡比如RTX 4080或者4090 Laptop也能跑但要開啟gradient checkpointing并調(diào)小batch size。這里我給大家一個粗略的顯存估算公式。6B模型在FP16下權(quán)重占用約為12GB60億參數(shù) × 2字節(jié)。LoRA訓(xùn)練時這12GB是固定的因?yàn)槟銉鼋Y(jié)了原始權(quán)重額外的開銷來自LoRA參數(shù)、梯度緩存、優(yōu)化器狀態(tài)和激活值。以per_device_train_batch_size1、max_length1024為例激活值大約占3~5GBLoRA參數(shù)及其優(yōu)化器狀態(tài)不到1GB模型梯度凍結(jié)層不需要存基本可忽略。所以整體算下來18~23GB是一個比較現(xiàn)實(shí)的區(qū)間。你準(zhǔn)備硬件之前可以拿這個公式粗算一下別等啟動訓(xùn)練才發(fā)現(xiàn)OOM。如果你只有一張12G的卡也并非完全不行。可以嘗試量化為4bit加載用bitsandbytes配合peft的load_in_4bitTrue把模型權(quán)重壓到3GB左右騰出空間給訓(xùn)練。當(dāng)然4bit量化會稍微損失精度訓(xùn)練出來的效果比16bit略差但確實(shí)能讓你在低端卡上跑通整個流程。2.2 環(huán)境搭建和依賴清單依賴這塊我直接給出經(jīng)過我驗(yàn)證的版本組合避免大家被各庫之間的版本沖突折磨Python 3.10transformers 4.36.0低于4.30會有ChatGLM3的tokenizer兼容問題peft 0.7.0datasets 2.16.0accelerate 0.26.0bitsandbytes 0.42.04bit量化用可選項(xiàng)sentencepiece 0.1.99protobuf 3.20.3這個版本很關(guān)鍵后面的版本會跟ChatGLM的tokenizer依賴沖突創(chuàng)建虛擬環(huán)境后用pip安裝這些包即可。有條件的話我建議用conda裝PyTorch然后pip裝其他庫。conda create -n glm3-lora python3.10 conda activate glm3-lora pip install torch2.1.0 --index-url https://download.pytorch.org/whl/cu121 pip install transformers4.36.0 peft0.7.0 datasets2.16.0 accelerate0.26.0 sentencepiece0.1.99 protobuf3.20.3裝完之后先把模型下載到本地。注意模型文件不小6B的FP16權(quán)重大約12GB提前留好磁盤空間。git lfs install git clone https://huggingface.co/THUDM/chatglm3-6b提示如果網(wǎng)絡(luò)不穩(wěn)定也可以從ModelScope的鏡像倉庫下載代碼上只要把model_name_or_path換成對應(yīng)的本地路徑就行。這一步踩過的坑也順便說一下protobuf版本不對時tokenizer加載會報TypeError: Descriptors cannot not be created directly我當(dāng)時卡了差不多半天最后鎖定到3.20.3才正常。別小看依賴版本微調(diào)項(xiàng)目大部分時間都耗在這些不起眼的小問題上。3. 數(shù)據(jù)準(zhǔn)備與預(yù)處理3.1 數(shù)據(jù)結(jié)構(gòu)讓模型看懂你的業(yè)務(wù)LoRA微調(diào)成敗的第一決定因素是數(shù)據(jù)而不是模型。ChatGLM3-6B的官方微調(diào)腳本期望的數(shù)據(jù)是對話格式的JSON。我通常使用ShareGPT格式每個樣本是一個多輪對話數(shù)組[ { conversations: [ { role: system, content: 你是云運(yùn)維助手熟悉Kubernetes、Docker、Prometheus等工具。 }, { role: user, content: Pod一直處于Pending狀態(tài)我該怎么排查 }, { role: assistant, content: 首先用 kubectl describe pod pod-name 查看事件重點(diǎn)看調(diào)度失敗原因。常見情況包括節(jié)點(diǎn)資源不足、節(jié)點(diǎn)被污點(diǎn)污化、PVC沒有綁定等。 } ] } ]注意這里有幾個細(xì)節(jié)。第一system字段可以顯式設(shè)定人設(shè)這是ChatGLM3相對早期模型的一大增強(qiáng)如果你要的是某個垂直領(lǐng)域的問答助手建議每個樣本都保留這個字段。第二多輪對話要保留完整的上下文不要只給單輪切出來的片段否則模型學(xué)不到記住前文的能力。第三字段名必須是conversations里面每輪的role只能是system、user、assistant三種之一連大小寫都不能錯。對于數(shù)據(jù)量我給個經(jīng)驗(yàn)區(qū)間。如果只是做風(fēng)格遷移或角色扮演兩三百條高質(zhì)量樣本就夠看到明顯變化如果是行業(yè)知識問答建議至少準(zhǔn)備一千條覆蓋主要問法的樣本最好是三千到一萬條的效果更穩(wěn)。數(shù)據(jù)再多的話普通LoRA可能就有點(diǎn)吃力了需要配合增量預(yù)訓(xùn)練或者調(diào)整數(shù)據(jù)采樣策略。3.2 數(shù)據(jù)清洗與質(zhì)量檢查這一步極其容易被忽略但恰恰決定了微調(diào)效果的天花板。我拿到原始數(shù)據(jù)后的處理順序是這樣的先做格式清洗再做內(nèi)容去重最后做質(zhì)量抽檢。格式清洗包括JSON解析驗(yàn)證、剔除空content的樣本、把內(nèi)容中的多余空白和不可見字符清掉、統(tǒng)一全角半角標(biāo)點(diǎn)。很多從業(yè)務(wù)數(shù)據(jù)庫里摳出來的數(shù)據(jù)格式亂得讓人頭大但模型學(xué)的就是這些文本垃圾進(jìn)垃圾出所以這一步不能省。內(nèi)容去重我用的是datasets庫的Dataset.from_list配合簡單的hash去重。因?yàn)橹貜?fù)樣本會在訓(xùn)練時被反復(fù)加權(quán)導(dǎo)致模型對某幾條回答過擬合降低泛化能力。實(shí)際操作中我還遇到過一種隱蔽問題文本內(nèi)容不同但語義高度重復(fù)的樣本比如K8s是什么和Kubernetes是什么同時大量出現(xiàn)這會讓模型對特定說法過度敏感。處理這類問題沒有捷徑只能靠人工抽樣看所以我的習(xí)慣是第一步先按字面去重第二步再抽檢語義重復(fù)率實(shí)在太多就做聚類后挑代表樣本。最后的人工抽檢是必須的。我習(xí)慣從清洗后的數(shù)據(jù)里隨機(jī)抽20~30條逐條看答案是否準(zhǔn)確、是否包含明顯錯誤信息。大模型微調(diào)最怕的就是數(shù)據(jù)本身有毒——模型一旦學(xué)進(jìn)去錯誤知識很難通過后續(xù)手段擦除。這塊用一句話總結(jié)就是寧可少十頁數(shù)據(jù)不要一條臟數(shù)據(jù)。4. LoRA微調(diào)核心實(shí)現(xiàn)4.1 LoRA參數(shù)到底怎么設(shè)在寫代碼之前先得對LoRA的幾個關(guān)鍵參數(shù)心里有數(shù)不然一行代碼都看不懂。LoRA的核心思想是把權(quán)重更新量拆成一個低秩矩陣的乘積即ΔW BA其中B的維度是d×rA的維度是r×k。訓(xùn)練時只更新B和A這個r就是秩。參數(shù)名推薦值作用備注r8低秩矩陣的秩決定可學(xué)習(xí)容量數(shù)據(jù)量大可加大到16新手別一上來就64lora_alpha32縮放因子控制LoRA影響強(qiáng)度和r搭配實(shí)際縮放系數(shù)為alpha/rlora_dropout0.1隨機(jī)失活比例防過擬合數(shù)據(jù)量小、過擬合時加到0.15target_modulesquery_key_value指定插入LoRA的網(wǎng)絡(luò)層ChatGLM3核心attention模塊biasnone是否訓(xùn)練偏置項(xiàng)一般設(shè)為none省參數(shù)省顯存這些參數(shù)不是拍腦袋定的我在不同任務(wù)上做過對比。r8配上alpha32在客服對答、知識抽取、文案改寫這些任務(wù)上都能拿到不錯的效果。如果你想追求更極致的推理速度可以把r降到4alpha調(diào)成16模型體積會小不少但表達(dá)能力的上限也會相應(yīng)降低。另外特別提醒一下target_modules不要亂加。有人為了充分微調(diào)把dense、dense_h_to_4h全都加上結(jié)果訓(xùn)練時間翻倍、顯存暴漲效果卻沒有明顯提升因?yàn)榇蟛糠挚蛇w移的知識就集中在attention層的value投影里。4.2 訓(xùn)練主流程代碼下面直接上核心代碼。首先是加載基座模型和tokenizerimport torch from transformers import AutoTokenizer, AutoModel, TrainingArguments, Trainer from peft import LoraConfig, get_peft_model model_name THUDM/chatglm3-6b tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) model AutoModel.from_pretrained( model_name, torch_dtypetorch.float16, trust_remote_codeTrue, device_mapauto ) # 如果顯存緊張用下面的方式加載4bit量化模型 # from transformers import BitsAndBytesConfig # bnb_config BitsAndBytesConfig(load_in_4bitTrue, bnb_4bit_compute_dtypetorch.float16) # model AutoModel.from_pretrained(model_name, quantization_configbnb_config, trust_remote_codeTrue)然后配置LoRA并注入模型lora_config LoraConfig( r8, lora_alpha32, lora_dropout0.1, target_modules[query_key_value], biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 輸出類似: trainable params: 8,388,608 || all params: 6,258,278,400 || trainable%: 0.134數(shù)據(jù)處理部分需要把對話樣本拼成ChatGLM3的prompt格式。這里的關(guān)鍵是讓tokenizer正確處理特殊token。我建議自己實(shí)現(xiàn)一個拼接函數(shù)這樣對batch處理更友好ChatGLM3的chat模板長這樣def build_prompt(conversations): system 你是一個智能助手 if conversations[0][role] system: system conversations[0][content] conversations conversations[1:] prompt [gMASK]sop system \n for msg in conversations: if msg[role] user: prompt |user|\n msg[content] |assistant|\n else: prompt msg[content] \n return prompt訓(xùn)練時我們對整條拼接后的文本做tokenize把user部分對應(yīng)的token在labels里設(shè)為-100忽略loss只讓模型學(xué)習(xí)assistant的回答部分。實(shí)現(xiàn)時可以用tokenizer(prompt, return_tensorspt)然后手動構(gòu)造labels數(shù)組凡是user位置的token label都置為-100。接著配置TrainingArgumentstraining_args TrainingArguments( output_dir./glm3-lora-checkpoints, per_device_train_batch_size1, gradient_accumulation_steps8, num_train_epochs3, learning_rate2e-4, warmup_ratio0.03, logging_steps10, save_steps500, evaluation_strategysteps, eval_steps500, fp16True, gradient_checkpointingTrue, report_tonone ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_dataseteval_dataset, tokenizertokenizer, ) trainer.train()這些參數(shù)里per_device_train_batch_size1加gradient_accumulation_steps8等效batch size是8但顯存壓力卻被控制得很好。gradient_checkpointingTrue會以少量計算換顯存實(shí)測能省下4GB左右的占用。learning_rate用2e-4是LoRA微調(diào)常見的起點(diǎn)比全參微調(diào)的1e-5到5e-5要大一兩個量級原因很簡單可訓(xùn)練參數(shù)太少學(xué)習(xí)率太小根本學(xué)不動。這個經(jīng)驗(yàn)我在多個模型上驗(yàn)證過用1e-5訓(xùn)練LoRAloss基本貼著原地不動。這里插一句非常重要的經(jīng)驗(yàn)不要盲目開大batch size。LoRA微調(diào)時batch size影響的是LoRA那一小部分參數(shù)的梯度估計8的等效batch已經(jīng)足夠。過大反而讓模型傾向于復(fù)刻訓(xùn)練集的常見回答損失多樣性模型會變得油嘴滑舌但缺乏真正的泛化能力。4.3 訓(xùn)練過程中的幾個坑這塊是純實(shí)操經(jīng)驗(yàn)了。第一個坑是loss不下降或下降極慢。我遇到過最典型的原因就是learning_rate設(shè)成了全參微調(diào)的1e-5LoRA參數(shù)更新本身就小1e-5基本等于沒訓(xùn)練。改成2e-4甚至5e-4后loss明顯開始往下走。排查時先看learning_rate再看target_modules是否正確注入最后看數(shù)據(jù)加載是否真的打到了模型上。第二個坑是顯存突然爆炸。有時候開始訓(xùn)練正常跑了幾百步之后OOM。這一般是激活值累積或者某個batch的文本特別長導(dǎo)致的。我后來習(xí)慣在數(shù)據(jù)集里加一個長度上限過濾比如超過2048 tokens的樣本直接截斷或丟棄訓(xùn)練穩(wěn)定性會好很多。具體做法是在preprocess函數(shù)里判斷l(xiāng)en(input_ids) max_length就跳過。第三個坑是梯度檢查點(diǎn)跟某些庫的兼容問題。如果你在加載模型時用了prepare_model_for_kbit_training又同時開啟gradient_checkpointing在模型反向傳播時可能報RuntimeError: None of the inputs have requires_gradTrue。這個問題的根源是4bit量化后部分參數(shù)被設(shè)為不需要梯度解決方法是確保你只對model.base_model調(diào)用gradient_checkpointing_enable()并檢查所有需要梯度的參數(shù)是否都在LoRA的adapter里。這個報錯信息看著嚇人其實(shí)原因非常具體按這個思路排查基本都能解決。5. 模型評估與推理部署5.1 模型保存與合并訓(xùn)練完成后trainer會直接把LoRA adapter保存在output_dir里。這里面有幾個文件adapter_config.json、adapter_model.bin還有tokenizer相關(guān)文件。adapter_model.bin通常只有幾十MB這就是你辛苦訓(xùn)練的成果。保存LoRA其實(shí)就夠了因?yàn)橥评頃r你可以在基座模型上臨時加載這個adapter。但如果你的最終目的是部署到生產(chǎn)環(huán)境不希望每次啟動都多一步加載adapter的邏輯那就需要把a(bǔ)dapter合并回原始模型from peft import PeftModel model PeftModel.from_pretrained(model, ./glm3-lora-checkpoints/checkpoint-1500) merged_model model.merge_and_unload() merged_model.save_pretrained(./glm3-lora-merged) tokenizer.save_pretrained(./glm3-lora-merged)合并后的模型跟普通ChatGLM3-6B的結(jié)構(gòu)完全一樣只是權(quán)重已經(jīng)被業(yè)務(wù)數(shù)據(jù)微調(diào)過可以用常規(guī)方式加載和部署。這樣在推理服務(wù)里就不需要依賴peft庫也能省掉adapter疊加的運(yùn)行時開銷。如果你的服務(wù)框架不支持peft合并導(dǎo)出幾乎是唯一的選擇。需要提醒的是合并操作會把LoRA權(quán)重加到原始權(quán)重上結(jié)果是一個新的6B完整模型磁盤占用又回到12GB左右。如果你同時維護(hù)多個業(yè)務(wù)場景的LoRA建議保留adapter文件按需加載合并而不是每個場景都存一份完整模型磁盤開銷會大很多。5.2 推理驗(yàn)證效果好不好自己先聊幾輪模型微調(diào)完光看loss曲線是不夠的loss低不代表效果好可能是過擬合了。我自己習(xí)慣先做一組固定的測試問題集蓋上訓(xùn)練分布專門測模型的遷移能力。比如我訓(xùn)練了一個客服助手測試時會問幾個訓(xùn)練數(shù)據(jù)里完全沒出現(xiàn)過的同義問題看它能否給出合理回答。推理測試的代碼很簡單model AutoModel.from_pretrained(./glm3-lora-merged, trust_remote_codeTrue, devicecuda) response, history model.chat(tokenizer, 你熟悉Kubernetes嗎請用一個例子說明Pod調(diào)度原理。, history[]) print(response)測試時重點(diǎn)看三個維度一是回答是否跟業(yè)務(wù)口徑一致二是多輪對話是否還能記得前面的內(nèi)容三是是否出現(xiàn)通用能力退化比如原本能做的數(shù)學(xué)題現(xiàn)在做不出來了。第三個維度很多人會忽略但恰恰是衡量LoRA微調(diào)是否健康的關(guān)鍵指標(biāo)。我在一個項(xiàng)目里就遇到過微調(diào)后模型對領(lǐng)域問題的回答非常漂亮但一讓它算11都開始胡說這就是典型的災(zāi)難性遺忘。解決方案是訓(xùn)練時在數(shù)據(jù)里摻10%左右的基礎(chǔ)通用數(shù)據(jù)效果立竿見影。后來我每次準(zhǔn)備數(shù)據(jù)都會刻意留出這個比例確保模型既懂業(yè)務(wù)又不丟常識。我還會把微調(diào)前后的回答放在一起做對比。用同一組問題先問原版ChatGLM3再問微調(diào)后的模型輸出差異一目了然。這個對比過程不只是看結(jié)果好壞還能幫你理解LoRA到底改了什么。比如我看到過原版模型回答你怎么看云原生時會給出標(biāo)準(zhǔn)百科式的定義而微調(diào)后模型的回答變成了結(jié)合業(yè)務(wù)場景的具體建議這就是LoRA在起作用。6. 常見問題與排查技巧實(shí)錄6.1 顯存不足怎么辦這個問題出現(xiàn)的頻率最高。如果你遇到CUDA out of memory按下面的順序排查確認(rèn)已經(jīng)開啟gradient_checkpointing這是性價比最高的手段。把per_device_train_batch_size降到1。檢查max_length設(shè)置很多數(shù)據(jù)里有個別超長樣本會在某個step突然撐爆顯存可以先把max_length設(shè)成512或768試跑。考慮4bit量化加載模型。我在一張24G卡上用上面的組合可以跑batch_size1 grad_accum8 max_length1024的訓(xùn)練顯存峰值不到22GB。如果這么調(diào)還是OOM那基本得換更大的卡或者減少數(shù)據(jù)長度了不要指望靠玄學(xué)優(yōu)化繞過物理限制。6.2 過擬合與欠擬合怎么判斷過擬合看eval loss。如果訓(xùn)練loss持續(xù)下降但eval loss先降后升那就是過擬合的典型信號。LoRA微調(diào)數(shù)據(jù)量一兩千條時過擬合非常容易發(fā)生。我的對策是加大lora_dropout到0.15降低學(xué)習(xí)率到1e-4訓(xùn)練輪數(shù)減少到2輪同時用early stopping回調(diào)來早停。如果過擬合嚴(yán)重到訓(xùn)練集loss都快到0了建議先別急著調(diào)參回到數(shù)據(jù)處理環(huán)節(jié)看是不是重復(fù)樣本太多、對話模式太單一。欠擬合則相反訓(xùn)練loss降不下去模型的回答還是跟原版差不多。這多半是學(xué)習(xí)率太小、訓(xùn)練輪數(shù)不夠或者LoRA的r設(shè)得太小??梢韵仍囍裭earning_rate提到5e-4觀察幾輪再決定。注意欠擬合和過擬合的調(diào)參方向是相反的別搞混。我見過有人把欠擬合誤判成過擬合加了dropout、降了學(xué)習(xí)率結(jié)果問題越來越嚴(yán)重。6.3 Loss變成NaN或訓(xùn)練崩潰Loss出現(xiàn)NaN最常見的原因是學(xué)習(xí)率過大導(dǎo)致梯度爆炸或者是fp16混合精度下的loss scale出了問題。處理方式先把lr降到1e-5確認(rèn)穩(wěn)定后再往上調(diào)。如果還不行把fp16關(guān)掉用純fp32訓(xùn)練顯存壓力會大但能排除精度問題。另外數(shù)據(jù)里如果混入了特別臟的文本比如超長亂碼也可能導(dǎo)致數(shù)值異常清洗數(shù)據(jù)時要把這類內(nèi)容過濾掉。還有一個容易被忽略的問題tokenizer的pad token沒設(shè)置好。ChatGLM3的tokenizer默認(rèn)沒有pad_token而Trainer在batch時需要對樣本pad到相同長度。解決辦法是在訓(xùn)練前顯式設(shè)置tokenizer.pad_token tokenizer.eos_token不設(shè)置的話可能報錯或訓(xùn)練行為異常這是我見很多人卡住的點(diǎn)。另外如果用了DataCollatorForSeq2Seq記得把paddingTrue開啟否則pad邏輯不會生效。這些小細(xì)節(jié)看著不起眼但往往就是它們決定了你能不能順利跑通一次訓(xùn)練。6.4 訓(xùn)練速度太慢怎么辦如果你覺得訓(xùn)練速度慢先別懷疑顯卡性能大概率是數(shù)據(jù)長度太長或者小batch拖累的。LoRA訓(xùn)練的計算量主要集中在attention的前向反向傳播上輸入token越長計算復(fù)雜度增長越快。我建議先統(tǒng)計一下訓(xùn)練數(shù)據(jù)的平均長度如果大部分樣本都能控制在512 token以內(nèi)就不要把max_length設(shè)成2048白白增加計算量。另外檢查一下數(shù)據(jù)加載是否有瓶頸比如每次都在內(nèi)存里重新讀寫大JSON換成datasets庫的內(nèi)存映射機(jī)制會快很多。還有一個訓(xùn)練加速的技巧在數(shù)據(jù)預(yù)處理時提前把所有樣本tokenize好并緩存到磁盤訓(xùn)練時直接加載預(yù)處理后的token可以省掉運(yùn)行時的tokenize開銷。數(shù)據(jù)量幾千條時這個優(yōu)化不明顯但如果數(shù)據(jù)量到了幾萬條能省下不少時間。我的習(xí)慣是第一次跑數(shù)據(jù)預(yù)處理時花些時間緩存之后每次調(diào)參訓(xùn)練都不用重復(fù)處理數(shù)據(jù)。這些坑回頭看看都不復(fù)雜但確實(shí)會一個接一個地消耗時間。我把它們整理成一張速查表方便你排查現(xiàn)象首要懷疑點(diǎn)處理建議顯存OOM激活值過大開gradient checkpointing、降batch、裁長文本Loss不降學(xué)習(xí)率太小調(diào)大到2e-4乃至5e-4Eval loss反彈過擬合加dropout、減訓(xùn)練輪數(shù)、查數(shù)據(jù)重復(fù)LossNaN學(xué)習(xí)率過大或fp16不穩(wěn)降lr、關(guān)fp16試跑、清理臟數(shù)據(jù)訓(xùn)練報pad相關(guān)錯誤未設(shè)置pad_token顯式設(shè)置tokenizer.pad_token自己在實(shí)際項(xiàng)目里把這一整套流程跑下來之后我最大的體會是LoRA微調(diào)這個事代碼層面還真不難難的是數(shù)據(jù)整理和對效果的系統(tǒng)驗(yàn)證。源碼和參數(shù)照著抄都能跑通可如果你的數(shù)據(jù)是臟的或者壓根沒有一套效果評估方法那微調(diào)出來的模型很可能只是看起來在訓(xùn)練實(shí)際離上線還有很大距離。我現(xiàn)在的固定流程是先花一天整理數(shù)據(jù)再花半天配環(huán)境訓(xùn)練一晚上第二天上午做定向測試對比微調(diào)前后在業(yè)務(wù)問題上的回答差異。這個節(jié)奏在我做過的幾個項(xiàng)目里都挺穩(wěn)定。如果你準(zhǔn)備在自己的場景里上手ChatGLM3-6B的LoRA微調(diào)我建議從小數(shù)據(jù)量開始跑通全流程再逐步加數(shù)據(jù)、調(diào)參數(shù)這樣可以省下很多不必要的試錯時間。本文還有配套的精品資源點(diǎn)擊獲取