skip to content
tlj 的工程笔记
← 项目

内容合规质检模型微调

个人

把 Qwen2.5-14B 微调成一个按规则做内容合规质检的模型。重点不在跑通训练,而在于看穿那些漂亮但会骗人的指标。

时间
2026-04 ~ 2026-05
角色
数据工程 / 训练 / 评估
成果
识破了 loss=0.0137 的假象和 93.6% 准确率的水分,定位到训练/推理分布错位的根因

把一个 14B 的基座模型微调成按既定规则做内容合规质检的工具。技术栈是常规的:Qwen2.5-14B-Instruct 做基座,LoRA(rank=64, alpha=128, target=all)微调,cosine 调度 lr=5e-5,bf16 加 gradient checkpointing,单卡 RTX 4090 上用 DeepSpeed ZeRO-2 扛住 4096 的 cutoff。

跑通训练不难。这个项目真正花时间、也真正长本事的地方,是识破几个看起来很漂亮、实际上在骗我的数字。我把这部分单独写成了一篇文章:微调里那些会骗你的数字。这里讲项目层面的脉络。

一个”完美”的 loss,和它的真相

第一版(exp_005)训练完,train_loss 是 0.0137。低得离谱,像是过拟合,又像是哪里不对。

真相是:当时 cutoff 设成 1024,而很多样本(尤其是长规则的那几个平台)的助手输出被截断了。被截掉的 token 不参与损失计算,于是平均 loss 被人为拉低——这个 0.0137 是个假象。

把 cutoff 放开重训(exp_006),train_loss 变成 0.245。这才是所有 token 都参与计算后的真实数字。关键结论是:不同截断设置下的 loss 根本不能直接比较。 一个更”漂亮”的 loss,可能只是因为它算的是更少的 token。

一个”很高”的准确率,和它的水分

评估时全量准确率跑出 93.6%,看着很能打。但我去拆了评估的实际输入,发现问题:批量推理用了 padding_side="left"、截断方向是去头保尾。尾部的 JSON 指令保住了,所以模型还能输出合法 JSON;但它实际只看到了最后 1024 个 token,前面大量的规则上下文根本没进到模型眼里

也就是说,这 93.6% 是在”几乎没看到规则”的情况下做出的。这个数字不能信。一个评估指标如果不去核对模型实际看到了什么,很容易把流程的 bug 当成模型的能力。

根因:训练和推理见到的世界不一样

把这些线索串起来,根因浮出水面:训练时模型每条样本只见到 1-3 条规则,但推理时 WebUI 一次给它 6 条、评估一次给它 53 条。模型从没在训练里见过”一次几十条规则”的格式,自然在真实使用时表现变差。

这是典型的训练/推理分布错位(train-serve skew)。它不体现在 loss 上,也不体现在与训练同分布的评估上,只在真实使用时暴露。找到它,靠的不是看指标,而是去对比”训练样本长什么样”和”线上请求长什么样”。

小结

这个项目给我最大的收获不是某个超参,而是一套”不信任漂亮数字”的习惯:

模型训练里,最危险的不是明显的报错,而是那些看起来一切正常的指标。