☰
LLM 微调实战(7):灾难性遗忘与模型合并:专业化的代价
2026/10/1 6:34:54 网站建设 项目流程

承接与场景

第六篇的分诊模型在评测集上拿了高分,但发布评审时被一条附加报告拦下:拿基座的通用题测了一遍,代码和英文写作能力明显下滑。这就是专业化的账单——灾难性遗忘。本篇先量化这张账单,再讨论工程上两种偿还方式:训练侧的惩罚与回放,以及一条看似白嫖的路——模型合并。场景设定为编程助手团队:代码补全模型(任务 A)已经上线,产品要求在同一权重上叠加英文作文批改(任务 B),两个能力会不会互相踩踏?实验依旧纯标准库确定性模拟(遗忘用 8 维线性分类器手撕 SGD,合并用二次损失面上的向量代数),生产对应 PEFT/DeepSpeed 的训练策略与 mergekit 类工具。

遗忘从哪来:共享参数上的梯度冲突

微调的本质是把权重推向"当前训练数据定义的极小点"。当新任务与旧能力共享同一组参数、且所需行为在参数方向上冲突时,更新会拆掉旧能力的脚手架——这不是优化器缺陷,是表示容量与参数复用的必然。三个工程杠杆决定账单大小:更新范围(全量微调每层每个矩阵都动,遗忘最重;LoRA 把改动锁在低秩旁路,原矩阵一个比特不变,LoRA 原论文专门论证了它"学得稍少、忘得更少");数据可见性(训练分布里完全没有旧任务样本,模型没有任何理由保留旧行为——掺通用数据回放是最朴素有效的解药,第五篇配比表里那 30% 通用指令就是干这个的);参数锚定(EWC 一类方法给重要的旧权重加二次惩罚,改动越贵罚金越高)。下面实验把三种典型策略放在同一个跷跷板上称重。

实验一:顺序训练、混合训练与 EWC 的三笔账

构造两个任务:输入 8 维高斯特征,标签由线性规则决定——A 规则占用第 1/3/5/8 维,B 规则占用另一组且在第 3 维与 A 反号(埋一个结构性冲突)。同一个线性 softmax 分类器先吃 A 再吃 B,对比交错混训,以及带 λ=2 二次惩罚(锚定 A 阶段结束权重)的顺序训练,手撕梯度下降:

"""灾难性遗忘模拟: 顺序训练 vs 混合训练 vs EWC 二次惩罚 (线性 softmax 分类器手撕 SGD)。"""importmathimportrandom D,CLS=8,2W_A=[1,0,-1,0,2,0,0,1]# 任务A(代码补全)的规则向量, 占用 1/3/5/8 维W_B=[0,2,1,-1,0,0,1,0]# 任务B(英文作文批改)规则, 与A共用第 3 维且符号相反defmake_data(w,seed,n=160):rng=random.Random(seed)data=[]for_inrange(n):x=[rng.gauss(0,1)for_inrange(D)]s=sum(wi*xiforwi,xiinzip(w,x))data.append((x,1ifs>0else0))returndatadefgrad(W,x,y):s=[sum(W[c][i]*x[i]foriinrange(D))forcinrange(CLS)]m=max(s)e=[math.exp(v-m)forvins]z=sum(e)return[[(e[c]/z-(1.0ifc==yelse0.0))*x[i]foriinrange(D)]forcinrange(CLS)]defevaluate(W,data):hit=0forx,yindata:s=[sum(W[c][i]*x[i]foriinrange(D))forcinrange(CLS)]hit+=(s.index(max(s))==y)returnhit/len(data)definit():r=random.Random(0)return[[r.gauss(0,0.01)for_inrange(D)]for_inrange(CLS)]deftrain(W,data,epochs,lr,anchor=None,lam=0.0):for_inrange(epochs):forx,yindata:g=grad(W,x,y)forcinrange(CLS):foriinrange(D):d=g[c][i]+(2*lam*(W[c][i]-anchor[c][i])ifanchorelse0.0)W[c][i]-=lr*dreturnW A,B=make_data(W_A,11),make_data(W_B,22)mix=[(A[i],B[i])foriinrange(len(A))]seq=init()train(seq,A,8,0.05)a_mid=evaluate(seq,A)train(seq,B,8,0.05)joint=init()for_inrange(8):forxa,xbinmix:train(joint,[xa],1,0.05)train(joint,[xb],1,0.05)ewc=init()train(ewc,A,8,0.05)anchor=[row[:]forrowinewc]train(ewc,B,8,0.05,anchor=anchor,lam=2.0)print("先训A后测A: %.3f (参照)"%a_mid)print("%-22s A 准确率 B 准确率"%"策略")print("%-22s %.3f %.3f"%("顺序 A->B",evaluate(seq,A),evaluate(seq,B)))print("%-22s %.3f %.3f"%("混合 A+B 交错",evaluate(joint,A),evaluate(joint,B)))print("%-22s %.3f %.3f"%("顺序+惩罚 λ=2.0",evaluate(ewc,A),evaluate(ewc,B)))

运行输出:

先训A后测A: 0.981 (参照) 策略 A 准确率 B 准确率 顺序 A->B 0.338 0.981 混合 A+B 交错 0.662 0.700 顺序+惩罚 λ=2.0 0.975 0.494

三行输出是一堂完整的跷跷板课。第一行是遗忘的裸形态:B 学会的同时 A 从 0.981 塌到 0.338——不是"忘了一点",是被新任务结构性拆除,因为第 3 维的权重符号必须反过去才能拟合 B。第二行混训拿到 0.662/0.700:冲突并未消失,只是被摊平——注意这个线性模型容量下两个反号规则不可兼得,真实 LLM 参数冗余大得多,混入 10~30% 旧任务数据回放通常能把两边都推回高位,但"共享关键参数=必留冲突"的几何直觉不变。第三行 EWC 把 A 锚回 0.975,代价是 B 只学到 0.494:惩罚项就是字面意义的"旧能力赎金",λ 越大保旧越狠、学新越钝。没有免费策略,只有显式选点:遗忘预算、新任务目标分、回放数据比例,三者在训练开始前就该写进配置,而不是训完看着评测哭。

模型合并:训练之后的代数

另一条路线不动训练,动权重。任务算术(task arithmetic)的观察是:微调产物可以抽象成"基座 + 任务向量 τ = θ_ft − θ_base",于是两个模型的合并按权重做加法即可:θ_merge = θ_base + λ₁τ₁ + λ₂τ₂。同一思想的三个变体:model soup(同一任务、不同种子/学习率训出的多个 checkpoint 平均,降随机噪声);跨任务合并(两个不同专精 LoRA 直接加,赌它们的任务向量近似正交);DARE(先把任务向量随机丢掉 90% 再等比放大——微调增量的绝大多数分量是冗余的,稀疏化后两个向量的碰撞概率骤降,合并更稳)。合并到底什么时候是白捡、什么时候是自毁?在二次损失面上可以精确算出来——每个任务的 loss 设为到其收敛点的距离平方:

"""模型合并的几何: 二次损失面上同任务平均降方差, 跨任务平均出干扰 (向量代数)。"""importrandom d=16rng=random.Random(9)cA=[rng.gauss(0,1)for_inrange(d)]# 代码任务的收敛权重(任务A)cB=[rng.gauss(0,1)for_inrange(d)]# 英文批改的收敛权重(任务B)SIG=0.25# 微调随机性造成的落点噪声defcheckpoints(c,seed,n=5):r=random.Random(seed)pts=[[c[i]+r.gauss(0,SIG)foriinrange(d)]for_inrange(n)]returnpts,[sum(p[i]forpinpts)/nforiinrange(d)]defloss(theta,c):returnsum((a-b)**2fora,binzip(theta,c))/d ptsA,avgA=checkpoints(cA,1)ptsB,avgB=checkpoints(cB,2)print("同任务 soup: 平均点到自身最优点 loss=%.4f | 单 checkpoint 均值 loss=%.4f"%(loss(avgA,cA),sum(loss(p,cA)forpinptsA)/len(ptsA)))print(" (理论上平均把方差除以 checkpoint 数: %.4f -> %.4f)"%(SIG*SIG,SIG*SIG/5))cross=[(avgA[i]+avgB[i])/2foriinrange(d)]print("跨任务直接平均: lossA %.3f | lossB %.3f | 两最优点距离²/4 = %.3f"%(loss(cross,cA),loss(cross,cB),loss(cA,cB)/4))print("task arithmetic: theta = 基座 + λ(τA + τB), τ 为任务向量")print(" λ lossA lossB 两任务合计")forlamin(0.25,0.5,0.75,1.0):m=[lam*(avgA[i]+avgB[i])foriinrange(d)]la,lb=loss(m,cA),loss(m,cB)print(" %.2f %6.3f %6.3f %8.3f"%(lam,la,lb,la+lb))

运行输出:

同任务 soup: 平均点到自身最优点 loss=0.0152 | 单 checkpoint 均值 loss=0.0537 (理论上平均把方差除以 checkpoint 数: 0.0625 -> 0.0125) 跨任务直接平均: lossA 0.696 | lossB 0.633 | 两最优点距离²/4 = 0.658 task arithmetic: theta = 基座 + λ(τA + τB), τ 为任务向量 λ lossA lossB 两任务合计 0.25 0.645 0.742 1.387 0.50 0.696 0.633 1.329 0.75 0.835 0.613 1.448 1.00 1.064 0.682 1.746

三组数对应三条定律。同任务平均:五个 checkpoint 取均值,到最优点的 loss 从 0.0537 掉到 0.0152,贴着理论值 σ²/5——soup 是唯一的免费午餐,因为它平均的是"同一个目标周围的噪声",不碰任务差异。跨任务直接平均:两个任务的 loss 双双爆到 0.6+,精确等于几何上的"距离²/4"——两个不相干的最优点取中点,等于同时背叛两边,这就是"把代码模型和批改模型简单平均做出个废物"的数学解释。task arithmetic 的 λ 扫描:合计 loss 在 λ=0.5 附近最低,且没有任何 λ 能让两边同时接近零——因为这两个任务向量本身不重叠(16 维随机方向),加法只是"各取一半份额"的折中。合并可用的真正条件是任务向量近似正交且各自不过饱和:越不相关的任务越能"各占各的方向"互不干扰;反过来,两个都在改造同一批注意力通道的相似任务,合并前请老老实实回到联合训练加数据回放。生产上的安全阀是 LoRA 多适配器模式:一个基座按请求路由不同 adapter,零合并零干扰——代价只是加载管理,第八篇上线篇会接这笔账。

常见陷阱

  • 只在专业集上报喜:评测体系里必须常设"通用回归集"(MMLU 抽样子集 + 代码 + 写作 + 多语各若干),每次微调必跑,遗忘当场现形——本篇就是第六篇那支"被拦下的发布"的前因。
  • 把 LoRA 当免遗忘金牌:低秩旁路忘得少,但 LoRA 学新任务时旁路本身也在覆盖——第五个 adapter 叠上去的互相干扰与遗忘是一回事,换的是量级不是原理。
  • 合并无评测:任何权重加法(soup、任务算术、DARE)之后,必须重跑完整三层评测+回归集;"论文说合并有效"不能替代自家题。
  • λ 当玄学旋钮:λ 扫描必做,且扫描对象是"两任务合计 loss/分数"曲线,挑拐点不是挑单边最高。
  • 遗忘不可逆焦虑:真金白银的保命符是旧模型权重+旧训练数据一起归档——重训比修复便宜,尤其是数据配比还能复用的时候。

落地清单

  • 专业化任务开工前先定三值:可接受的通用分回退、目标专业分、回放比例(起步 10~30%)
  • 策略对照表进实验记录:顺序 / 混训 / LoRA(冻结底座)各跑一列,跷跷板位置自己选
  • EWC/惩罚系数 λ 用 3 个点粗扫即可,选"新任务达标线上保旧最多"的那档
  • 合并路线只用于:同任务 soup(默认收益)、不相关任务向量 + 小 λ;相似任务一律重训
  • 多 adapter 路由是遗忘与干扰的最终兜底,架构上优先给"能力货架"留位置

模型训好了、账算清了,下一个问题是把它交到用户手里。下一篇《LLM 微调实战(8):微调模型上线全流程:打包、vLLM 部署与版本管理》:从合并导出到校验清单,从显存预算到并发规划,微调模型的发布工程一条线走完。

参考来源

  • Overcoming catastrophic forgetting in neural networks (EWC):https://arxiv.org/abs/1612.00796
  • Editing Models with Task Arithmetic:https://arxiv.org/abs/2212.04089
  • DARE: Language Models are Super Mario: Absorbing Abilities from Homologous Models as a Free Lunch:https://arxiv.org/abs/2311.12956
  • Model soups: averaging weights of multiple fine-tuned models improves accuracy without increasing inference time:https://arxiv.org/abs/2203.05482
  • Wikipedia: Catastrophic interference:https://en.wikipedia.org/wiki/Catastrophic_interference

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询