面包屑图标 当前位置: 首页
AI资讯
热点详情

LLM预训练与SFT对齐的Loss差异及代码解析

AI热点日报
AI热点日报时间:2026-06-28
热点解读

大型语言模型预训练与监督微调(SFT)的损失函数设计逻辑不同。预训练采用自监督学习,基于海量无标注数据学习通用语言表示;SFT采用监督学习,基于高质量标注数据迁移至特定任务。两者在数据、目标和代码实现上有本质差异。

说到大型语言模型(LLM)的训练,预训练和后续的对齐(SFT)阶段,虽然表面上都用loss函数来驱动模型学习,但背后的设计逻辑和目标其实天差地别。很多同学刚接触时容易混淆,觉得反正都是“算损失、反向传播”,但仔细拆开来看,这两个阶段的loss从数据、目标到代码实现,都有本质上的不同。下面我们就从原理到代码,把这两者的差异彻底捋清楚。

1. 预训练阶段

  • 目标:让模型学会语言的通用表示——语法、语义、常识知识,甚至一些推理能力。
  • 数据:海量、无标注的原始文本,比如整个互联网的网页、书籍、代码仓库。
  • Loss函数:自监督学习。主流方案有两种:
    • Masked Language Modeling (MLM) —— 随机遮住句子中的词,让模型猜被遮住的是什么。
    • Causal Language Modeling (CLM) —— 从左到右,根据已经出现的词预测下一个词。
  • Loss特点
    • 关注模型对语言结构和知识的整体理解。
    • 数值通常较大,因为模型要从零开始学习海量信息。
    • 随着训练进行,loss逐步下降,意味着模型对语言的建模能力在持续提升。
  • 预训练Loss代码解析:以transformers库的源码为例,loss计算封装在Trainer的compute_loss中,实际由LabelSmoother类完成。以下是核心实现:
 544 @dataclass
 545 class LabelSmoother:
 546     """
 547     Adds label-smoothing on a pre-computed output from a Transformers model.
 548
 549     Args:
 550         epsilon (`float`, *optional*, defaults to 0.1):
 551             The label smoothing factor.
 552         ignore_index (`int`, *optional*, defaults to -100):
 553             The index in the labels to ignore when computing the loss.
 554     """
 555
 556     epsilon: float = 0.1
 557     ignore_index: int = -100
 558
 559     def __call__(self, model_output, labels, shift_labels=False):
 560         logits = model_output["logits"] if isinstance(model_output, dict) else model_output[0]
 561         if shift_labels:
 562             logits = logits[..., :-1, :].contiguous()
 563             labels = labels[..., 1:].contiguous()
 564
 565         log_probs = -nn.functional.log_softmax(logits, dim=-1)
 566         if labels.dim() == log_probs.dim() - 1:
 567             labels = labels.unsqueeze(-1)
 568
 569         padding_mask = labels.eq(self.ignore_index)
 570         # In case the ignore_index is -100, the gather will fail, so we replace labels by 0. The padding_mask
 571         # will ignore them in any case.
 572         labels = torch.clamp(labels, min=0)
 573         nll_loss = log_probs.gather(dim=-1, index=labels)
 574         # works for fp16 input tensor too, by internally upcasting it to fp32
 575         smoothed_loss = log_probs.sum(dim=-1, keepdim=True, dtype=torch.float32)
 576
 577         nll_loss.masked_fill_(padding_mask, 0.0)
 578         smoothed_loss.masked_fill_(padding_mask, 0.0)
 579
 580         # Take the mean over the label dimensions, then divide by the number of active elements (i.e. not-padded):
 581         num_active_elements = padding_mask.numel() - padding_mask.long().sum()
 582         nll_loss = nll_loss.sum() / num_active_elements
 583         smoothed_loss = smoothed_loss.sum() / (num_active_elements * log_probs.shape[-1])
 584         return (1 - self.epsilon) * nll_loss + self.epsilon * smoothed_loss
  • shift_labels:是否需要位移。当设置shift_labels=True时,logits[:-1, :](第0到倒数第二个位置的预测),labels[1:](第1到最后一个位置的真实token)。这样做的意思是,用第0到倒数第二个位置的输出去预测第1到最后一个位置的真实词,实现自回归预测。
  • log_probs:先取log_softmax,将模型输出的logits转换成对数概率分布。
  • padding_mask:通过labels.eq(self.ignore_index)找到label中填充的特殊token(通常是pad_token_id),然后把这些位置在loss中忽略掉。
  • nll_loss:核心负对数似然。使用gather(dim=-1, index=labels),从log_probs中取出与真实标签对应的对数概率值。这里有个细节:log_probs的shape是 (batch_size, sequence_length, vocab_size)labels的shape是 (batch_size, sequence_length),gather会沿着vocab_size维度取出指定索引的值。比如log_probs[0, 2, 500]表示第一个样本第三个词预测为词汇表第500个词的对数概率,而labels[0, 2]就是该位置的真实词索引。取出来之后再做平滑处理,并去掉padding的影响。

2. 对齐阶段 (SFT: Supervised Fine-Tuning)

  • 目标:将预训练模型的能力“对齐”到特定任务或场景,比如对话生成、文本摘要、机器翻译,或者把LLM落地到某个垂直业务。
  • 数据:人工标注的高质量数据,例如对话记录、摘要文章、翻译句对、垂类业务问答对。
  • Loss函数:监督学习。根据任务选择:
    • Cross-Entropy Loss:用于分类任务(情感分析、意图识别等)。
    • Mean Squared Error (MSE) Loss:用于回归任务(文本评分、质量评估等)。
  • Loss特点
    • 关注模型在特定任务上的表现,而不是通用语言知识。
    • 数值通常较小,因为模型已经在预训练阶段学好了基础,只需微调。
    • 随着训练进行,loss下降,说明模型在目标任务上越做越好。
  • 对齐SFT Loss代码解析:不同框架实现细节略有差异,但核心思路一致:先剔除prompt部分(不让模型学习如何理解指令),只对输出(answer)部分计算loss。以开源框架LLaMA-Factory为例,其SFT loss计算在train/sft/trainer.pyprediction_step中:
 81     @override
 82     def prediction_step(
 83         self,
 84         model: "torch.nn.Module",
 85         inputs: Dict[str, Union["torch.Tensor", Any]],
 86         prediction_loss_only: bool,
 87         ignore_keys: Optional[List[str]] = None,
 88     ) -> Tuple[Optional[float], Optional["torch.Tensor"], Optional["torch.Tensor"]]:
 89         r"""
 90         Removes the prompt part in the generated tokens.
 91
 92         Subclass and override to inject custom beha vior.
 93         """
 94         labels = inputs["labels"] if "labels" in inputs else None
 95         if self.args.predict_with_generate:
 96             assert self.tokenizer.padding_side == "left", "This method only accepts left-padded tensor."
 97             labels = labels.detach().clone() if labels is not None else None  # backup labels
 98             prompt_len, label_len = inputs["input_ids"].size(-1), inputs["labels"].size(-1)
 99             if prompt_len > label_len:
100                 inputs["labels"] = self._pad_tensors_to_target_len(inputs["labels"], inputs["input_ids"])
101             if label_len > prompt_len:  # truncate the labels instead of padding the inputs (llama2 fp16 compatibility)
102                 inputs["labels"] = inputs["labels"][:, :prompt_len]
103
104         loss, generated_tokens, _ = super().prediction_step(  # ignore the returned labels (may be truncated)
105             model, inputs, prediction_loss_only=prediction_loss_only, ignore_keys=ignore_keys
106         )
107         if generated_tokens is not None and self.args.predict_with_generate:
108             generated_tokens[:, :prompt_len] = self.tokenizer.pad_token_id
109             generated_tokens = generated_tokens.contiguous()
110
111         return loss, generated_tokens, labels
112
113     def _pad_tensors_to_target_len(self, src_tensor: "torch.Tensor", tgt_tensor: "torch.Tensor") -> "torch.Tensor":
114         r"""
115         Pads the tensor to the same length as the target tensor.
116         """
117         assert self.tokenizer.pad_token_id is not None, "Pad token is required."
118         padded_tensor = self.tokenizer.pad_token_id * torch.ones_like(tgt_tensor)
119         padded_tensor[:, -src_tensor.shape[-1] :] = src_tensor  # adopt left-padding
120         return padded_tensor.contiguous()  # in contiguous memory
  • 关键逻辑:SFT计算loss时,prompt部分(即指令或输入)不参与。做法是:将labels中对应prompt的位置用pad_token_id掩码掉,这样后续调用预训练阶段的loss计算时,那些位置就会被忽略(因为预训练loss会通过ignore_index跳过padding)。具体来看:
    • padding_side == "left":要求左填充,这是生成任务常用的设置。
    • 如果prompt_len > label_len,说明labels长度短于输入(可能因为labels只包含输出部分),需要用_pad_tensors_to_target_len把labels向左填充到和input_ids一样长,填充值用pad_token_id,这样prompt对应的位置就被mask了。
    • 如果label_len > prompt_len,则截断labels使之与input_ids等长。
    • 处理好之后,调用父类(transformers的Trainer)的prediction_step,此时loss只计算输出部分(因为prompt部分已被标记为忽略)。

总结

特征预训练对齐 (SFT)
目标学习通用语言表示迁移到特定任务
数据海量未标注数据高质量标注数据
Loss函数自监督学习 (MLM, CLM)监督学习 (Cross-Entropy, MSE)
Loss特点数值较大,关注语言理解数值较小,关注任务表现

当然,现实中的训练方案往往更灵活。比如某些预训练任务会混入少量标注数据,而部分对齐任务也会采用对比学习之类的自监督方法。但核心框架就是这样:预训练负责打基础,SFT负责让模型“听懂人话”,两者在loss上的设计差异,从根本上决定了模型最终的能力边界。

热点追踪提示词
你是一名 AI 行业编辑,请围绕下面这条热点输出一份资讯解读:
热点:LLM预训练与SFT对齐的Loss差异及代码解析要求:
1. 先用一句话解释这条热点在讲什么
2. 再总结它为什么重要
3. 说明会影响哪些 AI 产品或内容方向
4. 最后给出 3 个适合资讯站使用的标题
来源:https://www.53ai.com/news/finetuning/2024121692748.html
ai 人工智能

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关热点
AI热点2026-07-10 12:34
专业Logo设计打造令人难忘的品牌形象

DiffusionLogoStudio是一款面向个人创业者、小型企业主和创业公司的AI工具,无需设计知识即可生成独特、可商用的品牌标识。它支持Logo定制、无限可伸缩、添加文字及模拟场景预览,低成本解决传统设计成本高、易撞脸的问题。

AI热点2026-07-10 12:33
Stratup.ai AI创业点子与工具平台

Stratup ai是一款基于人工智能的创业点子生成与探索工具,面向创业者、企业家和投资者。它能发现商业创意、分析市场需求与竞争格局,生成包括市场规模、风险评估在内的详细报告,辅助商业决策,将创意转化为系统流程。

AI热点2026-07-10 12:33
猫眼人工智能反欺凌软件学校智能监控与预警

猫眼是一套基于人工智能的校园反欺凌系统,通过分析音频与视频信号实时检测言语威胁和肢体冲突,秒级向教职工发送警报,将被动监控升级为主动防御,助力学校及时干预欺凌事件。

AI热点2026-07-10 12:33
SAP旗下最新推出的商业智能AI助手Joule全面介绍

SAP推出商业智能AI助手Joule,将生成式AI嵌入企业工作流,覆盖HR、财务、供应链等领域。能撰写招聘广告、分析销售业绩、提供供应链改善方案并自动联系系统,核心特色是理解业务语境,提供情景化建议并协助完成日常工作。

延伸阅读