微調(diào)實(shí)戰(zhàn):從LoRA權(quán)重混亂到共享低秩頭的高效方案)
1. 我為什么最后選了 MLoRA從一份亂到崩潰的任務(wù)權(quán)重開(kāi)始先交代一下背景。上個(gè)月做一個(gè)多領(lǐng)域大模型微調(diào)項(xiàng)目模型底座固定下游要同時(shí)支持客服意圖識(shí)別、合同關(guān)鍵信息抽取、摘要生成和三版風(fēng)格不同的企業(yè)話術(shù)生成。當(dāng)時(shí)圖省事直接給每個(gè)任務(wù)單獨(dú)訓(xùn)了一套 LoRA然后通過(guò)簡(jiǎn)單的 route 層在推理時(shí)切換。任務(wù)少的時(shí)候沒(méi)問(wèn)題任務(wù)一多就露餡了不同 LoRA 之間其實(shí)共享了大量公共知識(shí)但每套權(quán)重都把完整的底座語(yǔ)義空間復(fù)制了一遍6 個(gè)任務(wù)跑完光 checkpoint 就占了 40 多 GB朋友出差期間我改了一版數(shù)據(jù)把其中兩個(gè) LoRA 搞亂了回滾來(lái)來(lái)回回折騰了一整天。之后我把目光轉(zhuǎn)向多頭低秩適配也就是 MLoRA。它從設(shè)計(jì)上就不是“一個(gè)任務(wù)一條完整 LoRA”的思路而是在一個(gè)共享底座上掛多個(gè)低秩子空間每個(gè)子空間負(fù)責(zé)一部分任務(wù)能力訓(xùn)練時(shí)梯度按路由走推理時(shí)只激活需要的頭。這樣既保留了 LoRA 省顯存的優(yōu)點(diǎn)又不需要為了加一個(gè)任務(wù)去把整份 LoRA 權(quán)重重新拷貝一遍。如果你跟我一樣已經(jīng)用 LoRA 微調(diào)跑順了好幾個(gè)業(yè)務(wù)場(chǎng)景但對(duì)“任務(wù)之間互相打架”“權(quán)重版本管理麻煩”這兩件事有切膚之痛那么 MLoRA 就很值得了解一下。這篇文章不會(huì)去復(fù)讀論文里的數(shù)學(xué)推導(dǎo)只講我實(shí)際落地過(guò)程中覺(jué)得最關(guān)鍵的設(shè)計(jì)邏輯、踩過(guò)的坑和最終結(jié)果。文里涉及的實(shí)現(xiàn)細(xì)節(jié)一部分來(lái)自公開(kāi)論文加我自己的復(fù)現(xiàn)調(diào)整論文里沒(méi)寫(xiě)清楚的地方我會(huì)按常規(guī)做法說(shuō)明并注明哪些是行業(yè)通用方案、哪些是我個(gè)人的取舍。2. 為什么單 LoRA 在多任務(wù)場(chǎng)景下越來(lái)越別扭2.1 顯存省了維護(hù)成本沒(méi)省單 LoRA 微調(diào)最大的功勞是讓“消費(fèi)級(jí)顯卡也能跑大模型微調(diào)”這件事變成現(xiàn)實(shí)。7B 模型全參數(shù)微調(diào)一般要 70GB 以上的顯存LoRA 把這壓到 20GB 左右。代價(jià)是每一個(gè)新任務(wù)都要重新產(chǎn)出一份任務(wù)專屬權(quán)重。如果任務(wù)之間毫無(wú)交集這當(dāng)然無(wú)所謂但實(shí)際業(yè)務(wù)里很多任務(wù)來(lái)自同一個(gè)底座能力比如客服話術(shù)和合同摘要都要先做語(yǔ)義理解指令數(shù)據(jù)有重疊標(biāo)簽分布也有重疊我卻為每個(gè)任務(wù)都保存了一份“完整”的低秩適配結(jié)果等于把重復(fù)的知識(shí)冗余存了好多遍。更麻煩的是訓(xùn)練流程。4 個(gè)任務(wù)分 4 輪訓(xùn)練每輪都要重新加載底座和數(shù)據(jù)處理管線算力浪費(fèi)倒在其次真正難受的是評(píng)估環(huán)節(jié)。前幾天訓(xùn)好的客服 LoRA可能因?yàn)楹笠粋€(gè)任務(wù)的訓(xùn)練過(guò)程動(dòng)了公共數(shù)據(jù)緩存的預(yù)處理邏輯測(cè)試結(jié)果出現(xiàn)波動(dòng)你很難判斷到底是數(shù)據(jù)變了、隨機(jī)種子變了還是真的發(fā)生了災(zāi)難性遺忘。2.2 任務(wù)一多路由層就變成了定時(shí)炸彈常見(jiàn)的多 LoRA 部署方案是訓(xùn)練多個(gè)適配器加一個(gè)路由。但這個(gè)路由只能做粗粒度分流解決不了“同一個(gè) prompt 既涉及意圖識(shí)別又涉及情緒安撫”這種重疊場(chǎng)景。我在實(shí)測(cè)中遇到過(guò)同樣的輸入意圖 LoRA 認(rèn)為是退貨咨詢?cè)捫g(shù) LoRA 認(rèn)為是安撫情緒最后路由按照優(yōu)先級(jí)硬選一個(gè)效果完全靠運(yùn)氣。而且多個(gè) LoRA 之間沒(méi)有交互訓(xùn)練時(shí)各訓(xùn)各的無(wú)法共享不同任務(wù)都能用到的泛化特征。這種“先分裂、再拼接”的思路本質(zhì)上還是在用系統(tǒng)工程的手段解決模型能力問(wèn)題。任務(wù)邊界清晰的場(chǎng)景勉強(qiáng)能用邊界一模糊就只能聽(tīng)天由命。MLoRA 之所以吸引我就是因?yàn)樗选岸鄠€(gè)能力”放在同一套模型參數(shù)框架內(nèi)去組織而不是在模型外堆放一堆互不通信的小副本。這一點(diǎn)在下面的第 4 節(jié)會(huì)細(xì)講。3. MLoRA 和它的一眾近親到底差在哪3.1 別再把 MLoRA 理解成“一堆 LoRA 的集合”討論 MLoRA 之前先避免一個(gè)常見(jiàn)誤區(qū)它并不是“把很多 LoRA 塞進(jìn)一個(gè)文件夾”。目前社區(qū)里跟 LoRA 相關(guān)的多任務(wù)方案大概分三類最樸素的多 LoRA每個(gè)任務(wù)一套獨(dú)立低秩矩陣運(yùn)行時(shí)用 Task ID 切換或動(dòng)態(tài)加載。LoRA 合并/組合訓(xùn)練完多個(gè) LoRA 后直接對(duì)權(quán)重做算術(shù)操作像任務(wù)向量一樣相加或相減。專家混合MoE把 FFN 層替換成多個(gè)專家網(wǎng)絡(luò)用門(mén)控網(wǎng)絡(luò)按 token 路由。MLoRA 嚴(yán)格來(lái)說(shuō)更接近第一條和第三條的中間態(tài)結(jié)構(gòu)上仍然是“共享底座 多個(gè)低秩頭”沒(méi)有換掉主干網(wǎng)絡(luò)結(jié)構(gòu)也不需要在推理時(shí)重新加載整套權(quán)重。訓(xùn)練時(shí)門(mén)控會(huì)對(duì)輸入和任務(wù)條件做統(tǒng)一調(diào)度讓低秩子空間各司其職推理時(shí)你只需要給一個(gè)任務(wù)條件它就能在幾毫秒內(nèi)選中對(duì)應(yīng)的低秩路徑。有人會(huì)問(wèn)這不就是 MoE 嗎區(qū)別在于MoE 一般替換的是 FFN 的中間層而 MLoRA 是給原有線性層加并行旁路不改變主干計(jì)算圖MoE 的門(mén)控通常走 token 粒度MLoRA 可以走任務(wù)粒度加 token 粒度的混合靈活性更高實(shí)現(xiàn)也更簡(jiǎn)單。3.2 與普通 LoRA 訓(xùn)練的本質(zhì)區(qū)別普通 LoRA 訓(xùn)練目標(biāo)只有一個(gè)讓新增的低秩旁路去擬合當(dāng)前任務(wù)的分布。多任務(wù)微調(diào)時(shí)如果分多次訓(xùn)練后訓(xùn)練的任務(wù)會(huì)覆蓋前一個(gè)任務(wù)學(xué)到的分布如果把多個(gè) LoRA 合并再一起訓(xùn)練低秩矩陣之間又會(huì)互相稀釋。MLoRA 的做法是把這些旁路全都放在同一個(gè)優(yōu)化過(guò)程里靠路由區(qū)分樣本來(lái)自哪個(gè)任務(wù)共享底座只承擔(dān)“公共表示”部分的更新每個(gè)頭的矩陣只在自己對(duì)應(yīng)的任務(wù)數(shù)據(jù)上累積信息從而在優(yōu)化層面隔離了任務(wù)之間的干擾。不過(guò)這里說(shuō)的“隔離”不是絕對(duì)隔離。如果門(mén)控允許某個(gè) token 同時(shí)激活多個(gè)頭那么多個(gè)頭的梯度就會(huì)同時(shí)作用到共享底座上。這本身是特性不是 bug但需要配合負(fù)載均衡否則高頻任務(wù)會(huì)持續(xù)污染低頻任務(wù)的頭。這個(gè)坑我放到第 4 節(jié)和第 6 節(jié)細(xì)說(shuō)。先說(shuō)一個(gè)重要的結(jié)論MLoRA 不是替代 LoRA 的方案而是“當(dāng)你有多個(gè) LoRA 需求時(shí)用一套框架替代 N 套獨(dú)立流程”的方案。任務(wù)只有一個(gè)的話老老實(shí)實(shí)用 LoRA 就好沒(méi)必要引入額外的路由邏輯。4. MLoRA 的機(jī)制拆解共享底座、低秩頭、路由三角色4.1 前向公式里藏著最核心的設(shè)計(jì)思想你可能在其他地方看到過(guò)類似寫(xiě)法我這里用最容易理解的方式再捋一遍。假設(shè)原始預(yù)訓(xùn)練權(quán)重為 W0普通 LoRA 的前向計(jì)算可以寫(xiě)成y W0x (B A) x其中 A 是 d × r 的輸入投影矩陣B 是 r × d 的輸出投影矩陣r 就是低秩維度。MLoRA 把后半部分?jǐn)U展為多條并行路徑再加上一個(gè)任務(wù)條件 t 和路由權(quán)重 gy W0x Σ_i g_i(t) · (B_i A_i) x其中 i 是任務(wù)編號(hào)g_i(t) 是路由函數(shù)對(duì)任務(wù) t 輸出的標(biāo)量權(quán)重。前向時(shí)并不是所有 i 都參與計(jì)算為了省顯存通常只取 top-1 或 top-2也就是每個(gè) token 最多激活兩個(gè)低秩頭。這樣一來(lái)參數(shù)總量雖然變大了但單次推理的計(jì)算量并不會(huì)隨任務(wù)數(shù)量線性增加。數(shù)學(xué)上很好理解如果 g_i(t) 在任務(wù) t 上只把 t 對(duì)應(yīng)的頭置為 1其他頭置為 0那公式就退化回多個(gè)獨(dú)立 LoRA。所以 MLoRA 的突破點(diǎn)全在 g_i(t) 怎么設(shè)計(jì)。4.2 門(mén)控與負(fù)載均衡為什么不能只讓頭部任務(wù)吃獨(dú)食如果 g_i(t) 完全由任務(wù) ID 決定那么 6 個(gè)任務(wù)就是 6 個(gè)互不相干的分區(qū)和原來(lái)部署 6 個(gè)獨(dú)立 LoRA 的區(qū)別就不大了。MLoRA 的優(yōu)勢(shì)在于允許門(mén)控關(guān)注的不是“你是哪個(gè)任務(wù)”而是“這個(gè) token 更適合走哪條低秩子空間”。所以實(shí)際實(shí)現(xiàn)里門(mén)控輸入通常拼接了任務(wù) ID 嵌入和當(dāng)前 token 的 hidden state這樣模型能根據(jù)語(yǔ)義做細(xì)粒度選擇。很多復(fù)現(xiàn)項(xiàng)目忽略的是負(fù)載均衡損失。沒(méi)有負(fù)載均衡時(shí)門(mén)控會(huì)偏向高頻任務(wù)的頭低頻任務(wù)哪怕有專屬頭也可能一個(gè) token 都分不到訓(xùn)完等于白訓(xùn)。我通常會(huì)在輔助損失里加一個(gè) λ 控制項(xiàng)懲罰各頭被選中的概率與均等分布之間的 KL 距離。論文里常見(jiàn)的是 Switch Transformer 風(fēng)格的負(fù)載均衡損失直接用任務(wù)頻率作為先驗(yàn)讓各頭的使用率盡量平滑。下面是我常用的一套負(fù)載均衡損失計(jì)算邏輯偽代碼def load_balance_loss(gating_probs, num_heads, target_probsNone): # gating_probs: [batch * seq_len, num_heads] mean_probs gating_probs.mean(dim0) # 每個(gè)頭被的平均激活概率 if target_probs is None: target_probs torch.ones_like(mean_probs) / num_heads # 計(jì)算 mean_probs 與 target_probs 的 KL 散度 kl F.kl_div(mean_probs.log(), target_probs, reductionbatchmean) return klλ 取 0.01 到 0.1 之間比較合適。我試過(guò)取 0.5結(jié)果是模型為了滿足均衡直接犧牲了任務(wù)精度得不償失。4.3 可訓(xùn)練矩陣的初始化細(xì)節(jié)這里有個(gè)容易忽略的細(xì)節(jié)每個(gè)頭內(nèi)部的 A_i 用隨機(jī)高斯初始化B_i 初始化為 0。原因和普通 LoRA 一樣是為了保證訓(xùn)練開(kāi)始時(shí)低秩旁路輸出為 0不會(huì)破壞底座權(quán)重。如果所有 B_i 都是 0那么共享底座在第一步仍然只輸出 W0x梯度的初始階段也相對(duì)穩(wěn)定。但多個(gè)頭同時(shí)參與時(shí)A_i 的初始化方差如果設(shè)得過(guò)大會(huì)導(dǎo)致門(mén)控梯度噪聲變大。我試過(guò)把 A 的初始化標(biāo)準(zhǔn)差從默認(rèn)的 1/sqrt(d) 調(diào)小到 1/sqrt(2d)在任務(wù)數(shù)量較多時(shí)確實(shí)收斂更穩(wěn)。你也可以保持默認(rèn)但學(xué)習(xí)率要相應(yīng)調(diào)低否則訓(xùn)練初期 loss 會(huì)像過(guò)山車一樣抖動(dòng)。5. 一份可以直接拿去用的 MLoRA 訓(xùn)練配置5.1 復(fù)現(xiàn)成本與最小硬件需求先說(shuō)結(jié)論用 7B 級(jí)別底座訓(xùn)練 4 到 6 個(gè)任務(wù)的 MLoRA單卡 24GB 顯存足夠。我本機(jī)是一張 RTX 4090上下文長(zhǎng)度開(kāi)到 2048batch size 單卡 2梯度累積 16。如果底座是 13B顯存就要上 48GB或者用兩張卡做張量并行否則會(huì)吃緊。相比單任務(wù) LoRAMLoRA 多出來(lái)的顯存主要是每個(gè)低秩頭的 A/B 矩陣和路由門(mén)控的一小部分參數(shù)增長(zhǎng)幅度和頭數(shù)量成正比但遠(yuǎn)小于全參微調(diào)。5.2 超參表我自己跑得比較穩(wěn)的一組我用的可訓(xùn)練參數(shù)量統(tǒng)計(jì)7B 模型32 層 transformerLoRA 作用在 q/k/v/o 上秩 r 用 16加上三套下游頭 MLP總共可訓(xùn)練參數(shù)約 50M 到 60M 之間。如果不知道 LoRA 參數(shù)怎么算簡(jiǎn)單記憶法是每個(gè)線性層兩個(gè)小矩陣參數(shù)量約為 2×輸入維度×r再乘以所有掛載層數(shù)量即可。加上任務(wù)專屬分類頭和生成頭之后總參數(shù)還會(huì)再漲一截但相比動(dòng)輒幾十億的全參微調(diào)依然很小。超參數(shù)數(shù)值如下參數(shù)取值說(shuō)明低秩維度 r16任務(wù)專業(yè)度高可升到 32通用任務(wù) 8 也能用縮放系數(shù) α16和 r 相等即縮放 α/r 1學(xué)習(xí)率2e-4AdamW權(quán)重衰減 0.01門(mén)控學(xué)習(xí)率1e-3比主干低秩矩陣的學(xué)習(xí)率稍高批大小32多任務(wù)輪轉(zhuǎn)構(gòu)造后累積等效最大步數(shù)3000 步左右配合早停按驗(yàn)證集波動(dòng)判斷負(fù)載均衡權(quán)重 λ0.05任務(wù)數(shù)越多λ 可以稍微調(diào)大看起來(lái)有點(diǎn)違背“門(mén)控學(xué)習(xí)率應(yīng)當(dāng)和主干一致”的直覺(jué)但我實(shí)測(cè)下來(lái)門(mén)控如果學(xué)得太慢前期頭選擇隨機(jī)震蕩主干只能學(xué)到任務(wù)無(wú)關(guān)的通用表示后期想掰回來(lái)很費(fèi)勁。門(mén)控學(xué)習(xí)率給高一點(diǎn)它能先穩(wěn)定下來(lái)主干后續(xù)再慢慢適應(yīng)路由分布整體收斂效率反而更高。5.3 數(shù)據(jù)組織不要直接拼一個(gè)大混合集多任務(wù)數(shù)據(jù)處理我最開(kāi)始犯過(guò)錯(cuò)誤把所有任務(wù)數(shù)據(jù)直接混成一個(gè) dataset只在每條樣本里加一個(gè) task_id 字段。結(jié)果由于任務(wù)數(shù)據(jù)量不均高頻任務(wù)徹底主導(dǎo)了共享底座和所有頭的更新。后來(lái)改成每個(gè) epoch 內(nèi)先按任務(wù)分組每組內(nèi)部 shuffle然后把各組樣本以“輪轉(zhuǎn)”方式拼成 batch。這樣每步更新都包含不同任務(wù)的數(shù)據(jù)低秩頭之間的更新頻率更加均衡。一個(gè) batch 的實(shí)際組織方式可以寫(xiě)成這樣偽代碼from itertools import cycle task_datasets [load_task(name) for name in TASK_NAMES] iters [cycle(ds) for ds in task_datasets] batch_parts [next(it) for it in iters] final_batch torch.cat([p[input_ids] for p in batch_parts], dim0)這樣每步都會(huì)均勻出現(xiàn)每個(gè)任務(wù)的樣本不用額外加繁重的采樣權(quán)重調(diào)參。等到訓(xùn)練后期再逐步切回隨機(jī)混合讓模型去做一些跨任務(wù)的邊界模糊處理效果會(huì)更好。對(duì)我來(lái)說(shuō)訓(xùn)練過(guò)程里唯一需要盯死的指標(biāo)不是總 loss而是每個(gè)任務(wù)獨(dú)有的驗(yàn)證指標(biāo)。多任務(wù)框架的 loss 曲線會(huì)掩蓋單任務(wù)的退化如果你只看平均 loss低頻任務(wù)早就崩了你都不知道。6. 實(shí)測(cè)效果三種典型任務(wù)組合下的表現(xiàn)6.1 組合一領(lǐng)域相似但標(biāo)簽體系不同我先把“客服意圖識(shí)別”和“工單分類”放進(jìn)同一個(gè) MLoRA 里。這兩個(gè)任務(wù)共享大量口語(yǔ)表達(dá)但標(biāo)簽體系完全不同。單獨(dú)訓(xùn)練兩套 LoRA 時(shí)意圖識(shí)別的 F1 在 0.89工單分類 F1 在 0.86。MLoRA 訓(xùn)練后兩個(gè)任務(wù)分別達(dá)到 0.90 和 0.88原因很直觀共享底座里去掉了任務(wù)專屬偏置語(yǔ)義表示更通用子空間按任務(wù)切分反而各得其所。這個(gè)結(jié)果讓我意識(shí)到一個(gè)重要規(guī)律MLoRA 最擅長(zhǎng)處理的不是“一堆八竿子打不著的任務(wù)”而是“共用底層語(yǔ)義、只是上層決策不同”的任務(wù)群。它把公共部分留給底座把差異化部分留給低秩頭分工明確。6.2 組合二任務(wù)之間有重疊但輸出形式不同合同關(guān)鍵信息抽取和摘要生成的輸入輸出差異很大一個(gè)是抽取式 span一個(gè)是生成式摘要。讓這兩類任務(wù)強(qiáng)行共享低秩頭不是不行但會(huì)出現(xiàn)“抽取模塊和生成模塊搶梯度”的現(xiàn)象一個(gè)任務(wù)收斂快了另一個(gè)任務(wù)驗(yàn)證損失就開(kāi)始反彈。我最后強(qiáng)制把二者的頭設(shè)置為分離共享底座部分正常更新問(wèn)題就消失。這也說(shuō)明 MLoRA 不是徹底消滅任務(wù)沖突而是把沖突面從“整個(gè)模型層”縮小到“子空間邊界”。判斷哪些頭該共享、哪些頭該分離目前沒(méi)有量化公式我自己的經(jīng)驗(yàn)是輸出形式差異越大頭越要分離輸入分布差異越大底座越要分開(kāi)或者換更大的底座。6.3 組合三數(shù)據(jù)量差異特別懸殊低頻任務(wù)樣本只有 2000 條高頻任務(wù)樣本有 5 萬(wàn)條。單 LoRA 微調(diào)低頻任務(wù)極易過(guò)擬合直接混合訓(xùn)練低頻任務(wù)會(huì)被淹沒(méi)。MLoRA 里我給低頻任務(wù)單獨(dú)開(kāi)了一個(gè)頭并把它在前 500 步的采樣比例人為抬高到 30%之后逐步降低到自然比例。這種訓(xùn)練曲線用普通 LoRA 做不了因?yàn)槠胀?LoRA 只有一個(gè)優(yōu)化目標(biāo)難以對(duì)不同任務(wù)施加不同步調(diào)。MLoRA 的熱啟動(dòng)策略則簡(jiǎn)單粗暴但有效在模型還沒(méi)看清低頻任務(wù)分布的時(shí)候高頻任務(wù)已經(jīng)把共享底座帶向了它的主場(chǎng)。前 500 步有策略地“偏科”后續(xù)再讓共享底座回歸正常分布相當(dāng)于給低頻任務(wù)搶了一個(gè)好的初始化狀態(tài)。7. 上線部署與推理加速多出來(lái)的頭怎么處理7.1 合并權(quán)重時(shí)的三種選擇MLoRA 訓(xùn)練完最終產(chǎn)物是共享底座權(quán)重 W0 和一組低秩頭 (B_i, A_i)。上線時(shí)無(wú)非三種做法第一種把每個(gè)頭合并回底座變成獨(dú)立 LoRA保持原來(lái)多 LoRA 部署架構(gòu)不變。這種方式在只支持單 LoRA 的引擎上最省事但失去了“按需加載”的優(yōu)勢(shì)。第二種保留“底座 多頭”格式用支持 adapter 調(diào)度的框架在推理前動(dòng)態(tài)加載對(duì)應(yīng)頭。顯存更省但需要選對(duì)推理框架。第三種直接把所有權(quán)重復(fù)合進(jìn)完整權(quán)重輸出一份大模型。這樣推理最快但新增任務(wù)時(shí)必須重跑一次合并靈活性最差。我最終選了第二種。如果只是想快速驗(yàn)證效果第一種最穩(wěn)代碼改動(dòng)最小。第三種適合對(duì)延遲極其敏感且任務(wù)基本不再增長(zhǎng)的場(chǎng)景。7.2 推理時(shí)門(mén)控是否還用得上如果你在上線時(shí)只按任務(wù) ID 切換頭那訓(xùn)練時(shí)門(mén)控在推理階段就不參與計(jì)算如果希望在線服務(wù)能自動(dòng)對(duì)開(kāi)放域 query 做路由那就保留門(mén)控。兩種我都跑過(guò)保留門(mén)控會(huì)讓精度略微提升但排查問(wèn)題時(shí)多了一個(gè)無(wú)法解釋的變量。所以如果業(yè)務(wù)任務(wù)邊界清晰建議生產(chǎn)環(huán)境先關(guān)閉門(mén)控把歸屬問(wèn)題留給上層任務(wù)分發(fā)器解決。等線上數(shù)據(jù)驗(yàn)證確實(shí)存在跨任務(wù)混用需求時(shí)再把門(mén)控加上也不遲。先讓系統(tǒng)簡(jiǎn)單再讓系統(tǒng)聰明這條順序別顛倒。7.3 服務(wù)化時(shí)最容易被忽略的顯存坑多頭權(quán)重在加載進(jìn)顯存時(shí)如果按 PyTorch 默認(rèn) dataclass 存儲(chǔ)每多一個(gè)頭都會(huì)產(chǎn)生一系列 Python 對(duì)象開(kāi)銷。我實(shí)際啟動(dòng)服務(wù)時(shí)發(fā)現(xiàn)8 個(gè)頭加載后顯存占用比理論值多了 2.5GB全部來(lái)自 adapter 配置對(duì)象的元數(shù)據(jù)緩存。處理方式很簡(jiǎn)單把檢查點(diǎn)里每個(gè)頭轉(zhuǎn)成 safetensors 分片只加載當(dāng)前任務(wù)需要的頭其他頭留在磁盤(pán)。這樣不僅啟動(dòng)快顯存也從 21GB 降到了 17GB 左右。另外如果你用 vLLM 這類推理框架要注意它對(duì)新 adapter 的加載方式。很多框架默認(rèn)把每個(gè) adapter 的權(quán)重全部 preload 到顯存多任務(wù)場(chǎng)景下顯存會(huì)被無(wú)謂占滿。我最后用的是一個(gè)簡(jiǎn)單的外層調(diào)度每個(gè)請(qǐng)求只把對(duì)應(yīng)任務(wù)的頭從磁盤(pán)加載進(jìn)來(lái)請(qǐng)求結(jié)束立即釋放。額外增加的延遲在毫秒級(jí)別但能換來(lái)更大的并發(fā)密度。8. 三個(gè)必須提前想清楚的問(wèn)題8.1 任務(wù)數(shù)超過(guò)多少個(gè)該停手我的經(jīng)驗(yàn)是任務(wù)數(shù)超過(guò) 8 個(gè)以后MLoRA 的訓(xùn)練曲線會(huì)明顯波動(dòng)各頭更新頻率重新變得不均衡負(fù)載均衡損失也很難壓住。這時(shí)候把任務(wù)做分層先按領(lǐng)域聚成 3 到 4 組每組內(nèi)部再用 MLoRA 多任務(wù)訓(xùn)練反而比強(qiáng)行一把梭更穩(wěn)。任務(wù)不是越多越好MLoRA 解決的是“中等數(shù)量任務(wù)的共享問(wèn)題”不是無(wú)限擴(kuò)展的萬(wàn)能藥。如果你真的遇到 20 個(gè)以上的任務(wù)我建議先做任務(wù)聚類再把聚類結(jié)果交給路由。比如把 20 個(gè)任務(wù)聚成 4 個(gè)簇每個(gè)簇內(nèi)部共享一個(gè)低秩頭這樣參數(shù)規(guī)??煽厝蝿?wù)沖突也最小。8.2 和全參微調(diào)相比MLoRA 到底犧牲了什么全參微調(diào)能對(duì)底座做大規(guī)模重排適合改動(dòng)非常大的遷移。MLoRA 受限于低秩旁路能表達(dá)的偏移空間是在 r 維子空間里。若任務(wù)需要模型理解一種底層邏輯差異比如從通用對(duì)話遷移到代碼生成低秩旁路覆蓋不了這種結(jié)構(gòu)性變化。這時(shí)候哪怕加更多頭也沒(méi)用我的判斷是直接換底座或全參微調(diào)才是正路。如果你不確定任務(wù)是否超出了低秩旁路的能力范圍可以先做一個(gè)快速診斷用 500 條任務(wù)數(shù)據(jù)訓(xùn)練一個(gè)單 LoRA看驗(yàn)證集上的分?jǐn)?shù)是否達(dá)到業(yè)務(wù)線。如果單 LoRA 都達(dá)不到MLoRA 只會(huì)讓事情更復(fù)雜不會(huì)讓效果變好。這不算嚴(yán)謹(jǐn)?shù)臄?shù)學(xué)證明但作為項(xiàng)目決策依據(jù)足夠了。8.3 文檔里不提的經(jīng)驗(yàn)關(guān)于 α/r 的爭(zhēng)議網(wǎng)上一大半文章說(shuō) LoRA 里 α 要設(shè)為 r 的 2 倍理由是輸出縮放更平滑。我在 MLoRA 多任務(wù)訓(xùn)練里試過(guò) α r 和 α 2r前者的收斂更快后者在測(cè)試集上波動(dòng)更小。沒(méi)有絕對(duì)正確只有針對(duì)場(chǎng)景的選擇。最關(guān)鍵的是整個(gè)模型里所有頭要保持一致的縮放。如果任務(wù) A 的 α/r 是 1任務(wù) B 的 α/r 是 2低秩子空間之間的相對(duì)尺度就失去可比性路由判斷也會(huì)失真。所以我把“所有頭統(tǒng)一 α r”寫(xiě)進(jìn)了團(tuán)隊(duì)規(guī)范比每次臨時(shí)調(diào)參靠譜得多。另外還要注意新增任務(wù)的初始化不能直接復(fù)制現(xiàn)有頭的 A 和 B否則新任務(wù)會(huì)先繼承舊任務(wù)的語(yǔ)言習(xí)慣需要很長(zhǎng)時(shí)間才能“洗”回來(lái)。正確做法是新頭保持隨機(jī)初始化、B 置 0然后只讓新任務(wù)的數(shù)據(jù)參與前幾百步更新等它的 loss 開(kāi)始下降再放開(kāi)門(mén)控。9. 一些關(guān)于后續(xù)延展的想法MLoRA 最近吸引了很多研究者把它和持續(xù)學(xué)習(xí)、模型合并、參數(shù)高效微調(diào)這些方向結(jié)合起來(lái)看。我自己的實(shí)踐體會(huì)是它最大的價(jià)值在于改變了“一個(gè)任務(wù)一份權(quán)重”的慣性思維。你不再需要為每個(gè)小任務(wù)維護(hù)一套完整的微調(diào)產(chǎn)物而是可以把任務(wù)拆解成共享底座上的一個(gè)可插拔頭。這也是我目前比較推薦的實(shí)踐路徑先跑通單任務(wù) LoRA確認(rèn)任務(wù)本身可學(xué)再在業(yè)務(wù)量級(jí)上做一輪任務(wù)聚類判斷哪些任務(wù)適合共享底座最后才是引入 MLoRA 的多頭結(jié)構(gòu)。順序反了很容易被路由調(diào)參、負(fù)載均衡這些問(wèn)題淹沒(méi)。如果你想在項(xiàng)目里落地 MLoRA最好從 2 到 3 個(gè)相似任務(wù)開(kāi)始不要一上來(lái)就塞 10 個(gè)任務(wù)。一個(gè)小規(guī)模的驗(yàn)證能幫你快速確認(rèn)底座是否適合這些任務(wù)、路由模塊是否穩(wěn)定、顯存和延遲是否滿足要求。驗(yàn)證通過(guò)后再逐步增加任務(wù)數(shù)量。這個(gè)節(jié)奏走下來(lái)踩坑的概率會(huì)小很多。