上周三晚上,我干了件听起来很像炼丹的事:把一个擅长写代码的 7B 模型和一个擅长中文问答的 7B 模型,按七三开的权重"调"在了一起,全程没有训练一个参数。跑通只花了二十来分钟,但我盯着评测结果愣了半天——两个单项各自都还行的模型,合出来不是一加一等于二,也不是等于一,大概是个一点六。丢分丢得还特别刁钻:代码题照样能打,中文回答里开始隔三差五蹦英文单词,像个刚留学回来的插班生。
显存先给我上了一课。两个 7B 的 fp16 权重(半精度,用 16 位数字存一个参数)加起来 28G 起步,合并过程还得额外腾地方放中间结果,我那台 24G 显存的卡当场罢工。丹最后是借了张 A100 才炼上的——所谓"零训练",省的是训练成本,不是显存,这笔账你得先算明白。
这活儿有个正经名字,叫模型合并(Model Merging):把两个或多个已经训练好的模型权重,按一定策略组合成一份新权重,不跑训练、不碰数据。过去想做一个"既懂代码又懂中文"的模型,路子无非是再训一轮——要数据要钱要时间——或者同时养两个模型、按请求分流,两份权重两份部署。合并给了一条中间路:一份权重,一份显存,一份部署。
这玩法不算新东西,但它火起来的样子挺有意思。2023 年那阵子,Hugging Face 的开源榜单(Open LLM Leaderboard)前排一度出现一批来路一栏写着"Merged"的模型,社区还长出了 mergekit 这样的开源合并工具,把整个流程压进几行 YAML(一种配置文件格式)。我那次跑的是最常用的 SLERP(球面插值——把两份权重想象成球面上的两个点,沿弧线取中间路径,而不是硬走直线穿过球心),配置写出来就这么几行:
slices: - sources: - model: coder-7b - model: chat-7b merge_method: slerp parameters: t: 0.7 dtype: bfloat16问题恰恰就藏在这几行配置里:比例不是拍脑袋的事。SLERP 比硬平均稳,但这个 t 值,我是从 0.5 一路试到 0.8 才定的。顺嘴说下我栽过的另一个坑:第一轮评测我只跑了代码和中文问答两个目标项,看着都挺好,差点就交差了。后来多心补跑了通用能力,发现有一项掉了小几个点——合并没有"只动我想动的地方"这回事,权重是整个揉在一起的,你想要的和不想要的会一起进来。
那为什么直接做算术平均,大概率得到一个两边都不像的四不像?论文早就讲透了:微调在权重上留下的增量,不同任务的方向可能相反,一平均就互相抵消,谁的努力都没落地。2023 年 NeurIPS(机器学习领域的顶会)上有篇 TIES-Merging,给的解法拆开看就干了这么几件事:先把每个模型不重要的增量剪掉,再对方向打架的参数投票定一个统一方向,末了才做合并。真正颠覆我认知的是后续另一项工作(DARE,出自那篇标题起得很欢乐的论文《Language Models are Super Mario》)测出来的结果:微调带来的参数变化,随机扔掉九成、再按比例缩放回去,效果几乎不掉。九成。也就是说微调的改动里大半是冗余,这个发现直接把合并的容错空间撑大了一截——你甚至不用太纠结哪个参数该留,平均意义上的"杂质"比想象中无害。
还有个更麻烦的事:出了怪行为没法归因。后来同事在群里问"这模型出了 bug 算谁的",我盯着屏幕想了十秒,发现这问题我自己都答不上来——两个父模型的毛病混在一起,连排查从哪边下手都得靠猜。这也是很多团队不敢把合并模型直接放生产的原因:不是能力不够,是责任没法落账。
把这事放到更长的时间轴上看,我赌合并的性价比会越来越划算,但天花板也摆在明面上。划算的一面:基座越强,微调需要的增量越薄,DARE 那个九成冗余的发现其实就是个信号——改动本身就轻,把几份轻量改动组合起来的成本自然低。开源社区已经在这么玩了:你出一个数学特化版,我出一个法律特化版,有人专门按配方把它们合到一起,"配方"本身成了新的创作物。天花板也好理解:合并拼不出基座没有的能力,两杯淡水兑不出海水,底模不会的东西,怎么揉都不会。它是省钱的活,不是造神的活。
写到这,把两个我自己也没想明白的问题抛给大家:你们团队要是拿到一个合并模型,敢直接放生产吗?真出了诡异行为,你是拆开重合,还是干脆回到单模型加微调的老路?评论区聊聊,尤其想听听炼过丹的朋友,归因这个死结你们是怎么解的。