山东大学项目实训二 2
模型训练微调全流程总结
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?
- 训练目标不同:SFT 的目标是让模型学会"如何回答",而不是学会"如何提问"
- 避免"学歪":如果也计算 prompt 的 loss,模型可能会倾向于生成像用户提问的文本
- 信号纯度:只监督回答部分,梯度信号更纯净,训练更高效
- 类比:就像考试只判答案对不对,不判你看题看得对不对
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. 数据清洗和去重
高质量的数据是训练好模型的前提。常见的数据处理步骤:
- 去重:删除完全相同或高度相似的样本(MinHash、SimHash)
- 过滤:去除低质量文本(过短、乱码、广告)
- 敏感信息清理:去除个人隐私信息
- 语言过滤:保留目标语言的文本
- 格式标准化:统一编码、换行符等
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,从而:
- 减少显存占用:FP16 占用空间是 FP32 的一半
- 加速计算:现代 GPU(如 3090)有专门的 FP16 计算单元(Tensor Core)
- 保持精度:关键操作仍用 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),用于:
- 断点续训:训练中断后从最近的 checkpoint 恢复
- 选择最优模型:训练结束后选 loss 最低的 checkpoint
- 分析训练过程:对比不同阶段的模型能力
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 全参微调的三大问题
- 计算量巨大:需要为所有参数计算梯度并更新
- 显存不够:训练显存 ≈ 4 × 模型权重大小(参数 + 梯度 + 优化器状态)
- 容易过拟合:参数量远大于 SFT 数据量时,模型容易记忆训练集
- 存储成本高:每个下游任务都需要保存一份完整的模型权重
1.3 能否只训练一小部分参数?
最直觉的想法:冻结大部分参数,只训练最后几层。但这种方式效果有限,因为每一层都可能需要适配新任务。
LoRA 提供了一个更优雅的解决方案。
2. LoRA 的核心思想
2.1 核心假设
LoRA(Low-Rank Adaptation)的核心假设是:
模型在适配下游任务时,权重的变化量 ΔW 是低秩的。
换句话说,虽然权重矩阵 W 本身是高维的(比如 768×768),但从预训练到微调,权重的变化 ΔW 可以用一个低秩矩阵很好地近似。
2.2 低秩分解
2.3 LoRA 的工作方式
LoRA 的做法:
- 冻结原始预训练权重 (W)(不更新)
- 在旁路添加两个小矩阵 (B) 和 (A)
- 前向传播时:(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。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)