0. 系列闭环

本篇位置 上游 本篇产出 下游
第 5/10 篇 第 04 篇环境就绪 Tokenizer、基座、LoraConfig、Dataset 第 06 篇 SFTTrainer.train()

本篇结束时:模型已在 GPU 上,LoRA 配置已定义,数据已是 {"text": ...}但尚未有任何梯度更新


1. 要解决的实际问题

初学者读训练脚本常卡在:

  • device_map={"": 0} 是什么意思,为什么多卡不能这样写
  • 为什么 LoraConfig 写了还要传给 SFTTrainer
  • apply_chat_template 两个 boolean 搞反会导致什么

本篇按执行顺序对齐 main() 前四步。


2. 实现位置

符号 行号(约) 作用
parse_args() 72–79 --gpu_id
print_device_info() 130–144 打印有效 batch
load_jsonl_data() 102–127 JSONL → Dataset
main() Step 1–4 156–200 本篇范围

启动:

1
2
python train_lora_single.py
python train_lora_single.py --gpu_id 0

3. 配置区:改动影响面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# LoRA_Demo/train_lora_single.py 第 49-65 行
MODEL_PATH = "./models/Qwen3.5-4B"
DATA_PATH = "./data/elderly_chat.jsonl"
OUTPUT_DIR = "./output/lora_elderly_single"

MAX_SEQ_LEN = 512
BATCH_SIZE = 2
LEARNING_RATE = 2e-4
EPOCHS = 3
GRADIENT_ACCUMULATION_STEPS = 2

LORA_R = 8
LORA_ALPHA = 16
LORA_DROPOUT = 0.05
改动 影响
BATCH_SIZE / GRADIENT_ACCUMULATION_STEPS 显存、有效 batch、总 step 数
MAX_SEQ_LEN 显存线性相关、长对话截断
EPOCHS 过拟合风险、训练时间
LORA_* adapter 体积与表达能力

4. Step 1:Tokenizer

1
2
3
4
5
6
tokenizer = AutoTokenizer.from_pretrained(
MODEL_PATH,
trust_remote_code=True,
padding_side="right",
)
tokenizer.pad_token = tokenizer.eos_token

4.1 为什么 padding_side=”right”

因果语言模型训练:序列从左到右预测 next token。padding 在右侧,左侧有效内容对齐,mask 才正确。放左侧会把 pad 当成前文,污染 loss。

4.2 pad_token = eos_token

Qwen 系列常无独立 pad。Trainer 组 batch 时需要 pad_token_id;复用 eos 是常规做法,与日志里 pad_token_id: 248046 一致。

4.3 与 verify 的关系

verify_lora.py 第 97–98 行同样 from_pretrained(BASE_MODEL, trust_remote_code=True)不要验证时用另一套 tokenizer 路径。


5. Step 2:基座模型与 device_map

1
2
3
4
5
6
7
8
9
10
11
12
13
if torch.cuda.is_available():
device_map = {"": gpu_id}
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
device_map = {"": "mps"}
else:
device_map = {"": "cpu"}

model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
dtype=torch.bfloat16,
device_map=device_map,
trust_remote_code=True,
)

5.1 device_map={“”: 0} 语义

HuggingFace Accelerate 语法:键 "" 表示「所有未单独指定的模块」,值 0 为 GPU id。即整模上单卡

日志对照(all_logs.log 第 15–16 行):

1
2
并行策略      = 单卡(模型全放在 GPU 0)
device_map = {'': 0}

5.2 dtype=bfloat16

权重以 bf16 加载,配合 Step 6 的 SFTConfig(bf16=True) 做 autocast 训练。V100 支持;若 GPU 不支持 bf16,需改 fp16 并实测数值稳定。

5.3 此时尚未注入 LoRA

print_trainable_parameters() 若在 Step 6 之前调用,应接近 0% trainable。若已很高,说明误调了 get_peft_model


6. Step 3:LoraConfig(仅定义)

1
2
3
4
5
6
7
8
9
10
11
lora_config = LoraConfig(
r=LORA_R,
lora_alpha=LORA_ALPHA,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
lora_dropout=LORA_DROPOUT,
bias="none",
task_type="CAUSAL_LM",
)

日志在 Step 3 后打印 正在配置 LoRA...,随后加载数据——PEFT 注入发生在 Step 6 构造 Trainer 时

错误示例:

1
2
from peft import get_peft_model
model = get_peft_model(model, lora_config) # ❌ 与 SFTTrainer 重复

7. Step 4:load_jsonl_data

完整逻辑见第 02 篇。要点复述:

1
2
3
4
5
text = tokenizer.apply_chat_template(
obj["messages"],
tokenize=False,
add_generation_prompt=False,
)

加载日志(all_logs.log 第 31 行):

1
加载 JSONL 数据: 1000/1000 [00:00<00:00, 5684.83条/s]

之后 TRL 会:

  1. Adding EOS to train dataset
  2. Tokenizing train dataset with max_length=512

TrainingProgressCallback

第 82–99 行定义回调,Step 6 传入。在 logging_steps=1 时逐步打印 loss——all_logs.log[进度 xx%] Step ... 来自此回调的 on_log


8. 踩坑

坑 1:MPS 上训练 Qwen3.5-4B + LoRA
脚本虽写了 mps 分支,4B 在 Mac 上训练极慢且易 OOM。建议 Mac 只跑 verify,训练用云 GPU。

坑 2:DATA_PATH 相对路径
必须在项目根目录执行 python train_lora_single.py,否则 ./data/... 找不到。

坑 3:空行 JSONL
load_jsonl_data 会 skip 空行;若大量空行,len(dataset) 小于文件行数,与预期 1000 不符。

坑 4:修改 MAX_SEQ_LEN 忘记重新估显存
512 → 1024 约翻倍激活显存,V100 上 batch=2 可能 OOM。


9. 小结

  1. Step 1:Tokenizer,右 padding,pad=eos。
  2. Step 2:整模上单卡 device_map={"": gpu_id},bf16 加载。
  3. Step 3:只定义 LoraConfig,不手动 get_peft_model。
  4. Step 4:messages → chat_template → Dataset(text=...)
  5. 下一步 SFTTrainer 才注入 LoRA 并开训(第 06 篇)。

附录:main() 前四步调用链

1
2
3
4
5
6
7
8
9
10
# LoRA_Demo/train_lora_single.py

args = parse_args()
print_device_info(args.gpu_id)

tokenizer = AutoTokenizer.from_pretrained(...) # Step 1
model = AutoModelForCausalLM.from_pretrained(...) # Step 2
lora_config = LoraConfig(...) # Step 3
dataset = load_jsonl_data(DATA_PATH, tokenizer) # Step 4
# → 接 SFTConfig + SFTTrainer(第 06 篇)

系列导航

篇目 链接
上一篇 04 · 环境搭建
下一篇 06 · SFT 实战(下)
索引 README

← 返回 LoRA 老年陪伴专题