它们的核心区别在于其设计目标数据处理方式,这直接对应了预训练 (Pre-training)监督微调 (Supervised Fine-Tuning, SFT) 这两个阶段的根本不同。

  • PretrainDataset (预训练):目标是让模型学习通用的语言知识。它处理的是大段的、非结构化的文本,训练模型预测序列中的下一个词。
  • SFTDataset (监督微调):目标是让模型学习遵循指令和进行对话。它处理的是结构化的“指令-回答”或“多轮对话”数据,只训练模型去预测助手的回答部分

核心区别对比

特性 PretrainDataset (预训练) SFTDataset (监督微调)
训练目标 学习语言的通用模式、语法、事实知识。成为一个文本续写器 学习如何根据用户输入(指令/问题)生成有用的、符合要求的回答。成为一个对话助手
输入数据格式 非结构化的纯文本。通常是一个 JSON 对象,包含一个 'text' 字段。 结构化的对话数据。通常是一个 JSON 对象,包含一个 'conversations' 列表。
数据处理核心 直接对整段 'text' 进行 Tokenize。 使用 apply_chat_template'conversations' 列表转换成符合特定聊天格式(如 ChatML)的字符串。
损失计算 (loss_mask) 在所有非填充 (non-padding) 的 token 上计算损失。模型需要学习预测整个文本序列。 只在“助手回答”部分的 token 上计算损失。模型被强制只学习如何生成回答,而不会去学习预测用户的输入。
代码实现关键点 loss_mask = (input_ids != self.tokenizer.pad_token_id) _generate_loss_mask 函数,它会定位到 `<

详细代码层面解释

1. 输入数据 (Input Data)
  • PretrainDataset:

    • 它期望的数据是这样的 JSONL 文件:
      {"text": "第一篇维基百科文章的内容..."}
      {"text": "第二本小说的内容..."}
      
    • __getitem__ 中,它直接处理 sample['text']
  • SFTDataset:

    • 它期望的数据是这样的 JSONL 文件,包含多轮对话:
      {"conversations": [{"content": "你好"}, {"content": "你好!有什么可以帮助你的吗?"}]}
      {"conversations": [{"content": "帮我写一首关于月亮的诗"}, {"content": "好的,当然。静夜思,床前明月光..."}]}
      
    • __getitem__ 中,它处理的是 sample['conversations']
2. 数据处理与格式化 (Data Processing)
  • PretrainDataset:

    • 就是将长文本进行 tokenize,然后截断或填充到 max_length
    • encoding = self.tokenizer(str(sample['text']), ...)
  • SFTDataset:

    • 有一个关键的 _create_chat_prompt 方法。
    • 这个方法使用 tokenizer.apply_chat_template,它会把对话列表转换成一个带有特殊标记的字符串。例如,上面的对话可能会被转换成:
      <|im_start|>user
      你好<|im_end|>
      <|im_start|>assistant
      你好!有什么可以帮助你的吗?<|im_end|>
      
    • 这个格式化的过程对于让模型理解角色(谁是用户,谁是助手)至关重要。
3. 损失掩码 (loss_mask) - 这是最关键的区别!
  • PretrainDataset:

    • loss_mask 的生成非常简单:loss_mask = (input_ids != self.tokenizer.pad_token_id)
    • 这意味着,只要一个 token 不是填充符 [PAD],模型在预测它的时候就会计算损失。
    • 效果:模型学习预测整个文本中的每一个词。
  • SFTDataset:

    • loss_mask 的生成非常精巧,在 _generate_loss_mask 函数中实现。
    • 它会遍历 tokenized 之后的 input_ids,精确地找到助手回答的起始标记 (<|im_start|>assistant) 和结束标记 (<|im_end|>)。
    • 然后,它只将助手回答部分的 token 对应的 loss_mask 位置设为 1,其他所有部分(包括用户的提问和特殊标记)都为 0。
    • 效果:在反向传播和参数更新时,只有 loss_mask 为 1 的位置才会对梯度有贡献。这等于是在告诉模型:“你不需要学习如何说出用户的话,你只需要学习如何在我(助手)该说话的时候,说出正确的话。”
4. 训练样本的构建 (X, Y)

两个类在这一步是相同的:

  • X = torch.tensor(input_ids[:-1], ...)
  • Y = torch.tensor(input_ids[1:], ...)

这都是标准语言模型的 “next token prediction” 任务,即用前面的序列 X 来预测下一个词 Y。但由于 loss_mask 的存在,SFTDataset 在计算损失时会忽略掉大部分 Y 中的 token。

  • 预训练 (PretrainDataset) 就像是通识教育。一个人通过阅读海量的书籍、文章、网页(非结构化文本),学习语法、常识和推理能力。他学习的目标是理解并能补全任何一句话。

  • 监督微调 (SFTDataset) 就像是岗前培训。这个人已经具备了通用语言能力,现在要培训他成为一名客服。培训材料是大量的“客户问题-标准回答”对话录(结构化数据)。在培训中,我们只考核他回答的好不好(只在助手回答部分计算 loss),而不会去考核他复述客户问题的能力。通过这种方式,他学会了如何扮演好“客服”这个特定角色。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐