lora微调必须冻结原始参数,否则梯度污染导致loss不降;需用peft注入适配器并正确配置target_modules;训练必须调用peftmodel.forward,保存加载须用save_pretrained/from_pretrained。

LoRA微调必须先冻结原始参数,否则梯度会污染预训练权重
PyTorch本身不内置LoRA支持,得靠第三方库(如 peft)注入低秩适配器。关键前提是:原始模型参数必须设为 requires_grad = False,否则反向传播时更新的不只是LoRA层,整个预训练权重也会被拖垮——这是最常踩的坑,现象是loss不降、验证指标倒退。
实操建议:
- 用
model.requires_grad_(False)全局冻结,再对LoRA目标模块(如q_proj,v_proj)单独启用梯度 - 别手动改
Linear.weight.requires_grad,应通过peft.get_peft_model()统一管理,它会自动处理冻结与插入逻辑 - 检查冻结状态:运行
sum(p.numel() for p in model.parameters() if p.requires_grad),结果应接近LoRA参数量(通常几万到几十万),而非原模型千万级参数
target_modules 配置错一个字母就白跑一整轮训练
LoRA只作用于指定子模块,peft 默认只识别常见键名(如 "q_proj", "v_proj"),但不同模型结构命名差异大:LlamaForCausalLM 用 q_proj,OPTForCausalLM 却是 q_proj 和 kv_proj 混用,而某些自定义模型可能叫 self_attn.q_proj。
实操建议:
- 先用
model.named_modules()打印所有子模块名,过滤含"attn"或"self"的项,确认真实名称 - 配置
target_modules时用元组而非字符串(如target_modules=("q_proj", "v_proj")),避免传入单个字符串被误拆成字符 - 若模型报错
Module not found,大概率是名称不匹配,不要硬改LoRA代码,优先查模型源码中nn.Linear层的变量名
训练时必须用 peft.PeftModel.forward,不能直接调 model(input_ids)
加载LoRA后返回的是 PeftModel 实例,它重写了 forward 方法来融合LoRA权重。如果仍用原始模型的 forward(比如把 PeftModel 当普通 nn.Module 传给DataLoader或Trainer),LoRA偏置根本不会生效,训练等价于全参数微调——但你又没开那么多显存,大概率OOM或梯度爆炸。
实操建议:
- 确认类型:
isinstance(model, peft.PeftModel)应为True - 保存/加载必须用
model.save_pretrained()和PeftModel.from_pretrained(),不能用torch.save(model.state_dict()),后者只存LoRA delta,没上下文无法复原 - 推理时若需合并权重到原模型,调用
model.merge_and_unload(),但注意这会永久修改模型结构,不可逆
batch_size 太小会导致LoRA梯度噪声放大,尤其在低rank下
LoRA本质是低维空间扰动,当 r=8 且 batch_size=1 时,每个step只看到1个样本的梯度方向,容易震荡。现象是loss曲线锯齿剧烈、early stopping触发过早。
实操建议:
- 优先增大
per_device_train_batch_size,哪怕牺牲显存也比加gradient accumulation更稳 - rank
r不宜低于4(太小表达力不足),也不宜高于64(收益递减且显存占用陡增),中小模型推荐r=8~r=16 - 若必须小batch,用
lora_alpha缩放梯度(如lora_alpha=16),相当于放大LoRA更新步长,缓解噪声影响
target_modules 名称、requires_grad 状态、PeftModel 的生命周期管理,这三个点任何一个出错,模型就只是在假装微调。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











