2.2 训练循环里的超参数与踩坑:学习率、轮次、批次到底怎么设 读者读完这一节,应该建立一套「先保守后微调」的超参决策框架:知道每个旋钮的默认值、调节方向和常见翻车点,而不是一上来就把 rank 拉满、学习率调到最小,然后看着不动的 loss 怀疑人生。 2.2.1 先建立心智模型:LoRA 只有「少量参数在动」 为什么 LoRA 的学习率能比全量微调大一个数量级?因为地貌不同。全量微调要挪动整座山(所有参数一起更新),步子一大就塌方,所以学习率通常在 。LoRA 只微调一座小土丘(适配器里的低秩矩阵),优化面平坦得多,步子可以大些,常用 。 记住这个类比,下面所有超参的调节方向就都有了直觉依据:你在调一个很小的目标,所以可以更激进,但小目标也更容易被一步带崩。 2.2.
读者读完这一节,应该建立一套**「先保守后微调」的超参决策框架**:知道每个旋钮的默认值、调节方向和常见翻车点,而不是一上来就把 rank 拉满、学习率调到最小,然后看着不动的 loss 怀疑人生。
为什么 LoRA 的学习率能比全量微调大一个数量级?因为地貌不同。全量微调要挪动整座山(所有参数一起更新),步子一大就塌方,所以学习率通常在 1e-5 ~ 2e-5。LoRA 只微调一座小土丘(适配器里的低秩矩阵),优化面平坦得多,步子可以大些,常用 1e-4 ~ 3e-4。
记住这个类比,下面所有超参的调节方向就都有了直觉依据:你在调一个很小的目标,所以可以更激进,但小目标也更容易被一步带崩。
学习率是我建议第一个动手的参数,因为它对结果的影响最直接、最可见。
2e-4。这是 LoRA 的平衡点,多数任务开箱即用。warmup_ratio=0.03~0.1 让开头几步别太猛;调度器常用 cosine 或 linear。report_to="none",本地看 logging 最直观,等调稳了再接可视化工具。下面是「学习率诊断表」,建议你贴在屏幕上:
| 现象 | 可能原因 | 对策 |
|---|---|---|
| loss 直接变 NaN/Inf | 学习率过大,或量化 dtype 不对 | 降到 1e-4,确认 bnb_4bit_compute_dtype=bf16 |
| loss 一直线、不降 | 学习率过小,或适配器没接上 | 提到 2e-4;查 print_trainable_parameters 占比 |
| loss 降但生成乱码 | 数据/掩码问题,非纯学习率 | 回到 2.1.4 检查 labels 掩码 |
| 早期震荡剧烈 | warmup 太短 / batch 太小 | 加 warmup_ratio,或加大等效 batch |
很多人的直觉是「多训几轮更准」,在 LoRA 上这常常适得其反。LoRA 在小数据集上 1~3 个 epoch 通常就够,因为可训练参数少,模型很快就能「记住」适配器该做什么。
判断过拟合的唯一靠谱标准是盯 eval loss 的拐点:
小数据集(几百条)有时 1~2 个 epoch 就到拐点。一个实用纪律:先设 num_train_epochs=3,配合 eval_strategy="steps" 看 eval 曲线,谁先到拐点听谁的,而不是硬跑满。
per_device_train_batch_size 直接被显存卡死:一张卡放不下大 batch,就调小它,同时加大 gradient_accumulation_steps,让「等效 batch」维持稳定。
等效 batch 的公式:
等效 batch = per_device_train_batch_size × gradient_accumulation_steps × GPU 数量
举例:单卡显存只够 batch_size=2,想要等效 16,就设 gradient_accumulation_steps=8(2×8×1=16)。注意两个极端都要避免:
经验区间:等效 batch 在 8~32 对大多数 LoRA 任务都安全。显存实在紧张时,QLoRA + 小 batch + 大累积步数是标准解法。
LoraConfig 里 r 和 lora_alpha 决定了适配器的「容量」与「更新幅度」:
r(秩):低秩矩阵的维度,8 / 16 / 32 / 64 常见。r 越大,适配器能表达的变换越复杂,但参数更多、过拟合风险更高、显存占用略增。lora_alpha:缩放系数,有效增量 = (alpha / r) × B@A。所以真正起作用的是 alpha 与 r 的比值。r=16, alpha=32(比值 2),或 r=8, alpha=16。踩坑点:
容量选择决策:数据量小、任务简单 → r=8;数据量多、任务复杂(如专业术语密集的法律/医疗)→ r=32 起。这和第 4 章的案例会呼应。
除了前面几个大头,还有几个「稳训练」的旋钮:
lr_scheduler_type:cosine 最常用,后期自动收敛;linear 也稳妥。warmup_ratio:0.03~0.1,开头几步用小学习率探路,防第一步炸。max_grad_norm:梯度裁剪,默认 1.0。它的作用是把单步梯度范数截断,防止极端 batch 把更新带崩。一般不用动;若仍 NaN,先降 lr 而非盲目调它。weight_decay:权重衰减(L2 正则)。LoRA 因为只训少量参数,常设 0 或很小(如 0.0);全量微调才更需要它。一份「稳健超参模板」长这样:
from transformers import TrainingArguments args = TrainingArguments( output_dir="./ckpt", per_device_train_batch_size=2, gradient_accumulation_steps=8, # 等效 batch = 16 num_train_epochs=3, learning_rate=2e-4, # LoRA 默认 lr_scheduler_type="cosine", warmup_ratio=0.03, max_grad_norm=1.0, weight_decay=0.0, # LoRA 一般不开 bf16=True, logging_steps=10, eval_strategy="steps", eval_steps=50, save_strategy="steps", save_steps=200, report_to="none", save_total_limit=2, )
把前面所有旋钮串成一条可执行的调参路线,而不是凭感觉乱拧:
核心纪律只有一句:先跑通(用保守默认),看信号,再微调。绝大多数「调参焦虑」来自一上来就堆最大 rank、最小 lr、最多 epoch——结果哪个方向都看不出问题。下表是速查:
| 参数 | 稳健默认 | 常见范围 | 调节方向 |
|---|---|---|---|
| learning_rate | 2e-4 | 1e-4 ~ 3e-4 | 不稳↓,不动↑ |
| num_train_epochs | 3 | 1 ~ 5 | 过拟合↓,欠拟合↑ |
| per_device_batch_size | 2 | 1 ~ 8 | 显存允许就大 |
| gradient_accumulation_steps | 8 | 1 ~ 32 | 补等效 batch |
| r | 16 | 8 ~ 64 | 容量不足↑ |
| lora_alpha | 32 | r 的 1~2 倍 | 随 r 等比例 |
| warmup_ratio | 0.03 | 0.03 ~ 0.1 | 震荡就加 |
| weight_decay | 0.0 | 0 ~ 0.01 | LoRA 通常 0 |
超参设好、开跑之后,真正的功夫在「中途怎么看」。我建议你每 50~100 个 step 做一次决策复盘,而不是闷头跑满三圈:
save_total_limit=2 的检查点。过拟合时别纠结,直接加载前一个更稳的适配器即可,这比硬撑着跑更有价值。一句话:epoch 是上限不是目标,eval 拐点才是终点。下面这张图把三种中途信号和对应动作固化成肌肉记忆:
loss 会骗人。最经典的骗局是:loss 降得很漂亮,模型却只是把训练集背下来了。所以我强烈建议 loss 之外再加两道保险:
不同领域「容错率」和「术语密度」不同,起点就该不一样。下面是我给的常见起点(记住:这是起点,不是终点,要用 2.2.7 的路线图再去微调):
| 领域 | 特点 | r | lora_alpha | epoch | lr |
|---|---|---|---|---|---|
| 医疗 / 法律 | 术语密集、容错极低 | 32 | 64 | 2~3 | 1e-4(更稳) |
| 客服对话 | 句式固定、容错中等 | 16 | 32 | 3 | 2e-4 |
| 风格 / 角色 | 轻量风格迁移 | 8 | 16 | 1~2 | 3e-4 |
背后的逻辑是:越怕出错、术语越多,越要小学习率 + 大容量(r 大),让模型小心地吸收专业知识;越偏风格、越不怕错,越可以大学习率 + 小容量,快速贴近目标语气。GLM-5.2 作为通用底座,在医疗法律这类高专业度任务上尤其建议先把 lr 压到 1e-4 起步,避免把底座里好不容易学到的常识带崩。
当你手上不止一张卡,或单卡 QLoRA 都还差一点显存时,就该上分布式了。这里只讲决策,不堆配置:
device_map="auto"(单进程多卡):最简单,模型层自动切到不同卡。适合「一张装不下、但多张能装下」且不想折腾分布式配置的场景。代价是卡间通信有开销,速度不是最优。deepspeed 配置文件和版本兼容上。transformers 的 Trainer 集成较顺。我的判断:先用 QLoRA + 单卡 device_map 把流程跑通,确认数据和代码没问题,再为了更快/更大去上 DeepSpeed。太多人一上来就配 ZeRO,结果 80% 时间花在调分布式环境,本末倒置。
最后用三个我见得最多的真实翻车,把本节钉牢:
batch_size 从 8 降到 1 救显存,但 lr 还停在 2e-4。等效 batch 缩了 8 倍,梯度噪声暴涨,loss 全程震荡像心电图。教训:动 batch 必须重新评估 lr 与累积步数,使等效 batch 回到稳定区间。r=128,结果模型把训练集背得滚瓜烂熟,eval 一测全面崩。教训:容量要匹配数据量,r 不是越大越好,小数据用 r=8 往往更稳。开跑前那行输出很多人扫一眼就过了,其实它是最快的健康体检。典型输出类似:trainable params: 33,554,432 || all params: 13,000,000,000 || trainable%: 0.26%。三个数要一起看:
requires_grad 全被关,训练不会有任何效果。顺带一个「数据 vs 参数」的朴素判断:当你的训练集只有几百条,却给了 r=64 的大适配器,就容易出现 trainable% 并不低、但绝对参数仍有几千万的情况——这时候过拟合概率极高,宁可降 r 也不要堆数据量去硬扛。
补一句关于「要不要上分布式」的底线判断:如果 QLoRA 单卡已经能跑、只是慢,那就忍一忍速度,别上 DeepSpeed——分布式带来的工程复杂度往往比多等几小时更贵。只有当单卡怎么都装不下、且你已经用梯度检查点把显存榨干之后,才值得去碰 ZeRO。绝大多数个人项目,卡在「QLoRA + 单卡 device_map」就够用了。
一句话带走:LoRA 调参的核心纪律是「先跑通、看信号、再微调」,而不是一上来堆最大 rank 和最小 lr。把学习率当第一旋钮、用 eval loss 拐点判过拟合、用等效 batch 对抗显存,你就已经超过了绝大多数只会复制粘贴的人。