AdaLoRA
1.摘要
之前的微调方法(如低秩更新)通常将增量更新的预算均匀地分布在所有预训练的权重矩阵上,并且忽略了不同权重参数的不同重要性。结果,微调结果不是最优的。
为了弥补这一差距,我们提出了AdaLoRA,它根据权重矩阵的重要性得分自适应地在权重矩阵之间分配参数预算。特别地,AdaLoRA以奇异值分解的形式将增量更新参数化。
这种新的方法允许我们有效地修剪不重要更新的奇异值,这实质上是减少它们的参数预算,但避免了密集的精确 SVD 计算。
我们使用几个预先训练好的模型在自然语言处理、问题回答和自然语言生成方面进行了大量的实验,以验证AdaLoRA的有效性。结果表明,AdaLoRA 与基线相比有显著改善,尤其是在低预算环境中。
2.引言
图一:假设总可训练参数为0.28 M,我们仅将LoRA应用于DeBERTaV 3-base的选定权重矩阵(左)或选定层(右),并比较MNLI-m的微调性能。
图1a:我们仅微调每个Transformer层的所选类型的权重矩阵,包括查询/键/值投影(Wq,Wk,Wv)、自注意中的输出投影(Wo)以及两层FFN中的两个权重矩阵( W f 1 W_{f_1} Wf1, W f 2 W_{f_2} Wf2)。
在图1b中,我们将LoRA应用于选定层的每个权重矩阵
减少全量微调参数的微调方法有两条主线,
- 一个是增加小的神经模块给预训练语言模型并且只微调这个任务特定的模块,基础模型模块保持冻结和共享。
- 例如 adapter tuning、prefix tuning 和 prompt tuning 都只增加了少于 1% 的原始模型参数,显著减少了内存消耗
- 另一个是不改变模型的架构,对预训练模型的增量进行建模。
-
给定预训练的权重矩阵 W ( 0 ) W^{(0)} W(0),例如,diff 剪枝 将其增量更新 Δ 建模为稀疏矩阵。Diff剪枝 将 Δ 初始化为与 W ( 0 ) W^{(0)} W(0) 相同的维数,然后根据条目的大小对 Δ 元素进行剪枝。因此,通过自适应地保留重要的更新并修剪不重要的更新,diff修剪可以显著地提高参数效率。
-
但是,diff修剪 有几个限制。
- 首先,它依赖于底层实现来加速非结构化稀疏矩阵的计算,而现有的深度学习框架并不很好地支持这一点。因此,在训练过程中,我们必须将Δ存储为密集矩阵。
- 第二,它需要用导数来更新Δ的每一个条目,然后对其进行剪枝。这导致与完全微调类似的计算成本(Guo等人,2020年)的报告。
-
所以,有人提出了 LoRA(上文介绍了),通过两个小矩阵的点积来作为增量更新
W = W ( 0 ) + ∆ = W ( 0 ) + B A W = W^{(0)} +∆ = W^{(0)} +BA W=W(0)+∆=W(0)+BA
- 与完全微调相比,仅需不到 0.5% 的额外可训练参数,训练开销可减少高达70。
- LoRA实现了与完全微调相当或甚至更好的性能(Hu等人,2022年)的报告。同时,在diff剪枝中,两个小矩阵的乘积比非结构化稀疏矩阵更易于实现和部署。
-
但是,LoRA 也有局限性,因为它预先指定每个增量矩阵的秩r是相同的。
- 这忽略了这样一个事实,即在微调预训练模型时,权重矩阵的重要性在模块和层之间存在显着差异。为了说明这一点,我们在 图1 中给出了一个具体的例子。我们比较了LoRA在使用相同数量的可训练参数微调特定模块或层时的性能。图1a 显示微调前馈网络(FFN)比自注意模块实现更好的性能。此外,图1b 表明顶层的权重矩阵比底层的权重矩阵更重要。
-
很自然的,有了这个问题:如何根据模块的重要性自适应地分配参数预算,以提高参数有效微调的性能?
为了回答这个问题,作者提出了 AdaLoRA(Adaptive Low-Rank Adaptation),他可以在 类 LoRA 方法 微调期间在权重矩阵之间动态地分配参数预算。
具体的,AdaLoRA 通过调节增量矩阵的秩来控制预算。
对比较重要的矩阵给予一个更高的秩,来捕获更细粒度和特定于任务的信息。
对于不太重要的矩阵给予一个低的秩,防止过拟合和节约预算。
大多数算法直接计算矩阵奇异值分解(SVD),然后截断最小奇异值。这样的操作可以显式地操纵秩。但是对于大规模模型,SVD 很昂贵。因此,我们不需要精确计算SVD,而是通过参数化 Δ 为 Δ = PΛQ 来模拟 SVD。为了正则化 P 和 Q 的正交性,向训练损失添加额外的惩罚。这样的参数化避免了 SVD 的密集计算。另外,该方法的另一个优点是只需要去掉不重要的奇异值,而保持奇异向量不变。这就保留了未来恢复的可能性,并稳定了训练。
基于我们的 SVD 参数化,AdaLoRA 通过重要性评分来动态调整 Δ = PVQ 的秩。
具体地说,我们将增量矩阵 PΛQ 划分为三元组,其中每个三元组 G i G_i Gi 包含第 i 个奇异值和对应的奇异向量。
为了量化三元组的重要性,我们提出了一种新的重要性度量,该度量考虑了 G i G_i Gi 中每个条目对模型性能的贡献的报告。
具有低重要性分数的三元组被授予低优先级,因此奇异值被置零。具有高重要性的三元组被保留以进行微调。
此外,我们还提出了一个全局预算计划,以促进训练。特别是,我们从一个比最终预算略高的初始参数预算开始,然后逐渐降低,直到与目标相匹配。这种调度器可以提高训练的稳定性和模型的性能。
我们对各种任务和模型进行了广泛的实验,以证明 AdaLoRA 的有效性。具体地,我们使用 DeBERTaV 3-base 在自然语言理解(GLUE)和问答(SQuADv 1 和 SQuADv 2)数据集上。我们还将我们的方法应用于BART-large,并评估自然语言生成(XSum 和 CNN/DailyMail)任务的性能。我们发现 AdaLoRA 始终优于基线,特别是在低预算设置下。例如,与最先进的方法相比,AdaLoRA 在 SQuAD2.0 数据集上实现了不到 0.1% 的完全微调可训练参数的 1.2% F1改进。
3.背景
- 基于 Transformer 的模型
- MHA
- FFN
- LoRA
- h = W ( 0 ) x + ∆ x = W ( 0 ) x + B A x h = W^{(0)}x +∆x = W^{(0)}x +BAx h=W(0)x+∆x=W(0)x+BAx,
4.AdaLoRA 方法
AdaLoRA 有两个重要组件:
- 基于 SVD 的适应,该方法以奇异值分解的形式表示增量矩阵
- 重要性感知的 秩分配,根据我们新设计的重要性度量修剪冗余奇异值
1.SVD-Based Adaptation
作者提出参数化增量矩阵为
W = W ( 0 ) + ∆ = W ( 0 ) + P Λ Q , W = W^{(0)} +∆ = W^{(0)} +PΛQ, W=W(0)+∆=W(0)+PΛQ,
G i = { P ∗ i , λ i , Q i ∗ } G_i=\{P_{*i},\lambda_i,Q_{i*}\} Gi={P∗i,λi,Qi∗}是包含第 i 个奇异值和向量的三元组。
Λ Λ Λ 采用 零初始化,P 和 Q 采用随机高斯初始化,确保 ∆ ∆ ∆ 最开始等于零
我们注意到,还可以将结构化剪枝 应用于 LoRA 以控制秩(即,在 W = W ( 0 ) + ∆ = W ( 0 ) + B A W = W^{(0)} +∆ = W^{(0)} +BA W=W(0)+∆=W(0)+BA 中修剪 BA 两次),
2.重要性感知的秩分配
其中,
-
L ( P , E , Q ) = C ( P , E , Q ) + γ ∑ k = 1 n R ( P k , Q k ) L(P,E,Q)= C(P,E,Q)+ γ \sum^n_{k=1} R(P_k,Q_k) L(P,E,Q)=C(P,E,Q)+γ∑k=1nR(Pk,Qk), γ γ γ 是正则化系数
-
R ( P , Q ) = ∣ ∣ P T P − I ∣ ∣ F 2 + ∣ ∣ Q Q T − I ∣ ∣ F 2 R(P,Q)= ||P^T P − I|| ^2_ F + ||QQ ^T− I|| ^2_ F R(P,Q)=∣∣PTP−I∣∣F2+∣∣QQT−I∣∣F2
-
C C C 是训练代价
-
I ( w i j ) = ∣ w i j ∇ w i j L ∣ I(w_{ij})=|w_{ij}∇_{w_{ij}}L| I(wij)=∣wij∇wijL∣
-
如下
-
如下
7.如下
3.全局预算调度
如第1节所述,调整秩自然是为了控制低秩自适应上下文中的参数预算。因此,我们将预算 b ( t ) b^{(t)} b(t) 定义为所有增量矩阵的总秩,即,总奇异值的数量。
回想一下,预算分配是在微调期间迭代进行的。为了便于训练,我们提出了一个全局预算调度。
具体地,
我们从略高于目标预算 b ( t ) b^{(t)} b(t) 的初始预算 b ( 0 ) b^{(0)} b(0) 开始(例如,1.5乘以 b ( T ) b^{(T)} b(T))。我们将每个增量矩阵的初始秩设为 r = b ( 0 ) / n r = b^{(0)}/n r=b(0)/n。
我们对 t i t_i ti 步骤的训练进行热身,然后遵循立方体调度来减少预算 b ( t ) b^{(t)} b(t),直到达到 b ( T ) b^{(T)} b(T)。
最后,我们修正了最终的预算分布,并对 t f t_f tf步骤的模型进行了微调。
5.实验
1.各方法数据集的结果
2.相同参数量的效果比较(NLU)
3.相同参数量的效果比较(NLG)
4.与 LoRA 不同的预算水平的对比
5.AdaLoRA总是倾向于将更多的预算分配给FFN和顶层
6.训练细节
1.NLU
2.QA
3.NLG
4.训练成本
7.超参数微调
1.超参数
- 初始秩(initial rank)
- 每个权重矩阵的初始低秩维度
- 建议:
- 通常为 16-32
- 比传统 LoRA 稍大一些
- 因为后续会自动裁剪,起始可以激进一些
- 目标压缩率(target compression radio)
- 决定最终要压缩到的参数量
- 建议
- 通常 0.1-0.3 之间
- 越小意味着越大的压缩力度
- 需要根据具体任务和资源调整
- 重要性评估周期(importance evaluation interval)
- 多少步进行一次重要性评估和秩调整
- 建议:
- 通常设置为 50-200 步
- 太频繁会增加计算开销
- 太稀疏可能错过优化机会
- 裁剪步长(pruning step)
- 每次评估时裁剪的奇异值数量
- 建议
- 通常设置为 1-3
- 较小的不常能获得更平滑的压缩过程
- 但会增加总体训练时间
2.具体调参建议
-
初始配置建议:
adalora_config = {"initial_rank": 24,# 初始秩"target_ratio": 0.2,"eval_interval": 100,"prune_step": 2,"target_modules": ["q_proj", "v_proj"] }
-
不同规模模型的参数建议
- 中小型模型(7B以下):
- initial_rank: 24-32
- target_ratio: 0.2-0.3
- eval_interval: 100
- 大型模型(7B-70B):
- initial_rank: 16-24
- target_ratio: 0.1-0.2
- eval_interval: 150
- 超大模型(70B+):
- initial_rank: 8-16
- target_ratio: 0.05-0.1
- eval_interval: 200
- 中小型模型(7B以下):
-
重要性度量设置
- SVD 分解频率
- 建议每 100-200 进行一次
- 梯度敏感度计算
- 使用最近 100-500 步的梯度信息
- SVD 分解频率
3.最佳实践和注意事项
- 训练阶段:
- 第一阶段:先用较大的 initial rank 训练一段时间
- 第二阶段:开始逐步压缩,直到达到目标压缩率
- 第三阶段:保持压缩率进行微调
2.关键优化建议:
-
为不同层设置不同的 target-radio
- 底层可以设置较大的比例(0.2-0.3)
- 顶层可以设置较小的比例(0.1-0.2)
-
调整重要性评估策略
- 可以考虑基于验证集性能
- 或者使用 Fisher 信息矩阵
Fisher 信息矩阵 I ( θ ) I(\theta) I(θ) 表示的是模型参数 θ \theta θ 的 Fisher 信息,它定义为对数似然函数的二阶导数(即 Hessian 矩阵)的期望值,或等价地,它可以被视为似然函数的曲率矩阵:
I ( θ ) = − E [ ∂ 2 log L ( θ ) ∂ θ 2 ] I(\theta) = - \mathbb{E} \left[ \frac{\partial^2 \log L(\theta)}{\partial \theta^2} \right] I(θ)=−E[∂θ2∂2logL(θ)]
这里, L ( θ ) L(\theta) L(θ) 是似然函数, θ \theta θ 是模型的参数, E \mathbb{E} E 表示期望
- 常见问题解决
- 如果训练不稳定
- 降低裁剪不常
- 增加评估间隔
- 如果压缩效果不理想
- 调整初始秩
- 放缓压缩速度
- 重新评估目标压缩率
- 如果训练不稳定
4. 在 Hugging Face 中
-
核心参数
from peft import AdaLoraConfigadalora_config = AdaLoraConfig(init_r=12, # 初始秩target_r=4, # 目标秩beta1=0.85, # 重要性评估的momentumbeta2=0.85, # 自适应学习的momentumtarget_modules=["q_proj", "v_proj"],importance_measure="approximate_fish", # 重要性度量方法 )
-
主要参数建议
-
init_r 和 target_r
- 小模型:init_r=16, target_r=8
- 中型模型:init_r=12, target_r=4
- 大模型:init_r=8, target_r=2
-
importance_measure
# 建议配置 importance_measure="approximate_fish" # 更快 # 或 importance_measure="weight_norm" # 更稳定
-
feedback_interval
# 每多少步更新一次重要性 feedback_interval=100 # 小数据集 feedback_interval=500 # 大数据集
-