【Bug已解决】Understanding loss in Training LLM 解决方案
【Bug已解决】Understanding loss in Training LLM 解决方案一、现象长什么样训练自己的 LLM用transformers的Trainer或自己写的训练循环时遇到一类「看不懂 loss」的问题loss 数值异常大比如 10、20怎么调学习率都下不来loss 看上去在降但模型生成全是乱码、复读验证集 loss 比训练集还低或者两边都不正常切换到不同tokenizer/ 不同 padding 策略后loss 量级突然变了但模型结构没动。最典型的复现把一批长短不一的样本 pad 到同一长度送进模型直接用input_ids当labels算交叉熵发现 loss 被 padding 位置严重拉高训练目标其实是「学会预测padding token」而不是「学会预测下一个真实 token」。这类问题不报错但训练出来的模型就是「不懂人话」——因为 loss 的含义从一开始就算错了。二、背景自回归 LLM 的训练目标是「给定前 i 个 token预测第 i1 个 token」。交叉熵 loss 对每个位置算一次再平均。关键点padding 位置不该参与 loss。transformers的model(**inputs)在传入labels时会自动对labels -100的位置跳过用ignore_index。但很多人这么写# 错误写法直接把 input_ids 当 labels outputs model(input_idsbatch, labelsinput_ids) loss outputs.loss如果 batch 里有 paddinginput_ids含pad_token_id那么 padding 位置也被当成「要预测的真实标签」模型被迫去预测pad_token_id这些位置的 loss 被算进平均。后果loss 被 padding 稀释/拉高短样本多的 batchpadding 占比大loss 大部分在「学 padding」真实语义信号被淹没。训练目标错位模型花大量精力拟合 padding生成时容易吐 padding 或复读。跨 tokenizer 不可比不同 tokenizer 的 pad 比例不同loss 量级跟着变你以为换了模型其实只是 pad 多了。正确理解 loss 的前提就是「让 padding 不参与 loss」。三、根因根因一句话训练时labels没有把 padding 位置标成ignore_index-100导致交叉熵把 padding 也当成要预测的目标loss 含义错误、训练目标错位。三点展开padding 参与计算labels input_ids让 pad 位置进入 lossignore_index没生效。平均基准错loss 平均的分母包含 padding 位置数真实 token 的梯度被稀释。3.缺校验没有在送入模型前断言labels里 padding 已被-100覆盖于是错误静默存在。不是模型不会学是「学什么」被 padding 污染了。四、最小可运行复现不依赖真实大模型用一个最小交叉熵演示 padding 如何污染 lossimport torch import torch.nn.functional as F vocab, seq 10, 6 pad_id 0 logits torch.randn(1, seq, vocab) # 模型输出未归一化 targets_raw torch.tensor([[1, 2, 3, pad_id, pad_id, pad_id]]) # 含 padding # 错误直接拿含 pad 的 target 算 loss loss_with_pad F.cross_entropy( logits.view(-1, vocab), targets_raw.view(-1) ) # 正确padding 标成 ignore_index-100 targets_masked targets_raw.clone() targets_masked[targets_raw pad_id] -100 loss_no_pad F.cross_entropy( logits.view(-1, vocab), targets_masked.view(-1), ignore_index-100 ) print(含 padding 的 loss:, round(loss_with_pad.item(), 4)) print(忽略 padding 的 loss:, round(loss_no_pad.item(), 4)) print(两者是否相同:, torch.isclose(loss_with_pad, loss_no_pad))跑出来含 padding 的 loss 把 3 个 pad 位置也学进去了数值和「只看真实 3 个 token」的 loss 明显不同pad 多时差异更大。这就是「loss 算错」的精确复现。五、解决方案第一层最小直接修复最小修复构造labels时把所有pad_token_id位置替换成-100再送进模型。import torch from transformers import AutoModelForCausalLM, AutoTokenizer tokenizer AutoTokenizer.from_pretrained(your-model) model AutoModelForCausalLM.from_pretrained(your-model) def make_labels(input_ids: torch.Tensor) - torch.Tensor: labels input_ids.clone() # 关键padding 位置标成 -100交叉熵忽略它 labels[labels tokenizer.pad_token_id] -100 return labels # 训练循环 for batch_input_ids in dataloader: labels make_labels(batch_input_ids) outputs model(input_idsbatch_input_ids, labelslabels) loss outputs.loss # 现在只统计真实 token loss.backward() optimizer.step() optimizer.zero_grad()如果做「下一 token 预测」且输入已经是「输入标签移位」的格式注意自回归模型内部会自己处理移位你只需保证labels里 padding 是-100不要把labels再做一次[:, 1:]移位那会和模型内部的 shift 重复。要点labels[labels pad_token_id] -100一行解决 padding 污染。model(..., labelslabels)内部用ignore_index-100自动跳过。loss 现在只反映「真实 token 的预测质量」量级和训练目标都正确。这一步单独就让 loss 回归正确含义。六、解决方案第二层结构性改进第一层是「在循环里加一行」。但训练脚本里多个数据路径SFT、预训练、带 mask 的指令数据都构造 labels容易漏。更稳的做法把「labels 如何正确屏蔽 padding / 特殊 token」收敛成单一策略对象。from dataclasses import dataclass, field from typing import List, Optional import torch dataclass class LlmLossAuditor: LLM 训练 loss 标签屏蔽的单一策略。 # 需要忽略的 token id 集合padding、特殊 token 等 ignore_ids: List[int] field(default_factorylist) # 是否同时忽略序列左侧prompt只学回答SFT 常用 train_on_completion_only: bool False # completion 起始标记SFT 用 response_start_id: Optional[int] None def build_labels(self, input_ids: torch.Tensor) - torch.Tensor: labels input_ids.clone() for ig in self.ignore_ids: labels[labels ig] -100 if self.train_on_completion_only and self.response_start_id is not None: # 找到每个样本里 response_start 的位置其之前全标 -100 mask (input_ids self.response_start_id) # 用 cumsumstart 之前为 0之后为 1 pos mask.cumsum(dim-1) labels[pos 0] -100 return labels def check(self, labels: torch.Tensor): # 防御整行全 -100 意味着该样本无监督信号 all_ignored (labels -100).all(dim-1) if all_ignored.any(): print(f[LlmLossAuditor] 警告: {int(all_ignored.sum())} 条样本整行被忽略) # 用法 auditor LlmLossAuditor(ignore_ids[tokenizer.pad_token_id, tokenizer.bos_token_id]) for ids in dataloader: labels auditor.build_labels(ids) auditor.check(labels) loss model(input_idsids, labelslabels).loss loss.backward(); optimizer.step(); optimizer.zero_grad()结构收益单一策略padding、特殊 token、SFT「只学回答」的屏蔽都集中在一处。-可校验check抓出「整行无监督」的废样本。可扩展加新的忽略规则只改LlmLossAuditor不动训练循环。七、解决方案第三层断言 / CI 守护写 pytest 守三条(1) padding 被标-100(2) 真实 token 不被误标(3) 计算出的 loss 与「仅真实 token」一致。import torch import torch.nn.functional as F import pytest from your_lib import LlmLossAuditor pytest.fixture def auditor(): return LlmLossAuditor(ignore_ids[0]) # 假设 pad_id0 def test_pad_masked_to_neg100(auditor): ids torch.tensor([[1, 2, 0, 0]]) labels auditor.build_labels(ids) assert labels[0, 2].item() -100 assert labels[0, 3].item() -100 def test_real_tokens_kept(auditor): ids torch.tensor([[1, 2, 0, 0]]) labels auditor.build_labels(ids) assert labels[0, 0].item() 1 assert labels[0, 1].item() 2 def test_loss_ignores_pad(): vocab, seq 10, 4 pad_id 0 logits torch.randn(1, seq, vocab) raw torch.tensor([[1, 2, pad_id, pad_id]]) masked raw.clone(); masked[raw pad_id] -100 l_pad F.cross_entropy(logits.view(-1, vocab), raw.view(-1)) l_mask F.cross_entropy(logits.view(-1, vocab), masked.view(-1), ignore_index-100) assert not torch.isclose(l_pad, l_mask), 含 pad 的 loss 应与忽略 pad 的不同 def test_completion_only_mode(): a LlmLossAuditor(ignore_ids[0], train_on_completion_onlyTrue, response_start_id5) ids torch.tensor([[1, 5, 6, 7]]) # 5 之后才是回答 labels a.build_labels(ids) assert labels[0, 0].item() -100 # prompt 部分忽略 assert labels[0, 1].item() -100 # response_start 本身可忽略 assert labels[0, 2].item() 6 # 回答部分保留CI 常驻跑这四条后任何「padding 又混进 loss」「真实 token 被误标」的回归都会立刻爆红。八、排查清单训练 LLM「loss 看不懂」时按顺序查先打印labels里pad_token_id是否还在——在就说明 padding 参与了 loss。确认labels是input_ids的克隆并做了-100替换而不是直接用input_ids。确认用的loss来自model(..., labelslabels).loss而不是自己手写的、没传ignore_index的F.cross_entropy。SFT 场景确认是否「只学回答」prompt 部分应标-100否则模型在学复述问题。确认ignore_index-100与模型内部一致transformers 默认就是 -100别改成别的。换 tokenizer 后重新核对pad_token_id不同 tokenizer 的 pad id 可能不同。批量打印几个样本的labels肉眼确认除 pad/特殊位外真实 token 都保留。九、小结「训练 LLM 但 loss 看不懂 / 模型学不会」的常见根子是labels没把 padding 标成ignore_index-100交叉熵把 padding 也当成学习目标loss 含义错位、训练目标被污染。修复三层次第一层构造 labels 时labels[labelspad_token_id] -100第二层用LlmLossAuditordataclass 把 padding/特殊 token/SFT「只学回答」的屏蔽收敛为单一策略并加整行忽略校验第三层用 pytest 守「padding 被标 -100」「真实 token 保留」「含 pad 与忽略 pad 的 loss 不同」「completion-only 正确」。工程启示自回归训练里「labels 怎么构造」决定了「模型学什么」。padding 必须-100、promptSFT 时必须-100、特殊 token 通常也要-100。任何训练脚本上线前先肉眼看一眼labels再训比训完发现模型废了再回头查省事得多。

相关新闻

OpenClaw插件SDK重构:设计现代AI智能体平台的扩展架构

OpenClaw插件SDK重构:设计现代AI智能体平台的扩展架构

1. 项目概述:一次面向未来的插件SDK重构最近在社区里看到不少关于OpenClaw部署和使用的讨论,从安装报错到接入飞书,热度一直不减。作为一个深度参与过多个AI智能体平台开发的从业者,我对OpenClaw这套开源框架的设计理念一直很感兴…

2026/10/24 10:01:27 阅读更多 →
数据库主键方案对比:自增ID、UUID与雪花算法

数据库主键方案对比:自增ID、UUID与雪花算法

1. 数据库主键方案深度对比:自增ID、UUID与雪花算法 在数据库设计中,主键的选择直接影响着系统性能和架构扩展性。最近在技术社区看到不少关于gorm id自增配置和雪花算法实现的讨论,正好结合我这些年踩过的坑,系统梳理下三种主流方…

2026/10/25 1:52:14 阅读更多 →
YashanDB数据库性能优化实战技巧

YashanDB数据库性能优化实战技巧

1. YashanDB数据库性能优化概述 在数据量爆炸式增长的今天,数据库性能优化已经成为每个开发者必须掌握的技能。YashanDB作为一款国产高性能数据库,其独特的存储架构和查询引擎设计为数据处理提供了坚实基础。但即使是最优秀的数据库系统,也需…

2026/10/25 6:55:09 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/24 11:09:31 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/24 17:47:00 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/24 18:34:36 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/24 9:46:55 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/25 3:12:30 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/24 21:09:59 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/24 9:15:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/24 11:09:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 7:47:32 阅读更多 →