原生 Python 训练循环入门¶
这篇不是要你从零训练一个大模型。
目标是看懂训练循环里发生了什么:
文本
↓
tokenizer
↓
input_ids / labels
↓
model forward
↓
loss
↓
backward
↓
optimizer step
↓
generate
为什么要学原生训练循环¶
Unsloth、LLaMA-Factory、Transformers Trainer 都会把训练细节封装起来。
封装很好,但如果完全不知道底层在做什么,后面看到这些参数会很迷糊:
learning_ratebatch_sizegradient_accumulation_stepsmax_seq_lengthlabelslossoptimizer
所以先看一个最小训练循环。
训练数据长什么样¶
语言模型训练的基本目标是:
给定前面的 token,预测下一个 token。
一句话:
小明喜欢吃苹果。
会变成 token id:
[101, 2051, 734, 8842, 102]
训练时可以理解成:
输入:小明 喜欢 吃
答案:喜欢 吃 苹果
更准确地说,模型每个位置都在预测下一个 token。
tokenizer¶
tokenizer 负责把文本变成数字。
伪代码:
batch = tokenizer(
texts,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt",
)
得到:
input_ids token 编号
attention_mask 哪些位置是真 token,哪些是 padding
对于自回归语言模型,labels 常常就是 input_ids 的拷贝。
batch["labels"] = batch["input_ids"].clone()
模型内部会处理“当前位置预测下一个 token”的偏移。
dataset 和 dataloader¶
dataset 存样本。
dataloader 把样本组成 batch。
for batch in dataloader:
...
batch 的意义是:
一次给模型看多条样本,让 GPU 并行计算。
如果 batch 太大,显存会爆。
如果 batch 太小,训练不稳定,GPU 利用率也可能低。
forward 和 loss¶
训练循环核心:
outputs = model(**batch)
loss = outputs.loss
forward 做的是:
input_ids
↓
embedding
↓
Transformer blocks
↓
logits
↓
和 labels 对比
↓
loss
logits 是模型对每个候选 token 的分数。
loss 衡量模型猜得有多错。
loss 越低,说明模型在训练集上越会预测这些样本。
但注意:
loss 低不等于线上效果一定好。
还要看验证集、真实任务和评估指标。
backward¶
loss.backward()
这一步计算梯度。
梯度告诉模型:
哪些参数应该往哪个方向改,才能让 loss 下降。
optimizer step¶
optimizer.step()
optimizer.zero_grad()
optimizer.step() 更新参数。
optimizer.zero_grad() 清空梯度,为下一步训练做准备。
完整循环:
for batch in dataloader:
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
这就是训练循环的骨架。
gradient accumulation¶
如果显存放不下大 batch,可以累积多步梯度。
小 batch 跑 4 次
↓
梯度累积
↓
再更新一次参数
这就是:
gradient_accumulation_steps = 4
它模拟更大的 batch,但训练时间会变长。
evaluation¶
训练时要定期在验证集上评估。
训练集 loss 下降
验证集 loss 也下降:通常是好事
训练集 loss 下降
验证集 loss 上升:可能过拟合
验证集要和训练集分开。
否则模型可能只是记住训练样本,而不是真的泛化。
generate¶
训练后要试试模型会不会生成。
生成时不再 backward。
with torch.no_grad():
output_ids = model.generate(
input_ids,
max_new_tokens=100,
temperature=0.7,
top_p=0.9,
)
训练阶段关注:
loss 能不能下降
推理阶段关注:
回答是否正确、稳定、符合格式、速度能不能接受
和 LoRA 的关系¶
全量微调会更新模型大量参数。
LoRA 的做法是:
冻结原模型,只训练很小的适配器参数。
所以训练循环还是类似:
forward -> loss -> backward -> optimizer step
区别是:
只有 LoRA adapter 参数会被更新。
最小检查清单¶
看一个训练脚本时,先找这些东西:
- 数据从哪里来。
- tokenizer 如何处理文本。
labels怎么构造。max_seq_length是多少。- 模型是否加载了量化。
- 训练哪些参数。
- optimizer 和 learning rate 是多少。
- 是否有验证集。
- 如何保存 checkpoint。
- 如何生成样例做人工检查。
下一步¶
学完这篇后,继续看: