ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

微调Qwen3.5-4B实战:从loss下降到模型效果对齐的复盘

2026/8/31 10:31:24 拓冰建站 浏览量
微调Qwen3.5-4B实战:从loss下降到模型效果对齐的复盘 微调一个 4B 规模的模型最折磨人的不是显存也不是代码报错而是当你看着 loss 一路向下时以为马上要看到回报结果拿着真实需求去问模型却还是原来的味道。这是我第一次尝试微调 Qwen3.5-4B 的真实体感。第一次跑训练时我的目标很简单让模型学会回答某个垂直领域的问题。我准备了两万多条问答数据直接用全参微调开跑。训练过程没有任何异常loss 稳定下降最终停在 0.7 左右。我很开心赶紧把模型部署到本地问了几个领域问题结果一半是套话一半是幻觉。更麻烦的是原来一些通用能力还变弱了。第二次尝试前我花了一天时间复盘数据、训练脚本和评估方式。这篇文章就来记录这次复盘的结论以及第二次数值上看起来更保守的微调过程。我现在更愿意说微调不是把 loss 降下去而是让模型的输出分布靠近你的目标数据分布。有了这个判断后面的工作全部围绕它展开。1. 第一次失败loss 一路下降效果却没跟上复盘第一次尝试结论听起来有点反直觉代码越顺利反而越容易让人忽略真正的问题。当时的训练日志很干净没有 OOM没有爆显存loss 曲线也很漂亮所以我长时间把注意力放在“怎么把训练继续跑下去”而不是“这个训练到底在把模型带向哪里”。1.1 我把“能跑通”当成了“微调成功”第一次跑全参微调代码是从现成模板改的。数据文件、tokenizer、训练参数都能跑通loss 也在降我就以为成功了一半。实际上能跑通只代表代码正确、数据格式没炸、GPU 没 OOM这只是训练系统的下限不是模型效果的下限。loss 下降是优化器在做它该做的事。对语言模型来说只要数据顺序、掩码、标签基本正确loss 几乎一定会下降。但这并不代表模型学会了任务。尤其是当训练集里存在大量重复、低质量或格式不一致的问答时loss 可以通过“记住高频模式”来降低而不是真正理解指令和答案之间的映射。所以我后来把第一次尝试定位成“系统跑通实验”不是“微调成功实验”。这个定位很重要因为它直接改变了第二次的优先级先解决数据、评估和参数策略再追求训练时长和 loss 数值。1.2 数据问题比显存问题更隐蔽第一次失败真正的核心不在显存而在数据。当时我把“目标领域知识库”直接转成了问答对没有做足够的清洗和去重。问题包括同一个问题有多种问法但答案来自不同出处甚至内容相互矛盾有些问答对超长把上下文窗口撑满导致训练时多数样本被截断答案格式不统一有的是一段纯文本有的是 Markdown有的是编号列表训练集和验证集没有按“同源问题组”去重导致验证集评估虚高。这些问题在 loss 数值上看不出来但会直接影响模型行为。数据是微调中最难 debug 的部分因为报错不会告诉你“这条数据语义可能是错的”。对比第一次结果和外部解答对象时最明显的差异不是模型没有领域知识而是模型把领域风格学歪了它对某些问法非常敏感换一种问法就失效。这让我意识到一个关键教训微调的数据量不是越多越好而是越干净越好。两万条脏数据可能不如三千条经过去重、对齐、格式统一的样本。1.3 第一次忽略的评估标准那次训练结束后我没有任何基准集。只是凭感觉问了几个问题觉得“还行”就准备部署。后来发现如果一开始就固定一组问题做前后对比很多变化会更早暴露。我推荐的做法是微调前先准备三组测试问题。基础通用问题用来观察模型原有的通用能力有没有被破坏目标领域问题用来观察想要的领域行为有没有出现对抗性边界问题包括未见过的问法、超长问题、含干扰信息的问题用来观察鲁棒性。第一次失败的核心不是模型不行而是我没有建立评估意识。第二次尝试我把这三组测试放在训练前、训练中、训练后三个时间点固定执行。虽然技术上多花了一些时间但最后省掉了大量无效返工。2. 第二次尝试前先解决三个前置问题复盘结束后我没有立刻改训练脚本。我先做了三件事跑基线、治理数据、确定参数策略。这三件事的顺序不能乱。2.1 先用基线测试明确模型缺什么很多人微调前不看基线这是最大的误区。第二次我先用原始 Qwen3.5-4B 模型跑一遍目标领域的测试题记录下哪些回答不行。这一步有非常直接的价值它能帮你判断模型当前的问题到底是“不会”还是“会但回答风格不对”。以我这次为例原始模型对很多领域问题不是完全不会而是回答得太宽泛、太通用。比如问一个具体流程问题它会输出“应该综合考虑多个因素”而不是给出领域内真实的步骤顺序。这说明模型缺的不是语言生成能力而是对领域输出范式的对齐。如果基线测试显示模型在相关任务上已经不错只是偶尔输出格式不对那更适合用 few-shot prompting 或后处理不一定非得微调。微调是成本更高的方案只有在基线和期望行为之间有稳定差距时才值得做。2.2 数据治理先做减法再做微调第二次数据准备我没有再追求数量而是把流程拆成五步从原始材料里提取问答候选按问题和答案的语义相似度去重删除答案矛盾、残缺、过长的样本统一指令模板和答案格式划分训练集、验证集、测试集并按问题组去重。执行到第三步时原来的两万条数据只剩下一万四千条。我没有觉得可惜因为留下的是信息密度更高的部分。到第五步时我额外保留了 500 条边界问题作为测试集这些绝不进训练。这里有一个容易被忽略的细节验证集的定义方式。如果只是随机采样同一个问题的相似问法可能同时出现在训练集和验证集里最后你会看到验证 loss 很低但真实场景效果很差。正确做法是按问题组去重确保验证集里的问题和训练集不在同源集合内。2.3 参数策略LoRA 还是全参微调第一次我选了全参微调理由是“保险、都训练了参数全更新效果可能更好”。第二次我改成了 LoRA。原因不是全参微调绝对不能做而是在这个任务里LoRA 更符合我的目标。维度全参微调LoRA 微调显存占用高需要更新所有参数优化器状态很大低只训练低秩矩阵冻结原模型训练速度相对慢相对快通用能力保留更容易被破坏通常更稳定表达上限高但需要更多数据足够应对大部分指令和数据对齐任务调试成本参数多异常难定位参数少问题容易被聚焦这里要解释一下显存差距的来源。全参微调不仅要多保存一份梯度还要为每个参与更新的参数保存优化器状态比如 Adam 的动量项和方差项。LoRA 只训练插入在若干层里的低秩矩阵原模型权重全部冻结因此优化器状态小很多。4B 模型在 LoRA 方案下单卡 24G 通常可以尝试。如果使用更激进的量化加载显存还能进一步下降。但最终占用取决于序列长度、批次大小、是否开启梯度检查点这些参数必须自己实测。不要看网上一个数字就直接照搬。3. 核心实操Qwen3.5-4B 微调的最小可用配置第二次训练我没有追求一次跑满 30 个 epoch。我先做了一次 3 个 epoch 的小规模验证确认数据、参数和评估链路都正常后再重新启动完整训练。这个过程是第二次尝试里最值得借鉴的一点先用最小成本验证流程再扩大规模。3.1 环境准备与依赖确认环境准备最容易踩坑的往往不是“安装失败”而是“版本组合不兼容”。我在第二次尝试时先确认了以下几组依赖# 常见依赖组合版本需要结合自己的环境锁定 pip install transformers peft accelerate datasets训练用到的组件通常包括模型和分词器transformersLoRA 配置和注入peft训练参数与 Trainertransformers或trl数据加载datasets显存优化accelerate、梯度检查点、必要时使用量化加载启动训练前我建议先做一次“极干燥烟测试”只用 8 到 16 条样本跑一个 step确认前向、反向、梯度更新、保存 checkpoint 都能通过。这一步能省下后面大量定位问题的时间。3.2 指令数据的组织方式微调 Qwen3.5-4B 这类模型时数据通常需要组织成“指令 输入 输出”的结构。不同模型对 prompt 模板要求不一样所以不要直接套用其他模型的聊天模板。常见的数据组织方式是{ instruction: 请根据给定的产品说明回答用户问题。, input: 用户问这个功能在离线环境下能用吗, output: 在离线环境下该功能建议确认本地依赖是否完整并检查模型部署时的资源限制。 }训练阶段我们要把 instruction、input、output 拼接成完整的对话文本并构造适合语言模型训练的标签。一个常见错误是让模型去预测“指令 输入”部分导致它只学到复述输入。正确做法是loss 只计算 output 部分或者至少把 input 部分的损失 mask 掉。具体写法取决于 Trainer 如何处理 labels建议以实际使用的训练工具文档为准。3.3 LoRA 配置示例从 8 到 32 的选择第二次我用的 LoRA 配置可以作为一个起点。它不一定适合所有任务但可以作为你调参的参考from peft import LoraConfig lora_config LoraConfig( r16, lora_alpha32, target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM, )这里有几个参数需要调试r是低秩矩阵的秩控制可训练参数量。r8更省资源r32表达空间更大但也不一定更好lora_alpha是缩放系数我一般先用2 * r再根据 loss 和效果微调target_modules需要匹配模型结构不同版本可能名字不同训练前要先打印模型结构确认。如果训练时 loss 下降很慢我会先提高r再看学习率如果训练后输出出现明显重复或格式崩坏我会先降低r和lora_alpha做一次回归对比。不要一次性同时改多个参数否则出问题很难定位。3.4 训练时长、保存策略与恢复点训练参数方面我第二次没有使用过大的学习率。大模型微调常见学习率范围在1e-5到5e-5之间LoRA 可以把学习率调高一些但过高会出现 loss 震荡。from transformers import TrainingArguments training_args TrainingArguments( output_dir./qwen3.5-4b-lora-2nd, num_train_epochs3, per_device_train_batch_size2, per_device_eval_batch_size2, gradient_accumulation_steps4, learning_rate2e-5, lr_scheduler_typecosine, logging_steps10, eval_strategysteps, eval_steps100, save_strategysteps, save_steps500, load_best_model_at_endTrue, fp16True, )这些参数里per_device_train_batch_size2加上gradient_accumulation_steps4等效 batch size 是 8。如果显存不够可以进一步降低 batch size增大梯度累积。但要注意梯度累积可以缓解显存压力不一定完全等价于真正的 batch size 变大尤其对 BatchNorm 类模块有影响语言模型里主要影响 loss 曲线稳定性。保存策略也很重要。第一次训练我每隔 1 个 epoch 才保存一次第二次改成每 500 步保存一次并且开启load_best_model_at_end。这样即使训练后期过拟合也能回退到验证集上最好的 checkpoint。4. 调试微调不收敛、抖动和假收敛微调过程中第二次数值上最值钱的收获不是 loss 降到多少而是我建立了一套排查链路。训练出问题时按顺序排查比瞎调参快得多。4.1 不收敛时的排查链路如果 loss 一直不降或者训了半天还停在初始值附近我建议按下面顺序排查先看训练日志是否正常更新排除数据加载卡死或某一步异常再看数据样本是只有几条样本还是样本格式有明显错误再看 tokenizer 的 padding 和 truncation方向是否正确标签有没有被错位再看学习率是否太小或者 scheduler 预热过长再看 LoRA 配置target_modules是否正确匹配模型结构最后看模型本身是否加载了重复权重或者 tokenizer 与模型不匹配。这里最容易误判的是数据样本看起来正常但指令和答案拼接后过长大量输入被截断导致模型只学到片段。如果数据里既有长文本又有短文本我建议先按长度分桶再做截断策略。loss 抖动通常和 batch size、学习率、数据集噪声有关。抖动但不上升可以继续观察抖动幅度过大先调低学习率再检查是否存在异常样本。在训练脚本里记录每个 step 的 loss比只看平均 loss 更容易定位到是哪段数据引起抖动。4.2 loss 正常但输出错乱的常见原因第二次训练里loss 从 1.1 降到 0.6看起来非常不错。但我把模型拿来测试时发现它经常重复同一个句子。这就是典型的“假收敛”。常见原因有三个数据里存在大量重复模板模型直接把“生成高概率模板”当成最优策略loss 计算时没有正确 mask 掉指令部分模型误以为需要预测用户输入训练轮数过多模型在训练集上过拟合验证 loss 已经上升但你看的是训练 loss。检查手段也很直观在验证集上跑一下生成不要只看 loss。如果生成结果里出现大量重复片段先把训练轮数减半再检查数据模板多样性。4.3 用“锚定问题”验证微调真的生效第二次微调结束后我专门维护了一个“锚定问题集”。这些问题是不会被改写的固定测试题分布在通用、领域、边界三类。每次训练结束我都用同一套 prompt 去问微调前、微调后、最终 checkpoint 三个模型把回答放在一起对比。这样做的价值在于你可以看到微调到底改变了哪些行为。比如微调前回答很长但全是泛泛而谈微调后回答更短更直接但偶尔会丢失安全措辞最终 checkpoint回答稳定格式正确也能保留通用能力。如果锚定问题里微调后模型把通用能力搞坏了说明训练参数需要回退。如果领域问题变好了但边界问题崩了说明数据里缺少边界样本。这个验证流程本质上比任何 loss 数值都重要。5. 从单次实验到可复用流程第二次尝试最终没有停留在“这次跑通了”的层面。我把它拆成了一个可以反复执行的流程留给后续任务使用。这个流程不复杂但每一点都是在第一次失败里换来的。5.1 固定评测集和回归基准一个可复用的微调流程至少要包含三类数据训练集、验证集、测试集。测试集要尽量接近真实使用场景并且不能参与训练。我还会在每次微调后跑一份通用能力测试防止模型只会在目标领域里说话一出领域就不会表达。第一次失败最大的遗憾是没有在动手前固定评测基准。第二次我把锚定问题集放到一个eval_sets/目录里每次实验都跑同一个脚本然后对比结果。这不是什么高级操作但它会让你对模型的每次变化都有据可查。5.2 模型融合与蒸馏不是第一优先级在看过一些资料后我发现很多人把模型融合、模型蒸馏和微调放在一起考虑。其实它们解决的问题不同。微调是把模型行为对齐到目标分布模型融合是把多个模型的优势合并蒸馏是把大模型能力迁移到小模型通常是为了部署体积和推理速度。对一个还没有稳定跑通微调研判流程的团队来说第一优先级不是融合和蒸馏而是先把数据质量和评估体系建好。你连单次微调的效果边界都不清楚融合或蒸馏后出现问题会很难归因。如果你只是为了降低部署成本可以先直接量化模型观察效果再决定是否蒸馏。5.3 上线前还要补的工程能力第二次微调完成后模型看起来很强但要放到正式服务里还差几块拼图输出内容检查防止模型在低 confidence 时输出明显错误信息请求日志和输入输出审计方便后续复盘 bad case版本管理每个 checkpoint 对应哪份数据、哪组超参必须记录回滚方案新模型上线后发现行为异常是否能快速切回旧版本评测自动化把锚定问题集接入 CI每次微调后自动生成对比报告。这些工作不是微调本身但决定了微调能否在真实项目里持续产生价值。第一次尝试时我完全没有考虑这些第二次也只是补了其中一部分已经明显感觉后续迭代更受控了。微调模型这件事真正难的不是让 loss 下降而是搞清楚模型为什么变化、变化是否符合预期、以及如何稳定复现这种变化。第二次尝试让我更相信一个判断单次实验跑通只是起点数据治理、评估体系和工程化能力才是微调能不能走向可用的关键。如果你正准备微调 Qwen3.5-4B 或其他 4B 规模模型我会建议你先花一半时间准备数据和评估再去碰训练脚本。这个顺序不能反。