← 返回文章列表
LLM 微调

LoRA 微调踩坑实录:为什么 Loss 不收敛?

2025-05-18 · 约 8 分钟读完

用了将近两周,把一个基于 Qwen2-7B 的模型用 LoRA 调到能用的状态。期间 Loss 出现过三种让人头大的形态——一直不降、先降后震荡、训练 loss 降了但 eval 纹丝不动。最后定位下来,每一种背后的原因都不一样。这篇文章按排查顺序写,能帮你少踩几个坑。

先看 Loss 的形态,再找原因

LoRA 微调的 loss 异常通常落在三种情况里:

把这个对应关系记住,排查时会快很多。

第一个坑:Rank 设置

我一开始设了 r=64,理由是"越大容量越强"。结果 loss 曲线先降到一个值就不动了,换数据也没用。

问题在于:rank 越大,LoRA 模块的参数量越多,越容易过拟合训练集的格式,而不是真正学到任务能力。 对于 7B 模型的指令微调,r=8r=16 通常完全够用。rank 更高不是没有意义,但需要更多高质量数据去"撑起"这个容量。

lora_alpha 的设置也容易出问题。一个稳妥的出发点是 lora_alpha = 2 × r,即缩放系数约为 2。也可以直接用 r=8, alpha=16 的组合,大多数情况下都能正常收敛。

target_modules 的选择

只加 q_projv_proj 是最保守的做法,训练快、显存省,但效果有上限。我最后用的是:

target_modules=["q_proj", "v_proj", "k_proj", "o_proj"]

加上 k_projo_proj 后,模型的指令跟随能力明显提升,显存增加在可接受范围内。如果显存够,还可以再加 MLP 层的 gate/up/down proj。

第二个坑:学习率

LoRA 所需的学习率比全量微调低,但不需要低到 1e-5 那个量级。我踩过两个极端:

最后定在 2e-4,配合 cosine 调度器,收敛曲线才正常。推荐的起点范围是 1e-4 ~ 3e-4,优先用 2e-4 试第一炮。

Warmup 很重要: 设置 warmup_ratio=0.05(前 5% 步数线性升温)能有效避免训练初期的梯度爆炸,尤其在 batch 较小时。

第三个坑:数据配比

我的训练数据来源有两块:通用指令数据(开源清洗后约 80k 条)和领域专属数据(约 40k 条)。最初按 1:1 混合,模型跑了一段时间之后 eval 上的指令跟随评分开始下滑——它在"遗忘"怎么正确地回应指令。

调成 指令:领域 = 3:1 后明显改善。通用指令数据作为"锚点",保住了模型的基础能力,领域数据再往上叠。

数据质量检查清单

最终有效配置

整理我实际跑通的配置,供参考:

from peft import LoraConfig, TaskType

peft_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type=TaskType.CAUSAL_LM,
)

# TrainingArguments 关键参数
learning_rate       = 2e-4
num_train_epochs    = 3
per_device_batch    = 4
gradient_accumulation_steps = 4   # 等效 batch_size=16
warmup_ratio        = 0.05
lr_scheduler_type   = "cosine"
fp16                = True         # 或 bf16=True(A100/H100)

用这套配置在 2×A100 40G 上跑了约 14 小时,eval loss 收敛稳定,下游任务评测也达到了预期。


这几个坑基本都是"别人踩过我还踩"系列。如果你的 loss 出现了类似问题,优先排查数据质量,然后学习率,最后才是结构参数。有问题欢迎来聊。