模型训练微调全流程总结

1. LLM 训练数据的格式

训练 LLM 不同阶段需要不同格式的数据。可以把它想象成烹饪——不同菜式需要不同的食材加工方式。

1.1 预训练数据格式

预训练数据是最"原始"的——就是一段一段的纯文本。MiniMind 使用 JSONL 格式(每行一个 JSON 对象):

{"text": "人工智能(Artificial Intelligence,简称AI)是计算机科学的一个分支..."}
{"text": "量子力学是物理学的一个基本理论,描述了微观粒子的行为..."}

每一行就是一个训练样本。模型的任务是:给定前面的 token,预测下一个 token(Next Token Prediction)。

MiniMind 的预训练数据集:

数据集 文件名 大小 说明
小规模 pretrain_t2t_mini.jsonl 1.2GB 快速实验用
全量 pretrain_t2t.jsonl 10GB 完整训练用

1.2 SFT(监督微调)数据格式

SFT 数据是多轮对话格式,让模型学会"你问我答":

{
  "conversations": [
    {"role": "system", "content": "你是一个有用的助手。"},
    {"role": "user", "content": "什么是机器学习?"},
    {"role": "assistant", "content": "机器学习是人工智能的一个子领域..."}
  ]
}

关键区别:SFT 数据有明确的角色划分(system/user/assistant),模型需要学会根据用户输入生成合适的回复。

MiniMind 的 SFT 数据集:

数据集 文件名 大小 说明
小规模 sft_t2t_mini.jsonl 1.6GB 快速实验用
全量 sft_t2t.jsonl 14GB 完整训练用,包含 Tool Call 数据

1.3 DPO(偏好对齐)数据格式

DPO 数据包含"好回答"和"坏回答"的对比:

{
  "chosen": [
    {"role": "user", "content": "解释一下量子纠缠"},
    {"role": "assistant", "content": "量子纠缠是一种量子力学现象...(准确详细的回答)"}
  ],
  "rejected": [
    {"role": "user", "content": "解释一下量子纠缠"},
    {"role": "assistant", "content": "量子纠缠就是两个粒子心灵感应...(不准确的回答)"}
  ]
}

MiniMind 的对齐数据集:

数据集 文件名 大小
DPO dpo.jsonl 53MB
RLAIF rlaif.jsonl 24MB

2. Dataset 和 DataLoader

2.1 Dataset:数据的"仓库"

PyTorch 的 Dataset 类定义了数据集的结构。你需要实现两个核心方法:

class PretrainDataset(Dataset):
    def __init__(self, data_path, tokenizer, max_length):
        # 加载数据
        self.data = []
        with open(data_path, 'r') as f:
            for line in f:
                self.data.append(json.loads(line))
        self.tokenizer = tokenizer
        self.max_length = max_length

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        text = self.data[idx]['text']
        # 将文本转换为 token ids
        token_ids = self.tokenizer.encode(text)
        # 截断到 max_length
        token_ids = token_ids[:self.max_length]
        return token_ids

__getitem__ 每次返回一个样本,这就像是从仓库里取出一箱食材。

2.2 DataLoader:数据的"传送带"

DataLoader 负责把 Dataset 中的数据批量送给模型:

dataloader = DataLoader(
    dataset,
    batch_size=32,          # 每批 32 个样本
    shuffle=True,           # 打乱顺序
    num_workers=4,          # 4 个进程并行加载
    collate_fn=collate_fn   # 自定义批量整合函数
)

为什么需要 collate_fn?因为不同样本的长度可能不同,需要统一处理(padding)。


3. Padding 和 Truncation 策略

3.1 为什么需要 Padding?

GPU 进行并行计算时,要求同一 batch 内所有序列长度相同。但自然语言文本长度参差不齐,所以需要 padding——用特殊的 pad token 填充短序列。

样本 1: [我, 喜, 欢, 学, 习, AI]          → 长度 6
样本 2: [今, 天, 天, 气, 好]              → 长度 5
样本 3: [机, 器, 学, 习, 是, 一, 个, 领, 域] → 长度 9

Padding 后(max_length=9):
样本 1: [我, 喜, 欢, 学, 习, AI, PAD, PAD, PAD]
样本 2: [今, 天, 天, 气, 好, PAD, PAD, PAD, PAD]
样本 3: [机, 器, 学, 习, 是, 一, 个, 领, 域]

3.2 Truncation(截断)

当文本超过 max_seq_len 时,直接截断:

token_ids = token_ids[:max_seq_len]

3.3 max_seq_len 的含义与选择

max_seq_len(最大序列长度)是模型能处理的最长 token 序列。MiniMind 默认 max_seq_len=512

选择原则:

  • 太短:信息被截断,模型无法学到长距离依赖
  • 太长:显存占用剧增(Attention 的计算复杂度是 (O(n^2))),训练变慢
  • 经验法则:覆盖 95% 以上的样本长度即可

4. Loss Mask:预训练与 SFT 的关键区别

4.1 什么是 Loss Mask?

Loss Mask 是一个与 token 序列等长的 0/1 向量,用来控制哪些 token 参与 loss 计算

  • 1 表示该位置的 loss 参与计算
  • 0 表示忽略该位置的 loss

4.2 预训练的 Loss Mask

预训练阶段,所有 token 都参与 loss 计算(除了 padding):

文本:      [我, 喜, 欢, 学, 习, PAD, PAD]
Loss Mask: [1,  1,  1,  1,  1,  0,   0  ]

这很合理——预训练的目标是学习整个语言的统计规律,每个 token 都有价值。

4.3 SFT 的 Loss Mask(重点!)

SFT 阶段,只计算 assistant 回复部分的 loss,prompt(system + user)部分的 loss 被 mask 掉:

Token序列: [<|im_start|>, system, ..., <|im_end|>, <|im_start|>, user, 什么是AI?, <|im_end|>, <|im_start|>, assistant, AI是..., <|im_end|>]
Loss Mask: [0,           0,      ..., 0,          0,           0,    0, ..., 0, 0,          0,         1, ..., 1,         1        ]

4.4 为什么 SFT 只计算回答部分的 loss?

  1. 训练目标不同:SFT 的目标是让模型学会"如何回答",而不是学会"如何提问"
  2. 避免"学歪":如果也计算 prompt 的 loss,模型可能会倾向于生成像用户提问的文本
  3. 信号纯度:只监督回答部分,梯度信号更纯净,训练更高效
  4. 类比:就像考试只判答案对不对,不判你看题看得对不对

4.5 Loss Mask 的代码实现

def create_sft_loss_mask(input_ids, tokenizer):
    """为SFT创建loss mask,只在assistant回复部分计算loss"""
    loss_mask = torch.zeros_like(input_ids, dtype=torch.float)
    in_assistant = False

    for i, token_id in enumerate(input_ids):
        if is_assistant_start(token_id, tokenizer):
            in_assistant = True
            continue
        if is_turn_end(token_id, tokenizer):
            if in_assistant:
                loss_mask[i] = 1.0  # <|im_end|> 也计入 loss
            in_assistant = False
            continue
        if in_assistant:
            loss_mask[i] = 1.0

    return loss_mask

5. Batch Size 与梯度累积

5.1 为什么需要梯度累积?

理想的 batch size 可能很大(比如 256),但 GPU 显存有限,一次只能放下 batch_size=16。怎么办?

梯度累积(Gradient Accumulation):把一个大 batch 拆成多个小 batch,累加梯度后再更新参数。

5.3 Batch Size 选择的影响

Batch Size 优点 缺点
过小 显存友好,正则化效果 训练不稳定,收敛慢
过大 训练稳定,梯度估计准确 显存大,泛化可能差
适中 平衡速度和效果 需要实验确定

6. 数据清洗和去重

高质量的数据是训练好模型的前提。常见的数据处理步骤:

  1. 去重:删除完全相同或高度相似的样本(MinHash、SimHash)
  2. 过滤:去除低质量文本(过短、乱码、广告)
  3. 敏感信息清理:去除个人隐私信息
  4. 语言过滤:保留目标语言的文本
  5. 格式标准化:统一编码、换行符等

MiniMind 的训练数据已经经过清洗和去重处理,可以直接使用。在实际工程中,数据清洗通常占据整个项目 60% 以上的时间。

MiniMind 的数据加载流程大致如下:

JSONL文件 → 逐行读取JSON → Tokenizer编码 → Padding/Truncation → 构造Loss Mask → DataLoader批量输出

1. 预训练的目标:Next Token Prediction

1.1 什么是预训练?

预训练是 LLM 训练的第一阶段,目标是让模型从海量文本中学习语言规律和世界知识

打个比方:预训练就像让一个孩子读遍所有的书——百科全书、小说、论文、新闻……读完之后,这个孩子虽然还不会"对话",但已经掌握了丰富的语言能力和知识。

1.2 Next Token Prediction

预训练的任务出奇地简单——给定前面的所有 token,预测下一个 token

输入: "人工智能是计算机科学的一个"
目标: "分"

输入: "人工智能是计算机科学的一个分"
目标: "支"

就像一个超级词语接龙游戏。模型要学会在所有可能的上下文中,预测最合理的下一个 token。

1.3 自回归语言模型

这种"基于前文预测下一个 token"的方式叫做自回归(Autoregressive)。形式化地表达:

P(x1,x2,...,xn)=∏t=1nP(xt|x1,x2,...,xt−1)

模型把整个文本的联合概率分解为一系列条件概率的乘积。训练时,每个位置都在做一次分类任务:在词表大小的候选中,选出正确的下一个 token。


2. 交叉熵损失函数

2.1 直觉理解

模型每预测一个 token,会输出一个概率分布(对词表中每个 token 的概率预测)。我们希望正确 token 的概率尽可能高

交叉熵损失衡量的就是模型预测的分布和真实分布之间的"差距"。

3.1 训练循环

for each epoch:
    for each batch in dataloader:
        1. 将 input_ids 送入模型 → 得到 logits
        2. 计算 cross_entropy loss
        3. loss.backward() → 计算梯度
        4. 梯度裁剪
        5. optimizer.step() → 更新参数
        6. scheduler.step() → 更新学习率
        7. optimizer.zero_grad() → 清零梯度

3.2 输入和标签的构造

预训练时,输入和标签有一个 token 的偏移:

原始序列:  [A, B, C, D, E]
输入 (x):  [A, B, C, D]    → 模型的输入
标签 (y):  [B, C, D, E]    → 模型需要预测的目标

4. 学习率调度:余弦退火 + Warmup

4.1 为什么需要学习率调度?

固定学习率训练存在问题:

  • 太大:训练不稳定,loss 震荡
  • 太小:训练太慢
  • 训练早期和后期对学习率的需求不同

4.2 Warmup 阶段

训练刚开始时,模型参数是随机初始化的,梯度方向不可靠。如果一上来就用大学习率,参数可能"飞了"。

Warmup:在前几百步中,让学习率从 0 线性增长到目标值。

4.3 余弦退火(Cosine Annealing)

Warmup 之后,学习率按余弦函数逐渐衰减:

为什么用余弦而不是线性衰减? 余弦退火在训练中期衰减较慢,给模型更多时间学习;在训练末期加速衰减,帮助模型精细调整。

4.4 学习率曲线示意

学习率
  |    /\
  |   /  \
  |  /    \
  | /      \____
  |/            \
  +-------------→ 训练步数
  ↑warmup  ↑cosine decay

5. 混合精度训练(AMP)

5.1 什么是混合精度?

默认情况下,模型参数和计算使用 FP32(32位浮点数)。混合精度训练在计算时使用 FP16/BF16,存储主权重时保留 FP32,从而:

  1. 减少显存占用:FP16 占用空间是 FP32 的一半
  2. 加速计算:现代 GPU(如 3090)有专门的 FP16 计算单元(Tensor Core)
  3. 保持精度:关键操作仍用 FP32

5.2 FP16 vs BF16

特性 FP16 BF16
指数位 5 位 8 位
尾数位 10 位 7 位
数值范围 较小 与 FP32 相同
精度 较高 较低
适用场景 需要 loss scaling 更稳定,推荐使用

BF16 的数值范围与 FP32 相同,不容易出现溢出(overflow),是目前训练 LLM 的首选。


6. 梯度裁剪(Gradient Clipping)

6.1 为什么需要梯度裁剪?

训练过程中,某些 batch 可能产生异常大的梯度(梯度爆炸),导致参数更新过大,训练崩溃。

6.3 梯度裁剪 vs 梯度归一化

  • 梯度裁剪:只在梯度过大时裁剪,正常梯度不受影响
  • 梯度归一化:总是将梯度缩放到固定范数

梯度裁剪更常用,因为它不会影响正常的梯度更新。


7. 训练开销与检查点

7.1 MiniMind 训练开销

MiniMind 64M 参数模型在 NVIDIA 3090(24GB)上的训练时间参考:

训练阶段 数据集 大约时间
预训练(小) pretrain_t2t_mini.jsonl ~1.21h
预训练(全量) pretrain_t2t.jsonl ~10h

7.2 检查点保存

训练过程中定期保存模型权重(checkpoint),用于:

  1. 断点续训:训练中断后从最近的 checkpoint 恢复
  2. 选择最优模型:训练结束后选 loss 最低的 checkpoint
  3. 分析训练过程:对比不同阶段的模型能力
if step % save_interval == 0:
    torch.save({
        'model_state_dict': model.state_dict(),
        'optimizer_state_dict': optimizer.state_dict(),
        'step': step,
        'loss': loss.item(),
    }, f'checkpoint_step_{step}.pt')

7.3 断点续训

checkpoint = torch.load('checkpoint_step_1000.pt')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_step = checkpoint['step'

8.预训练后的模型能做什么?

预训练完成后,模型学会了:

  • 语法和语言结构
  • 常识和世界知识
  • 基本的逻辑推理能力

但是,预训练模型不会对话!如果你给它输入"什么是机器学习?",它可能会续写成一篇论文风格的文章,而不是给你一个结构化的回答。

输入: "什么是机器学习?"
预训练模型输出: "机器学习是近年来备受关注的研究领域之一。在过去的十年中..."
                (续写模式,而非对话模式)

SFT 后的模型输出: "机器学习是人工智能的一个子领域,它让计算机能够从数据中
                    自动学习规律,而不需要被显式编程。主要分为三类:..."
                  (对话模式,结构化回答)

这就是为什么预训练之后还需要 SFT(监督微调)。

9. 训练 Loss 曲线解读

一条健康的预训练 loss 曲线应该是:

Loss
  |
8 |*
  | *
6 |  *
  |   **
4 |     ***
  |        ****
2 |            **********
  |                      **********
  +------------------------------------→ Steps
  • 快速下降阶段:模型快速学习基本的语言模式
  • 缓慢下降阶段:学习更细粒度的语言规律
  • 趋于平稳:模型接近收敛

异常情况:

  • loss 突然上升:学习率太大,或数据有问题
  • loss 持续不下降:学习率太小,或模型太小
  • loss 震荡剧烈:batch size 太小,梯度估计噪声

1. 全参微调的问题

1.1 什么是全参微调?

全参微调(Full Fine-Tuning)就是在 SFT 时更新模型的所有参数。对于 MiniMind 的 64M 参数来说这还可以接受,但对于大模型来说问题就来了:

模型 参数量 FP16 权重大小 训练显存(估算)
MiniMind 64M ~128MB ~1GB
LLaMA-7B 7B ~14GB ~56GB
LLaMA-70B 70B ~140GB ~560GB

1.2 全参微调的三大问题

  1. 计算量巨大:需要为所有参数计算梯度并更新
  2. 显存不够:训练显存 ≈ 4 × 模型权重大小(参数 + 梯度 + 优化器状态)
  3. 容易过拟合:参数量远大于 SFT 数据量时,模型容易记忆训练集
  4. 存储成本高:每个下游任务都需要保存一份完整的模型权重

1.3 能否只训练一小部分参数?

最直觉的想法:冻结大部分参数,只训练最后几层。但这种方式效果有限,因为每一层都可能需要适配新任务。

LoRA 提供了一个更优雅的解决方案。


2. LoRA 的核心思想

2.1 核心假设

LoRA(Low-Rank Adaptation)的核心假设是:

模型在适配下游任务时,权重的变化量 ΔW 是低秩的。

换句话说,虽然权重矩阵 W 本身是高维的(比如 768×768),但从预训练到微调,权重的变化 ΔW 可以用一个低秩矩阵很好地近似。

2.2 低秩分解

2.3 LoRA 的工作方式

LoRA 的做法:

  1. 冻结原始预训练权重 (W)(不更新)
  2. 在旁路添加两个小矩阵 (B) 和 (A)
  3. 前向传播时:(W' = W + BA)
         ┌─────────────┐
   x ──→ │  W (冻结)   │ ──→  Wx
   │     └─────────────┘       │
   │                           + ──→ 输出 = Wx + BAx
   │     ┌───┐   ┌───┐        │
   └───→ │ A │ → │ B │ ──→  BAx
         └───┘   └───┘
         r×d     d×r
         (可训练)

训练时只更新 A 和 B,原始权重 W 完全不变。

2.4 初始化策

  • A 矩阵:使用随机初始化(通常是高斯分布)
  • B 矩阵:初始化为全零

为什么 B 初始化为零?因为这样训练开始时 (BA = 0),模型的输出和原始模型完全一致,LoRA 不会干扰预训练学到的知识。随着训练进行,B 逐渐学到有意义的值。

2.5 缩放因子

实际使用时还有一个缩放因子 (\alpha):

W′=W+αr⋅BA

(\alpha / r) 控制 LoRA 的"影响力"。通常 (\alpha) 设为 (r) 的 1-2 倍,使缩放因子接近 1。


3. 参数量对比

3.1 全参 vs LoRA

以 MiniMind 中一个典型的线性层为例:

原始线性层(768 → 768): 参数量=768×768=589,824

LoRA(r=8): 参数量=768×8+8×768=6,144+6,144=12,288

压缩比: 12,288589,824≈2.1

只训练 2% 的参数!

3.2 不同秩 r 的参数量

秩 r LoRA 参数量 占原始比例
1 1,536 0.26%
4 6,144 1.04%
8 12,288 2.08%
16 24,576 4.17%
32 49,152 8.33%
64 98,304 16.67%

3.3 秩 r 如何选择?

  • r 太小:表达能力不足,无法充分适配下游任务
  • r 太大:参数量增加,失去 LoRA 的效率优势
  • 经验值:r = 4~16 通常就够了
  • 复杂任务用更大的 r,简单任务用更小的 r

原始论文中的实验表明,对于大多数 NLP 任务,r = 4 就能达到接近全参微调的效果。


4. LoRA 加在哪些层?

4.1 通常的选择

LoRA 不需要加在模型的每一层上。最常见的做法是加在 Attention 层的投影矩阵上:

投影层 维度 说明
Q 投影(Wq) 768 → 768 Query 投影
K 投影(Wk) 768 → 384(GQA) Key 投影
V 投影(Wv) 768 → 384(GQA) Value 投影
O 投影(Wo) 768 → 768 Output 投影

有些实现也会在 FFN 层加 LoRA,但性价比不如 Attention 层。

4.2 为什么 Attention 层更有效?

Attention 层是模型学习"关注什么"的核心。不同下游任务需要模型关注不同的信息,因此 Attention 层的权重变化最大。在这些层加 LoRA,能以最少的参数实现最大的适配效果。

1. 什么是知识蒸馏?

1.1 直觉理解

想象一位经验丰富的教授(教师模型,Teacher)和一位刚入学的学生(学生模型,Student)。

传统训练方式:学生只看教科书(标签数据)学习。 知识蒸馏:学生不仅看教科书,还听教授讲课(获取教授的思考过程)。

教授的"思考过程"是什么?就是教师模型对每个 token 输出的完整概率分布——不仅告诉你答案是"猫",还告诉你"狗"的概率是 0.1、"虎"的概率是 0.05......这些"软信息"包含了丰富的知识。

1.2 为什么需要蒸馏?

问题 说明
大模型太大 GPT-4 级别的模型无法部署到手机或嵌入式设备
推理成本高 大模型的 API 调用成本高
小模型太弱 直接训练小模型效果不够好
蒸馏的价值 让小模型获得接近大模型的效果

2. 黑盒蒸馏 vs 白盒蒸馏

2.1 黑盒蒸馏

黑盒蒸馏不需要接触教师模型的内部状态,只使用教师模型的输出结果

教师模型(大模型)
    │
    ▼ 生成文本(硬标签)
"量子纠缠是一种量子力学现象,当两个粒子..."
    │
    ▼ 作为训练数据
学生模型(小模型)← 像正常 SFT 一样训练

本质上就是:用大模型生成高质量数据,然后用这些数据训练小模型

MiniMind 的黑盒蒸馏:SFT 数据中大量高质量回答来自 Qwen3、DeepSeek R1 等强模型。这些数据本身就是黑盒蒸馏的产物。

2.2 白盒蒸馏

白盒蒸馏需要访问教师模型的内部状态(logits 分布),不仅学习"正确答案",还学习教师的"思考方式"。

教师模型(大模型)
    │
    ▼ 输出 logits 分布(软标签)
[猫: 0.7, 狗: 0.15, 虎: 0.05, 兔: 0.03, ...]
    │
    ▼ KL 散度损失
学生模型(小模型)← 学习匹配教师的分布

2.3 对比总结

维度 黑盒蒸馏 白盒蒸馏
需要教师模型内部状态 是(需要 logits)
信息量 少(只有硬标签) 多(完整概率分布)
实现难度 低(生成数据+SFT) 高(需要同时运行两个模型)
适用场景 教师模型只能通过 API 访问 教师模型可以本地运行
效果 通常更好

3. 硬标签 vs 软标签

3.1 什么是硬标签?

硬标签就是 one-hot 编码——只告诉你正确答案是什么:

正确答案: "猫"
硬标签: [0, 0, 1, 0, 0, ...]  ← 只有"猫"的位置是1

3.2 什么是软标签?

软标签是模型输出的完整概率分布:

教师模型输出:
软标签: [0.01, 0.02, 0.70, 0.15, 0.05, 0.03, ...]
         鸟     鱼     猫     狗     虎     兔

3.3 软标签包含更多信息

关键洞察:软标签中"猫和狗的概率接近"这个信息非常有价值——它告诉学生模型,猫和狗在视觉/语义上是相似的,而猫和鱼的差异很大。

硬标签只说"答案是猫"。 软标签说"答案最可能是猫,但和狗也有一定相似性,和鱼差很远"。

这些隐含的关系信息被称为 dark knowledge(暗知识),是蒸馏的核心价值。

4.3 为什么蒸馏需要高温度?

当 (T=1) 时,教师模型的输出分布通常非常"尖锐"——正确答案的概率远高于其他选项,接近硬标签。此时软标签携带的额外信息有限。

提高温度 (T),分布变得更"平缓":

  • 不同类别之间的概率差异被放大
  • "暗知识"得以显现
  • 学生能学到更多类别间的关系

常用温度:T = 2~5。MiniMind 通常使用 T = 2。

4.4 温度的直觉类比

想象一位教授批改试卷:

  • T=1(低温):"A 选项对,BCD 都错。"——信息量少
  • T=3(高温):"A 选项最对(70分),B 也有道理(20分),C 部分正确(8分),D 完全不对(2分)。"——信息量丰富

一、为什么 SFT 之后还需要对齐?

1.1 SFT 的局限

经过预训练和 SFT 之后,模型已经能够按照指令格式输出回答。但 SFT 本质上是模仿学习——它让模型学会"像训练数据那样说话",但无法让模型理解"什么是好的回答"。

举个例子:

用户: 如何减肥?

回答A(有害): 不吃饭就行了,一周瘦10斤。
回答B(有益): 建议通过合理饮食和适量运动来减重,每周减0.5-1kg比较健康。

SFT 模型可能同时在训练数据中见过这两种风格的回答,它并不"知道"应该优先输出哪一种。

1.2 对齐(Alignment)的目标

对齐要解决的核心问题是:让模型的输出符合人类的偏好和价值观

三个关键维度:

  • 有帮助(Helpful):回答切题、信息丰富
  • 诚实(Honest):不编造事实、承认不确定性
  • 无害(Harmless):不输出有害、歧视性内容

1.3 RLHF 的经典流程

OpenAI 在 InstructGPT 论文中提出了经典的三步 RLHF:

Step 1: SFT        → 让模型学会指令跟随
Step 2: 训练 RM    → 训练一个奖励模型来评分回答质量
Step 3: PPO 训练   → 用 RL 优化模型,让高奖励的回答概率更高

但这个流程有个巨大问题:太复杂了。需要同时维护 4 个模型(SFT 模型、RM 模型、PPO 策略模型、PPO 价值模型),训练不稳定,超参数难调。


二、DPO:直接偏好优化

2.1 核心思想

DPO(Direct Preference Optimization)的核心洞见是:

不需要显式训练奖励模型,可以直接从偏好数据中学习策略。

DPO 论文证明了一个数学等价关系:在 RLHF 框架下,最优策略可以用一个封闭形式的解表达出来,这个解只依赖于偏好数据和参考模型,不需要中间的奖励模型。

2.2 DPO 的数据格式

DPO 需要偏好对(preference pairs)数据:

{
  "prompt": "如何学好编程?",
  "chosen": "学好编程需要多练习,建议从Python入门...",
  "rejected": "编程很难学,你还是放弃吧"
}

每条数据包含:

  • prompt:用户输入
  • chosen(( y_w )):人类偏好的"好回答"(winner)
  • rejected(( y_l )):人类不偏好的"坏回答"(loser)

MiniMind 使用的偏好数据文件是 dpo.jsonl

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐