把权衡换到选择时踩过的坑

这篇笔记想解决几件具体事:

  • 在有限硬件和有限时间里,哪种 PEFT 方法最可能出效果
  • 不同任务类型下,参数效率、训练速度和效果提升的真实 trade-off
  • 哪些坑是文档里不写但几乎一定会踩的

根据我最近试过的几个场景,给一个可以直接用的选择建议:

  • 文本分类 / 情感分析:优先 LoRa,rank=16-32,alpha=32-64,qproj 和 vproj 加上 adapter
  • 指令遵循 / 代码生成:AdaLoRa 或 LoRa + rank=64-128,至少要训 2-3 轮
  • 领域知识注入:LoRa 稳妥,但如果要保留基座通用能力,用 Prefix Tuning 配上较长 prompt
  • 推理成本敏感:LoRa,因为推理时可以合并权重,不增加额外计算
  • 显存极其受限:Prefix Tuning 或 Prefix-LM,但效果会明显打折扣

为什么要再写一篇 PEFT 对比

“微调一个模型不就是跑几轮训练吗?“半年前我也这么想的。

直到我在一台 24GB 显存的机器上尝试对 Qwen2-7B 做 full fine-tuning,结果训练到一半就爆显存了。更尴尬的是,好不容易用各种技巧把模型训完,下游任务的 F1 只比基座提升了 0.8%,而 LoRa 的配置只用了不到 1/10 的参数量,效果还略好一点。

这就让我意识到一个问题:PEFT 不是"穷人的替代方案”,而是需要根据场景认真权衡的技术选择。市面上技术文章已经写了一堆,但要么偏向原理综述,要么给的是通用建议——“数据少用 LoRa,要改行为用 Prefix Tuning"这种话在实际项目里根本帮不上忙。

这篇笔记想解决几件具体事:

  • 在有限硬件和有限时间里,哪种 PEFT 方法最可能出效果
  • 不同任务类型下,参数效率、训练速度和效果提升的真实 trade-off
  • 哪些坑是文档里不写但几乎一定会踩的

先说结论:如果不想读细节

根据我最近试过的几个场景,给一个可以直接用的选择建议:

  • 文本分类 / 情感分析:优先 LoRa,rank=16-32,alpha=32-64,qproj 和 vproj 加上 adapter
  • 指令遵循 / 代码生成:AdaLoRa 或 LoRa + rank=64-128,至少要训 2-3 轮
  • 领域知识注入:LoRa 稳妥,但如果要保留基座通用能力,用 Prefix Tuning 配上较长 prompt
  • 推理成本敏感:LoRa,因为推理时可以合并权重,不增加额外计算
  • 显存极其受限:Prefix Tuning 或 Prefix-LM,但效果会明显打折扣

下面分场景细讲一下为什么这么选。

场景一:文本分类任务,显存有限

需求

一个中文金融文本分类任务,训练数据约 2 万条,标签 12 类,需要在一台 16GB 显存的 3090 上完成微调。基座模型是 Baichuan2-7B-Base。

尝试过程

最开始试了 full fine-tuning,不到 100 步就 OOM 了。然后查了一圈文档,决定先从 LoRa 开始。

LoRa 配置:

lora_config = {
    "r": 16,           # rank,控制 adapter 矩阵的维度
    "lora_alpha": 32,  # scaling factor,通常设为 2r
    "target_modules": ["q_proj", "v_proj"],  # 在注意力机制的 Q 和 V 上加 adapter
    "lora_dropout": 0.1,
    "bias": "none",
    "task_type": "SEQ_CLS"
}

训练参数:

  • batch size: 4 + gradient accumulation 4
  • learning rate: 2e-4
  • epochs: 3
  • 优化器:AdamW

踩坑

  1. rank 太小导致欠拟合 最初 rank=8,训练 loss 掉得很慢,验证集 F1 只到 76%。改到 rank=16 后,训练速度明显变快,F1 提升到 82%。

  2. target_modules 选不全 一开始只在 q_proj 上加 adapter,效果一般。加上 v_proj 后,F1 又提升约 1.5%。

  3. learning rate 过大 1e-3 的 lr 直接训炸了,loss 走出了一个奇怪的 U 型曲线。最后稳定在 2e-4 到 5e-4 之间。

  4. gradient accumulation 忘记调整 lr batch size 4 改成 16(accumulation 4)后,lr 没同步调小,训练变得不稳定。

结果

  • LoRa rank=16:F1 = 82.3%,训练时间约 3 小时
  • LoRa rank=32:F1 = 83.1%,训练时间约 4.5 小时
  • Full fine-tuning(换到 24GB 机器后试的):F1 = 83.5%,训练时间约 8 小时

文本分类任务不同方法的 F1 分数和训练时间对比,可以看到 LoRa 在成本效益上的优势

结论:在文本分类这种相对简单的任务上,LoRa 的性价比极高——用不到一半的时间,能拿到 95% 以上的效果。如果要极限榨取性能,可以调到 rank=64,但训练成本会明显上升。

graph TD A[开始微调] --> B{任务类型?} B -->|文本分类| C[LoRa r=16-32] B -->|指令遵循| D[AdaLoRa/LoRa r=64-128] B -->|领域知识| E[LoRa 或 Prefix Tuning] C --> F[训练 epochs=2-3] D --> G[训练 epochs=3-5] E --> H[根据保留通用能力程度选择] F --> I[检查验证集指标] G --> I H --> I I --> J{是否达到目标?} J -->|否| K[调整 rank/alpha/lr] J -->|是| L[保存模型并评估]

场景二:指令遵循任务,需要更强的行为改变

需求

一个医疗领域问答任务,数据约 5k 条高质量指令对,需要让基座模型学会按照特定格式输出,同时保留对未见过问题的一定泛化能力。硬件还是那台 3090。

尝试过程

LoRa 一开始表现还行,但有两个问题:

  1. 训练数据少,rank 不敢设太大,担心过拟合
  2. 对格式之外的回答质量提升不明显

然后试了 AdaLoRa,它能自动调整不同层的 rank 分配:

adalora_config = {
    "r": 32,
    "target_modules": ["q_proj", "v_proj", "k_proj", "o_proj"],
    "lora_alpha": 64,
    "lora_dropout": 0.1,
    "init_lora_weights": "gaussian",
    "inference_mode": False
}

踩坑

  1. AdaLoRa 的训练时间更长 相同 epoch 数下,AdaLoRa 比普通 LoRa 慢了约 40%,因为它要动态调整 rank。

  2. 格式学会了,但泛化能力下降 训练集上的格式准确率到了 95%,但在测试集上,对未见过的领域问题,模型开始"编造"格式——强行输出它并不确定的结构化信息。

  3. early stopping 很重要 AdaLoRa 容易过拟合,尤其是数据量小的时候。设置了 patience=3 后,效果明显更稳定。

结果

  • LoRa rank=32:格式准确率 85%,泛化能力一般
  • AdaLoRa rank=32:格式准确率 92%,泛化能力略差
  • LoRa rank=64 + 更多训练数据(10k):格式准确率 90%,泛化能力较好

指令遵循任务不同方法的格式准确率对比,AdaLoRa 在准确率上领先但泛化能力略差

结论:指令遵循这种需要改变"行为"的任务,LoRa 的 rank 不能太小,但数据量也得跟上。如果数据真的很少,可以考虑 Prefix Tuning,但它对 prompt 长度很敏感。

场景三:领域知识注入,要兼顾通用能力

需求

在基座模型里注入法律领域的专业知识,但不能破坏它原有的通用能力。数据主要是法律文书和案例,约 10k 条。

尝试过程

LoRa 直接上,但效果不太对:

  • 法律问题回答得很专业
  • 但普通日常问题开始"法言法语”,说话很奇怪

改用 Prefix Tuning:

prefix_config = {
    "num_virtual_tokens": 20,
    "prefix_projection": False,
    "task_type": "CAUSAL_LM"
}

Prefix Tuning 会在输入前加上一组可训练的"虚拟 token”,这些 token 会影响后续的生成。

踩坑

  1. 虚拟 token 数量不好调 太少(<10)效果不明显,太多(>50)又会让生成偏离基座风格。试了 10、20、50,最后稳定在 20-30 之间。

  2. 推理时必须保留 prefix 不像 LoRa 可以把 adapter 合并回原模型,Prefix Tuning 在推理时必须带着那组虚拟 token,部署时稍微麻烦一点。

  3. 对长文本不友好 超过 2k token 的长文档,Prefix Tuning 的效果会明显下降,可能是因为 prefix 的"控制能力"被稀释了。

结果

  • LoRa:领域问题好,通用问题变味
  • Prefix Tuning(20 tokens):领域问题尚可,通用问题保持不错
  • Prefix Tuning(30 tokens):领域问题更好,但通用问题开始受影响

结论:如果真的很在意保留基座通用能力,Prefix Tuning 值得一试。但如果你有足够的领域数据,其实可以先 LoRa 微调,然后在推理时用 prompt 控制风格,这样更可控。

几个被文档忽略但很影响结果的细节

1. 不同 PEFT 方法对 batch size 的敏感度不一样

LoRa 对 batch size 相对宽容,batch 小一点也能训。但 Prefix Tuning 对 batch size 很敏感,小于 8 的时候训练会很不稳定,可能是因为 prefix 需要足够的上下文才能学到东西。

2. 数据增强的收益因方法而异

LoRa 的数据增强收益很明显——从 5k 扩充到 10k,F1 通常能提升 1-2%。但 Prefix Tuning 对数据量不那么敏感,有时加了反而效果下降,可能是因为它更容易过拟合到数据的特定模式。

3. 不同层的 target_modules 影响很大

不是所有层都适合加 adapter。一般来说:

  • 注意力机制的 q_proj、v_proj 效果最好
  • 前几层少加 adapter,保留基座的通用特征
  • 后几层可以多加,因为它们更靠近任务特定的输出

4. 验证集的构建方式很重要

对于 LoRa,验证集要尽量覆盖训练集的分布,否则容易欠拟合。对于 AdaLoRa,验证集最好设得"严"一点,因为它容易过拟合。

最后说两句选型建议

PEFT 方法没有绝对好坏,只有适不适合。如果你现在就要拍板做一个决定,我会这么建议:

  1. 先问自己三个问题

    • 硬件显存有多大?
    • 训练数据有多少?
    • 需要改变的是"知识"还是"行为"?
  2. 根据回答选方法

    • 显存小 + 数据少 + 只要改知识 → Prefix Tuning
    • 显存一般 + 数据一般 + 要改行为 → LoRa(rank 调大点)
    • 显存充裕 + 数据充足 + 要改行为 → AdaLoRa 或 LoRa rank=128
    • 任何情况 + 推理成本敏感 → LoRa(可以合并权重)
  3. 训练时记得这几件事

    • 从小 rank 开始试,逐步加到能接受的训练时间
    • validation set 要精心设计,不要随便切
    • early stopping 别忘开,尤其是 AdaLoRa
    • 训练完一定要在真实的下游任务上测,不要只看训练 loss

折腾到现在,最大的感受是:PEFT 不是一个"省力方案",而是一个需要根据场景精细调优的技术。选对了方法,确实能用更少的成本拿到不输 full fine-tuning 的效果;但如果选错了,可能会花更多时间还不一定能解决问题。

技术文章总喜欢给一个"最优解",但现实里哪有那么多最优解,更多时候是在几个都不完美的方案里挑一个最不坑的。这篇笔记也没什么终极答案,只是把自己踩过的坑摊开来讲,希望下次你在选 PEFT 方法时,能少走点弯路。

毕竟,机器学习这行,本质上就是不断在时间和效果之间做 trade-off。PEFT 只是把这种权衡变得更精细了一点。

参考资源

版权声明: 本文首发于 指尖魔法屋-把权衡换到选择时踩过的坑https://blog.thinkmoon.cn/post/392-ai-peft-comparison-tradeoff-selection-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!