导读:本期聚焦于新井创作的《大模型微调训练时Loss一直不下降应该怎么系统排查》,敬请观看详情。刚跑起来的微调任务盯着曲线半小时,发现损失值像焊死了一样毫无波动,这种状况往往不是显卡出了问题。先确认数据管道有没有把标签弄错,很多训练脚本在分词阶段就把目标张量填成了全零,模型自然学不到任何梯度信号。学习率设得过大也会让参数在最优解附近反复横跳,小到千分之一都未必够用。另外要检查冻结层配置,不小心把全部底座锁死只剩分类头,面对分布差异大的语料根本带不动。把这几处基础项挨个验证,多数异常都能在改完配置后看见Loss正常下滑。

在大模型微调的实际工程里,Loss指标是衡量模型是否正在学习的核心信号。当训练启动后观察面板上的损失值始终停留在初始区间,既没有震荡也没有缓降,就意味着反向传播链路中某个环节已经失效。这种问题通常不会自己恢复,必须沿着数据、模型、优化器三条主线逐层拆解,才能定位到阻断梯度更新的真实原因。

大模型微调训练时Loss一直不下降应该怎么系统排查

数据侧:标签错乱与采样偏差

数据管道是大模型微调中最容易被忽视却最常出错的环节。很多自研训练框架在构造 dataset 时,会把文本编码后的 input_ids 同时复制给 labels,或者将 padding 部分的 token 也计入损失计算却没有正确设置 ignore_index。这样一来,模型被迫去预测无意义的填充符,有效语料的监督信号被稀释,整体 Loss 就会表现为平坦不降。正确的做法是在 collate_fn 中明确区分输入与标签,并对非目标位置打上负索引屏蔽。

另一个隐蔽问题是类别或长度分布极端倾斜。例如用指令微调数据训练时,百分之九十的样本都是极短问答,剩下少量长文本被 batch 采样均匀混合,导致梯度方向被短样本主导。此时即便模型有能力拟合长指令,平均 Loss 也难以下探。可以通过按长度分桶采样,或单独为困难样本提升采样权重来缓解。下面这段代码展示了如何过滤无效标签并屏蔽 padding:

def collate_fn(batch):
    input_ids = []
    labels = []
    for item in batch:
        ids = item['input_ids']
        # 将问题部分设为-100,只计算回答部分的损失
        lab = [-100] * len(ids)
        start = item['answer_start']
        lab[start:] = ids[start:]
        input_ids.append(ids)
        labels.append(lab)
    # 统一padding,padding位置标签保持-100
    max_len = max(len(x) for x in input_ids)
    for i in range(len(input_ids)):
        pad = max_len - len(input_ids[i])
        input_ids[i] = input_ids[i] + [0] * pad
        labels[i] = labels[i] + [-100] * pad
    return {'input_ids': input_ids, 'labels': labels}

除了代码层面的处理,还要肉眼抽检几个 batch 的原始数据。曾经有团队发现标注平台导出的 JSON 里,正样本字段名从 answer 被误改为 ans,加载脚本取不到值后用空串填充,模型全程在拟合空标签。这类错误不会报异常,但 Loss 曲线会忠诚地停在交叉熵初始值附近。

模型侧:冻结策略与参数精度

微调时为了省显存,常用冻结底座、只训 adapter 或分类头的方案。但若配置文件里 layer_freeze 范围写错,把全部 transformer 块都锁死,仅剩最后一层线性层可学,当新领域数据和预训练分布差距较大时,单层容量根本无法承载映射,Loss 就会卡住。建议先放开前三层或全部底座用较小学习率试跑,确认 Loss 有下降趋势后再逐步收回冻结比例。

混合精度训练中的数值溢出也会让梯度变 NaN 或恒为零。某些旧版深度学习框架在 fp16 下对大模型 layernorm 计算不稳定,反向时梯度直接消失。可改用 bf16,或在优化器上挂梯度裁剪。下面示例演示了用 PyTorch 开启 bf16 并加上 clip_grad_norm:

import torch
from torch.cuda.amp import autocast

scaler = torch.cuda.amp.GradScaler(enabled=False)
for step, batch in enumerate(loader):
    with autocast(dtype=torch.bfloat16):
        out = model(**batch)
        loss = out.loss
    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
    optimizer.step()
    optimizer.zero_grad()

此外,加载预训练权重时若漏掉 strict=False 导致部分 key 不匹配,模型实际在用随机初始化的顶层跑训练,也会造成 Loss 不降。每次载入 checkpoint 后应打印 missing_keys 与 unexpected_keys,确认底座参数确实就位。参数精度方面,若用 LoRA 微调却把基础模型设成 int8 且未配合合适反量化,梯度同样传不进去。

优化器侧:学习率与调度器误区

学习率是大模型微调里最敏感的旋钮。很多人直接套用预训练时的 1e-3,结果在微调小数据集上参数一步跨过最优点,Loss 剧烈震荡后停滞。通常指令微调建议 1e-5 到 5e-5,LoRA 可稍大。若用了带 warmup 的调度器但 warmup_steps 设得比总步数还长,模型前期一直在极小学习率下缓慢移动,短时间看就像没训一样。

优化器状态异常也值得排查。例如从断点续训时只存了模型没存 optimizer.state_dict,恢复后动量项归零,初期更新节奏被打乱。还有人把 weight_decay 设得过大,把可训参数都正则到接近零,等效于变相冻结。可以参考下表对照常见配置与现象:

配置项错误取值Loss表现
learning_rate1e-3震荡后平坦
warmup_ratio0.5且总步数少前中期无下降
weight_decay0.3缓慢锁死
freeze_layers全部底座恒高于预训练损失

最后要确认损失函数本身。有些多任务脚本把不同任务损失直接相加,而某个子任务数据量为零,除以零后得到常数被加进总损失,拉高了均值。应在计算前判断任务 batch 非空,或用掩码损失。把上述数据、模型、优化器三个角度的检查项做成启动前 checklist,基本可以覆盖九成以上的 Loss 不下降故障。

大模型微调loss不下降训练排查修改时间:2026-08-17 03:34:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。