Day30 | LoRA 合并实战——合并完模型没坏,但悄悄替你扣了 3 分

发布时间:2026/7/18 20:16:08
Day30 | LoRA 合并实战——合并完模型没坏,但悄悄替你扣了 3 分 苦猿的大模型日记 · Day30 · LoRA 合并实战-帮普通人把AI学进简历系列前言两次评测一个 87.3一个 82.1上周我在整理一个 LoRA 微调项目的验收报告翻到两行数字愣住了。合并前用 peft 加载 adapter 跑评测集综合得分87.3。合并后执行完merge_and_unload()同一套评测集再跑一遍综合得分82.1。中间掉了5.2 分。我第一反应是代码写错了——重新跑分数没变。我又找了几条模型输出人工看一句一句读下来模型说话完全正常没有乱码没有重复没有答非所问。就是评测分数实打实地掉了。这才是最麻烦的地方。要是模型合并完直接胡言乱语你三秒就能发现回滚重来就行。可它现在看起来一切正常你甚至可能会把这 5 分的差距归咎于评测集有波动——直到某天线上效果被投诉你才想起来查这一步。今天这篇就拆一拆合并这两行代码背后到底藏着哪些容易被忽略的假设。PART 01先搞懂——合并这两个字合的是什么LoRA 的核心公式很简单W W (alpha / r) * B AW是 base 模型冻结的原始权重A、B是训练出来的两个小矩阵alpha和r是缩放系数和秩。训练时推理走的是双路径输入先过一遍冻结的W再单独过一遍B A这条旁路两边算完的结果加起来才是最终输出。W全程没动adapter 是挂在旁边的。合并时你把(alpha/r) * B A这坨东西真正加进W里生成一个新的、单一的权重矩阵。合并完之后A、B可以直接扔掉——模型看起来就是个普普通通的完整模型推理路径变成单路径。理论上这两种算法路径输出应该完全一致。这是合并操作的数学基础也是几乎所有教程讲到这一步就结束的原因——公式写完了代码两行收工。但理论上一致和工程上一致中间隔着不少东西。PART 02三个真实会让合并悄悄掉点的病因我把自己踩过和在 issue 区翻到的案例归了归类基本逃不出这三种。病因 1精度没对齐加法悄悄丢了尾数LoRA 训练时A、B两个矩阵通常用 bf16 或 fp16 存。但很多流程里base 权重W加载进来的精度可能不完全一致——尤其是从 QLoRANF4 量化训练完之后直接合并情况更复杂。merge_and_unload()本质是做一次加法。如果加法发生在低精度上或者中间有一次隐式的类型转换每个权重值的误差可能只有小数点后几位但这个误差会被逐层放大尤其在需要精细数值判断的任务上——比如多选题里几个选项概率很接近、或者长链推理需要保持数值稳定——误差就开始体现在分数上。写代码的时候养成一个习惯合并前后手动 assert 一下 dtype。from peft import PeftModel from transformers import AutoModelForCausalLM import torch base AutoModelForCausalLM.from_pretrained( Qwen/Qwen3-8B, torch_dtypetorch.bfloat16, # 显式指定别让它自动推断 ) model PeftModel.from_pretrained(base, ./lora-adapter) # 合并前检查adapter 和 base 精度是否一致 for name, param in model.named_parameters(): if lora_A in name or lora_B in name: assert param.dtype torch.bfloat16, f{name} 精度不对: {param.dtype} merged model.merge_and_unload() merged.save_pretrained(./qwen3-8b-merged, safe_serializationTrue)症状模型说话完全正常但涉及精细判断的任务选择题、数值推理分数下滑。验证方法合并前后固定同一个输入对比 logits 的分布而不是只看生成文本。病因 2scaling 系数算错幅度对不上方向LoRA 真正生效的增量是(alpha/r) * B A不是单纯的B A。这个缩放系数peft 自带的merge_and_unload()会自动处理——但一旦你为了更灵活手写合并逻辑这一步就是最容易漏掉的地方。我见过最典型的错法合并脚本是从别的项目里抄来的alpha和r直接沿用了那个项目的默认值比如alpha16, r8但自己这次训练用的其实是alpha32, r16——比值凑巧数值上接近但不完全相等合并出来的模型不会报错也不会完全跑偏就是权重方向对幅度悄悄错了。# 手动实现 merge 的核心公式标出最容易漏的一行 def manual_merge(W, A, B, alpha, r): scaling alpha / r # -- 这一行最容易漏乘或算错 delta scaling * (B A) return W delta症状模型整体表现偏保守或偏激进——比如训练目标是让模型更果断地给出结论合并后却发现它又开始模棱两可。验证方法检查合并脚本里alpha、r的取值是否和训练配置文件adapter_config.json里的完全一致而不是手抄或记忆。病因 3多个 adapter 依次合并顺序反了如果你训练了不止一个 LoRA——比如先做了一版领域知识微调又在此基础上做了一版工具调用微调——依次合并时第二个 adapter 训练时用的base到底是原始 base还是已经合并了第一个 adapter 的模型这个问题决定了合并顺序对不对。顺序反了不会报错模型也能正常输出。但等于把工具调用这个增量加到了一个错误的基座上——单独测每项能力可能都还凑合组合能力既有领域知识又会调用工具会明显下降。验证方法合并前回头确认每个 adapter 训练时from_pretrained加载的 base 路径画一条清晰的训练血缘线别凭记忆合并。PART 03实测——两条推理路径差多少光说容易掉点没有说服力做一次最小对比实验。思路很简单同一个 adapter分别用训练态不合并peft 直接加载推理和合并态执行merge_and_unload()后推理固定输入、固定随机种子跑两遍对比输出。import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel base_path Qwen/Qwen3-8B adapter_path ./lora-adapter prompt 帮我总结一下 LoRA 合并容易踩的坑 tokenizer AutoTokenizer.from_pretrained(base_path) inputs tokenizer(prompt, return_tensorspt) # 路径一训练态adapter 挂载不合并 base AutoModelForCausalLM.from_pretrained(base_path, torch_dtypetorch.bfloat16) model_unmerged PeftModel.from_pretrained(base, adapter_path) model_unmerged.eval() with torch.no_grad(): logits_unmerged model_unmerged(**inputs).logits # 路径二合并态merge_and_unload 之后 model_merged model_unmerged.merge_and_unload() model_merged.eval() with torch.no_grad(): logits_merged model_merged(**inputs).logits # 对比两条路径的 logits 差异而不是只看生成文本 diff (logits_unmerged - logits_merged).abs().max().item() print(f最大 logits 差异: {diff})在我自己跑的这一版小流程里dtype 和 scaling 都对齐的情况下diff是一个非常小的数值接近浮点误差量级——这说明合并操作本身的数学没问题。但当我故意把 base 权重的加载精度改成 fp32和训练用的 bf16 不一致重跑一遍diff直接跳大了两个数量级评测分数也应声下滑。这里补一个合并后自检三件套不需要跑完整评测集也能先做一轮排查diff 一下前后输出上面这段代码固定输入跑两遍看 logits 差异量级是否正常看权重范数变化合并前后对比几层关键权重的 L2 范数如果变化幅度和预期的alpha/r缩放不匹配基本就是系数算错了抽查一遍小型评测集哪怕只有几十条也能比聊几句看着还行更快发现问题检查项训练态基线合并态dtype/scaling 对齐合并态dtype 未对齐评测集综合得分87.387.182.1logits 最大差异—~1e-4 量级明显跳大人工看输出正常正常看起来也正常最后一行才是这篇文章真正想说的东西——人工看输出这一项三种情况下的结论都是正常。合并这一步出问题靠聊几句是发现不了的。结尾模型没变笨是你把它算错了合并这一步代码永远只有两行merged model.merge_and_unload() merged.save_pretrained(./merged-model)但这两行背后是三个容易被默认为理所当然的假设——dtype 对不对scaling 系数对不对合并顺序对不对。少一个模型不会报错也不会说胡话它只会在评测集上悄悄替你多扣几分。这不是模型能力的问题是工程实现的问题——好在可复现也可以修。互动时间你合并 LoRA 的时候踩过哪个坑评论区说说我挑几个案例补充进踩坑实录专栏。下一篇预告合并这一步查明白了下一个问题自然是——怎么科学地评测一个微调完的模型才不会被聊几句感觉还行骗过去Day31 我们聊聊评测集怎么搭才靠谱。— END —苦猿 · 帮普通人把 AI 学进简历