目录
- 第一部分:模型合并基础理论
- 第一章:绪论------为什么需要模型合并
- 第二章:损失曲面几何与线性模式连通性
- 第三章:权重空间的对齐问题
- 第二部分:经典合并方法
- 第四章:简单平均与加权平均
- 第五章:球面线性插值(SLERP)
- 第六章:任务算术(Task Arithmetic)
- 第三部分:高级合并方法
- 第七章:TIES-Merging------修剪冗余符号
- 第八章:DARE------随机丢弃与重缩放
- 第九章:Model Soups 与多模型融合
- 第四部分:理论分析与前沿
- 第十章:合并的理论保证
- 第十一章:合并与泛化
- 第十二章:大语言模型的合并实践
- 第五部分:完整可运行代码实现
- 第十三章:从零实现权重对齐
- 第十四章:从零实现 SLERP 与任务算术
- 第十五章:从零实现 TIES 与 DARE
- 第十六章:完整合并 Pipeline 与精度对比
- 附录
第一部分:模型合并基础理论
第一章:绪论------为什么需要模型合并
1.1 模型合并的动机
1.1.1 单一模型的局限
在实际应用中,我们通常面临以下场景:
- 多个专家模型:针对不同任务微调了多个模型,每个模型在各自任务上表现优异
- 计算资源有限:无法部署多个大模型
- 多任务需求:需要一个模型同时擅长多种任务
模型合并(Model Merging) 的核心思想是:将多个模型的权重直接合并为一个模型,无需额外训练(或仅需极少训练)。
1.1.2 模型合并 vs 其他方法
| 方法 | 是否需要训练 | 是否需要数据 | 计算成本 | 多任务能力 |
|---|---|---|---|---|
| 多任务微调 | 是 | 是 | 高 | 好 |
| 模型集成 | 否 | 否 | 高(推理时) | 好 |
| 知识蒸馏 | 是 | 是 | 高 | 中等 |
| 模型合并 | 否 | 否/极少 | 低 | 好 |
模型合并的独特优势:
- 零成本融合:不需要训练数据和计算资源
- 即插即用:直接对权重进行操作
- 可组合性:可以任意组合不同任务的模型
- 理论优美:基于损失曲面的几何性质
1.2 模型合并的历史
1.2.1 早期工作
模型平均(Model Averaging):最简单的合并方法------对多个模型的权重取平均。
Polyak-Ruppert 平均:在 SGD 训练过程中,对多个检查点取平均以提高收敛性。
1.2.2 现代发展
Git Rebasin(Ainsworth et al., 2023):解决了权重空间的对齐问题。
Task Arithmetic(Ilharco et al., 2023):发现任务向量可以进行加减运算。
TIES-Merging(Yadav et al., 2023):修剪冗余符号,解决冲突。
DARE(Yu et al., 2024):随机丢弃 delta 参数并重缩放。
1.3 模型合并的分类
| 类别 | 说明 | 代表方法 |
|---|---|---|
| 线性插值 | 在权重空间中线性插值 | 平均、SLERP |
| 任务向量 | 使用任务向量进行算术运算 | Task Arithmetic |
| 符号处理 | 处理权重的符号冲突 | TIES-Merging |
| 随机化 | 随机丢弃 delta 参数 | DARE |
| 对齐合并 | 先对齐再合并 | Git Rebasin |
第二章:损失曲面几何与线性模式连通性
2.1 损失曲面的基本概念
2.1.1 损失曲面
定义 2.1(损失曲面) :模型参数 θ∈Rd\theta \in \mathbb{R}^dθ∈Rd 的损失曲面定义为:
L(θ)=E(x,y)∼Dℓ(fθ(x),y)\mathcal{L}(\theta) = \mathbb{E}_{(x,y) \sim \mathcal{D}} \\ell(f_\\theta(x), y)L(θ)=E(x,y)∼Dℓ(fθ(x),y)
其中 fθf_\thetafθ 是参数为 θ\thetaθ 的模型,ℓ\ellℓ 是损失函数。
2.1.2 损失曲面的几何性质
定义 2.2(局部极小值) :θ∗\theta^*θ∗ 是局部极小值,如果存在 ϵ>0\epsilon > 0ϵ>0 使得:
L(θ)≥L(θ∗),∀∥θ−θ∗∥<ϵ\mathcal{L}(\theta) \geq \mathcal{L}(\theta^*), \quad \forall \|\theta - \theta^*\| < \epsilonL(θ)≥L(θ∗),∀∥θ−θ∗∥<ϵ
定义 2.3(全局极小值) :θ∗\theta^*θ∗ 是全局极小值,如果:
L(θ)≥L(θ∗),∀θ∈Rd\mathcal{L}(\theta) \geq \mathcal{L}(\theta^*), \quad \forall \theta \in \mathbb{R}^dL(θ)≥L(θ∗),∀θ∈Rd
2.2 线性模式连通性
2.2.1 定义
定义 2.4(线性模式连通性,Linear Mode Connectivity, LMC) :两个参数 θ1\theta_1θ1 和 θ2\theta_2θ2 是线性模式连通的,如果沿它们之间的线性插值路径,损失不增加:
L(αθ1+(1−α)θ2)≤max(L(θ1),L(θ2)),∀α∈0,1\mathcal{L}(\alpha \theta_1 + (1-\alpha) \theta_2) \leq \max(\mathcal{L}(\theta_1), \mathcal{L}(\theta_2)), \quad \forall \alpha \in 0, 1L(αθ1+(1−α)θ2)≤max(L(θ1),L(θ2)),∀α∈0,1
2.2.2 LMC 的理论分析
定理 2.1(LMC 的充分条件) :如果损失函数 L\mathcal{L}L 在 θ1\theta_1θ1 和 θ2\theta_2θ2 之间的线段上是凸的 ,则 θ1\theta_1θ1 和 θ2\theta_2θ2 是线性模式连通的。
证明:凸性意味着:
L(αθ1+(1−α)θ2)≤αL(θ1)+(1−α)L(θ2)≤max(L(θ1),L(θ2))\mathcal{L}(\alpha \theta_1 + (1-\alpha) \theta_2) \leq \alpha \mathcal{L}(\theta_1) + (1-\alpha) \mathcal{L}(\theta_2) \leq \max(\mathcal{L}(\theta_1), \mathcal{L}(\theta_2))L(αθ1+(1−α)θ2)≤αL(θ1)+(1−α)L(θ2)≤max(L(θ1),L(θ2))
□\square□
定理 2.2(神经网络的 LMC):对于过参数化的神经网络,在训练初期(SGD 的随机性使不同初始化的模型收敛到同一"盆地"),不同初始化训练得到的模型通常是线性模式连通的。
实验支持:Neyshabur et al. (2020) 发现,从同一预训练模型出发,不同微调得到的模型之间存在低损失的线性路径。
2.2.3 LMC 的几何解释
图示:考虑损失曲面的等高线:
θ₁* θ₂*
· ·
/|\ /|\
/ | \ __ / | \
/ | \_/ \_/ | \
/ | / \ | \
/ | / \ | \
如果 θ1∗\theta_1^*θ1∗ 和 θ2∗\theta_2^*θ2∗ 在同一个"盆地"中,它们之间的线性路径不会穿过高损失区域。
2.3 损失曲面的曲率分析
2.3.1 Hessian 与曲率
定义 2.5(Hessian 矩阵):
H(θ)=∇2L(θ)H(\theta) = \nabla^2 \mathcal{L}(\theta)H(θ)=∇2L(θ)
定理 2.3(LMC 与 Hessian 的关系) :如果沿 θ1\theta_1θ1 到 θ2\theta_2θ2 的路径上,Hessian 的最大特征值 λmax(H)\lambda_{\max}(H)λmax(H) 满足:
λmax(H(θ))≤0,∀θ=αθ1+(1−α)θ2\lambda_{\max}(H(\theta)) \leq 0, \quad \forall \theta = \alpha \theta_1 + (1-\alpha) \theta_2λmax(H(θ))≤0,∀θ=αθ1+(1−α)θ2
则路径上损失是凸的,θ1\theta_1θ1 和 θ2\theta_2θ2 是线性模式连通的。
2.3.2 窄盆地假设
假设 2.1(窄盆地假设):神经网络的损失曲面由许多"窄而深"的盆地组成,每个盆地内的模型可以通过线性插值连接。
推论:如果两个模型在同一盆地中,它们的加权平均仍然在该盆地中------这就是模型合并有效的根本原因。
第三章:权重空间的对齐问题
3.1 权重空间的对称性
3.1.1 权重排列不变性
定理 3.1(权重排列不变性) :对于全连接层 y=Wxy = Wxy=Wx,将权重矩阵 WWW 的行(或列)重新排列,同时相应地调整下一层(或上一层)的权重,模型的输入-输出映射不变。
形式化 :设 PPP 是排列矩阵,则:
y=Wx=(WPT)(Px)y = Wx = (WP^T)(Px)y=Wx=(WPT)(Px)
因此 WWW 和 WPTWP^TWPT 表示同一个函数(只是隐藏单元的排列不同)。
3.1.2 对齐问题的定义
定义 3.1(对齐问题) :给定两个模型 θ1\theta_1θ1 和 θ2\theta_2θ2(从不同初始化训练得到),找到排列矩阵 {Pl}\{P_l\}{Pl} 使得变换后的模型 θ2′={Plθ2,l}\theta_2' = \{P_l \theta_{2,l}\}θ2′={Plθ2,l} 与 θ1\theta_1θ1 在权重空间中尽可能接近:
min{Pl}∑l∥θ1,l−Plθ2,lPlT∥F2\min_{\{P_l\}} \sum_l \|\theta_{1,l} - P_l \theta_{2,l} P_l^T\|_F^2{Pl}minl∑∥θ1,l−Plθ2,lPlT∥F2
重要性:如果不对齐,直接平均两个模型的权重是没有意义的------因为隐藏单元的排列可能完全不同。
3.2 对齐算法
3.2.1 基于激活的对齐
算法 3.1(基于激活的对齐):
输入:模型 θ₁, θ₂, 校准数据 X
输出:对齐后的模型 θ₂'
对每一层 l:
1. 计算两个模型在该层的激活:
A₁ = f₁⁽ˡ⁾(X), A₂ = f₂⁽ˡ⁾(X)
2. 计算激活的协方差:
C₁ = A₁ᵀA₁, C₂ = A₂ᵀA₂
3. 找到排列 P 使 C₁ 和 C₂ 尽可能相似:
P = argmin_P ||C₁ - P C₂ Pᵀ||_F
4. 应用排列:θ₂' = P θ₂ Pᵀ
3.2.2 基于权重的对齐
算法 3.2(基于权重的对齐):
输入:模型 θ₁, θ₂
输出:对齐后的模型 θ₂'
对每一层 l:
1. 计算权重的相似度矩阵:
S = |θ₁||θ₂ᵀ|
2. 使用匈牙利算法找到最优匹配:
P = hungarian(S)
3. 应用排列:θ₂' = P θ₂
3.2.3 匈牙利算法
问题 :给定相似度矩阵 S∈Rn×nS \in \mathbb{R}^{n \times n}S∈Rn×n,找到排列 π\piπ 最大化:
maxπ∑i=1nSi,π(i)\max_\pi \sum_{i=1}^n S_{i, \pi(i)}πmaxi=1∑nSi,π(i)
复杂度 :O(n3)O(n^3)O(n3)(Kuhn-Munkres 算法)
3.3 Git Rebasin
3.3.1 核心思想
Git Rebasin(Ainsworth et al., 2023)将模型合并类比为 Git 的 rebase 操作:
- 对齐 :将模型 θ2\theta_2θ2 的权重空间对齐到 θ1\theta_1θ1
- 合并:在对齐后的空间中进行线性插值
3.3.2 算法
算法 3.3(Git Rebasin):
输入:模型 θ₁, θ₂, 校准数据 X
输出:合并后的模型 θ_merged
1. 对齐 θ₂ 到 θ₁:
θ₂' = align(θ₂, θ₁, X)
2. 线性插值:
θ_merged = α θ₁ + (1-α) θ₂'
3.3.3 理论分析
定理 3.2(对齐后的 LMC) :如果 θ1\theta_1θ1 和 θ2\theta_2θ2 是从同一预训练模型微调得到的,且 θ2\theta_2θ2 已对齐到 θ1\theta_1θ1,则它们通常是线性模式连通的。
证明思路 :对齐消除了隐藏单元排列的歧义,使得权重空间中的线性插值对应于函数空间中的有意义插值。□\square□
第二部分:经典合并方法
第四章:简单平均与加权平均
4.1 简单平均
4.1.1 定义
定义 4.1(简单平均) :对于 KKK 个模型 θ1,...,θK\theta_1, \dots, \theta_Kθ1,...,θK,简单平均为:
θˉ=1K∑k=1Kθk\bar{\theta} = \frac{1}{K} \sum_{k=1}^K \theta_kθˉ=K1k=1∑Kθk
4.1.2 理论分析
定理 4.1(简单平均的泛化界) :设每个模型 θk\theta_kθk 的测试损失为 Ltest(θk)\mathcal{L}_{\text{test}}(\theta_k)Ltest(θk),则平均模型的测试损失满足:
Ltest(θˉ)≤1K∑k=1KLtest(θk)+1K∑k=1K∥θk−θˉ∥2⋅λmax(H)\mathcal{L}{\text{test}}(\bar{\theta}) \leq \frac{1}{K} \sum{k=1}^K \mathcal{L}{\text{test}}(\theta_k) + \frac{1}{K} \sum{k=1}^K \|\theta_k - \bar{\theta}\|^2 \cdot \lambda_{\max}(H)Ltest(θˉ)≤K1k=1∑KLtest(θk)+K1k=1∑K∥θk−θˉ∥2⋅λmax(H)
其中 λmax(H)\lambda_{\max}(H)λmax(H) 是 Hessian 的最大特征值。
推论 4.1 :如果损失曲面在模型之间的区域是平坦的(λmax(H)\lambda_{\max}(H)λmax(H) 小),则简单平均接近最优。
4.2 加权平均
4.2.1 定义
定义 4.2(加权平均):
θmerged=∑k=1Kwkθk,∑k=1Kwk=1\theta_{\text{merged}} = \sum_{k=1}^K w_k \theta_k, \quad \sum_{k=1}^K w_k = 1θmerged=k=1∑Kwkθk,k=1∑Kwk=1
4.2.2 最优权重
定理 4.2(最优权重):在二次损失假设下,使合并模型测试损失最小的权重为:
w∗=argminwL(∑kwkθk)w^* = \arg\min_w \mathcal{L}\left(\sum_k w_k \theta_k\right)w∗=argwminL(k∑wkθk)
求解:在验证集上搜索最优权重。
4.2.3 模型平均的集成视角
定理 4.3(平均与集成的关系):在二次损失假设下,模型平均的输出等于模型集成的输出:
fθˉ(x)≈1K∑k=1Kfθk(x)f_{\bar{\theta}}(x) \approx \frac{1}{K} \sum_{k=1}^K f_{\theta_k}(x)fθˉ(x)≈K1k=1∑Kfθk(x)
证明 :设 fθ(x)=θTxf_\theta(x) = \theta^T xfθ(x)=θTx(线性模型),则:
fθˉ(x)=θˉTx=(1K∑kθk)Tx=1K∑kθkTx=1K∑kfθk(x)f_{\bar{\theta}}(x) = \bar{\theta}^T x = \left(\frac{1}{K}\sum_k \theta_k\right)^T x = \frac{1}{K} \sum_k \theta_k^T x = \frac{1}{K} \sum_k f_{\theta_k}(x)fθˉ(x)=θˉTx=(K1k∑θk)Tx=K1k∑θkTx=K1k∑fθk(x)
对于非线性模型,这个等式只在 θk\theta_kθk 接近时近似成立。□\square□
第五章:球面线性插值(SLERP)
5.1 动机
5.1.1 线性插值的问题
问题 :线性插值 θ=αθ1+(1−α)θ2\theta = \alpha \theta_1 + (1-\alpha) \theta_2θ=αθ1+(1−α)θ2 可能导致:
- 权重范数变化 :∥θ∥\|\theta\|∥θ∥ 可能不等于 ∥θ1∥\|\theta_1\|∥θ1∥ 或 ∥θ2∥\|\theta_2\|∥θ2∥
- 损失增加:穿过高损失区域
5.1.2 SLERP 的思想
球面线性插值(Spherical Linear Interpolation, SLERP) :在超球面上进行插值,保持权重范数不变。
5.2 SLERP 的数学定义
5.2.1 2D 球面插值
定义 5.1(2D SLERP) :对于单位圆上的两个点 v1,v2\mathbf{v}_1, \mathbf{v}_2v1,v2,它们之间的球面插值为:
SLERP(v1,v2;t)=sin((1−t)Ω)sinΩv1+sin(tΩ)sinΩv2\text{SLERP}(\mathbf{v}_1, \mathbf{v}_2; t) = \frac{\sin((1-t)\Omega)}{\sin \Omega} \mathbf{v}_1 + \frac{\sin(t\Omega)}{\sin \Omega} \mathbf{v}_2SLERP(v1,v2;t)=sinΩsin((1−t)Ω)v1+sinΩsin(tΩ)v2
其中 Ω=arccos(v1⋅v2)\Omega = \arccos(\mathbf{v}_1 \cdot \mathbf{v}_2)Ω=arccos(v1⋅v2) 是两个向量之间的夹角。
5.2.2 高维推广
定义 5.2(高维 SLERP) :对于 Rn\mathbb{R}^nRn 中的两个向量 w1,w2\mathbf{w}_1, \mathbf{w}_2w1,w2:
- 归一化:w^i=wi/∥wi∥\hat{\mathbf{w}}_i = \mathbf{w}_i / \|\mathbf{w}_i\|w^i=wi/∥wi∥
- 计算夹角:Ω=arccos(w^1⋅w^2)\Omega = \arccos(\hat{\mathbf{w}}_1 \cdot \hat{\mathbf{w}}_2)Ω=arccos(w^1⋅w^2)
- 球面插值:w^=sin((1−t)Ω)sinΩw^1+sin(tΩ)sinΩw^2\hat{\mathbf{w}} = \frac{\sin((1-t)\Omega)}{\sin \Omega} \hat{\mathbf{w}}_1 + \frac{\sin(t\Omega)}{\sin \Omega} \hat{\mathbf{w}}_2w^=sinΩsin((1−t)Ω)w^1+sinΩsin(tΩ)w^2
- 恢复范数:w=∥w1∥⋅w^\mathbf{w} = \|\mathbf{w}_1\| \cdot \hat{\mathbf{w}}w=∥w1∥⋅w^(或使用其他范数插值策略)
5.3 SLERP 的理论分析
5.3.1 几何解释
定理 5.1(SLERP 的几何性质) :SLERP 沿着连接两个点的大圆弧(geodesic) 进行插值,这是球面上两点之间的最短路径。
证明 :在单位球面上,两点之间的最短路径是大圆弧。SLERP 正是沿着这条弧线等速运动。□\square□
5.3.2 与线性插值的比较
定理 5.2(SLERP 保持范数) :对于单位向量 w^1,w^2\hat{\mathbf{w}}_1, \hat{\mathbf{w}}_2w^1,w^2:
∥SLERP(w^1,w^2;t)∥=1,∀t∈0,1\|\text{SLERP}(\hat{\mathbf{w}}_1, \hat{\mathbf{w}}_2; t)\| = 1, \quad \forall t \in 0, 1∥SLERP(w^1,w^2;t)∥=1,∀t∈0,1
证明 :由 SLERP 的定义,∥SLERP∥2=sin2((1−t)Ω)sin2Ω+sin2(tΩ)sin2Ω+2sin((1−t)Ω)sin(tΩ)sin2ΩcosΩ\|\text{SLERP}\|^2 = \frac{\sin^2((1-t)\Omega)}{\sin^2\Omega} + \frac{\sin^2(t\Omega)}{\sin^2\Omega} + 2\frac{\sin((1-t)\Omega)\sin(t\Omega)}{\sin^2\Omega}\cos\Omega∥SLERP∥2=sin2Ωsin2((1−t)Ω)+sin2Ωsin2(tΩ)+2sin2Ωsin((1−t)Ω)sin(tΩ)cosΩ
利用三角恒等式可以证明这个等于 1。□\square□
推论 5.1:SLERP 避免了线性插值中权重范数变化的问题。
5.3.3 SLERP 的适用条件
定理 5.3(SLERP 的最优性条件):SLERP 在以下条件下优于线性插值:
- 权重向量的范数包含重要信息
- 损失曲面在球面上更平坦
- 两个模型的权重范数接近
第六章:任务算术(Task Arithmetic)
6.1 任务向量
6.1.1 定义
定义 6.1(任务向量) :对于预训练模型 θpre\theta_{\text{pre}}θpre 和微调模型 θft\theta_{\text{ft}}θft,任务向量定义为:
τ=θft−θpre\tau = \theta_{\text{ft}} - \theta_{\text{pre}}τ=θft−θpre
物理含义 :任务向量 τ\tauτ 编码了"从预训练到微调"的知识增量------即模型为了适应特定任务所做的修改。
6.1.2 任务向量的性质
定理 6.1(任务向量的低秩性) :在一定假设下,任务向量 τ\tauτ 的有效秩远小于权重矩阵的维度:
reff(τ)≪min(m,n)r_{\text{eff}}(\tau) \ll \min(m, n)reff(τ)≪min(m,n)
证明 :微调通常只调整模型的一小部分方向(对应于任务相关的低维子空间),因此 τ\tauτ 是近似低秩的。□\square□
推论 6.1:任务向量可以用低秩分解来近似------这与 LoRA 的思想一致。
6.2 任务向量的算术运算
6.2.1 加法------多任务合并
定义 6.2(任务向量加法) :对于多个任务的任务向量 τ1,...,τK\tau_1, \dots, \tau_Kτ1,...,τK,合并后的模型为:
θmerged=θpre+λ∑k=1Kτk\theta_{\text{merged}} = \theta_{\text{pre}} + \lambda \sum_{k=1}^K \tau_kθmerged=θpre+λk=1∑Kτk
其中 λ\lambdaλ 是缩放系数。
物理含义:将多个任务的知识叠加到预训练模型上。
6.2.2 减法------任务遗忘
定义 6.3(任务向量减法):从模型中"减去"某个任务的知识:
θnew=θft−λτforget\theta_{\text{new}} = \theta_{\text{ft}} - \lambda \tau_{\text{forget}}θnew=θft−λτforget
物理含义:让模型"忘记"某个任务,同时保留其他能力。
6.2.3 线性插值------任务混合
定义 6.4(任务向量插值):
θmixed=θpre+ατ1+(1−α)τ2\theta_{\text{mixed}} = \theta_{\text{pre}} + \alpha \tau_1 + (1-\alpha) \tau_2θmixed=θpre+ατ1+(1−α)τ2
6.3 任务算术的理论分析
6.3.1 加法的理论
定理 6.2(任务向量加法的近似保证) :如果各任务的任务向量 τk\tau_kτk 近似正交(即 τiTτj≈0\tau_i^T \tau_j \approx 0τiTτj≈0 对 i≠ji \neq ji=j),则:
Lk(θpre+∑jτj)≈Lk(θpre+τk)\mathcal{L}k(\theta{\text{pre}} + \sum_j \tau_j) \approx \mathcal{L}k(\theta{\text{pre}} + \tau_k)Lk(θpre+j∑τj)≈Lk(θpre+τk)
证明 :设损失函数在 θpre\theta_{\text{pre}}θpre 附近是二次的:
Lk(θ)≈Lk(θpre)+gkT(θ−θpre)+12(θ−θpre)THk(θ−θpre)\mathcal{L}k(\theta) \approx \mathcal{L}k(\theta{\text{pre}}) + g_k^T (\theta - \theta{\text{pre}}) + \frac{1}{2} (\theta - \theta_{\text{pre}})^T H_k (\theta - \theta_{\text{pre}})Lk(θ)≈Lk(θpre)+gkT(θ−θpre)+21(θ−θpre)THk(θ−θpre)
当 τi\tau_iτi 和 τj\tau_jτj 正交时,交叉项 τiTHkτj\tau_i^T H_k \tau_jτiTHkτj 很小,因此:
Lk(θpre+∑jτj)≈Lk(θpre)+gkTτk+12τkTHkτk=Lk(θpre+τk)\mathcal{L}k(\theta{\text{pre}} + \sum_j \tau_j) \approx \mathcal{L}k(\theta{\text{pre}}) + g_k^T \tau_k + \frac{1}{2} \tau_k^T H_k \tau_k = \mathcal{L}k(\theta{\text{pre}} + \tau_k)Lk(θpre+j∑τj)≈Lk(θpre)+gkTτk+21τkTHkτk=Lk(θpre+τk)
□\square□
6.3.2 缩放系数的选择
定理 6.3(最优缩放系数) :对于 KKK 个任务,最优缩放系数 λ∗\lambda^*λ∗ 满足:
λ∗=argminλ1K∑k=1KLk(θpre+λ∑jτj)\lambda^* = \arg\min_\lambda \frac{1}{K} \sum_{k=1}^K \mathcal{L}k(\theta{\text{pre}} + \lambda \sum_j \tau_j)λ∗=argλminK1k=1∑KLk(θpre+λj∑τj)
经验 :λ\lambdaλ 通常在 0.1,1.00.1, 1.00.1,1.0 之间,需要在验证集上搜索。
第三部分:高级合并方法
第七章:TIES-Merging------修剪冗余符号
7.1 核心观察
7.1.1 冲突问题
问题 :当多个任务向量在同一参数上有相反的符号(一个增加,一个减少)时,简单平均会导致抵消------两个任务的知识都被丢失。
例子:
- 任务 A 的 τA=+0.1,−0.2,+0.3\tau_A = +0.1, -0.2, +0.3τA=+0.1,−0.2,+0.3
- 任务 B 的 τB=−0.1,+0.3,−0.2\tau_B = -0.1, +0.3, -0.2τB=−0.1,+0.3,−0.2
- 平均:τˉ=0,+0.05,+0.05\bar{\tau} = 0, +0.05, +0.05τˉ=0,+0.05,+0.05------大部分信息丢失
7.1.2 冗余问题
问题 :许多 delta 参数的值很小,对模型性能影响微小------它们是冗余的。
7.2 TIES-Merging 的算法
7.2.1 三步流程
算法 7.1(TIES-Merging):
输入:预训练模型 θ_pre, 任务向量 {τ_1, ..., τ_K}, 修剪比例 p
输出:合并后的模型 θ_merged
1. 修剪(Trim):移除绝对值最小的 p% delta 参数
对每个 τ_k:
τ_k[|τ_k| < threshold] = 0
2. 符号选举(Elect Sign):确定每个参数的符号
对每个参数 i:
sign_i = majority_sign({τ_k[i] : τ_k[i] ≠ 0})
3. 合并(Merge):只合并与主符号一致的 delta
τ_merged[i] = mean({τ_k[i] : τ_k[i] ≠ 0, sign(τ_k[i]) == sign_i})
4. 返回:θ_merged = θ_pre + λ * τ_merged
7.2.2 符号选举
定义 7.1(符号选举) :对于参数位置 iii,定义符号选举函数:
signi=sgn(∑k=1Ksgn(τki)⋅∣τki∣)\text{sign}i = \text{sgn}\left(\sum{k=1}^K \text{sgn}(\tau_ki) \cdot |\tau_ki|\right)signi=sgn(k=1∑Ksgn(τki)⋅∣τki∣)
即:按幅度加权投票决定符号。
定理 7.1(符号选举的最优性):在一定假设下,符号选举最小化合并后的总损失。
7.2.3 修剪策略
定义 7.2(幅度修剪) :对每个任务向量,移除绝对值最小的 p%p\%p% 参数:
τk′=τk⋅1∣τk∣≥τ(p)\tau_k' = \tau_k \cdot \mathbb{1}\|\\tau_k\| \\geq \\tau_{(p)}τk′=τk⋅1∣τk∣≥τ(p)
其中 τ(p)\tau_{(p)}τ(p) 是 ∣τk∣|\tau_k|∣τk∣ 的第 ppp 百分位数。
第八章:DARE------随机丢弃与重缩放
8.1 核心思想
8.1.1 观察
观察 :任务向量中的大部分 delta 参数是冗余的------移除它们对模型性能影响很小。
DARE (Yu et al., 2024)利用这个观察:随机丢弃 大部分 delta 参数,然后重缩放剩余参数以补偿。
8.1.2 DARE 的算法
算法 8.1(DARE):
输入:预训练模型 θ_pre, 任务向量 τ, 丢弃率 p
输出:合并后的模型 θ_merged
1. 随机丢弃:对每个参数 i,以概率 p 将 τ[i] 设为 0
mask ~ Bernoulli(1-p)
τ' = τ * mask
2. 重缩放:将剩余参数放大以补偿
τ'' = τ' / (1-p)
3. 合并:θ_merged = θ_pre + λ * τ''
8.1.3 理论分析
定理 8.1(DARE 的无偏性):DARE 的重缩放确保了期望值不变:
Eτ′′=Eτ⋅mask1−p=τ⋅(1−p)1−p=τ\mathbb{E}\\tau'' = \mathbb{E}\left\\frac{\\tau \\cdot \\text{mask}}{1-p}\\right = \frac{\tau \cdot (1-p)}{1-p} = \tauEτ′′=E1−pτ⋅mask=1−pτ⋅(1−p)=τ
证明 :每个 mask 元素的期望为 Emaski=1−p\mathbb{E}\\text{mask}_i = 1-pEmaski=1−p,因此 Eτi′′=τi⋅(1−p)/(1−p)=τi\mathbb{E}\\tau_i'' = \tau_i \cdot (1-p) / (1-p) = \tau_iEτi′′=τi⋅(1−p)/(1−p)=τi。□\square□
定理 8.2(DARE 的方差增加):DARE 增加了参数的方差:
Varτi′′=p1−pτi2\text{Var}\\tau_i'' = \frac{p}{1-p} \tau_i^2Varτi′′=1−ppτi2
推论 8.1 :丢弃率 ppp 越大,方差增加越多。但当 delta 参数接近零时(冗余参数),方差增加的影响很小。
8.2 DARE 与 TIES 的结合
8.2.1 DARE + TIES
算法 8.2(DARE + TIES):
1. 对每个任务向量应用 DARE(随机丢弃 + 重缩放)
2. 对 DARE 后的任务向量应用 TIES(符号选举 + 合并)
8.2.2 理论分析
定理 8.3(DARE + TIES 的优势):DARE 解决了冗余问题,TIES 解决了冲突问题。两者结合可以更有效地合并多个任务向量。
第九章:Model Soups 与多模型融合
9.1 Model Soups
9.1.1 核心思想
Model Soups(Wortsman et al., 2022):将多个在不同超参数或数据增强下训练的模型进行平均。
9.1.2 Uniform Soup
定义 9.1(Uniform Soup):
θsoup=1K∑k=1Kθk\theta_{\text{soup}} = \frac{1}{K} \sum_{k=1}^K \theta_kθsoup=K1k=1∑Kθk
9.1.3 Greedy Soup
算法 9.1(Greedy Soup):
输入:候选模型 {θ_1, ..., θ_K}, 验证集
输出:Greedy Soup 模型
1. 初始化:选择验证集上最好的模型作为初始 soup
2. 重复:
a. 对每个未加入的候选模型,尝试加入 soup
b. 计算加入后的验证集性能
c. 如果性能提升,永久加入该模型
3. 直到没有模型能提升性能
9.2 Task Vectors 的多模型融合
9.2.1 符号共识
定义 9.2(符号共识) :对于 KKK 个任务向量,符号共识定义为:
signi=majority(sign(τ1i),...,sign(τKi))\text{sign}_i = \text{majority}(\text{sign}(\tau_1i), \dots, \text{sign}(\tau_Ki))signi=majority(sign(τ1i),...,sign(τKi))
9.2.2 幅度加权合并
定义 9.3(幅度加权合并):
τmergedi=∑k=1K∣τki∣⋅1sign(τk\[i)=signi]⋅τki∑k=1K1sign(τk\[i)=signi]\tau_{\text{merged}}i = \frac{\sum_{k=1}^K |\tau_ki| \cdot \mathbb{1}\\text{sign}(\\tau_k\[i) = \text{sign}i] \cdot \tau_ki}{\sum{k=1}^K \mathbb{1}\\text{sign}(\\tau_k\[i) = \text{sign}_i]}τmergedi=∑k=1K1sign(τk\[i)=signi]∑k=1K∣τki∣⋅1sign(τk\[i)=signi]⋅τki
第四部分:理论分析与前沿
第十章:合并的理论保证
10.1 合并误差的上界
10.1.1 二次损失假设
定理 10.1(合并误差上界):在二次损失假设下,合并模型与各任务最优模型之间的差距为:
Lk(θmerged)−Lk(θk∗)≤λmax(Hk)2∥θmerged−θk∗∥2\mathcal{L}k(\theta{\text{merged}}) - \mathcal{L}k(\theta_k^*) \leq \frac{\lambda{\max}(H_k)}{2} \|\theta_{\text{merged}} - \theta_k^*\|^2Lk(θmerged)−Lk(θk∗)≤2λmax(Hk)∥θmerged−θk∗∥2
推论 10.1:如果合并后的模型与各任务最优模型的距离很小,合并误差也很小。
10.1.2 正交任务假设
定理 10.2(正交任务的完美合并) :如果各任务的任务向量正交(τiTτj=0\tau_i^T \tau_j = 0τiTτj=0 对 i≠ji \neq ji=j),且损失函数是二次的,则存在缩放系数 λ\lambdaλ 使得:
Lk(θpre+λ∑jτj)=Lk(θpre+τk),∀k\mathcal{L}k(\theta{\text{pre}} + \lambda \sum_j \tau_j) = \mathcal{L}k(\theta{\text{pre}} + \tau_k), \quad \forall kLk(θpre+λj∑τj)=Lk(θpre+τk),∀k
证明 :在二次损失下,正交性确保了交叉项为零。□\square□
10.2 合并的不可能性定理
10.2.1 冲突任务
定理 10.3(冲突任务的合并下界):如果两个任务在某些参数上有相反的最优更新方向,则合并模型的性能不可能同时达到两个任务的最优。
证明 :设 τ1∗=+ϵei\tau_1^* = +\epsilon \mathbf{e}_iτ1∗=+ϵei,τ2∗=−ϵei\tau_2^* = -\epsilon \mathbf{e}iτ2∗=−ϵei(在参数 iii 上相反)。对任何合并 τmerged\tau{\text{merged}}τmerged:
∥τmerged−τ1∗∥2+∥τmerged−τ2∗∥2≥12∥τ1∗−τ2∗∥2=2ϵ2\|\tau_{\text{merged}} - \tau_1^*\|^2 + \|\tau_{\text{merged}} - \tau_2^*\|^2 \geq \frac{1}{2}\|\tau_1^* - \tau_2^*\|^2 = 2\epsilon^2∥τmerged−τ1∗∥2+∥τmerged−τ2∗∥2≥21∥τ1∗−τ2∗∥2=2ϵ2
因此不可能同时精确合并两个任务。□\square□
第十一章:合并与泛化
11.1 合并的正则化效应
11.1.1 平均作为正则化
定理 11.1(模型平均的正则化) :模型平均等价于在参数空间中施加 ℓ2\ell_2ℓ2 正则化:
θˉ=argminθ∑k=1K∥θ−θk∥2\bar{\theta} = \arg\min_\theta \sum_{k=1}^K \|\theta - \theta_k\|^2θˉ=argθmink=1∑K∥θ−θk∥2
推论:模型平均倾向于找到各模型的"中心"------这可能在损失曲面的平坦区域,有利于泛化。
11.1.2 合并与平坦极小值
定理 11.2(合并与平坦性) :合并后的模型通常位于损失曲面的平坦区域------因为它是多个极小值的"平均"。
推论:平坦极小值通常具有更好的泛化性------这解释了为什么合并后的模型泛化性能好。
第十二章:大语言模型的合并实践
12.1 LLM 合并的特殊挑战
12.1.1 模型规模
问题:LLM 的参数量巨大(7B-70B),直接在权重空间中操作需要大量内存。
解决方案:
- 使用低精度存储(FP16/BF16)
- 逐层处理,不需要同时加载所有模型
- 使用任务向量而非完整权重
12.1.2 任务多样性
问题:LLM 的微调任务多种多样(指令跟随、代码生成、数学推理等),任务向量之间的冲突更严重。
解决方案:
- 使用 TIES 或 DARE 处理冲突
- 对不同任务使用不同的缩放系数
12.2 实践建议
12.2.1 合并策略选择
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 2-3 个相似任务 | SLERP | 保持权重范数 |
| 3-10 个不同任务 | TIES-Merging | 处理冲突 |
| 10+ 个任务 | DARE + TIES | 处理冗余和冲突 |
| 需要精确控制 | 任务算术 | 可解释性强 |
12.2.2 超参数选择
| 超参数 | 推荐范围 | 说明 |
|---|---|---|
| 缩放系数 λ\lambdaλ | 0.1 - 1.0 | 在验证集上搜索 |
| 修剪比例 ppp | 50% - 90% | DARE 的丢弃率 |
| 秩 rrr | 16 - 64 | LoRA 的秩 |
第五部分:完整可运行代码实现
第十三章:从零实现权重对齐
python
"""
权重对齐的完整实现。
包含:基于激活的对齐、匈牙利算法。
"""
import numpy as np
from typing import Tuple, List
def compute_activation_similarity(
A1: np.ndarray,
A2: np.ndarray,
) -> np.ndarray:
"""计算两个模型激活的相似度矩阵。
Args:
A1: 模型 1 的激活 (T, n)
A2: 模型 2 的激活 (T, n)
Returns:
S: 相似度矩阵 (n, n)
"""
# 归一化
A1_norm = A1 / (np.linalg.norm(A1, axis=0, keepdims=True) + 1e-8)
A2_norm = A2 / (np.linalg.norm(A2, axis=0, keepdims=True) + 1e-8)
# 余弦相似度
S = A1_norm.T @ A2_norm # (n, n)
return S
def hungarian_algorithm(cost_matrix: np.ndarray) -> np.ndarray:
"""匈牙利算法(简化版)------贪心近似。
Args:
cost_matrix: 代价矩阵 (n, n)
Returns:
permutation: 最优排列
"""
n = cost_matrix.shape[0]
used_rows = set()
used_cols = set()
permutation = np.zeros(n, dtype=int)
# 贪心匹配
for _ in range(n):
best_val = -np.inf
best_i, best_j = -1, -1
for i in range(n):
if i in used_rows:
continue
for j in range(n):
if j in used_cols:
continue
if cost_matrix[i, j] > best_val:
best_val = cost_matrix[i, j]
best_i, best_j = i, j
permutation[best_i] = best_j
used_rows.add(best_i)
used_cols.add(best_j)
return permutation
def align_models_by_activation(
W1: np.ndarray,
W2: np.ndarray,
X: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray]:
"""基于激活的模型对齐。
Args:
W1: 模型 1 的权重 (m, n)
W2: 模型 2 的权重 (m, n)
X: 校准输入 (T, n)
Returns:
W2_aligned: 对齐后的模型 2 权重
perm: 排列
"""
# 计算激活
A1 = X @ W1.T # (T, m)
A2 = X @ W2.T # (T, m)
# 计算相似度
S = compute_activation_similarity(A1, A2)
# 找到最优排列
perm = hungarian_algorithm(S)
# 应用排列
W2_aligned = W2[perm, :]
return W2_aligned, perm
def align_models_by_weight(
W1: np.ndarray,
W2: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray]:
"""基于权重的模型对齐。
Args:
W1: 模型 1 的权重 (m, n)
W2: 模型 2 的权重 (m, n)
Returns:
W2_aligned: 对齐后的模型 2 权重
perm: 排列
"""
# 计算权重相似度
S = np.abs(W1 @ W2.T) # (m, m)
# 找到最优排列
perm = hungarian_algorithm(S)
# 应用排列
W2_aligned = W2[perm, :]
return W2_aligned, perm
def demonstrate_alignment():
"""演示权重对齐。"""
np.random.seed(42)
print("=" * 70)
print("权重对齐演示")
print("=" * 70)
m, n = 32, 64
T = 100
# 创建两个"相关"的模型(模拟从同一预训练模型微调)
W_pre = np.random.randn(m, n) * 0.02
# 模型 1:微调方向 1
delta1 = np.random.randn(m, n) * 0.005
W1 = W_pre + delta1
# 模型 2:微调方向 2,但排列不同
perm_true = np.random.permutation(m)
delta2 = np.random.randn(m, n) * 0.005
W2 = W_pre[perm_true, :] + delta2
X = np.random.randn(T, n) * 0.5
print(f"\n 权重矩阵: {m}x{n}")
print(f" 真实排列: {perm_true[:10]}...")
# 对齐前的距离
dist_before = np.linalg.norm(W1 - W2, 'fro')
print(f"\n 对齐前距离: {dist_before:.6f}")
# 基于权重的对齐
W2_aligned_w, perm_w = align_models_by_weight(W1, W2)
dist_after_w = np.linalg.norm(W1 - W2_aligned_w, 'fro')
print(f" 权重对齐后距离: {dist_after_w:.6f}")
print(f" 排列匹配率: {np.mean(perm_w == perm_true):.2%}")
# 基于激活的对齐
W2_aligned_a, perm_a = align_models_by_activation(W1, W2, X)
dist_after_a = np.linalg.norm(W1 - W2_aligned_a, 'fro')
print(f" 激活对齐后距离: {dist_after_a:.6f}")
print(f" 排列匹配率: {np.mean(perm_a == perm_true):.2%}")
# 对齐后合并
print("\n 对齐后合并效果:")
for alpha in [0.0, 0.25, 0.5, 0.75, 1.0]:
# 不对齐的合并
W_merge_no_align = alpha * W1 + (1 - alpha) * W2
Y_ref = X @ W1.T
Y_no_align = X @ W_merge_no_align.T
mse_no_align = np.mean((Y_ref - Y_no_align) ** 2)
# 对齐后的合并
W_merge_align = alpha * W1 + (1 - alpha) * W2_aligned_a
Y_align = X @ W_merge_align.T
mse_align = np.mean((Y_ref - Y_align) ** 2)
print(f" α={alpha:.2f}: 不对齐 MSE={mse_no_align:.8f}, "
f"对齐 MSE={mse_align:.8f}")
if __name__ == "__main__":
demonstrate_alignment()
第十四章:从零实现 SLERP 与任务算术
python
"""
SLERP 和任务算术的完整实现。
"""
import numpy as np
from typing import Tuple
def slerp(
w1: np.ndarray,
w2: np.ndarray,
t: float,
) -> np.ndarray:
"""球面线性插值(SLERP)。
Args:
w1: 向量 1
w2: 向量 2
t: 插值系数 [0, 1]
Returns:
w: 插值结果
"""
# 归一化
norm1 = np.linalg.norm(w1)
norm2 = np.linalg.norm(w2)
if norm1 < 1e-10 or norm2 < 1e-10:
return (1 - t) * w1 + t * w2
w1_hat = w1 / norm1
w2_hat = w2 / norm2
# 计算夹角
cos_omega = np.clip(np.dot(w1_hat, w2_hat), -1.0, 1.0)
omega = np.arccos(cos_omega)
if omega < 1e-6:
# 夹角很小,退化为线性插值
return (1 - t) * w1 + t * w2
# SLERP
sin_omega = np.sin(omega)
w_hat = (np.sin((1 - t) * omega) / sin_omega) * w1_hat + \
(np.sin(t * omega) / sin_omega) * w2_hat
# 恢复范数(线性插值范数)
norm = (1 - t) * norm1 + t * norm2
w = w_hat * norm
return w
def slerp_matrices(
W1: np.ndarray,
W2: np.ndarray,
t: float,
) -> np.ndarray:
"""对矩阵的每一行进行 SLERP。
Args:
W1: 权重矩阵 1 (m, n)
W2: 权重矩阵 2 (m, n)
t: 插值系数
Returns:
W: 插值结果
"""
m = W1.shape[0]
W = np.zeros_like(W1)
for i in range(m):
W[i] = slerp(W1[i], W2[i], t)
return W
def compute_task_vector(
W_ft: np.ndarray,
W_pre: np.ndarray,
) -> np.ndarray:
"""计算任务向量。
Args:
W_ft: 微调后的权重
W_pre: 预训练权重
Returns:
tau: 任务向量
"""
return W_ft - W_pre
def task_arithmetic_add(
W_pre: np.ndarray,
task_vectors: list,
lambda_scale: float = 1.0,
) -> np.ndarray:
"""任务向量加法。
Args:
W_pre: 预训练权重
task_vectors: 任务向量列表
lambda_scale: 缩放系数
Returns:
W_merged: 合并后的权重
"""
tau_sum = sum(task_vectors)
return W_pre + lambda_scale * tau_sum
def task_arithmetic_subtract(
W_ft: np.ndarray,
tau_forget: np.ndarray,
lambda_scale: float = 1.0,
) -> np.ndarray:
"""任务向量减法(遗忘)。
Args:
W_ft: 微调后的权重
tau_forget: 要遗忘的任务向量
lambda_scale: 缩放系数
Returns:
W_new: 新权重
"""
return W_ft - lambda_scale * tau_forget
def demonstrate_slerp():
"""演示 SLERP 和任务算术。"""
np.random.seed(42)
print("=" * 70)
print("SLERP 与任务算术演示")
print("=" * 70)
m, n = 64, 128
T = 200
# 预训练模型
W_pre = np.random.randn(m, n) * 0.02
# 任务 1 的微调模型
tau1 = np.random.randn(m, n) * 0.005
W_task1 = W_pre + tau1
# 任务 2 的微调模型
tau2 = np.random.randn(m, n) * 0.005
W_task2 = W_pre + tau2
# 测试数据
X = np.random.randn(T, n) * 0.5
# SLERP 演示
print("\n 1. SLERP vs 线性插值")
print(" " + "-" * 40)
print(f" {'t':>6} {'线性插值 MSE':>15} {'SLERP MSE':>15} {'范数变化(线性)':>15} {'范数变化(SLERP)':>15}")
print(f" {'-'*6} {'-'*15} {'-'*15} {'-'*15} {'-'*15}")
Y1 = X @ W_task1.T
Y2 = X @ W_task2.T
for t in [0.0, 0.25, 0.5, 0.75, 1.0]:
# 线性插值
W_linear = (1 - t) * W_task1 + t * W_task2
Y_linear = X @ W_linear.T
mse_linear = np.mean((Y1 - Y_linear) ** 2)
norm_change_linear = np.linalg.norm(W_linear) / np.linalg.norm(W_task1)
# SLERP
W_slerp = slerp_matrices(W_task1, W_task2, t)
Y_slerp = X @ W_slerp.T
mse_slerp = np.mean((Y1 - Y_slerp) ** 2)
norm_change_slerp = np.linalg.norm(W_slerp) / np.linalg.norm(W_task1)
print(f" {t:>6.2f} {mse_linear:>15.8f} {mse_slerp:>15.8f} "
f"{norm_change_linear:>15.4f} {norm_change_slerp:>15.4f}")
# 任务算术演示
print("\n 2. 任务算术")
print(" " + "-" * 40)
# 计算任务向量
tau1 = compute_task_vector(W_task1, W_pre)
tau2 = compute_task_vector(W_task2, W_pre)
print(f" 任务向量 1 范数: {np.linalg.norm(tau1):.6f}")
print(f" 任务向量 2 范数: {np.linalg.norm(tau2):.6f}")
print(f" 任务向量余弦相似度: {np.dot(tau1.flatten(), tau2.flatten()) / (np.linalg.norm(tau1) * np.linalg.norm(tau2)):.4f}")
# 任务向量加法
print(f"\n 任务向量加法 (不同 λ):")
print(f" {'λ':>6} {'任务1 MSE':>12} {'任务2 MSE':>12} {'平均 MSE':>12}")
print(f" {'-'*6} {'-'*12} {'-'*12} {'-'*12}")
Y_task1 = X @ W_task1.T
Y_task2 = X @ W_task2.T
for lam in [0.1, 0.3, 0.5, 0.7, 1.0, 1.5]:
W_merged = task_arithmetic_add(W_pre, [tau1, tau2], lambda_scale=lam)
Y_merged = X @ W_merged.T
mse1 = np.mean((Y_task1 - Y_merged) ** 2)
mse2 = np.mean((Y_task2 - Y_merged) ** 2)
avg_mse = (mse1 + mse2) / 2
print(f" {lam:>6.1f} {mse1:>12.8f} {mse2:>12.8f} {avg_mse:>12.8f}")
# 任务向量减法(遗忘)
print(f"\n 任务向量减法 (遗忘任务 1):")
for lam in [0.0, 0.5, 1.0, 1.5]:
W_new = task_arithmetic_subtract(W_task1, tau1, lambda_scale=lam)
Y_new = X @ W_new.T
mse_vs_pre = np.mean((X @ W_pre.T - Y_new) ** 2)
mse_vs_task1 = np.mean((Y_task1 - Y_new) ** 2)
print(f" λ={lam:.1f}: 与预训练距离={mse_vs_pre:.8f}, 与任务1距离={mse_vs_task1:.8f}")
if __name__ == "__main__":
demonstrate_slerp()
第十五章:从零实现 TIES 与 DARE
python
"""
TIES-Merging 和 DARE 的完整实现。
"""
import numpy as np
from typing import List, Tuple
def ties_trim(
tau: np.ndarray,
trim_ratio: float = 0.5,
) -> np.ndarray:
"""TIES 修剪:移除绝对值最小的 delta 参数。
Args:
tau: 任务向量
trim_ratio: 修剪比例
Returns:
tau_trimmed: 修剪后的任务向量
"""
threshold = np.percentile(np.abs(tau.flatten()), trim_ratio * 100)
mask = np.abs(tau) >= threshold
return tau * mask
def ties_elect_sign(
task_vectors: List[np.ndarray],
) -> np.ndarray:
"""TIES 符号选举:确定每个参数的主符号。
Args:
task_vectors: 任务向量列表
Returns:
sign_mask: 主符号 (+1 或 -1)
"""
# 计算每个参数位置的符号投票
sign_votes = np.zeros_like(task_vectors[0])
for tau in task_vectors:
sign_votes += np.sign(tau)
# 主符号
sign_mask = np.sign(sign_votes)
# 处理零值(默认为 +1)
sign_mask[sign_mask == 0] = 1
return sign_mask
def ties_merge(
task_vectors: List[np.ndarray],
trim_ratio: float = 0.5,
) -> np.ndarray:
"""TIES-Merging 完整流程。
Args:
task_vectors: 任务向量列表
trim_ratio: 修剪比例
Returns:
tau_merged: 合并后的任务向量
"""
# 步骤 1:修剪
trimmed_vectors = [ties_trim(tau, trim_ratio) for tau in task_vectors]
# 步骤 2:符号选举
sign_mask = ties_elect_sign(trimmed_vectors)
# 步骤 3:合并(只保留与主符号一致的参数)
tau_merged = np.zeros_like(task_vectors[0])
count = np.zeros_like(task_vectors[0])
for tau in trimmed_vectors:
# 只保留符号一致的参数
consistent = (np.sign(tau) == sign_mask) | (tau == 0)
tau_merged += tau * consistent
count += consistent.astype(float)
# 平均
count = np.maximum(count, 1)
tau_merged = tau_merged / count
return tau_merged
def dare_drop(
tau: np.ndarray,
drop_rate: float = 0.9,
) -> np.ndarray:
"""DARE 随机丢弃。
Args:
tau: 任务向量
drop_rate: 丢弃率
Returns:
tau_dropped: 丢弃后的任务向量
"""
mask = np.random.random(tau.shape) >= drop_rate
tau_dropped = tau * mask / (1 - drop_rate)
return tau_dropped
def dare_merge(
task_vectors: List[np.ndarray],
drop_rate: float = 0.9,
) -> np.ndarray:
"""DARE 合并。
Args:
task_vectors: 任务向量列表
drop_rate: 丢弃率
Returns:
tau_merged: 合并后的任务向量
"""
# 对每个任务向量应用 DARE
dare_vectors = [dare_drop(tau, drop_rate) for tau in task_vectors]
# 平均
tau_merged = np.mean(dare_vectors, axis=0)
return tau_merged
def dare_ties_merge(
task_vectors: List[np.ndarray],
drop_rate: float = 0.9,
trim_ratio: float = 0.5,
) -> np.ndarray:
"""DARE + TIES 合并。
Args:
task_vectors: 任务向量列表
drop_rate: DARE 丢弃率
trim_ratio: TIES 修剪比例
Returns:
tau_merged: 合并后的任务向量
"""
# 步骤 1:DARE 随机丢弃
dare_vectors = [dare_drop(tau, drop_rate) for tau in task_vectors]
# 步骤 2:TIES 合并
tau_merged = ties_merge(dare_vectors, trim_ratio)
return tau_merged
def demonstrate_ties_dare():
"""演示 TIES 和 DARE。"""
np.random.seed(42)
print("=" * 70)
print("TIES-Merging 与 DARE 演示")
print("=" * 70)
m, n = 64, 128
K = 5 # 任务数量
T = 200
# 预训练模型
W_pre = np.random.randn(m, n) * 0.02
# 生成多个任务的任务向量
task_vectors = []
W_tasks = []
for k in range(K):
tau = np.random.randn(m, n) * 0.005
task_vectors.append(tau)
W_tasks.append(W_pre + tau)
# 测试数据
X = np.random.randn(T, n) * 0.5
print(f"\n 权重矩阵: {m}x{n}")
print(f" 任务数量: {K}")
# 各方法对比
print("\n 1. 不同合并方法对比")
print(" " + "-" * 40)
# 计算各任务的参考输出
Y_refs = [X @ W.T for W in W_tasks]
methods = {}
# 简单平均
tau_avg = np.mean(task_vectors, axis=0)
W_avg = W_pre + tau_avg
Y_avg = X @ W_avg.T
mse_avg = np.mean([np.mean((Y_ref - Y_avg) ** 2) for Y_ref in Y_refs])
methods["简单平均"] = mse_avg
# TIES
tau_ties = ties_merge(task_vectors, trim_ratio=0.5)
W_ties = W_pre + tau_ties
Y_ties = X @ W_ties.T
mse_ties = np.mean([np.mean((Y_ref - Y_ties) ** 2) for Y_ref in Y_refs])
methods["TIES (trim=0.5)"] = mse_ties
# DARE
tau_dare = dare_merge(task_vectors, drop_rate=0.9)
W_dare = W_pre + tau_dare
Y_dare = X @ W_dare.T
mse_dare = np.mean([np.mean((Y_ref - Y_dare) ** 2) for Y_ref in Y_refs])
methods["DARE (drop=0.9)"] = mse_dare
# DARE + TIES
tau_dt = dare_ties_merge(task_vectors, drop_rate=0.9, trim_ratio=0.5)
W_dt = W_pre + tau_dt
Y_dt = X @ W_dt.T
mse_dt = np.mean([np.mean((Y_ref - Y_dt) ** 2) for Y_ref in Y_refs])
methods["DARE + TIES"] = mse_dt
print(f"\n {'方法':>20} {'平均 MSE':>15}")
print(f" {'-'*20} {'-'*15}")
for name, mse in methods.items():
print(f" {name:>20} {mse:>15.8f}")
# 不同丢弃率的影响
print("\n 2. DARE 丢弃率的影响")
print(" " + "-" * 40)
print(f" {'丢弃率':>10} {'平均 MSE':>15} {'参数非零率':>12}")
print(f" {'-'*10} {'-'*15} {'-'*12}")
for drop_rate in [0.0, 0.5, 0.7, 0.9, 0.95, 0.99]:
tau_d = dare_merge(task_vectors, drop_rate=drop_rate)
W_d = W_pre + tau_d
Y_d = X @ W_d.T
mse_d = np.mean([np.mean((Y_ref - Y_d) ** 2) for Y_ref in Y_refs])
nnz = np.mean(tau_d != 0)
print(f" {drop_rate:>10.2f} {mse_d:>15.8f} {nnz:>12.2%}")
# 不同修剪比例的影响
print("\n 3. TIES 修剪比例的影响")
print(" " + "-" * 40)
print(f" {'修剪比例':>10} {'平均 MSE':>15}")
print(f" {'-'*10} {'-'*15}")
for trim_ratio in [0.0, 0.3, 0.5, 0.7, 0.9]:
tau_t = ties_merge(task_vectors, trim_ratio=trim_ratio)
W_t = W_pre + tau_t
Y_t = X @ W_t.T
mse_t = np.mean([np.mean((Y_ref - Y_t) ** 2) for Y_ref in Y_refs])
print(f" {trim_ratio:>10.2f} {mse_t:>15.8f}")
if __name__ == "__main__":
demonstrate_ties_dare()
第十六章:完整合并 Pipeline 与精度对比
python
"""
完整的模型合并 Pipeline。
对比所有合并方法。
"""
import numpy as np
from typing import List
def run_full_comparison():
"""运行完整的合并方法对比。"""
np.random.seed(42)
print("=" * 70)
print("模型合并方法综合对比")
print("=" * 70)
# 设置
m, n = 64, 128
K = 5
T = 300
# 预训练模型
W_pre = np.random.randn(m, n) * 0.02
# 任务向量(模拟不同任务)
task_vectors = []
W_tasks = []
for k in range(K):
tau = np.random.randn(m, n) * 0.005
task_vectors.append(tau)
W_tasks.append(W_pre + tau)
X = np.random.randn(T, n) * 0.5
Y_refs = [X @ W.T for W in W_tasks]
print(f"\n 权重: {m}x{n}, 任务数: {K}")
# 定义所有方法
def simple_average(taus, **kwargs):
return np.mean(taus, axis=0)
def slerp_average(taus, **kwargs):
# 简化:对每个参数位置独立 SLERP
result = taus[0].copy()
for i in range(1, len(taus)):
t = 1.0 / (i + 1)
# 简化的 SLERP(使用线性插值近似)
result = (1 - t) * result + t * taus[i]
return result
def ties_method(taus, trim_ratio=0.5, **kwargs):
# 修剪
trimmed = []
for tau in taus:
threshold = np.percentile(np.abs(tau.flatten()), trim_ratio * 100)
trimmed.append(tau * (np.abs(tau) >= threshold))
# 符号选举
sign_votes = sum(np.sign(tau) for tau in trimmed)
sign_mask = np.sign(sign_votes)
sign_mask[sign_mask == 0] = 1
# 合并
result = np.zeros_like(taus[0])
count = np.zeros_like(taus[0])
for tau in trimmed:
consistent = (np.sign(tau) == sign_mask) | (tau == 0)
result += tau * consistent
count += consistent.astype(float)
count = np.maximum(count, 1)
return result / count
def dare_method(taus, drop_rate=0.9, **kwargs):
dropped = []
for tau in taus:
mask = np.random.random(tau.shape) >= drop_rate
dropped.append(tau * mask / (1 - drop_rate))
return np.mean(dropped, axis=0)
def dare_ties_method(taus, drop_rate=0.9, trim_ratio=0.5, **kwargs):
dropped = []
for tau in taus:
mask = np.random.random(tau.shape) >= drop_rate
dropped.append(tau * mask / (1 - drop_rate))
return ties_method(dropped, trim_ratio=trim_ratio)
# 测试所有方法
methods = {
"简单平均": simple_average,
"TIES (trim=0.5)": lambda taus: ties_method(taus, trim_ratio=0.5),
"DARE (drop=0.9)": lambda taus: dare_method(taus, drop_rate=0.9),
"DARE + TIES": lambda taus: dare_ties_method(taus, drop_rate=0.9, trim_ratio=0.5),
}
print(f"\n {'方法':>20} {'任务1':>10} {'任务2':>10} {'任务3':>10} {'任务4':>10} {'任务5':>10} {'平均':>10}")
print(f" {'-'*20} {'-'*10} {'-'*10} {'-'*10} {'-'*10} {'-'*10} {'-'*10}")
for name, method_fn in methods.items():
tau_merged = method_fn(task_vectors)
W_merged = W_pre + tau_merged
Y_merged = X @ W_merged.T
mses = [np.mean((Y_ref - Y_merged) ** 2) for Y_ref in Y_refs]
avg_mse = np.mean(mses)
print(f" {name:>20} ", end="")
for mse in mses:
print(f"{mse:>10.6f} ", end="")
print(f"{avg_mse:>10.6f}")
# 缩放系数的影响
print(f"\n 缩放系数 λ 的影响 (简单平均):")
print(f" {'λ':>6} {'平均 MSE':>15}")
print(f" {'-'*6} {'-'*15}")
for lam in [0.1, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0]:
tau_merged = lam * simple_average(task_vectors)
W_merged = W_pre + tau_merged
Y_merged = X @ W_merged.T
avg_mse = np.mean([np.mean((Y_ref - Y_merged) ** 2) for Y_ref in Y_refs])
print(f" {lam:>6.1f} {avg_mse:>15.8f}")
if __name__ == "__main__":
run_full_comparison()
附录:关键公式汇总
A.1 损失曲面与 LMC
| 公式 | 表达式 |
|---|---|
| 损失曲面 | L(θ)=Eℓ(fθ(x),y)\mathcal{L}(\theta) = \mathbb{E}\\ell(f_\\theta(x), y)L(θ)=Eℓ(fθ(x),y) |
| LMC 条件 | L(αθ1+(1−α)θ2)≤max(L(θ1),L(θ2))\mathcal{L}(\alpha\theta_1 + (1-\alpha)\theta_2) \leq \max(\mathcal{L}(\theta_1), \mathcal{L}(\theta_2))L(αθ1+(1−α)θ2)≤max(L(θ1),L(θ2)) |
| 凸性条件 | λmax(H)≤0\lambda_{\max}(H) \leq 0λmax(H)≤0 |
A.2 任务算术
| 公式 | 表达式 |
|---|---|
| 任务向量 | τ=θft−θpre\tau = \theta_{\text{ft}} - \theta_{\text{pre}}τ=θft−θpre |
| 加法合并 | θmerged=θpre+λ∑kτk\theta_{\text{merged}} = \theta_{\text{pre}} + \lambda \sum_k \tau_kθmerged=θpre+λ∑kτk |
| 减法遗忘 | θnew=θft−λτforget\theta_{\text{new}} = \theta_{\text{ft}} - \lambda \tau_{\text{forget}}θnew=θft−λτforget |
A.3 SLERP
| 公式 | 表达式 |
|---|---|
| SLERP | SLERP(v1,v2;t)=sin((1−t)Ω)sinΩv1+sin(tΩ)sinΩv2\text{SLERP}(\mathbf{v}_1, \mathbf{v}_2; t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}\mathbf{v}_1 + \frac{\sin(t\Omega)}{\sin\Omega}\mathbf{v}_2SLERP(v1,v2;t)=sinΩsin((1−t)Ω)v1+sinΩsin(tΩ)v2 |
| 夹角 | Ω=arccos(v^1⋅v^2)\Omega = \arccos(\hat{\mathbf{v}}_1 \cdot \hat{\mathbf{v}}_2)Ω=arccos(v^1⋅v^2) |
A.4 TIES-Merging
| 步骤 | 操作 |
|---|---|
| 修剪 | $\tau[ |
| 符号选举 | signi=majority(sign(τki))\text{sign}_i = \text{majority}(\text{sign}(\tau_ki))signi=majority(sign(τki)) |
| 合并 | τmergedi=mean(τki:sign(τki)=signi)\tau_{\text{merged}}i = \text{mean}(\tau_ki : \text{sign}(\tau_ki) = \text{sign}_i)τmergedi=mean(τki:sign(τki)=signi) |
A.5 DARE
| 公式 | 表达式 |
|---|---|
| 随机丢弃 | τ′=τ⋅mask\tau' = \tau \cdot \text{mask}τ′=τ⋅mask, mask ∼\sim∼ Bernoulli(1−p)(1-p)(1−p) |
| 重缩放 | τ′′=τ′/(1−p)\tau'' = \tau' / (1-p)τ′′=τ′/(1−p) |
| 无偏性 | Eτ′′=τ\mathbb{E}\\tau'' = \tauEτ′′=τ |
参考文献
- Wortsman, M., et al. (2022). Model soups: averaging weights of multiple fine-tuned models improves accuracy without increasing inference time. ICML.
- Ilharco, G., et al. (2023). Editing models with task arithmetic. ICLR.
- Ainsworth, S., et al. (2023). Git rebasin: Merging models modulo permutation symmetries. ICLR.
- Yadav, P., et al. (2023). TIES-Merging: Resolving interference when merging models. NeurIPS.
- Yu, L., et al. (2024). Language models are super mario: Absorbing abilities from homologous models as a free lunch. ICML.
- Neyshabur, B., et al. (2020). What is being transferred in transfer learning? NeurIPS.
- Frankle, J., & Carlin, M. (2019). The lottery ticket hypothesis. ICLR.
- Matena, M., & Raffel, C. (2022). Merging models with Fisher-weighted averaging. NeurIPS.
- Singh, S., & Jaggi, M. (2020). Model fusion via optimal transport. NeurIPS.
- Don-Yehiya, S., et al. (2022). ColD fusion: Collaborative descent for distributed multitask finetuning. ACL.