
文章目录
-
- [1. 先把问题讲清楚:模型能力不能像插件一样相加](#1. 先把问题讲清楚:模型能力不能像插件一样相加)
- [2. 合并前提:不是所有模型都能合](#2. 合并前提:不是所有模型都能合)
- [3. 权重平均:最简单也最粗糙](#3. 权重平均:最简单也最粗糙)
- [4. Task Arithmetic:合并相对 base 的变化](#4. Task Arithmetic:合并相对 base 的变化)
- [5. 方向冲突:为什么简单相加会出问题](#5. 方向冲突:为什么简单相加会出问题)
- [6. SLERP:在球面上做平滑插值](#6. SLERP:在球面上做平滑插值)
- [7. TIES-Merge:先处理符号冲突](#7. TIES-Merge:先处理符号冲突)
- [8. DARE:随机丢弃 delta 再缩放](#8. DARE:随机丢弃 delta 再缩放)
- [9. LoRA/adapter 合并与全模型合并](#9. LoRA/adapter 合并与全模型合并)
- [10. 合并实验怎么做](#10. 合并实验怎么做)
- [11. 常见失败模式](#11. 常见失败模式)
- [12. 常见误区](#12. 常见误区)
- [13. 小结](#13. 小结)
- 大模型视角
- 下一篇
摘要:模型合并试图把多个同源模型或 adapter 的能力组合起来,减少重新训练成本。它不是"把代码能力模型 + 客服能力模型 = 全能模型"的魔法,而是在权重空间里做有约束的实验。本文会从最简单的权重平均讲起,解释 task vector、delta、SLERP 球面插值、TIES-Merge 的符号冲突处理、DARE 的随机丢弃和缩放,并给出可运行的小代码帮助你理解这些方法的直觉。最后会重点讨论模型合并的前提条件、评估清单和常见失败模式。
- 前置知识:专栏一的向量、矩阵、线性插值、低秩分解;专栏二 SFT/LoRA 可配合阅读
- 阅读时间:50-60 分钟
- 代码环境:Python 3.10+,示例只依赖标准库
- 读完目标:能解释为什么同源模型才适合合并,能理解 delta/task vector 的意义,能知道 SLERP、TIES、DARE 分别想缓解什么问题
1. 先把问题讲清楚:模型能力不能像插件一样相加
假设你有一个基础模型 Base,又基于它微调了两个模型:
text
Code-Model:更擅长代码解释和修复
Customer-Model:更擅长客服话术和业务问答
你可能会问:能不能不重新训练,直接把两个模型合成一个,同时保留代码和客服能力?这就是模型合并想探索的问题。
但权重不是插件市场。模型参数是高维空间里的连续数值,能力分布在大量层和矩阵中。两个微调模型在某些权重方向上可能互相增强,也可能互相冲突。合并结果可能更好,也可能两个能力都下降。
模型合并的价值在于:
| 价值 | 说明 |
|---|---|
| 成本低 | 不需要完整重新训练 |
| 实验快 | 可以快速比较多个合并权重 |
| 适配 adapter | 多个 LoRA/adapter 可以尝试组合 |
| 可探索 | 帮助观察任务变化在权重空间里的关系 |
它的风险也很明显:
| 风险 | 说明 |
|---|---|
| 能力冲突 | 两个任务对同一权重方向要求相反 |
| 格式错乱 | chat template 或对齐行为被破坏 |
| 安全退化 | 拒答边界和偏好优化结果被冲淡 |
| 不可解释 | 单个 prompt 看似好,整体评估下降 |
| 前提严格 | 架构、tokenizer、base 不一致通常不能直接合并 |
所以本文的核心态度是:模型合并可以做低成本实验,但必须建立前提检查和评估闭环。
2. 合并前提:不是所有模型都能合
开始讲算法前,先讲更重要的前提。直接合并通常要求:
text
1. 架构完全一致;
2. 参数形状完全一致;
3. tokenizer 完全一致;
4. 最好来自同一个 base model;
5. chat template 和特殊 token 一致;
6. 微调方法和目标不要严重冲突;
7. 有独立评估集验证合并结果。
如果两个模型一个来自 LLaMA 基座,一个来自 Qwen 基座,直接按权重平均没有意义。即使参数形状碰巧一样,权重空间和 tokenizer 语义也不对齐。
最适合合并的情况通常是:
text
Base -> FineTune A
Base -> FineTune B
Base -> FineTune C
也就是多个模型都从同一个 Base 出发,只是在不同任务或数据上微调。这样它们的权重差异可以被解释为"相对同一个基座的任务变化"。
3. 权重平均:最简单也最粗糙
最简单的合并是加权平均:
W m e r g e = α W A + ( 1 − α ) W B W_{merge} = \alpha W_A + (1 - \alpha) W_B Wmerge=αWA+(1−α)WB
先用大白话解释:把两个模型同名参数逐元素平均, α \alpha α 控制更偏向哪个模型。
逐项解释:
- W A W_A WA:模型 A 的某个权重张量;
- W B W_B WB:模型 B 的对应权重张量;
- α \alpha α:合并权重,越大越偏向 A;
- W m e r g e W_{merge} Wmerge:合并后的权重。
用列表模拟:
python
# Python 3.10+
def weighted_average(a, b, alpha):
return [alpha * x + (1 - alpha) * y for x, y in zip(a, b)]
w_a = [1.0, 2.0, 3.0]
w_b = [1.4, 1.8, 2.6]
for alpha in [0.25, 0.5, 0.75]:
print(alpha, weighted_average(w_a, w_b, alpha))
权重平均的优点是简单。缺点是它完全不知道哪些参数变化重要、哪些方向冲突、哪些层更敏感。它适合做 baseline,不适合作为无需评估的最终答案。
4. Task Arithmetic:合并相对 base 的变化
如果多个模型来自同一个 base,更常见的思路是先计算相对 base 的变化量,也叫 task vector 或 delta。
对于任务 A:
Δ A = W A − W 0 \Delta_A = W_A - W_0 ΔA=WA−W0
对于任务 B:
Δ B = W B − W 0 \Delta_B = W_B - W_0 ΔB=WB−W0
合并时写成:
W m e r g e = W 0 + λ A Δ A + λ B Δ B W_{merge} = W_0 + \lambda_A \Delta_A + \lambda_B \Delta_B Wmerge=W0+λAΔA+λBΔB
逐项解释:
- W 0 W_0 W0:基础模型权重;
- W A , W B W_A, W_B WA,WB:两个微调模型权重;
- Δ A , Δ B \Delta_A, \Delta_B ΔA,ΔB:微调相对 base 改了什么;
- λ A , λ B \lambda_A, \lambda_B λA,λB:每个任务变化的缩放系数。
直觉上,delta 比完整权重更有意义。完整权重里绝大部分是基础能力,两个模型大部分相同;delta 才是任务微调带来的变化。
代码模拟:
python
# Python 3.10+
def sub(a, b):
return [x - y for x, y in zip(a, b)]
def add(a, b):
return [x + y for x, y in zip(a, b)]
def scale(a, s):
return [x * s for x in a]
base = [1.0, 1.0, 1.0, 1.0]
model_a = [1.2, 0.9, 1.0, 1.1]
model_b = [0.9, 1.3, 1.0, 0.8]
delta_a = sub(model_a, base)
delta_b = sub(model_b, base)
merged = add(base, add(scale(delta_a, 0.7), scale(delta_b, 0.5)))
print("delta_a", delta_a)
print("delta_b", delta_b)
print("merged", merged)
这段代码告诉我们:模型合并关注的是"每个任务把 base 往哪个方向推"。如果两个任务在同一位置方向相反,就会产生冲突。
5. 方向冲突:为什么简单相加会出问题
看一个 toy 例子:
text
base 权重某位置 = 1.0
代码微调希望变成 1.3,delta = +0.3
客服微调希望变成 0.7,delta = -0.3
如果直接相加,两个 delta 抵消,回到 base;如果权重设置不当,还可能偏向某一方,损害另一方能力。
在真实模型中,这种冲突发生在高维权重空间里。它不一定对应单个可解释参数,但会影响层的表示、注意力投影、FFN 方向和输出风格。
方向冲突常见于:
| 冲突来源 | 例子 |
|---|---|
| 任务目标不同 | 一个模型鼓励详细解释,另一个鼓励短答 |
| 安全边界不同 | 一个模型拒答更强,另一个更开放 |
| 数据风格不同 | 代码模型和客服模型输出习惯差异大 |
| chat template 不同 | 特殊 token 和角色格式不一致 |
| 微调强度不同 | 一个 delta 很大,主导合并结果 |
TIES-Merge 和 DARE 等方法,部分就是为了缓解这些冲突。
6. SLERP:在球面上做平滑插值
SLERP(Spherical Linear Interpolation,球面线性插值)原本常用于在两个向量方向之间平滑插值。与普通线性插值不同,SLERP 更关注方向和范数的平滑变化。
普通线性插值是:
( 1 − t ) A + t B (1-t)A + tB (1−t)A+tB
SLERP 可以写成:
slerp ( A , B , t ) = sin ( ( 1 − t ) θ ) sin θ A + sin ( t θ ) sin θ B \text{slerp}(A,B,t) = \frac{\sin((1-t)\theta)}{\sin\theta}A + \frac{\sin(t\theta)}{\sin\theta}B slerp(A,B,t)=sinθsin((1−t)θ)A+sinθsin(tθ)B
其中 θ \theta θ 是向量 A A A 和 B B B 的夹角。
不用被公式吓到。它想做的事是:如果两个权重向量可以看成高维空间里的方向,普通插值走直线,SLERP 在球面上沿角度平滑过渡。
可运行代码:
python
# Python 3.10+
import math
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def norm(a):
return math.sqrt(dot(a, a))
def slerp(a, b, t):
na = norm(a)
nb = norm(b)
if na == 0 or nb == 0:
return [(1 - t) * x + t * y for x, y in zip(a, b)]
cos_theta = dot(a, b) / (na * nb)
cos_theta = max(-1.0, min(1.0, cos_theta))
theta = math.acos(cos_theta)
if abs(theta) < 1e-8:
return a[:]
sin_theta = math.sin(theta)
left = math.sin((1 - t) * theta) / sin_theta
right = math.sin(t * theta) / sin_theta
return [left * x + right * y for x, y in zip(a, b)]
print(slerp([1.0, 0.0], [0.0, 1.0], 0.5))
SLERP 常用于两个模型之间的平滑合并,尤其是希望保持权重方向性质时。但它也不是万能:如果两个模型本身能力冲突严重,平滑插值仍然可能得到中庸甚至退化结果。
7. TIES-Merge:先处理符号冲突
TIES-Merge 的直觉是:多个 task vector 合并时,不应该把所有 delta 直接相加。很多小变化可能是噪声;同一位置方向相反的变化可能会互相破坏。
可以把它简化成三步:
text
Trim:只保留重要 delta,裁掉小幅变化;
Elect Sign:为每个位置选择主导符号;
Merge:只合并与主导符号一致的变化。
下面写一个非常简化的 toy 版本。它不是论文级实现,只用来解释直觉:
python
# Python 3.10+
def sign(x):
return 1 if x > 0 else (-1 if x < 0 else 0)
def ties_merge(deltas, threshold=0.05):
merged = []
width = len(deltas[0])
for j in range(width):
values = [d[j] if abs(d[j]) >= threshold else 0.0 for d in deltas]
sign_score = sum(sign(v) for v in values)
elected = sign(sign_score)
if elected == 0:
merged.append(0.0)
continue
aligned = [v for v in values if sign(v) == elected]
merged.append(sum(aligned) / max(len(aligned), 1))
return merged
delta_code = [0.20, -0.10, 0.02, 0.30]
delta_chat = [0.15, 0.12, -0.03, -0.25]
print(ties_merge([delta_code, delta_chat], threshold=0.05))
这个例子里,小于阈值的变化被裁掉;符号冲突的位置会选主导方向;最后只合并方向一致的 delta。
TIES 的核心不是"符号投票一定正确",而是提醒我们:模型合并要处理冲突,不能无脑相加。
8. DARE:随机丢弃 delta 再缩放
DARE 的直觉是:微调 delta 中有很多变化可能是冗余的。随机丢弃一部分 delta,再对保留部分做缩放,有时可以在保留任务能力的同时减少冲突。
一个简化公式可以理解为:
text
以概率 p 丢弃 delta 中的元素;
保留的元素除以 (1-p),保持期望幅度不变。
代码模拟:
python
# Python 3.10+
import random
def dare(delta, drop_rate, seed=0):
random.seed(seed)
keep_prob = 1.0 - drop_rate
result = []
for x in delta:
if random.random() < keep_prob:
result.append(x / keep_prob)
else:
result.append(0.0)
return result
print(dare([0.1, -0.2, 0.05, 0.3, -0.4], drop_rate=0.4, seed=42))
为什么丢弃后还要缩放?因为如果只丢弃不缩放,delta 的整体幅度会变小。除以保留概率,可以让保留下来的元素在期望上维持原来的总强度。
DARE 不意味着随机就一定更好。它是一种降低冗余和冲突的策略,实际效果依赖模型、任务、drop rate 和评估。
9. LoRA/adapter 合并与全模型合并
合并不一定发生在完整模型权重上,也可以发生在 LoRA adapter 上。
LoRA 微调后,基础模型不变,任务变化主要在 adapter 权重里。多个 adapter 如果来自同一个 base 和相近训练设置,可以尝试:
text
1. 合并 adapter delta;
2. 调整每个 adapter 权重系数;
3. 将 adapter merge 回 base 后再评估;
4. 保留多 adapter 动态路由,而不是强行合成一个。
全模型合并与 adapter 合并的取舍:
| 方式 | 优点 | 风险 |
|---|---|---|
| 全模型合并 | 推理时单模型,部署简单 | 权重冲突和回滚成本高 |
| Adapter 合并 | 成本低,易实验 | 仍可能冲突,需要同基座 |
| 多 adapter 路由 | 可按任务选择能力 | 推理系统更复杂 |
如果任务边界清晰,比如代码任务用代码 adapter、客服任务用客服 adapter,动态选择 adapter 可能比强行合并更稳。
10. 合并实验怎么做
模型合并应该像实验,而不是像发布流程。推荐步骤:
text
1. 确认 base、tokenizer、架构、chat template 一致;
2. 准备模型 A、B、C 的单独评估结果;
3. 选择合并方法:平均、task arithmetic、SLERP、TIES、DARE;
4. 设置少量合并权重网格,例如 0.2/0.5/0.8;
5. 生成多个候选合并模型;
6. 跑自动评估和人工样例;
7. 检查安全边界和格式;
8. 与原模型和单任务模型对比;
9. 只保留有明确收益的合并结果。
不要只试一个 prompt。模型合并非常容易出现"演示样例很好,系统评估变差"的情况。
评估表可以这样设计:
| 评估项 | 目的 |
|---|---|
| Base 通用能力 | 是否损害基础能力 |
| 任务 A 能力 | 是否保留模型 A 优势 |
| 任务 B 能力 | 是否保留模型 B 优势 |
| 格式遵循 | JSON、Markdown、工具调用是否稳定 |
| 安全拒答 | 是否破坏对齐边界 |
| 长上下文 | 是否出现位置或记忆退化 |
| 人工样例 | 是否有明显风格混乱和幻觉 |
如果合并后每项都比单任务模型差一点,整体可能并不值得发布。
11. 常见失败模式
模型合并常见失败包括:
| 失败模式 | 表现 | 可能原因 |
|---|---|---|
| 能力互相抵消 | 两个任务都没保住 | delta 方向冲突 |
| 输出风格混乱 | 有时像代码模型,有时像客服模型 | 数据风格差异大 |
| 安全边界变弱 | 更容易回答高风险问题 | 对齐模型被未对齐 delta 冲淡 |
| 过度拒答 | 正常问题也拒答 | 安全模型权重过高 |
| 格式错乱 | chat template、JSON、工具调用不稳定 | 模板或微调目标不一致 |
| 长文本退化 | 长上下文任务变差 | 合并影响注意力或位置相关能力 |
出现失败时,不要只调合并系数。要回到前提检查:是否同基座、同 tokenizer、同模板、评估是否覆盖冲突任务、微调强度是否差异太大。
12. 常见误区
误区一:模型合并等于能力相加。 合并可能增强,也可能互相干扰。能力是否保留必须靠评估验证。
误区二:不同基座只要参数形状一样也能合。 权重空间和 tokenizer 语义不对齐时,直接合并通常没有意义。
误区三:SLERP/TIES/DARE 是万能高级方法。 它们只是缓解某些合并问题,无法解决任务本身冲突和数据质量问题。
误区四:合并可以替代训练。 合并适合低成本探索,不等于系统性训练和对齐。
误区五:只看几个 prompt 就能判断成功。 合并后的安全、格式、通用能力和各任务能力都要重新评估。
13. 小结
模型合并是在权重空间里组合能力的工程实验。权重平均简单但粗糙;Task Arithmetic 关注相对 base 的任务变化;SLERP 用球面插值让两个权重方向平滑过渡;TIES-Merge 尝试处理符号冲突;DARE 通过随机丢弃和缩放减少冗余 delta。它们都建立在重要前提上:最好同基座、同架构、同 tokenizer、同模板,并且必须有完整评估。
对开发者来说,最重要的不是记住每个方法名字,而是理解模型合并的边界:它能快速试验,不保证能力叠加;它能降低训练成本,不替代数据、训练和对齐;它能生成候选模型,但能不能上线要靠评估说话。
大模型视角
从大模型技术主线看,模型合并代表了"在权重空间做工程"的思路:不重新训练,而是把已有的能力向量组合复用。开源社区大量合并模型出自这种思路,它便宜、快速,但也提醒我们:没有数据和训练支撑的组合,能力上限是有限的。
下一篇
下一篇进入 SFT。有监督微调是把基础模型调成可用助手的关键步骤,也是后续 LoRA、QLoRA、DPO 和 RLHF 的基础。