SFT / LoRA:一次真实实验闭环怎么读
response-only loss、LoRA、best/final checkpoint、三路评测与 experiment.md 纪律。历史训练记录不是当前 Capstone。
对应课件:工程查阅(原 Job-Ready)
第 30 章 · SFT / LoRA:一次真实实验闭环怎么读
不再「从零手写 SFT trainer」,而是看工业生态里一次真实实验闭环:训练 LoRA → 同一套评测对比 base/tuned → 分析 bad case → 实验报告。这是算法阅读材料,不是当前主线 Capstone。
当前主线作业仍是 Attention。本章保留 response-only loss、best/final checkpoint、过拟合曲线和评测纪律。projects/sft-lora/ 是可选历史参考,非当前作业。
历史运行:Qwen2.5-0.5B-Instruct,48 训练 / 12 验证 / 20 评测,CPU 469s。
30.1 这次历史实验覆盖了什么
| 环节 | 当时做了什么 |
|---|---|
| Dataset | 真实指令格式 {"messages": [...]}(48+12 条) |
| Response-only loss | prompt 置 -100,只学 assistant 回答(接通第 21 章) |
| LoRA | r/alpha/dropout/target_modules 全部可配(接通第 17 章) |
| Train | grad accum + 梯度裁剪 + warmup/余弦 + 验证 loss 记录 |
| 保存 | adapter 保存 / 可选 merge 合并权重 |
| Eval | 同一 held-out + 同一套评测分层(第 27 章原理)的 before/after |
| Bad case | 20 条结构化失败样本 + 粗分类 |
| Report | experiment.md 自动生成(设置/曲线/对比/badcase/局限) |
30.2 训练:真实曲线与一个刻意的反例
train loss:3.8878 → 1.4638(150 步,等效 batch 8,lr 2e-4,CPU 469s)
LoRA 可训练参数:540,672 / 494,573,440 = 0.1093%
验证曲线(每 30 步,本次运行):
| step | 30 | 60 | 90 | 120 | 150 |
|---|---|---|---|---|---|
| val loss | 2.8677 | 2.7301 | 2.9822 | 3.2427 | 3.2947 |
本轮实现:每次 val 改善自动保存 adapter_best/;训练结束保存 adapter_final/。
train loss 一路降到 1.46,但 val loss 从 step 60 起持续上升——经典过拟合。 正规做法:保存 best checkpoint 或 early stopping;当时的实验同时保留 final 作为反例, 让你直接对比两份 checkpoint 的差异。
30.3 评测:把「变好了吗」交给数字回答
同一 held-out、同一套评测分层(第 27 章),Base / Best / Final 三路:
| 指标 | Base | Best (step 60) | Final (step 150) |
|---|---|---|---|
| held-out response-only loss | 3.7565 | 2.7301 | 3.2947 |
| QA F1(20 题) | 20.2% | 24.4% | 22.5% |
| QA EM | 0.0% | 0.0% | 0.0% |
本次运行的观察:best val loss 的 checkpoint 同时也是 QA F1 最高点。 但这只是单次运行的经验观察——val 最优不必然等于 downstream 最优(反之亦然), 所以实验报告固定输出三路对比来检查一致性。
- 同一 held-out 集(12 条验证 + 20 条 QA,训练时从未见过);
- 同一 prompt 模板(同一 chat template 与生成参数);
- 干净加载的 base——PEFT 就地注入陷阱:训练用的
model变量已被注入 LoRA,拿它当 base 会得到 Δ=0.0000 的假结论(第 26 章踩过的坑,当时用独立加载规避)。
30.4 生成风格:SFT 到底学到了什么
Q: 用一句话解释什么是早停。
- base :"早停"通常指的是在某些情况下,人们或组织提前停止某种活动、项目或计划,以避免风险……
- tuned:早停时模型不等待下游损失下降就停止优化,避免鞍点死循环。
- 风格迁移成功:tuned 回答变短、更直接(训练数据是「一句话解释」风格);
- 内容仍然经常错误:上例把「早停」解释成了别的东西(部分回答偏离)。
SFT 可以改变模型的行为、格式与任务能力,也可能注入一定的任务知识;但在这次 「0.5B + 48 条训练样本」的小数据 regime 下,最明显的现象是输出风格变化, 而不是稳定的事实能力提升。RAG 与 SFT 不是互斥方案:
- RAG 适合:外部知识、频繁更新的知识、需要引用的知识;
- SFT 适合:行为格式、instruction following、领域风格、任务适配;
- 实际系统常常两者叠加(先微调行为,再挂检索补充知识)。
30.4b Training Data Correctness:错误答案也会被认真学习
SFT 的一个残酷现实:
错误 teacher answer
↓
training loss 一样会下降(模型在认真拟合你的数据)
↓
模型会认真地学错
本项目在 Correctness Pass 中逐条 auditing 了训练数据,修正了过度绝对的表述。典型例子(修正前后):
| 主题 | 修正前(过度绝对) | 修正后(准确口径) |
|---|---|---|
| LoRA alpha | 「通常取两倍 rank」 | alpha 是缩放超参数,常见取 rank / 2×rank 或其它值,需实验选择 |
| RoPE | 「支持外推」 | 不天然保证可靠外推;长上下文通常配合位置插值/缩放 |
| 量化 | 「代价是少量精度」 | 精度影响取决于 bit width / 算法 / 校准 / 模型与任务 |
| SwiGLU | 「比 ReLU 更好」 | 广泛采用,但配置不同,不能抽象成对所有任务必然更优 |
| 混合精度 | 「FP32 做关键状态」 | 保留哪些高精度状态取决于 AMP / optimizer / 分布式实现 |
工程纪律:合成数据 / teacher 模型产出的答案必须做事实审查——loss 不会替你发现数据是错的。 这条对后续做 DPO/GRPO 与合成数据管线同样适用。
30.5 Bad case:20 条失败样本怎么用
每条 bad case 记录 {id, question, gold, base, tuned, error_type},粗分类(教学启发式):
| 分类 | 含义 | 该修什么 |
|---|---|---|
ok |
F1 ≥ 0.6 | — |
partial |
部分匹配 | 数据质量 / 更大模型 |
verbose |
太长(>80 字) | 风格数据 / 长度惩罚 |
wrong |
完全无关 | 数据覆盖 / 知识注入 |
refusal |
拒绝回答 | 训练数据里的拒绝样本比例 |
本实验 20 条全部 partial(F1 ∈ (0, 0.6))——如果只报告「F1 +3.8pt」而不看 bad case,你会错过「模型在瞎说但格式很像」这个事实。
30.6 实验报告:让别人能复现你的结论
experiment.md 自动生成,必须包含七要素:
model / dataset / hyperparameters / hardware / training curve / eval / failure cases / limitations
复现命令(可选对照,非当前作业):
# 仅在你明确要看历史实现时使用;不是当前主线任务
cd projects/sft-lora
python scripts/run_experiment.py --steps 150 --output outputs/sft_run --run-harness
「CPU 469s」和「8×A100 20 分钟」是两个完全不同的成本事实。报告里不写硬件,读者无法判断实验规模,也无法复现。当时如实标注 无 CUDA(CPU 运行)。
30.7 面试复盘:SFT / 算法岗
- 为什么用 LoRA 而不是全量微调?可训练参数占比怎么算?
- response-only loss 的掩码怎么构造?为什么 prompt 置 -100?
- 训练 loss 下降但验证 loss 上升,你怎么处理?
- Base vs SFT 怎么公平比较?为什么 base 必须干净加载?
- 为什么 F1 提高了但 EM 还是 0?这两个指标差在哪?
- bad case 怎么分类?分类之后各自的修法?
- 如果要把「知识」加进模型,SFT 和 RAG 你怎么选?
- 你的实验报告里哪些数字是必须的?为什么硬件与数据规模缺一不可?
显示答案与解析
正确答案:B
必须同时读 train 与 val 两条曲线:val 的最低点在 step 60,之后上升说明过拟合。报告应给 best checkpoint 的成绩并诚实标注最终 checkpoint 的状态;「loss 下降 60%」是被 train loss 蒙蔽的典型误读;C 会让过拟合更严重;D 是把「格式问题」与「知识问题」混淆了——这次实验 F1 +3.8pt 说明 SFT 有效,只是数据规模限制了上限。| 站点 | 核心结论 |
|---|---|
| 数据 | 指令 messages 格式 + response-only loss(-100 掩码) |
| 训练 | grad accum + clip + warmup/余弦;val 曲线是必看信号 |
| 反例 | final 已过拟合;本轮已实现 best checkpoint 自动保存 |
| 对比 | 同一 held-out / 同一模板 / 干净 base;Base–Best–Final 三路 |
| 发现 | 小数据 regime 下 SFT 主要改风格;能力提升需更大数据与更严评测 |
| 报告 | model/data/hyperparams/hardware/curve/eval/badcase/limits 七要素 |