一个很典型的场景:
- 训练脚本跑完,损失从 2.1 降到 0.4,曲线很漂亮;
- 测试集上跑一遍------指标没有提升,甚至不如直接用提示词的效果;
- 换个说法再测,还是不行。
于是开始怀疑:是不是要换更大的秩?是不是要加更多数据?是不是 LoRA 本来就不行?
这些方向都可能对,但它们不是首要嫌疑。
实践中,微调"没效果"的原因高度集中在少数几个地方 ,而且前两个原因占了绝大多数。
这篇文章给出一份按命中概率排序的排查清单。建议按顺序走,别跳------因为跳着排查的人,会在错误的基础上反复试验。
第一部分:排查第一步------训练与推理的输入格式是否一致
这是最高频的原因,也是最少被检查的一项。
1.1 具体错在哪
看一个真实的对照。
你训练时的数据长这样:
text
指令:把以下文本分类为正面或负面。
文本:这家店服务很好。
答案:正面
而推理时,你的程序发给模型的是:
text
这句话是好评还是差评?"这家店服务很好"
这两个是形态完全不同的任务。
模型学的是 "看到『指令:...文本:...答案:』这种模式时输出标签";而你让它做的是一个开放问句。
它当然表现不好------它没学过这个格式。
1.2 怎么检查
关键动作 :把推理时真实发送的那段文本 (不是你以为的模板)打印出来,和训练数据里的一条样本并排放在一起,逐字符对比。
重点检查四项:
| 检查项 | 具体看什么 |
|--------------|-----------------------------|----------|----------|
| 角色标签与分隔符 | 是否有相同的标签、分隔符、换行方式 |
| 系统提示 | 训练时用了系统提示,推理时必须也用 |
| 输出起始方式 | 训练时答案前有"答案:",推理时要给同样的引导 |
| 特殊标记 | 有没有模型特有的 token(如 `< | im_start | >`)被漏掉 |
1.3 为什么这一条如此高频
因为它有一个结构性的成因:
- 训练数据的构造代码,通常写在训练脚本里;
- 推理时的提示词,通常写在业务服务里。
这两份代码往往由不同的人、在不同的时间写,而且没有任何机制保证它们一致。
这一条能解释的失败案例,比后面九条加起来还多。
1.4 一个五分钟的验证
如果你只能做一件事,就做这件事:
python
# 打印推理时真实发送的完整文本
print(repr(messages)) # 用 repr 显示所有空白字符
# 打印训练数据里的一条样本
print(repr(train_samples[0]))
并排看这两段字符串。 五分钟就能发现问题。
用 repr 而不是直接 print 的原因 :它能暴露看不到的空白字符差异(多余的空格、不同的换行符)。而这些恰好是最常见的坑。
第二部分:排查第二步------数据本身有没有问题
数据是第二个高发区。四类问题按严重程度排列。
2.1 问题一:标签/答案有错
尤其常见于自动生成的数据。
模型会忠实地学会错误。
做法:随机抽 30 到 50 条人工过一遍,估算错误率。
超过 5% 就该重新清洗。
2.2 问题二:样本高度重复
大量近似相同的样本,会让模型记住模板而不是学会泛化。
做法:做一次近似去重,看样本的多样性分布。
怎么判断"太重复"? 一个简单的方法:抽样 50 条,看看有多少条是"换个说法但内容一样"的。如果超过一半,重复度就偏高。
2.3 问题三:格式不统一
有些样本用中文标点,有些用英文;有些输出带解释,有些只给结论。
模型面对矛盾信号,只能学出一个"平均版本"。
这个"平均版本"的表现就是 :有时候带解释,有时候不带------看起来像"不稳定",实际上是它忠实反映了你给的矛盾。
2.4 问题四:数据量与任务不匹配
任务越复杂,需要的数据越多。
但要注意:质量比数量重要得多------几百条高质量样本常常打败上万条脏数据。
2.5 一个有用的判断标准
| 症状 | 更可能的原因 |
|---|---|
| "学得会但学不稳、时好时坏" | 数据一致性问题 |
| "完全学不会" | 格式或任务定义问题 |
这个区分能帮你快速定位到"数据"还是"链路"。
第三部分:排查第三步------这个任务适合微调吗
数据没问题,就要问:这个问题是不是根本不该用微调解决?
回到那条判据:微调擅长改"表达形态",不擅长注"新知识"或提"推理上限"。
3.1 三个不适合微调的信号
信号一:模型答错是因为"不知道"。
比如问一个私有的产品参数。
这是知识问题,应该用检索,用微调就是让模型硬猜。
"硬猜"的具体表现 :模型会输出一个看起来合理但实际错误的参数值。这在生产环境里比"拒答"危险得多。
信号二:模型答错是因为"想不明白"。
比如需要多步推理的计算题,基座本身做不对。
微调改变不了能力上限。
信号三:需求是"每次都不一样"。
比如要求根据上下文灵活变通,而你的训练数据却在教一个固定套路。
这会降低灵活性。
这条比较隐蔽:你以为在"教它做好这件事",实际上是在"把它限制在一个模板里"。
3.2 一个很便宜的验证方法
手工构造 20 个测试样本,先验证"在训练数据里,模型是否 100% 学得会"。
| 观察 | 结论 |
|---|---|
| 连训练集都学不会 | 问题在数据或超参 |
| 训练集完美、验证集不行 | 泛化问题或任务不匹配 |
这个"训练集能不能拟合"的检查,是区分"管线问题"和"能力问题"的分水岭。
它的逻辑很简单:如果模型连它训练时见过的东西都学不会,那说明训练流程本身有问题;如果它学会了训练集但泛化不了,那是数据分布或任务定义的问题。
第四部分:排查第四步------超参是否合理
到了这一步,才轮到调参。
按影响从大到小:
4.1 学习率
| 情况 | 表现 |
|---|---|
| 太大 | 损失震荡不收敛、输出发散 |
| 太小 | 损失几乎不动、学不进去 |
LoRA 的学习率通常可以设得比全参大,但"大"是有上限的。
4.2 训练轮数
- 太少:学不到位;
- 太多:过拟合。
看损失曲线:验证损失回升就是过头的信号。
4.3 秩 r
| 情况 | 表现 |
|---|---|
| 太小 | "损失降不下去",模型表达力不够 |
| 太大 | 容易过拟合 |
建议从小到大试,先 8 再到 16、32。
4.4 目标模块
只挂了注意力投影时效果不足,把前馈层加进来往往有明显提升------代价是参数量增加。
4.5 一个实用建议
用"训练集能否拟合"作为健全性检查。一个健康的配置应该能在训练集上把损失降到很低。
如果训练集都学不会,先别管泛化。
这句话的顺序很重要 :它把"能不能学"和"学得好不好"分成了两个独立的问题 。很多人直接跳到后者,但前者不成立时,后者的讨论都是空的。
第五部分:排查第五步------推理侧的配置是否同步
这一条非常隐蔽:训练对了,推理时却没配对。
5.1 四个检查点
检查点一:推理时是否加载了适配器。
听上去荒谬,但真实发生过------部署时忘了挂载。
具体表现 :模型表现得就像没微调过一样。而这一旦发生,你会花大量时间去怀疑训练本身。
检查点二:推理时的提示词是否还是旧版。
训练数据是按新提示词格式造的,但线上还是老提示词。
检查点三:是否残留了"引导串"。
训练数据里答案前有固定前缀,推理时也需要同样的前缀来触发,否则输出形态会不对。
举例 :如果训练数据都是 答案:正面,而推理时模型输出的第一句是 "这句话是正面的"------格式完全不对。 解决方法是在推理时也加上 答案: 作为引导。
检查点四:序列长度与截断策略。
如果推理时把输入截断了,模型看到的内容和训练分布不同。
5.2 排查方法
直接对比"训练样本"和"线上请求"的原始文本。这一步通常五分钟就能发现问题。
注意"原始文本"这四个字 :不要对比"你脑子里的模板",要对比实际发出去的字符串。
第六部分:一份十步排查清单
把上面的内容整理成可执行顺序,按命中概率排序。
| 步骤 | 检查项 | 命中概率 | 修复成本 |
|---|---|---|---|
| 1 | 训练与推理的输入格式是否一致(含系统提示) | 极高 | 极低 |
| 2 | 推理时是否真的加载了适配器 | 高 | 极低 |
| 3 | 数据标签是否有错、是否重复、格式是否统一 | 高 | 低 |
| 4 | 任务是否适合微调(知识/推理类需求) | 中高 | 低(改方案) |
| 5 | 训练集能否被拟合(健全性检查) | 中 | 低 |
| 6 | 学习率是否过大或过小 | 中 | 低 |
| 7 | 训练轮数是否不足或过多 | 中 | 低 |
| 8 | 秩 r 与目标模块是否够用 | 中 | 中(重训) |
| 9 | 评测方法本身是否可靠(判分口径、样本量) | 中 | 中 |
| 10 | 是否需要更强基座或拆分任务 | 低 | 高 |
6.1 为什么这个排序是这样的
排序依据是两个因素:
- 命中概率(有多常见);
- 修复成本(修起来有多麻烦)。
规律是:命中概率高的,修复成本都很低;修复成本高的,命中概率都很低。
这不是巧合 ------它反映了"高成本的错误通常会被及早发现":如果你花了大力气调超参,那你一定已经试过更简单的排查了。
6.2 一条硬性建议
前 5 步不解决,不要动第 8 步。
换超参之前先确认数据与格式没问题,否则你会在一个错误的基础上反复试验。
这句话对应一个很典型的时间浪费模式 :有人花了三天调各种 r 和学习率组合,最后发现是训练数据里多了一个空格。而这种浪费是可以避免的。
第七部分:训练前就该做的三件事
与其事后排查,不如事前设计。 这三件事能让排查成本降低一个数量级。
7.1 第一件:造一份"极小验证集"
20 到 50 条,人工确认答案正确。
每次改动都在这份集子上跑一遍。它不需要全面,只需要灵敏。
注意"灵敏"这个要求 :它不需要覆盖所有场景,但必须能在你改动有效时立刻反映出来。
为什么是 20~50 条? 因为:
- 太少(如 5 条)→ 噪声大,看不出趋势;
- 太多(如 500 条)→ 跑一次太慢,你会懒得跑。
20~50 条的量级,跑一遍只要几分钟,你会愿意每次都跑。
7.2 第二件:做一次"格式一致性检查"
写一个脚本,把训练样本的构造函数和推理时的提示词构造函数指向同一个来源。
让它们共用代码,就不会不一致。
这是根治第一步问题的唯一办法。
具体实现:
python
# 关键:只有一个地方定义格式
def build_prompt(task_input, system_prompt=SYSTEM_PROMPT):
return [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"指令:{task_input}\n答案:"}
]
# 训练数据构造时用它
train_samples = [build_prompt(x) for x in raw_train]
# 推理时也用同一个函数
messages = build_prompt(user_input)
共用代码的收益是结构性的 :你不必"记得检查一致性",因为不一致在结构上不可能发生。
7.3 第三件:先跑一个"过拟合实验"
用 50 条数据、较大学习率、多训几轮,看模型能不能把训练集背下来。
- 能背下来 → 说明链路是通的,再扩展到全量数据做泛化;
- 背不下来 → 问题在管线,不在能力。
这一步能区分"管线问题"和"能力问题"。
7.4 三件事的总成本
三件事加起来不到一天,但能省掉后面几天的瞎试。
这个投入产出比很划算 ------尤其是当你考虑到"瞎试"往往是按周计的。
常见误区
误区一:"效果不好就加大数据量。"
数据量是最后考虑的杠杆之一。
先确认格式、标签、任务适配性。
原因 :如果格式不一致,再加一万条数据也没用------训练集越大,模型学那个错误格式学得越牢。
误区二:"损失降得低就说明训得好。"
训练损失低只说明"拟合了训练集"。
要同时看验证损失和实际任务指标。
一个具体的反例 :训练损失 0.1(很低),验证损失 2.5(在上升)------这是典型的过拟合,但只看训练损失会误以为"训得很好"。
误区三:"微调没效果,说明要用全参。"
全参不会修复格式不一致或数据错误。
同样的错误在全参上通常表现得更彻底。
"更彻底"的意思是 :全参的容量更大,所以它能更精准地记住那个错误的格式。问题会被放大,而不是被修复。
误区四:"多训几轮总会有提升。"
训练轮数与效果不是单调关系。
超过最优点之后,每一轮都在加重过拟合与遗忘。
误区五:"评测结果不好,一定是模型的问题。"
评测方法本身也可能是问题:
- 判分口径太严(格式差异被算作错误);
- 样本量太小(噪声大,结论不可靠);
- 评测集与训练集有重叠(虚高)。
所以第 9 步要检查评测本身 ------在你怀疑模型之前,先确认尺子是准的。
误区六:"训练集指标好就够了。"
训练集指标只是必要条件,不是充分条件。
它只能说明"链路通了",而不能说明"模型学会了泛化"。
真正该看的是验证集和业务指标。
实战问答
Q1:微调完全没有效果,第一步应该做什么?
A :打印推理时真实发送的文本,和一条训练样本并排比对。
python
print(repr(actual_request_text))
print(repr(train_sample_text))
如果格式不一致,问题就找到了。
这一步五分钟,命中率最高。
注意用 repr------它能暴露不可见的空白字符差异。
Q2:怎么快速判断是"数据问题"还是"超参问题"?
A :做过拟合实验。
拿 50 条干净数据,用较大学习率多训几轮。
| 结果 | 结论 |
|---|---|
| 能拟合 | 链路是通的,问题在数据泛化或规模 |
| 不能拟合 | 问题在超参、秩或数据错误 |
这个实验的价值在于它是一次"二分" :把问题空间一刀切成两半,然后你就知道该往哪个方向查了。
Q3:训练集指标很好,但线上没变化,怎么办?
A :八成是推理侧配置问题。
依次确认:
- 适配器是否加载(最容易漏);
- 提示词是否与训练一致;
- 是否有必要的引导串;
- 输入是否被截断。
"训练集好但线上差"这个组合本身就是线索------它说明训练是有效的,问题出在从训练到上线的这一段。
Q4:怎么避免这类问题反复出现?
A :把一致性做成机制,而不是靠人记得检查。
具体两件事:
① 让训练与推理共用同一套提示词构造逻辑(同一份模板、同一个函数);
② 把"极小验证集"纳入训练脚本自动跑。
第二件事的意思是 :训练完成后自动跑一遍小验证集,如果指标没有改善,就直接报警------而不是等人去测。
这两件事都是"一次性投入、长期收益",而且它们的成本都不高。
Q5:训练数据的标签错了 5%,影响有多大?
A :影响比大多数人以为的大。
原因 :模型会忠实地学习你给的所有信号,包括错误的那些。它不是"从错误中过滤出正确规律",而是"在噪声中拟合出一个平均结果"。
具体表现:
| 错误率 | 典型表现 |
|---|---|
| 1%~2% | 通常影响不大 |
| 5% | 明显拉低效果上限,且会出现"时好时坏" |
| 10%+ | 可能完全学不出正确模式 |
所以"超过 5% 就该重新清洗"这条建议,是有依据的。
而且要注意 :自动生成的数据里,错误往往不是随机分布 的------它可能集中在某几类难例上,而这几类恰恰是你最关心的。
Q6:怎么判断该不该扩大训练数据?
A :先看当前数据能不能拟合。
text
训练集学不会 → 不是数据量的问题,是格式/超参/数据质量问题
训练集学会了、验证集不行 → 可能是数据量不足或分布太单一
训练集好、验证集也好、业务指标不行 → 可能是评测或任务定义问题
这个顺序很重要 :在"训练集都学不会"的情况下加数据量,是最常见的浪费。
只有在"训练集能学会、但泛化不好"的情况下,加数据(尤其是加多样性)才是对症的。
Q7:我们的微调效果"有时候好有时候坏",怎么查?
A :"时好时坏"这个模式本身就是线索 ------ 它指向数据一致性问题,而不是格式问题。
排查顺序:
| 检查项 | 说明 |
|---|---|
| 样本格式是否统一 | 有的带解释、有的不带 → 模型学出"随机选择" |
| 标点/空格是否统一 | 中英文标点混用 |
| 同一类输入的答案是否矛盾 | 同一问题两种答案 → 模型学到随机 |
| 是否有重复样本 | 重复样本会放大其影响 |
特别注意"矛盾样本" :如果同一个输入在数据里出现了两次、但答案不同,模型会学到"在这种情况下可以二选一" 。这会直接导致输出不稳定。
而这类问题在不做数据检查时很难发现,因为单看每条样本都是"对的"。
术语表
| 术语 | 含义 |
|---|---|
| 输入格式一致性 | 训练样本与推理请求在角色标签、分隔符、系统提示上完全对齐 |
| 过拟合实验 | 用小数据集验证训练链路是否通畅的健全性检查 |
| 引导串 | 训练时出现在答案前的固定前缀,推理时需同样提供以触发期望输出形态 |
| 极小验证集 | 20~50 条人工确认的样本,用于快速判断每次改动是否有效 |
| 拟合能力 | 模型在训练集上达到低损失的能力,用于区分链路问题与泛化问题 |
| 目标模块 | LoRA 作用的层集合,决定改动范围与效果上限 |
结语
把这篇压缩成三句话:
第一,微调失败的排查顺序是:
text
① 对比训练与推理的输入格式(命中率最高,修复成本最低)
② 检查数据质量与任务适配性
③ 最后才是超参
注意"最后才是超参" ------大多数人第一反应是调参,而调参排在第 6 到第 8 步。
第二,"训练集能不能拟合"是最有信息量的一个检查。 它一次就把问题空间切成两半:能拟合 → 查泛化;不能拟合 → 查管线。
第三,也是最重要的: 避免这一切的最好方式,是让训练和推理共用同一套提示词构造逻辑。
把一致性做成机制,而不是靠人记得检查。
而如果只允许带走一个动作,那就是:在怀疑模型之前,先打印出推理时真实发送的那段字符串,和训练样本并排看一遍。 五分钟,命中率最高。