ChatGLM3-6B模型微调实战:学习率设置策略与调优指南
背景:为什么“大”模型也要“小”调
ChatGLM3-6B 在 6B 量级里属于“身材苗条”的生成式语言模型,既保留了双语对话能力,又能在单卡 A100-80G 上跑起来。可一旦进入垂直场景——医疗问答、客服对话、代码补全——通用权重往往“说人话但不专业”。微调成了刚需,而微调的第一只“拦路虎”就是学习率:设大了,Loss 蹦迪;设小了,GPU 烧到冒烟也不收敛。本文把我在三个业务场景里踩过的坑汇总成一份“学习率说明书”,尽量让你一次跑通。学习率到底是啥?三个关键词先对齐
- 基础学习率(lr):优化器迈出第一步的“腿长”,PyTorch 里默认 1e-3,但对 6B 模型通常 1e-5 起步。
- Warmup:训练前 N 步把 lr 从 0 线性拉到最大值,避免模型“起床气”。
- 衰减策略:cosine、linear、poly、ReduceLROnPlateau……让 lr 训练后期“踩刹车”,别把最优点冲过头。
- 不当学习率现场翻车集锦
- 震荡:lr=2e-4,Loss 像心电图,评估指标忽上忽下,最后模型“精神分裂”。
- 欠拟合:lr=5e-6,训练 Loss 慢悠悠下降,验证集 BLEU 纹丝不动,钱花了效果没涨。
- 过拟合:lr=1e-4+cosine 衰减,前期飞太快,后期 lr 太小“爬不出”局部坑,训练 Loss 一骑绝尘,验证集却反向增长。
- 技术方案:一份“拿来即用”的 lr 菜谱
4.1 任务类型 vs 推荐区间
| 任务 | 总 batch | 峰值 lr | Warmup 步数 | 衰减 |
|---|---|---|---|---|
| 文本分类(单句 128 len) | 32 | 1e-5 ~ 3e-5 | 总步数 6% | cosine |
| 多轮对话生成(512 len) | 64 | 2e-5 ~ 5e-5 | 总步数 8% | cosine |
| 代码续写(1024 len) | 128 | 5e-5 ~ 1e-4 | 总步数 10% | linear |
经验:batch 越大,峰值 lr 可同比例放大,但别超过 1e-4,否则 AdamW 的 ε 兜不住。
4.2 PyTorch Lightning 代码模板(单卡可跑,多卡自动适配)
# lr_config.py from torch.optim import AdamW from pytorch_lightning import LightningModule from transformers import AutoTokenizer, AutoModelForCausalLM from transformers. get_linear_schedule_with_warmup, get_cosine_schedule_with_warmup class ChatGLM3Finetune(LightningModule): def __init__(self, model_path, lr=2e-5, warmup=0.06, decay="cosine"): super().__init__() self.save_hyperparameters() self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True) def forward(self, input_ids, labels): return self.model(input_ids=input_ids, labels=labels) def training_step(self, batch, batch_idx): out = self.forward(**batch) self.log("train_loss", out.loss, prog_bar=True) return out.loss def configure_optimizers(self): # 1. 优化器:AdamW + weight_decay no_decay = ["bias", "LayerNorm.weight"] opt_group = [ { "params": [p for n, p in self.named_parameters() if not any(nd in n for nd in no_decay)], "weight_decay": 0.1, }, { "params": [p for n, p in self.named_parameters() if any(nd in n for nd in no_decay)], "weight_decay": 0.0, }, ] optimizer = AdamW(opt_group, lr=self.hparams.lr, eps=1e-8) # 2. 调度器:先 warmup 再衰减 total_steps = self.trainer.estimated_stepping_batches warmup_steps = int(total_steps * self.hparams.warmup) if self.hparams.decay == "cosine": scheduler = get_cosine_schedule_with_warmup( optimizer, num_warmup_steps=warmup_steps, num_training_steps=total_steps ) else: scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=warmup_steps, num_training_steps=total_steps ) return [optimizer], [{"scheduler": scheduler, "interval": "step"}]4.3 分布式训练时的线性缩放原则
若用 4 卡 DDP,总 batch=32*4=128,可把峰值 lr 从 2e-5 提到 8e-5,同时 warmup 步数保持“总步数 8%”不变,训练曲线更平滑。
- 实验对比:三条曲线告诉你答案
- 固定超参:对话生成任务,单卡 A100,batch=64,训练 3 epoch。
- 变量:峰值 lr 分别取 1e-5、3e-5、8e-5。
结果可视化(TensorBoard 截图文字描述):
- 1e-5:Loss 单调下降,验证 BLEU 4 轮后才到 42,收敛慢但稳定。
- 3e-5:训练 Loss 前 200 步轻微震荡,后快速下探,BLEU 在 epoch1 末即 44.2,最佳。
- 8e-5:训练 Loss 前 100 步“跳水”,随后进入震荡平台,BLEU 最高 43.1,且后期掉点。
结论:3e-5 是甜蜜点,8e-5 虽快却牺牲泛化。
- 生产环境 checklist
- 硬件:A100-40G 以上推荐用 bf16,lr 可上浮 20%;V100 只能 fp16,lr 需下调 10% 并打开 grad_clip=1.0。
- 监控:每 50 步记录 lr、loss、grad_norm,grad_norm 连续 3 次 >10 立即停实验,八成 lr 过大。
- 早停:验证 BLEU 2 轮不升即停,同时把当前 lr 除以 2 做恢复实验,可避免“假平台”。
- 日志:Lightning 的 LearningRateMonitor 一键打出 lr 曲线,和 Loss 放同一张图,肉眼找震荡。
- 总结与下一步
学习率不是玄学,而是“ batch-峰值-warmup-衰减”四位一体的系统工程。建议你把本文模板 clone 下来,先跑通官方示例数据,再换成自己的 1w 条业务语料,用 3e-5 做基线,左右各试 0.3× 和 3× 的网格,记录 BLEU、 Rouge、人工满意度,把结果分享到社区。等你把 lr 玩顺了,再调“最大长度”“LoRA 秩”“样本配比”就会事半功倍。
- 彩蛋:把“调模型”变成“聊模型”
如果你嫌静态日志不过瘾,可以试试从0打造个人豆包实时通话AI动手实验——把刚微调好的 ChatGLM3-6B 接入实时语音通话,让模型亲口告诉你它喜欢多大的学习率。我亲测把模型音色换成“程序员小哥”后,一边语音对话一边看 lr 曲线,调参的枯燥感瞬间归零,小白也能 30 分钟跑通。祝玩得开心,调参路上不孤单!