實(shí)戰(zhàn):4-bit量化與低秩適配讓7B模型訓(xùn)練不再OOM)
1. 為什么要糾結(jié)單卡微調(diào)算力門檻與QLoRA的破局思路去年年底我接手一個(gè)內(nèi)部對(duì)話系統(tǒng)優(yōu)化任務(wù)第一版方案用了全參微調(diào)結(jié)果直接被顯存問題卡住。手頭只有一張24GB的卡加載7B模型的全量權(quán)重就逼近20GB更別提反向傳播時(shí)激活值、梯度和優(yōu)化器狀態(tài)的額外開銷。模型甚至還沒開始訓(xùn)練OOM已經(jīng)先來問候了。身邊不少同學(xué)遇到同樣的情況——最流行的開源模型動(dòng)輒7B、13B起步單卡跑推理勉強(qiáng)及格微調(diào)就是一個(gè)奢侈的愿望。后來切換到QLoRA之前那個(gè)幾乎判了死刑的項(xiàng)目才真正跑起來。QLoRA全稱是Quantized Low-Rank Adaptation核心思路是先對(duì)預(yù)訓(xùn)練模型做4-bit量化壓縮再在低秩層面插入可訓(xùn)練的適配器。一句話概括把模型凍在低精度位寬下只在旁邊搭一條輕量的可訓(xùn)練支路。這樣做不會(huì)對(duì)原始權(quán)重做任何梯度更新因此優(yōu)化器狀態(tài)、梯度這些顯存大戶全部被排除在主干之外顯存占用大幅下降。我之前也試過直接用LoRA它本身已經(jīng)很省顯存但FP16下基座模型權(quán)重仍然占著一大塊空間。QLoRA更進(jìn)一步把基座壓到4-bit后又用分頁優(yōu)化器管理偶發(fā)的顯存波動(dòng)硬生生把門檻往下拉了一檔。在同一張顯卡上體驗(yàn)非常明顯7B模型從推理勉強(qiáng)、微調(diào)妄想變成訓(xùn)練輕快、還有余力跑驗(yàn)證。這篇文章不聊空泛概念直接給一套可以照抄的單卡微調(diào)作業(yè)。核心內(nèi)容包括QLoRA省顯存的底層原理、環(huán)境依賴的坑、訓(xùn)練腳本每個(gè)參數(shù)的含義以及我在幾個(gè)主流模型上實(shí)測(cè)的顯存與效果數(shù)據(jù)。無論你是剛接觸LLM微調(diào)的新手還是被OOM困擾已久想找替代方案的老手這篇都值得花幾分鐘看完。2. 顯存賬本參數(shù)凍結(jié)、4-bit量化、低秩適配如何疊出省顯存三重奏很多人以為QLoRA只是LoRA加個(gè)低精度實(shí)際上這里面的變化并不像看起來那樣簡(jiǎn)單。它里面至少疊加了三層省顯存機(jī)制每層都有自己的貢獻(xiàn)。2.1 第一重低秩適配器把訓(xùn)練參數(shù)從億級(jí)降到百萬級(jí)LoRALow-Rank Adaptation的核心操作是在原模型權(quán)重旁邊掛兩個(gè)小矩陣。如果原矩陣是d×dLoRA不直接更新全部參數(shù)而是訓(xùn)練兩個(gè)小矩陣Ad×r和Br×dr通常只有8、16、64。前向傳播時(shí)輸出變成原權(quán)重輸出加上AB組合的增量。這個(gè)低秩假設(shè)來自于一個(gè)經(jīng)驗(yàn)觀察大語言模型在下游任務(wù)上的參數(shù)變化本身處在一個(gè)很低的固有維度中并不需要?jiǎng)尤繀?shù)。以7B模型為例全參微調(diào)要更新70億個(gè)參數(shù)LoRA通常只訓(xùn)練一兩千萬個(gè)參數(shù)參數(shù)量縮了三十到五十倍。訓(xùn)練過程中只有這兩個(gè)小矩陣有梯度和優(yōu)化器狀態(tài)顯存壓力從源頭下降一個(gè)量級(jí)。更妙的是基座權(quán)重完全凍結(jié)不參與計(jì)算圖更新在顯存里它只需要存放一份前向推理的數(shù)據(jù)不用額外預(yù)留梯度空間。2.2 第二重4-bit量化讓基座權(quán)重瘦身到八分之一光有LoRA還不夠因?yàn)閮鼋Y(jié)的基座權(quán)重本身也有體積。FP16下一個(gè)參數(shù)占2字節(jié)7B模型光權(quán)重就是14GB這還沒算KV Cache和中間激活。QLoRA的關(guān)鍵在于把基座模型量化到4-bit一個(gè)參數(shù)只占0.5字節(jié)同樣是7B模型權(quán)重體積直接降到3.5GB左右整卡顯存開銷立刻舒服了很多。這里要注意的是QLoRA用的不是普通4-bit量化而是專門為LLM設(shè)計(jì)的兩項(xiàng)技術(shù)其一4-bit NormalFloatNF4。它基于分位數(shù)量化把數(shù)值映射到正態(tài)分布的分位數(shù)上。相比傳統(tǒng)均勻量化NF4在權(quán)重分布接近正態(tài)的Transformer層中保留了更多有效精度。實(shí)際體感是在多數(shù)評(píng)測(cè)集上NF4的困惑度劣化明顯小于直接砍到4-bit整型。其二雙量化Double Quantization。量化過程本身需要保存一組量化常數(shù)scale和zero-point這組常數(shù)如果用FP32存也是一筆不可忽視的開銷。QLoRA把這組常數(shù)再做一次8-bit量化整體省下大約0.37GB/7B模型的額外顯存。這招非常摳門但積少成多在24GB卡上影響不小。這兩項(xiàng)經(jīng)過HF社區(qū)廣泛驗(yàn)證你不需要自己重新訓(xùn)練量化器直接用官方封裝就能拿到質(zhì)量不錯(cuò)的4-bit基座。2.3 第三重分頁優(yōu)化器解決梯度的偶發(fā)尖峰訓(xùn)練過程中最大的顯存刺客不是常態(tài)數(shù)據(jù)而是偶爾出現(xiàn)的顯存尖峰——某個(gè)batch里偶然出現(xiàn)特別長的序列或某個(gè)層的梯度異常膨脹瞬間把顯存推上去。傳統(tǒng)優(yōu)化器只能看著OOM干瞪眼。QLoRA結(jié)合了分頁優(yōu)化器的設(shè)計(jì)思路它借鑒了操作系統(tǒng)虛擬內(nèi)存的分頁機(jī)制。當(dāng)顯存不足時(shí)優(yōu)化器狀態(tài)會(huì)暫存到CPU內(nèi)存等顯存釋放后再自動(dòng)換回GPU。實(shí)際操作中這套機(jī)制極少被觸發(fā)但它成為一道安全網(wǎng)給本來就捉襟見肘的單卡場(chǎng)景提供了兜底保護(hù)。這三重機(jī)制疊加后顯存賬本從模型權(quán)重梯度優(yōu)化器狀態(tài)激活值變成了4-bit權(quán)重小型低秩矩陣梯度小型優(yōu)化器狀態(tài)激活值。以7B模型為例4-bit基座約3.5GBLoRA參數(shù)不到0.2GB中間激活取決于上下文長度通??刂圃?~4GB以內(nèi)??傦@存占用大約6~8GB這就是24GB卡可以真正跑起來的原因。3. 環(huán)境準(zhǔn)備與模型選型哪些坑不踩后面能少折騰一天3.1 硬件的真實(shí)底線不是看顯存大小寫這篇文章之前我看到不少教程說QLoRA只需要8GB顯存就能微調(diào)7B模型。這句話理論上成立但實(shí)際使用中體驗(yàn)不太一樣。我個(gè)人的建議是模型規(guī)模最低顯存理論推薦顯存順滑上下文長度限制1B類小模型4GB6~8GB可開8K7B類主流模型8GB12~16GB建議不超過4K13B模型12GB24GB建議不超過2K70B模型48GB64GB極限制約8GB能跑7B純推理但要加LoRA訓(xùn)練建議還是準(zhǔn)備12GB起步。理由很簡(jiǎn)單訓(xùn)完要留空間跑驗(yàn)證和推理采樣8GB卡在batch size1、max_seq_len2048的情況下都已經(jīng)非常逼近極限稍微出現(xiàn)序列長度波動(dòng)就直接OOM。3.2 依賴版本搭配這里坑最密集環(huán)境裝好后報(bào)錯(cuò)大半來自版本不匹配。我這里給出一套我反復(fù)驗(yàn)證過可以穩(wěn)定運(yùn)行的組合Python 3.10 或 3.11CUDA 11.8 或 12.1 均可注意驅(qū)動(dòng)不要太老PyTorch 2.x2.1以后對(duì)顯存管理有明顯優(yōu)化transformers 4.35peft 最新版0.7以上bitsandbytes 0.43acceleratetrl如果你打算用SFTTrainer做指令微調(diào)datasets用于加載訓(xùn)練數(shù)據(jù)可選flash-attn 2如果訓(xùn)練速度太慢值得裝這里提一個(gè)容易踩的坑bitsandbytes版本與CUDA版本的匹配非常敏感。如果導(dǎo)入庫時(shí)報(bào)CUDA SETUP: TODO或者libbitsandbytes_cuda117.so not found這類錯(cuò)誤第一反應(yīng)不是重裝CUDA而是檢查bitsandbytes是不是與你當(dāng)前的PyTorch/CUDA版本匹配。我建議通過from bitsandbytes.cuda_setup import get_cuda_version之類的接口查看編譯信息也可以直接卸載后pip install --upgrade bitsandbytes拉最新版編譯包。另外transformers版本也不能太舊因?yàn)?-bit量化依賴BitsAndBytesConfig這個(gè)類在較新的transformers中才被完整支持。老版本在某些模型上會(huì)靜默降級(jí)回8-bit甚至FP16你以為在用QLoRA實(shí)際顯存占用根本沒降。3.3 模型選型的一個(gè)建議從指令微調(diào)版本入手很多QLoRA教程讓你直接加載基座模型去微調(diào)。但如果你做的是對(duì)話任務(wù)我更推薦用已經(jīng)經(jīng)過Chat/Instruction版本作為底座。原因很樸素基座模型在對(duì)話格式上沒有基礎(chǔ)你要同時(shí)教它格式和任務(wù)目標(biāo)QLoRA本身的容量有限學(xué)習(xí)負(fù)擔(dān)更重。而指令微調(diào)過的模型已經(jīng)會(huì)用問答格式輸出你只需要注入特定領(lǐng)域知識(shí)收斂速度和效果通常都會(huì)更好。比如做中文領(lǐng)域任務(wù)可以根據(jù)你的業(yè)務(wù)場(chǎng)景從Hugging Face上選合適的指令微調(diào)版本。需要注意驗(yàn)證其許可證是否允許商用或自定義二次微調(diào)不同的模型條款差異比較大這塊建議正式開發(fā)前弄清楚。4. 核心腳本拆解從四比特基座到可訓(xùn)練適配器的完整組裝流程理論部分說完了真正動(dòng)手時(shí)最關(guān)鍵的環(huán)節(jié)就是把訓(xùn)練腳本的每一行看明白。我直接給出一套修改過多次、已經(jīng)跑通幾個(gè)模型的模板然后逐段講透參數(shù)意圖。4.1 基礎(chǔ)準(zhǔn)備加載4-bit基座模型import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig ) bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_use_double_quantTrue, bnb_4bit_compute_dtypetorch.bfloat16, ) model AutoModelForCausalLM.from_pretrained( model_name, quantization_configbnb_config, device_mapauto, trust_remote_codeTrue, ) tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue)幾個(gè)關(guān)鍵參數(shù)逐一說一下load_in_4bitTrue告訴transformers按4-bit方式裝載權(quán)重。這是開啟整條QLoRA鏈路的核心開關(guān)。bnb_4bit_quant_typenf4指定用NF4量化。另一個(gè)選項(xiàng)是fp4兼容性更好但精度稍低。不是特殊需求直接用nf4即可。bnb_4bit_use_double_quantTrue開啟雙量化。能把量化常數(shù)二次壓縮省顯存建議開著。bnb_4bit_compute_dtypetorch.bfloat16這一項(xiàng)常被忽略但它決定計(jì)算精度?;鶛?quán)重以4-bit存儲(chǔ)但做矩陣乘法時(shí)會(huì)被臨時(shí)反量化到bfloat16/float16來運(yùn)算以保證訓(xùn)練穩(wěn)定性。如果計(jì)算精度設(shè)成float32反量化的臨時(shí)張量會(huì)變大顯存優(yōu)勢(shì)減弱一半。這里還想多提一句用device_mapauto時(shí)模型會(huì)被自動(dòng)分布到GPU和CPU上以顯存容量為優(yōu)先。如果你希望在單卡上強(qiáng)制完整加載也可以手動(dòng)指定device_map{: 0}。單卡場(chǎng)景下這兩種寫法效果接近但后者的顯存分配路徑更明確適合想精細(xì)控制的時(shí)候使用。裝載完成后建議做一個(gè)簡(jiǎn)單打印檢查print(model.hf_device_map) print(model.memory_usage)這一步能確認(rèn)模型權(quán)重確實(shí)進(jìn)入了量化后的瘦身狀態(tài)。如果意外看到模型被分配到CPU上的層數(shù)過多訓(xùn)練速度會(huì)非常受影響。4.2 LoRA配置r、alpha和target_modules的選擇邏輯基座就位之后接下來掛LoRA適配器。以下是我常用的配置from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training model prepare_model_for_kbit_training(model) lora_config LoraConfig( r16, lora_alpha32, target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM, ) model get_peft_model(model, lora_config) model.print_trainable_parameters()這里幾個(gè)參數(shù)怎么理解r是LoRA矩陣的秩直接控制可訓(xùn)練參數(shù)量。r16在7B模型上大約引入一千萬到兩千萬可訓(xùn)練參數(shù)性價(jià)比比較高。r8更省顯存但表達(dá)容量變小r32表達(dá)更強(qiáng)但顯存和訓(xùn)練時(shí)間同步增加。我的經(jīng)驗(yàn)是先從r16開始如果訓(xùn)練損失降不下去再往32調(diào)始終把r16作為第一基準(zhǔn)。lora_alpha是縮放因子實(shí)際生效的LoRA增量為(alpha / r) * AB。alpha設(shè)置為r的兩倍是一種常見起始配置。重點(diǎn)提醒a(bǔ)lpha不是越大越好。它調(diào)大相當(dāng)于放大了適配器的初始學(xué)習(xí)率如果任務(wù)數(shù)據(jù)量不大很容易出現(xiàn)訓(xùn)練后期損失震蕩。target_modules決定哪些模塊掛LoRA。對(duì)LLaMA類架構(gòu)常見配置是投影矩陣q/k/v/o和MLP部分的gate/up/down。更激進(jìn)的做法是連embedding層也加入訓(xùn)練但實(shí)際收益不顯著顯存開銷卻明顯增加。這里給出一個(gè)通用原則優(yōu)先覆蓋attention和MLP的核心線性層覆蓋全比覆蓋精更重要某些層分支以后發(fā)現(xiàn)效果不行再精簡(jiǎn)也不遲。prepare_model_for_kbit_training這行必須講清楚它會(huì)做兩件事一是把已經(jīng)量化過的模型基座內(nèi)部所有參數(shù)設(shè)置為requires_gradFalse二是把輸入層的數(shù)據(jù)類型統(tǒng)一到計(jì)算精度比如bfloat16避免因量化反序列類型不一致導(dǎo)致前向傳播報(bào)錯(cuò)。漏掉這行會(huì)在訓(xùn)練啟動(dòng)后不定時(shí)爆出LayerNorm kernel error或者expected scalar type Half but found Float一類的底層錯(cuò)誤。最后model.print_trainable_parameters()會(huì)打印類似trainable params: 16,384,512 || all params: 6,742,327,296 || trainable%: 0.2430的信息??吹娇捎?xùn)練占比在0.1%~0.5%之間說明LoRA裝配成功。4.3 訓(xùn)練器參數(shù)batch、梯度累積與學(xué)習(xí)率調(diào)節(jié)模型裝好了下一步是配置訓(xùn)練參數(shù)。建議直接使用TRL中的SFTTrainer它專門為指令跟隨類微調(diào)做了封裝省去自己寫數(shù)據(jù)整理的麻煩from trl import SFTTrainer from transformers import TrainingArguments training_args TrainingArguments( output_dir./output, per_device_train_batch_size2, gradient_accumulation_steps8, gradient_checkpointingTrue, learning_rate2e-4, num_train_epochs3, logging_steps10, save_steps200, save_total_limit3, remove_unused_columnsFalse, fp16True, report_tonone, optimpaged_adamw_8bit, warmup_ratio0.03, lr_scheduler_typecosine, ) dataset load_dataset(json, data_filesyour_data.jsonl, splittrain) trainer SFTTrainer( modelmodel, argstraining_args, train_datasetdataset, tokenizertokenizer, max_seq_length2048, dataset_text_fieldtext, )這里面的核心動(dòng)作再說透一點(diǎn)。per_device_train_batch_size配合gradient_accumulation_steps。單卡場(chǎng)景batch不能設(shè)太大2是7B模型上比較安全的值。很多人看到batch2就擔(dān)心效果不好其實(shí)這里的等效batch_size等于2×816只是在梯度累積中把大batch拆成了若干個(gè)小的前向-反向步驟。注意梯度累積會(huì)增加訓(xùn)練時(shí)間但它能緩解單卡小batch帶來的訓(xùn)練不穩(wěn)定問題同時(shí)把有效優(yōu)化步數(shù)保持在合理范圍。gradient_checkpointingTrue繼續(xù)省顯存。它在前向傳播時(shí)只保存少量中間結(jié)果反向傳播時(shí)重新計(jì)算激活值。代價(jià)是訓(xùn)練速度下降約30%但顯存節(jié)省非??捎^。沒有它長序列微調(diào)大概率還是站不住。optimpaged_adamw_8bit正是前面提到的分頁優(yōu)化器。傳統(tǒng)AdamW優(yōu)化器狀態(tài)要占用兩倍參數(shù)量的顯存先用8-bit把狀態(tài)壓縮到原來的四分之一再用分頁機(jī)制兜底顯存尖峰在QLoRA流程中屬于標(biāo)配設(shè)置。max_seq_length2048不是越大越好。長度翻倍激活值顯存也大約翻倍。如果訓(xùn)練數(shù)據(jù)多數(shù)是短文本開4096純屬浪費(fèi)。先按數(shù)據(jù)分布取p95分位再往上加一檔余量是比較務(wù)實(shí)的選法。dataset_text_fieldtext是SFTTrainer特定的文本字段指定它會(huì)把數(shù)據(jù)自動(dòng)按塊切分并拼接成模型輸入的格式。如果你的數(shù)據(jù)已經(jīng)是小塊獨(dú)立問答對(duì)不想被自動(dòng)拼接打斷可以提前把數(shù)據(jù)拼成帶模板的文本交給這個(gè)字段處理。整理好后啟動(dòng)訓(xùn)練觀察loss曲線。正常情況下第一輪loss就應(yīng)該有明顯下降到第二輪后半段逐漸平穩(wěn)。如果loss一直不降先回頭檢查是不是learning_rate設(shè)太高或數(shù)據(jù)格式本身有問題不急著動(dòng)r和alpha。5. 實(shí)測(cè)數(shù)據(jù)常見模型在單卡上的顯存占用與微調(diào)效果只看參數(shù)配置還不夠直接看實(shí)測(cè)更有說服力。我將同樣的LoRA配置r16, alpha32, batch2, max_seq_len2048分別跑在幾個(gè)常見模型上24GB單卡環(huán)境下獲得如下數(shù)據(jù)基座模型量化顯存占用微調(diào)額外顯存總顯存峰值單步耗時(shí)7B級(jí)別1B級(jí)模型0.8GB2~3GB約3.5GB約0.8秒7B級(jí)模型LLaMA類架構(gòu)4.2GB4~6GB約10GB約1.5秒13B級(jí)模型7.8GB7~10GB約18GB約2.8秒70B級(jí)模型需雙卡或多卡35GB10~15GB/卡約48GB數(shù)秒級(jí)幾點(diǎn)實(shí)踐感受7B模型在24GB卡上最從容batch2、seq_len2048下還有余量把batch提到4甚至開8K上下文也能勉強(qiáng)塞進(jìn)去。如果你現(xiàn)在不確定從什么規(guī)模下手7B是安全牌。13B模型在24GB卡上能用但基本到了極限。max_seq_len超過4096容易觸發(fā)分頁機(jī)制把優(yōu)化器狀態(tài)暫時(shí)挪到CPU內(nèi)存性能明顯下降。日常實(shí)驗(yàn)建議控制在3072以下。微調(diào)效果方面幾個(gè)領(lǐng)域的區(qū)分度很直觀。代碼生成任務(wù)中7B經(jīng)過QLoRA后的效果能逼近同模型全參微調(diào)九成以上套入特定知識(shí)問答時(shí)效果與全參微調(diào)的差距會(huì)稍微拉大但遠(yuǎn)強(qiáng)于直接套提示詞模板。LoRA容量本身是有限的如果真的遇到復(fù)雜推理任務(wù)需要同步考慮用更大的基座模型配合數(shù)據(jù)篩選而不是一味堆訓(xùn)練時(shí)長。6. 推理階段處理微調(diào)完的模型如何保存、加載和實(shí)際使用訓(xùn)練結(jié)束后最常見的新手操作是把整個(gè)model直接save_pretrained下來得到一個(gè)完整的量化模型。這實(shí)際上是冗余的你只是訓(xùn)練了LoRA適配器沒必要把幾GB的基座權(quán)重再復(fù)制一份。更標(biāo)準(zhǔn)的操作是model.save_pretrained(./output/lora_adapter) tokenizer.save_pretrained(./output/lora_adapter)這樣保存下來通常只有幾百M(fèi)B只包含可訓(xùn)練的LoRA權(quán)重。下次推理時(shí)先加載4-bit基座再掛載LoRA適配器from peft import PeftModel base_model AutoModelForCausalLM.from_pretrained( model_name, quantization_configbnb_config, device_mapauto, ) model PeftModel.from_pretrained(base_model, ./output/lora_adapter) model model.merge_and_unload()這里解釋一下merge_and_unload。它會(huì)嘗試把LoRA增量合并回基座權(quán)重然后卸載掉Peft封裝。合并后模型的推理速度通常比基座LoRA前向增量的組合方式更快因?yàn)樗辉偈莾蓷l支路相加而是直接使用融合后的權(quán)重矩陣做單次乘法。有一個(gè)舊版本遺留問題值得注意某些情況下4-bit量化基座不支持直接merge因?yàn)楹喜⒑蟮慕Y(jié)果無法無損地再塞回4-bit存儲(chǔ)。如果merge時(shí)報(bào)類型錯(cuò)誤可以放棄merge用原始的PeftModel包裝層直接做生成效果一樣只是單次前向略慢一點(diǎn)。合并后是否需要重新量化我的建議是如果最終模型要部署到低資源環(huán)境先merge成FP16再量化成4-bit是比較穩(wěn)妥的路線。直接在4-bit基座上merge某些模型會(huì)出現(xiàn)導(dǎo)出的文件類型混亂。如果你只是本地實(shí)驗(yàn)驗(yàn)證效果不merge、直接加載LoRA層運(yùn)行生成也沒問題。另一個(gè)小細(xì)節(jié)推理時(shí)的do_sampleTrue、temperature0.7、top_p0.9這類生成參數(shù)未必適用于你自己的任務(wù)。做知識(shí)問答時(shí)temperature設(shè)低一點(diǎn)0.2~0.3更可控做創(chuàng)意寫作再拉高。不要照抄網(wǎng)上生成參數(shù)模板。7. 微調(diào)之外的擴(kuò)展合并LoRA、多輪對(duì)話格式與硬件升級(jí)判斷回到這個(gè)主題的邊界。QLoRA解決的是有卡但不夠用的困境但它不是一個(gè)萬能答案。如果你后續(xù)有更強(qiáng)的算力全參微調(diào)肯定更好——因?yàn)榭捎?xùn)練的參數(shù)量更多、對(duì)特定任務(wù)的擬合上限更高、對(duì)數(shù)據(jù)分布的適配也更深。QLoRA的價(jià)值是讓大多數(shù)只有單卡的個(gè)人開發(fā)者和中小團(tuán)隊(duì)也能在一個(gè)晚上迭代出能用的垂直模型。這個(gè)能用和上限更高之間的取舍取決于你的業(yè)務(wù)目標(biāo)——是快速做出一個(gè)Demo還是要追求該基座模型在某條數(shù)據(jù)分布上的極致表現(xiàn)。實(shí)際開發(fā)中另一個(gè)常見需求是把LoRA合并進(jìn)基座后做私有化部署。前面提到合并且重新量化后部署比部署原Peft形式的模型要省心很多——沒有額外依賴也不用處理加載時(shí)各種封裝層的兼容問題。我在多輪對(duì)話項(xiàng)目的二次開發(fā)中發(fā)現(xiàn)把上個(gè)session的對(duì)話歷史拼進(jìn)text字段本身就是一個(gè)微調(diào)可優(yōu)化點(diǎn)。與其設(shè)定一堆多輪模板參數(shù)不如直接在數(shù)據(jù)里體現(xiàn)多輪樣式讓模型在訓(xùn)練中自然學(xué)習(xí)對(duì)話結(jié)構(gòu)。這個(gè)思路對(duì)任意基座模型都適用碰到什么參數(shù)沒效果時(shí)先回頭審視數(shù)據(jù)的組織方式。8. 踩過的坑和最后的一點(diǎn)心得文章最后把實(shí)際使用中遇到頻率較高的幾個(gè)問題列出來免得后面的人再繞路。坑一OOM發(fā)生在評(píng)估階段而不是訓(xùn)練階段。訓(xùn)練loss正常但eval時(shí)突然OOM多半是評(píng)估batch設(shè)置太大評(píng)估時(shí)沒有開啟gradient_checkpointing。解決方法是把eval_batch_size降成1或者把評(píng)估的max_length限制到訓(xùn)練長度以內(nèi)。評(píng)估時(shí)的KV Cache開銷往往比訓(xùn)練想象中還高。坑二量化基座 多GPU時(shí)device_map分配不均。如果機(jī)器上有兩塊卡device_mapauto有時(shí)會(huì)把模型按層數(shù)大致對(duì)半分配而QLoRA的LoRA參數(shù)全部跟著基座層走導(dǎo)致一塊卡只負(fù)責(zé)前向計(jì)算的一部分層另一塊卡反而承擔(dān)了主要權(quán)重顯存利用率不均衡。解決方案是啟用transformers的uniform_device_map策略或者干脆用單卡訓(xùn)練雙卡推理。坑三驗(yàn)證集上效果不錯(cuò)但真實(shí)數(shù)據(jù)上效果很差。這個(gè)鍋通常不在QLoRA而在數(shù)據(jù)配比。如果驗(yàn)證集和訓(xùn)練集同源同分布指標(biāo)有欺騙性真實(shí)數(shù)據(jù)的分布偏移會(huì)把那些背答案而非學(xué)能力的模型打回原形。建議訓(xùn)練前留出一部分帶時(shí)間戳的真實(shí)請(qǐng)求做盲測(cè)至少能看出來模型是在泛化還是在過擬合。坑四4-bit基座在CPU上做推理非常慢。如果你開發(fā)機(jī)上沒顯卡想在CPU上快速驗(yàn)證微調(diào)效果加載4-bit模型會(huì)跑得很痛苦??梢韵绒D(zhuǎn)成8-bit或者直接用FP16推理反正只是驗(yàn)證效果顯存不再是瓶頸。用QLoRA訓(xùn)練自己的第一個(gè)單卡微調(diào)模型全過程走通后你會(huì)發(fā)現(xiàn)它不像論文里寫的那樣神秘本質(zhì)就是把量化壓縮存儲(chǔ)和低秩增量訓(xùn)練組合起來讓單個(gè)GPU重新成為個(gè)人級(jí)LLM微調(diào)的可行平臺(tái)。即使以后算力更充裕了這套流程和排查思路也依然有用——畢竟訓(xùn)練LLM這件事永遠(yuǎn)是資源越省實(shí)驗(yàn)越多迭代越快。