MiniMind SFT:流程、验证与实验结论

本页定位:在已验证 Pretrain 底座上完成 Full SFT,并记录真实产物、对照验证和失败边界。Pretrain 的环境与底座流程见 MiniMind Pretrain;本页不重复其数据读取和 CUDA 排障细节。

阅读顺序为:目标与前置 → 可复现步骤 → 成功判据 → 产物验证 → 常见失败 → 后续路线。

1. 训练目标与基础概念

目标:记录如何在 Windows + Anaconda + E 盘项目结构 下,把 MiniMind 的 Full SFT 训练成功跑起来,并补充 SFT 的核心概念、默认参数、日志怎么看、训练完成后如何验证权重是否真的被使用。 适合人群:LLM 小白,已经跑通过 Pretrain,准备继续做 SFT。

1.1 本次验证目标

这次 SFT 的目标,不是重新学语言本身,而是:

让已经做过 Pretrain 的模型,更像一个会按指令回答的助手。

如果 SFT 跑通了,通常会看到这些结果:

  • 能成功启动 trainer\train_full_sft.py
  • 能正确读取 pretrain_512.pth
  • 能正确读取 sft_mini_512.jsonl 或别的 SFT 数据
  • 训练日志持续输出 step / loss / lr
  • 最终在 out 里得到 full_sft_512.pth
  • 可以用 python eval_llm.py --weight full_sft 测试模型

简单理解:

  • Pretrain:让模型学会“会说话”
  • SFT:让模型学会“按指令说话”

1.2 推荐目录结构

推荐并已验证比较清晰的目录结构如下:

E:\minimind
├─ MiniMind2
│  ├─ config.json
│  ├─ model.safetensors
│  ├─ tokenizer.json
│  ├─ tokenizer_config.json
│  ├─ special_tokens_map.json
│  └─ ...
├─ dataset
│  ├─ pretrain_hq.jsonl
│  ├─ sft_mini_512.jsonl
│  ├─ sft_512.jsonl
│  ├─ sft_1024.jsonl
│  ├─ sft_2048.jsonl
│  ├─ dpo.jsonl
│  └─ ...
├─ checkpoints
├─ out
├─ trainer
│  ├─ train_pretrain.py
│  ├─ train_full_sft.py
│  ├─ train_lora.py
│  ├─ train_dpo.py
│  └─ ...
├─ model
├─ scripts
├─ eval_llm.py
└─ ...

说明:

  • MiniMind2:基础模型和 tokenizer 目录
  • dataset:训练数据目录
  • out:保存最终权重
  • checkpoints:保存中间断点和 resume 文件
  • trainer:训练脚本目录

1.3 SFT 的作用

1. 最直白的理解

SFT 是 Supervised Fine-Tuning,中文通常叫:

监督微调

它做的事情不是让模型从零开始学语言,而是让模型学会:

  • 遇到用户提问时,应该怎么回答
  • 什么样的语气更像助手
  • 什么样的输出结构更符合指令要求
  • 如何更稳定地做翻译、总结、列步骤、问答

2. 为什么先 Pretrain,再 SFT

可以把流程理解成:

  • Pretrain:学“语言能力”
  • SFT:学“指令跟随”
  • LoRA:在已有模型上做更便宜的小专项微调
  • DPO / GRPO:再往后做偏好优化或强化学习式优化

所以:

SFT 是建立在 Pretrain 权重之上的。

如果没有 Pretrain,SFT 通常不是理想的起点。


1.4 MiniMind 中的 SFT 脚本

MiniMind 里负责 Full SFT 的核心脚本是:

E:\minimind\trainer\train_full_sft.py

这次最重要的事实有两个:

  • 默认使用的数据是 sft_mini_512.jsonl
  • 默认是在 pretrain 权重基础上继续训练

按你当前这套环境整理出来的默认参数,大致是:

  • -save_dir ../out
  • -save_weight full_sft
  • -epochs 2
  • -batch_size 16
  • -learning_rate 1e-6
  • -dtype bfloat16
  • -save_interval 1000
  • -hidden_size 512
  • -num_hidden_layers 8
  • -max_seq_len 340
  • -data_path ../dataset/sft_mini_512.jsonl
  • -from_weight pretrain
  • -from_resume 0

这组默认值的含义是:

  • 脚本默认产出的是 full_sft_512.pth
  • 默认数据是轻量版 sft_mini_512.jsonl
  • 默认不是从零训,而是接着 pretrain_512.pth 继续训

1.5 开始前的检查

开始训练前,至少确认这 4 件事:

1. 你的 pretrain 权重已经存在

例如:

E:\minimind\out\pretrain_512.pth

这是 SFT 默认要加载的底座权重。

2. 模型目录和 tokenizer 已经放好

例如:

E:\minimind\MiniMind2

里面至少应该有:

  • config.json
  • model.safetensors
  • tokenizer.json
  • tokenizer_config.json
  • special_tokens_map.json

3. 数据集目录已经准备好

至少要有:

E:\minimind\dataset\sft_mini_512.jsonl

如果你想做更完整路线,还要有:

  • sft_512.jsonl
  • sft_2048.jsonl

4. tokenizer 路径不要乱切

如果你之前为了适配 E 盘目录,已经在 trainer_utils.py 里把 tokenizer 路径改成:

tokenizer_path = r"E:\minimind\MiniMind2"

那就尽量保持训练和推理都使用同一套 tokenizer 文件,不要训练时读 MiniMind2,推理时又换成另一套不一致的 tokenizer。


2. 可复现训练流程

1. 进入环境和项目根目录

conda activate minimind
cd /d E:\minimind

建议固定从仓库根目录启动,这样最不容易出现路径混乱。


2. 确认 Pretrain 权重已经存在

最关键的是确认这类文件已经有了:

E:\minimind\out\pretrain_512.pth

如果没有这个文件,SFT 默认通常无法顺利接着训。


3. 确认模型目录和 tokenizer 可读

如果你已经像 Pretrain 阶段那样,把 tokenizer 路径修到了 MiniMind2,这一步只要保证 MiniMind2 目录文件齐全即可。

如果你没改过脚本,那就要确认脚本默认读取的 tokenizer 路径在你当前目录结构下是成立的。


4. 确认 SFT 数据集存在

最基础的是检查这个文件:

E:\minimind\dataset\sft_mini_512.jsonl

如果你打算先跑官方默认轻量路线,这个文件就够了。


5. 先跑官方默认的快速 SFT 路线

如果你的目标是:

先把 SFT 链路完整跑通

推荐先用官方默认的轻量路线,也就是:

  • 数据:sft_mini_512.jsonl
  • 底座:pretrain_512.pth
  • 产物:full_sft_512.pth

最稳妥的命令建议把关键路径都写清楚:

conda activate minimind
cd /d E:\minimind
python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_mini_512.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --max_seq_len 340 `
  --from_weight pretrain

如果你只是沿用脚本默认参数,也可以直接:

python trainer\train_full_sft.py --device cuda:0

但更推荐显式写出 data_path 和 save_dir,这样以后回看时更清楚,也能减少相对路径跑偏的风险。


6. 如果想做更完整的 SFT 路线

如果你的目标不是只复现轻量 MiniMind-Zero 风格,而是希望比 sft_mini_512 更强一点,可以继续往下做:

路线 A:先做 sft_512.jsonl

conda activate minimind
cd /d E:\minimind
python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_512.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --from_weight pretrain

这一步的作用是:

  • 比 sft_mini_512 使用更完整的 SFT 数据
  • 让模型更像真正的问答助手

路线 B:再做更长上下文的 sft_2048.jsonl

如果你已经完成了一轮 full_sft,还想让模型接触更长上下文的数据,可以再继续一轮:

conda activate minimind
cd /d E:\minimind
python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_2048.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --from_weight full_sft

这里的意思是:

  • 第一轮:pretrain -> full_sft
  • 第二轮:full_sft -> 更长上下文 SFT

如果你只是第一次跑通 SFT,不建议一上来就把路线拉太长。最稳的顺序通常是:

  1. 先把 sft_mini_512 跑通
  2. 再做 sft_512
  3. 再考虑 sft_2048

7. 如何停止和继续训练

停止训练

训练过程中如果想手动停掉:

Ctrl + C

继续训练

如果你已经生成了类似这样的文件:

E:\minimind\checkpoints\full_sft_512_resume.pth

通常说明脚本已经保存了可继续训练的断点信息。

如果脚本支持 --from_resume 1,可以尝试:

python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_mini_512.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --max_seq_len 340 `
  --from_weight pretrain `
  --from_resume 1

3. 成功判据

如果 SFT 成功启动,日志通常会不断出现类似输出:

Epoch:[1/2](100/75921), loss: 2.8xxx, logits_loss: 2.8xxx, aux_loss: 0.0000, lr: 0.00000xxx, epoch_time: xx.xmin

你要重点看这些现象:

  • Epoch 在推进
  • step 在增加
  • loss 整体有下降趋势
  • lr 在变化
  • epoch_time 在更新

如果训练结束后出现:

E:\minimind\out\full_sft_512.pth

那就说明主权重已经保存出来了。


3.1 训练日志解读

假设看到一行日志:

Epoch:[2/2](75000/75921), loss: 2.1306, logits_loss: 2.1306, aux_loss: 0.0000, lr: 0.00000010, epoch_time: 0.0min

可以这样理解:

Epoch:[2/2]

  • 总共训练 2 个 epoch
  • 当前处于第 2 个 epoch

(75000/75921)

  • 当前 step 大约在 7.5 万
  • 这个数字说明训练已经接近尾声

loss: 2.1306

  • 当前总损失
  • 不是越接近 0 越神,但整体下降通常是好现象

logits_loss: 2.1306

  • 主损失项
  • 对语言模型来说通常是最关键的

aux_loss: 0.0000

  • 辅助损失
  • 当前这次训练里没有额外贡献

lr: 0.00000010

  • 当前学习率
  • 接近训练末尾时,学习率通常已经衰减得比较小

epoch_time: 0.0min

  • 按当前速度估计,本 epoch 即将结束

4. 训练产物

通常至少会看到这几类文件:

1. 主权重

E:\minimind\out\full_sft_512.pth

这是最核心的 SFT 结果。

2. checkpoint

E:\minimind\checkpoints\full_sft_512.pth

更像中间过程的权重快照。

3. resume checkpoint

E:\minimind\checkpoints\full_sft_512_resume.pth

里面通常会包含:

  • 模型参数
  • 优化器状态
  • 当前 step
  • 当前 epoch

它的意义是:

下次可以继续训练,而不是从头开始。

历史验证:至少在 2026-03-26 21:50:15,本机已经保存了主权重、checkpoint 和 resume checkpoint。是否使用最新数据集、训练到哪个 step,仍应以对应日志和评测命令为准。


5. 训练完成后的验证

最简单的验证方式:

conda activate minimind
cd /d E:\minimind
python eval_llm.py --weight full_sft

如果启动后出现:

[0] 自动测试
[1] 手动输入

输入 1,就可以自己和模型对话。

如何判断自己真的在用 SFT

你可以做一个对照测试。

用 Pretrain 跑

python eval_llm.py --weight pretrain

用 Full SFT 跑

python eval_llm.py --weight full_sft

然后用同样的问题各问一遍,例如:

请用一句话介绍你自己。
把“今天天气不错”翻译成英文。
请按 1、2、3 列出番茄炒蛋的做法。

如果 full_sft 比 pretrain 更像在按要求回答,那就说明 SFT 已经生效了。


6. 常见问题

1. 以为“能启动对话界面”就等于“肯定在用 SFT”

不是。

eval_llm.py 只是推理脚本,真正决定加载哪份权重的是:

  • -weight pretrain
  • -weight full_sft

2. 误以为效果一般,就是没训练成功

也不一定。

如果你走的是:

pretrain_hq.jsonl + sft_mini_512.jsonl

那更像官方快速复现的轻量路线,模型能聊,但不一定很稳。

3. 相对路径容易跑偏

像 ../out、../checkpoints、../dataset/... 这种写法,在不同启动目录下可能会解析到意料之外的位置。

最稳妥的办法:

  • 固定从 E:\minimind 根目录启动
  • 关键参数尽量写绝对路径

4. tokenizer 路径前后不一致

如果训练时读取的是 MiniMind2 里的 tokenizer,推理时最好也保持一致,不要混用不一致的词表文件。

5. pynvml warning

如果你看到:

FutureWarning: The pynvml package is deprecated

这通常只是警告,不是这次 SFT 跑不通的核心问题。


7. 推荐命令清单

1. 进入环境

conda activate minimind
cd /d E:\minimind

2. 跑默认轻量 SFT

python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_mini_512.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --max_seq_len 340 `
  --from_weight pretrain

3. 跑更完整的 SFT-512

python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_512.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --from_weight pretrain

4. 接着做更长上下文 SFT

python trainer\train_full_sft.py `
  --device cuda:0 `
  --data_path E:\minimind\dataset\sft_2048.jsonl `
  --save_dir E:\minimind\out `
  --hidden_size 512 `
  --num_hidden_layers 8 `
  --from_weight full_sft

5. 验证 SFT 权重

python eval_llm.py --weight full_sft

6. 对照测试 Pretrain

python eval_llm.py --weight pretrain

8. 下一步建议

如果你已经把 SFT 跑通,后面推荐按这个顺序推进:

1. 先确认当前 full_sft_512.pth 的真实效果

重点看:

  • 是否比 pretrain 更像助手
  • 是否更听指令
  • 是否对简单翻译、问答、列步骤更稳定

2. 如果效果一般,不要先怀疑命令

更优先怀疑的是:

  • 用的是 sft_mini_512.jsonl 这种轻量数据
  • 模型规模只有 25M 左右,本来就有限
  • tokenizer 路径前后不一致

3. 如果想明显提升效果

优先做:

  1. sft_512.jsonl
  2. sft_2048.jsonl
  3. dpo.jsonl

4. LoRA 和 DPO 通常接在 full_sft 后面

也就是说:

SFT 不是终点,而是后续 LoRA / DPO 的基础。


9. 参考来源

这份指南主要依据下面两类信息整理:

1. 官方资料

  • GitHub:jingyaogong/minimind
  • ModelScope:gongjy/minimind_dataset

2. 你当前这条链路里已经确认过的信息

  • train_full_sft.py 是 SFT 主脚本
  • 默认数据是 sft_mini_512.jsonl
  • 默认底座是 pretrain
  • 产物是 full_sft_512.pth
  • 你已经成功跑出并测试过 full_sft_512.pth

总结

如果只记住一句话,那就是:

MiniMind 的 Full SFT 是用 trainer\train_full_sft.py 读取 pretrain_512.pth,结合 sft_mini_512.jsonl 或更完整的 SFT 数据继续训练,产出 full_sft_512.pth,再用 python eval_llm.py --weight full_sft 验证。

MiniMind 的标准链路是 Tokenizer → Pretrain → Full SFT → LoRA → DPO / GRPO。SFT 建立在 pretrain 权重上,目标是指令跟随和问答格式,不等同于从零训练,也不保证自动获得成熟对话能力。本仓库没有保留完整终端训练日志,因此不虚构 loss、step 或效果指标;验收时按上方命令与 pretrain 对照即可。