LoRA系列详解
LoRA 原理(低秩适配)
LoRA(Low-Rank Adaptation)是一种参数高效微调方法,核心思想是用低秩分解来近似参数更新量,从而只训练少量参数即可达到接近全参数微调的效果。
核心思想
原模型参数更新可以表示为:
W=W0+ΔW
LoRA不直接更新 W0,而是用低秩分解来近似 ΔW:
ΔW=BA⋅rα
其中:
- W0∈Rd×k:原模型参数,冻结不变
- A∈Rr×k:降维矩阵
- B∈Rd×r:升维矩阵
- r:秩(rank),远小于 d 和 k
- α:缩放系数,通常 α=r
网络结构
关键特性
- 冻结原模型:原模型参数全部冻结,只训练A和B两个小矩阵
- 推理不增加延迟:推理时可以将BA合并到原权重W0中,不增加推理延迟
- 可插拔式切换:不同任务只需要切换不同的LoRA权重,无需修改原模型
- 参数量少:可训练参数量为 r×(d+k),远小于全参数微调
为什么低秩有效?
研究发现,大模型微调时参数更新具有较低的"内在秩",即使使用很小的r也能达到很好的效果。
LoRA 初始化与超参选择
初始化策略
- 矩阵A:使用高斯分布(或Kaiming均匀分布)初始化
- 矩阵B:初始化为全0
- 为什么这样初始化?:
- 如果全部初始化为0,所有神经元功能等价,容易导致梯度消失
- 如果全部高斯初始化,训练开始时偏移ΔW可能过大,引入太多噪声,难以收敛
- A高斯+B全0保证初始状态ΔW=0,不改变原输出,同时保证梯度能正常流动
Rank(r)选择
- 原论文对比了r从1到64,r=4~8效果就很好,再增大rank效果提升不明显
- 这是针对单一下游任务的结论
- 如果是指令微调,覆盖广泛任务分布,建议r选8以上再做测试
Alpha 参数
- alpha是缩放系数,ΔW 会乘以 rα
- 本质上和学习率作用类似,为了简化调参,通常让 alpha = rank,只调学习率即可
Target Modules(作用目标)
- LoRA应该作用在Transformer的哪些参数矩阵上?
- 实践发现:将可微调参数分配到多种类型权重矩阵效果更好,比只用更大rank微调单一权重效果好
- 通常选择:
q_proj, v_proj(LLaMA)或 query_key_value(ChatGLM)
- 也可以扩展到所有Linear层:
q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
参数量计算
可训练参数量 = r×(in_features+out_features)×number_of_target_modules
举例:ChatGLM-6B,r=8,只作用于query_key_value,可训练参数量大约15M,非常小。
QLoRA(4-bit 量化 + LoRA)
核心思想
QLoRA是LoRA的进一步优化,通过4-bit量化将整个大模型量化到4位存储,只微调少量LoRA参数,实现在单张消费级GPU上微调大模型。
关键技术:
- 4-bit NormalFloat量化:一种新颖的高精度4-bit量化技术
- 双重量化:对量化常数也进行量化,进一步压缩
- 分页优化器:优化显存管理
- 保持原模型量化冻结,只微调LoRA权重,梯度通过量化权重反向传播
特点
- 显著降低显存要求,使得7B模型可以在单张24GB显存卡上微调
- 效果接近全参数微调
- 训练速度比原生LoRA慢
AdaLoRA、DoRA 等变体
AdaLoRA(Adaptive LoRA)
- 核心思想:动态分配参数预算
- 根据重要性评分,给关键权重矩阵分配更高的秩,捕捉更精细的任务信息;给不重要的矩阵分配更低的秩,防止过拟合,节省计算
- 论文结果显示效果略好于全参数微调
DoRA(Weight-Decomposed LoRA)
- 核心思想:将权重分解为方向和范数两部分,LoRA只调整方向,大模型本身已经有很好的基方向,只需要微调方向;范数部分单独学习
- 相比原始LoRA,在不增加参数量的情况下效果提升
- 不需要对LoRA权重进行合并就能推理,推理效率不变
其他变体
- LoRA++:改进初始化和缩放策略
- LoHA:低秩适配器用于注意力机制的变体
LoRA 优缺点
优点
- 参数量少:只训练低秩矩阵,显存占用低,训练快
- 无推理延迟:可以合并到原权重,推理不增加计算量
- 多任务便捷:一个基础模型可以切换多个LoRA权重,节省存储
- 缓解遗忘:原模型参数冻结,不容易遗忘通用知识
- 效果好:很多任务上效果接近全参数微调,与其他方法正交,可以组合使用
缺点
- 效果略差于全参数微调:尤其是数据量大的时候,因为可训练参数量有限
- 训练时间不一定更快:虽然参数量少,但不减少训练步数,总体训练时间可能更长,只是省显存
- 如果不合并权重,推理时需要额外计算,增加延迟(可以合并解决)
PEFT库中LoRA使用要点
配置示例
1from peft import LoraConfig
2
3config = LoraConfig(
4 r=8, # 秩
5 lora_alpha=16, # 缩放系数,通常是2*r
6 target_modules=["q_proj", "v_proj"], # 目标模块
7 lora_dropout=0.05, # dropout率
8 bias="none", # 是否训练bias
9 task_type="CAUSAL_LM" # 任务类型
10)
显存优化
- 8bit量化:将原模型量化为8bit,减少静态显存占用,从FP32压缩到INT8,显存减少到1/4,使用absmax量化+outlier分离优化精度
- 梯度检查点:不存储中间激活值,反向传播时重新计算,用时间换空间,减少动态显存占用
- device_map="auto":自动分配模型到多个GPU
存储与推理
- 存储:只保存LoRA权重,文件很小(通常几MB到几十MB)
- 推理两种方式:
- 保留LoRA层,动态添加计算:简单但增加推理延迟
- 合并到原权重:合并后存储为新模型,无推理延迟
多LoRA切换
PEFT >= 0.3.0支持加载多个LoRA并随时切换:
1# 加载第一个LoRA
2model = PeftModel.from_pretrained(model, "lora1", adapter_name="task1")
3# 加载第二个LoRA
4model.load_adapter("lora2", "task2")
5# 切换
6model.set_adapter("task2")
7# 禁用
8with model.disable_adapter():
9 # 原模型推理
面试常见问题
Q: LoRA为什么能工作?低秩假设成立吗?
A: LoRA基于"大模型参数更新具有低内在秩"的观察,实践中确实只需要很小的秩(4-8)就能达到接近全参数微调的效果,说明低秩假设在大多数下游任务中是成立的。这可能是因为下游任务只需要对原模型知识做"方向性调整",不需要改变整个参数空间。
Q: LoRA权重可以合并到原模型吗?
A: 可以。训练完成后,计算 W=W0+BA⋅rα,直接得到新的权重,之后就和普通模型一样推理,没有任何额外延迟。
Q: LoRA训练中如何避免过拟合?
A:
- 减小rank r
- 增大数据集大小
- 增加权重衰减
- 在LoRA层使用dropout
- 早停
Q: QLoRA相比LoRA有什么优势?
A: QLoRA通过4-bit量化整个原模型,进一步大幅降低显存需求,使得更大模型可以在单GPU上微调,而LoRA只冻结原模型,不做量化,显存要求更高。但QLoRA训练速度更慢。
Q: 全参数微调 vs LoRA,怎么选择?
A:
- 计算资源有限,数据量不大:选LoRA,省显存,快速迭代
- 计算资源充足,数据量很大(10k+高质量数据):选全参数微调,效果更好
- 多任务部署:选LoRA,一个基础模型切换多个任务权重,节省存储
Q: 如何在已有LoRA基础上继续训练?
A: 先将已有LoRA和原模型合并,然后在合并后的模型上训练新的LoRA即可。建议加入一部分旧数据,保留之前学到的知识。