Chapter 11
调试与踩坑合集
PEFT 大多数错误集中在三个环节:选 target_modules、save/load、量化集成。 本章把这些高频问题列成清单,按"现象 → 原因 → 定位 → 处理"四列写。
11.1定位类问题
| 现象 | 原因 | 定位 | 处理 |
|---|---|---|---|
| print_trainable_parameters() = 0% | target_modules 没匹配上任何层 | 遍历 named_modules,看真实层名 | 改 target_modules 或用 "all-linear" |
ValueError: Target modules ... not found |
模型层名跟你写的不一样 | print 一遍 nn.Linear 的 name | 用真实 suffix 或 regex |
| 同一脚本第二次跑 trainable% 改变 | 第一次 get_peft_model 把 base 改了,第二次基于已改的 base | 查 model.peft_config 是否多了一项 | 每次重新 from_pretrained 加载 base |
11.2训练类问题
| 现象 | 处理 |
|---|---|
| loss 一直不降 | (1) lr 太低(LoRA 通常 1e-4 ~ 2e-4,比全参高 10×) (2) target_modules 只覆盖 q/v 不够,加 k/o/MLP (3) r 太小,先翻倍试试 (4) lora_alpha 没相应放大 |
| loss 第一步就特别大 | 用了 init_lora_weights=False,起点 ΔW ≠ 0;改成 True |
| 训到后期 loss 飙升 | lr 过大;上 cosine 衰减 + warmup_steps=100 |
| loss 下降但 eval 效果变差 | 过拟合,加 lora_dropout=0.1 或减 epoch |
| QLoRA 训练 NaN | 没调 prepare_model_for_kbit_training;或 compute_dtype 用了 fp16,换 bf16 |
| 显存比预期高 | 开了 use_dora=True(多 30% 显存);或 gradient_checkpointing 没开 |
11.3save/load 类问题
| 现象 | 处理 |
|---|---|
| save 后只有 adapter_config.json 没有 .safetensors | 用 model.save_pretrained(),不要用 torch.save |
| load 后 print_trainable_parameters() = 0 | load 出来默认 inference_mode=True;要训练再加 is_trainable=True |
RuntimeError: weight is on the meta device |
是 deepspeed/accelerate 加载顺序问题;先把 base 完整加载到 GPU 再 load_adapter |
| load 后推理结果跟训完一致但接 vLLM 不一致 | vLLM 需要的是 merge 后或 LoRA 单文件格式;用 merge_and_unload 或保存 lora_only |
| save 时报"adapter_name not in model" | 检查 active_adapter;或显式 selected_adapters=["xxx"] |
| load 不同 r 的 adapter 报 size mismatch | 同一目录只能放一个 adapter,不同 r 用不同子目录 |
11.4合并 / 卸载类问题
| 现象 | 处理 |
|---|---|
| merge_and_unload 后效果变差 | (1) base 是量化模型不能直接 merge (2) DoRA 的合并不等价(数值近似);用 safe_merge=True |
| merge 后 forward 比未 merge 慢 | 大概率没真正合并;查 m.merged 是否为 True |
| 4bit base merge 报 NotImplementedError | 先 dequantize_module_weight 升回 bf16 base,再 merge |
| merge 后再切回 4bit 部署 | 用 AutoAWQ / AutoGPTQ 重新量化合并后的模型 |
11.5与 transformers Trainer 配合的问题
| 现象 | 处理 |
|---|---|
| Trainer 自动保存的 ckpt 加载失败 | Trainer save 时会调 model.save_pretrained,自动只保 adapter;load 时用 PeftModel.from_pretrained |
| resume_from_checkpoint 报错 | 升级 transformers ≥ 4.41;旧版本对 PEFT resume 兼容不完整 |
| 开 deepspeed ZeRO-3 时 LoRA 训不起来 | ZeRO-3 切了 LoRA 矩阵,PEFT 现在已支持;老版本需要降到 ZeRO-2 |
| 多卡(DDP)训完 save 后 base 也被保了 | 用 rank0 上的 model 调 save_pretrained,不要让所有 rank 都 save |
11.6调试小工具
看 LoRA 真的接上了吗
from peft.tuners.lora import LoraLayer
for name, m in model.named_modules():
if isinstance(m, LoraLayer):
print(name, "→ active:", m.active_adapters,
"merged:", m.merged_adapters,
"r:", m.r if hasattr(m, "r") else "n/a")
看 LoRA 矩阵的范数(训练健康度)
for name, m in model.named_modules():
if isinstance(m, LoraLayer):
for adapter in m.active_adapters:
A = m.lora_A[adapter].weight
B = m.lora_B[adapter].weight
print(f"{name}/{adapter} ||A||={A.norm():.4f} ||B||={B.norm():.4f}")
训练初期 B 应该接近 0(零初始化),训练后 B 应该有非平凡 magnitude(如 > 0.01)。如果训完 B 还接近 0,说明梯度没流过来。
用 hook 看 LoRA 输出的贡献
activations = []
def hook(module, x, y):
base_out = module.base_layer(x[0])
delta = y - base_out
activations.append((module, delta.abs().mean().item()))
for name, m in model.named_modules():
if isinstance(m, LoraLayer):
m.register_forward_hook(hook)
# 跑一次推理,看每层 LoRA 贡献多少
model(input_ids=...)
for m, d in activations[:5]:
print(m, d)
11.7常用环境变量
| 变量 | 作用 |
|---|---|
PEFT_DEBUG=1 |
开 PEFT 内部 debug 日志(部分版本有) |
TOKENIZERS_PARALLELISM=false |
消掉常见的 tokenizer fork warning |
TRANSFORMERS_OFFLINE=1 |
禁止访问 HF Hub(断网时必备) |
BNB_FORCE_NO_DOUBLE_QUANT=1 |
排查 QLoRA 时关掉 double quant |
11.8定位思路(通用)
- 先在 0.5B 小模型上跑通(Qwen2.5-0.5B 等),再换大模型;
- 先纯 LoRA 跑通,再加 QLoRA / DoRA / 多 adapter;
- 每加一个开关跑一次,避免一次开 5 个开关定位不到;
- 用 print_trainable_parameters() 当 sanity check,没匹配就别往下走;
- 断网时设
TRANSFORMERS_OFFLINE=1避免无谓重试。
11.9DoRA + ZeRO-3 / FSDP 的已知坑
DoRA 在分布式训练时有几个隐形陷阱。tuners/lora/dora.py:94 的 get_lora_weight 注释里写得明白:"use eye matrix to avoid FSDP issues",这是因为:
| 现象 | 原因 | 修复 |
|---|---|---|
FSDP + DoRA 报 DTensor 相关错误 | DoRA forward 直接乘 LoRA 矩阵会触发 FSDP 切片冲突 | 升 PEFT >= 0.13;FSDP 用 use_orig_params=True |
| ZeRO-3 + DoRA 训练慢 50%+ | DoRA 每 step 要 all-gather 完整权重算 norm | 评估是否真需要 DoRA;不需要切回普通 LoRA |
| DoRA + 4bit quant + ZeRO-3 OOM | 三者叠加显存峰值高(dequant 时是 fp16) | 用 ZeRO-2 替代 ZeRO-3;或关 DoRA |
| DoRA + bnb 4bit 训完 merge 后输出乱码 | magnitude vector 没正确 dequant | 升 PEFT;显式 cast_mixed_precision_params_to_fp32=True |
11.10PEFT + FSDP 的 use_orig_params
FSDP 1 默认 use_orig_params=False——FSDP 把多个参数展平打包成一个大张量,分块切到各 rank。这跟 PEFT 的"trainable / frozen 混合"完全不兼容(FSDP 看到一堆 frozen 参数夹着少量 trainable)。两种修复:
| flag | 含义 | 什么时候用 |
|---|---|---|
use_orig_params=True | FSDP 保留原参数对象的视图,不强行展平 | PEFT 必开(自 PEFT 0.7 起强制要求) |
| FSDP2(PyTorch 2.4+) | 新架构基于 DTensor,原生支持混合 trainable | 新项目首选 |
accelerate config 里写 fsdp_use_orig_params: true(FSDP v1)或 fsdp_version: 2 即可。Trainable Tokens 这种特殊 tuner(tuners/trainable_tokens/)也明确要求 use_orig_params。
11.11量化 + bias 的互斥
有几条 PEFT 不会显式报错但行为反常的"已知不兼容":
| 不兼容组合 | 原因 |
|---|---|
| GPTQ / AWQ / AQLM / EETQ 的 LoRA 不能 merge_and_unload | packed int4 无法回 fp16;ch06.9 详述 |
LoraConfig(bias="all") + 量化 base | 量化模型 bias 通常缺失或被合并 |
use_dora=True + Prompt Tuning | 两个完全不同抽象,不能混用 |
| AdaLoRA + 量化 base | SVD 形式 LoRA 与量化矩阵不兼容 |
| PiSSA init + 量化 base | PiSSA 需要 fp32 base 做 SVD |
| 多 adapter 中混 use_dora + 不 use_dora | variant 分歧,merge 路径无法统一 |
11.12这章你需要带走的
- 50% PEFT 问题来自
target_modules没匹配上,每次都先 print 一下; - 另 25% 来自 save/load 顺序,load 时记得
PeftModel.from_pretrained; - QLoRA 训练前必调
prepare_model_for_kbit_training; - 调试 LoRA 健康度:看
||B||的 norm 训完是否远离 0; - 遇到诡异问题先在 0.5B 模型上复现,节约时间。