0. 系列闭环

本篇位置 上游 本篇产出 下游
第 6/10 篇 第 05 篇 Dataset 就绪 final_lora/、15 个 checkpoint 第 07 篇读日志 · 第 08 篇 verify

本篇是唯一会改 LoRA 权重的代码段。跑完应看到:

1
✅ 单卡微调完成!LoRA 权重: ./output/lora_elderly_single/final_lora

(与 all_logs.log 最后一行一致,路径略有措辞差异)


1. 要解决的实际问题

Step 5–7 负责:

  1. 把 HuggingFace TrainingArguments 与 TRL 特有项(dataset_text_fieldmax_length)合并进 SFTConfig
  2. 创建 SFTTrainer,在此刻注入 LoRA
  3. train() 750 步,save_pretrained 只存 adapter

很多人卡在 TRL 1.x API 变更:processing_class 替代 tokenizermax_length 替代 max_seq_length


2. 实现位置

代码块 行号
SFTConfig(...) 204–221
SFTTrainer(...) 226–233
trainer.train() 240
save_pretrained 244–246
TrainingProgressCallback 82–99

产出目录结构:

1
2
3
output/lora_elderly_single/
├── checkpoint-50/ ... checkpoint-750/ # 各含 optimizer,约 81MB
└── final_lora/ # 仅 adapter + tokenizer,约 41MB

3. SFTConfig 逐项说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
training_args = SFTConfig(
output_dir=OUTPUT_DIR,
per_device_train_batch_size=BATCH_SIZE,
gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
learning_rate=LEARNING_RATE,
num_train_epochs=EPOCHS,
bf16=True,
logging_steps=1,
logging_first_step=True,
save_steps=50,
optim="paged_adamw_8bit",
report_to="none",
remove_unused_columns=True,
disable_tqdm=False,
logging_strategy="steps",
dataset_text_field="text",
max_length=MAX_SEQ_LEN,
)
参数 原理
per_device_train_batch_size=2 micro-batch 每次 forward 2 条
gradient_accumulation_steps=2 累积 2×2=4 才 optimizer.step
learning_rate=2e-4 LoRA 常用 线性衰减到 ~0
bf16=True 混合精度 与加载 dtype 配合
logging_steps=1 逐步 log 配合 Callback 打印
save_steps=50 checkpoint 750/50=15 个
optim="paged_adamw_8bit" 8bit Adam 优化器状态省显存
dataset_text_field="text" 字段名 对应 load_jsonl_data
max_length=512 截断 超长样本切尾
report_to="none" 不上传 无 W&B

未设置 eval_strategy:无验证集,日志里没有 eval_loss(第 07 篇)。


4. SFTTrainer 构造与 train()

1
2
3
4
5
6
7
8
9
10
11
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=lora_config,
processing_class=tokenizer,
args=training_args,
callbacks=[TrainingProgressCallback()],
)

trainer.model.print_trainable_parameters()
trainer.train()

4.1 注入时机

SFTTrainer 内部调用 PEFT,把 LoRA 层挂到 target_modules。此时打印:

1
trainable params: 10,616,832 || all params: 4,216,368,128 || trainable%: 0.2518

4.2 训练循环内发生了什么(概念)

1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram
participant T as SFTTrainer
participant M as 基座+LoRA
participant O as paged_adamw_8bit

loop 每 step(共 750)
T->>M: forward(text batch)
M-->>T: loss(因果 LM)
T->>M: backward(仅 LoRA 有 grad)
Note over T: 每 2 micro-step 累积
T->>O: step 更新 LoRA 权重
end

基座 (W) 无梯度;只有 (A,B) 更新。

4.3 日志双轨

同一步会有:

  1. tqdm 进度条(3.31s/it
  2. Callback 行:[进度 33.3%] Step 250/750 | Epoch 1.00 | loss=0.2402
  3. Transformers JSON log:{'loss': '0.24', 'mean_token_accuracy': '0.957', ...}

写复盘时以 Callback 行 + 汇总行 为准。


5. Step 7:保存

1
2
3
save_path = f"{OUTPUT_DIR}/final_lora"
trainer.model.save_pretrained(save_path)
tokenizer.save_pretrained(save_path)

保存的是 PeftModel 的 adapter,不是合并后的全量。推理:

1
2
base = AutoModelForCausalLM.from_pretrained(BASE_MODEL, ...)
model = PeftModel.from_pretrained(base, LORA_PATH)

vLLM 则 --lora-modules elderly=./output/.../final_lora(第 10 篇)。


6. checkpoint 与 resume

checkpoint-750/trainer_state.json 含:

  • global_step: 750
  • log_history: 每步 loss
  • optimizer / scheduler 状态(用于 --resume_from_checkpoint

日常验证用 final_lora;只有中断续训才需要中间 checkpoint。


7. OOM 应急(按优先级)

1
2
3
4
5
6
7
8
BATCH_SIZE = 1                    # 显存减半
GRADIENT_ACCUMULATION_STEPS = 4 # 保持有效 batch=4
# 或
MAX_SEQ_LEN = 256
# 或
LORA_R = 4
# 最后手段
optim = "adamw_torch" # 放弃 8bit 优化器,显存反增

改参数后 750 step 数会变(若改 batch 或 epochs),不要与旧日志直接对比。


8. 踩坑

坑 1:TRL 0.x 教程的 max_seq_length 写在 SFTConfig
TRL 1.x 用 max_length。写错参数名会 silently 用默认值,你以为训 512 实际可能是 1024。

坑 2:重复 get_peft_model
见第 05 篇,trainable% 异常。

坑 3:save_steps 太小
每 10 step 存一次,磁盘爆满 + IO 拖慢。50 步对本项目合理。

坑 4:训练完只复制 checkpoint-750 forgetting final_lora
trainer.train() 结束后 save_pretrained(final_lora) 才是干净 adapter;checkpoint 带 optimizer 体积大。


9. 小结

  1. SFTConfig 管超参 + max_length + dataset_text_field
  2. SFTTrainer + peft_config 是唯一 LoRA 注入点。
  3. 750 step / 41min / train_loss 0.2587 为 V100 实测。
  4. final_lora 是部署与验证路径;checkpoint 用于续训与 loss 曲线。
  5. 无验证集,效果靠第 08 篇推理验证。

附录:TrainingProgressCallback.on_log

1
2
3
4
5
6
7
8
9
10
11
12
# LoRA_Demo/train_lora_single.py 第 88-96 行

def on_log(self, args, state, control, logs=None, **kwargs):
if not logs or "loss" not in logs:
return
pct = (state.global_step / state.max_steps * 100) if state.max_steps else 0
print(
f"[进度 {pct:5.1f}%] Step {state.global_step}/{state.max_steps} | "
f"Epoch {logs.get('epoch', state.epoch):.2f} | "
f"loss={logs['loss']:.4f} | lr={logs.get('learning_rate', 'N/A')}"
)
# logging_steps=1 时,all_logs.log 里 750 行量级进度由此产生

系列导航

篇目 链接
上一篇 05 · SFT 实战(上)
下一篇 07 · 训练曲线
索引 README

← 返回 LoRA 老年陪伴专题