身为一名AI工程师,我过去的工作主要集中在应用层开发,对算法的理解并不深入。然而,近期我开始对算法产生了浓厚的兴趣,并转向研究模型微调。在众多微调算法中,Lora以其普遍应用引起了我的关注,我计划在本文中对它进行详细介绍。将Lora仅仅视为一种算法可能并不准确,它更像是一种精妙的技巧或策略。下文将围绕几个核心问题,全面探讨和解析Lora技术,希望这些内容能为对模型微调感兴趣的你提供有用的参考和帮助。
Lora是什么
假设大模型的原始的权重矩阵w是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> W = [ 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 ] W = \begin{bmatrix} 1.0 & 2.0 & 3.0 & 4.0 \\ 5.0 & 6.0 & 7.0 & 8.0 \\ 9.0 & 10.0 & 11.0 & 12.0 \\ 13.0 & 14.0 & 15.0 & 16.0 \\ 17.0 & 18.0 & 19.0 & 20.0 \end{bmatrix} </math>W= 1.05.09.013.017.02.06.010.014.018.03.07.011.015.019.04.08.012.016.020.0
全量微调需要更新 5 * 4 = 20个参数,假设微调后的参数是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> W ′ = [ 1.41 2.44 3.47 4.50 5.93 7.00 8.07 9.14 10.45 11.56 12.67 13.78 14.97 16.12 17.27 18.42 19.49 20.68 21.87 23.06 ] W' = \begin{bmatrix} 1.41 & 2.44 & 3.47 & 4.50 \\ 5.93 & 7.00 & 8.07 & 9.14 \\ 10.45 & 11.56 & 12.67 & 13.78 \\ 14.97 & 16.12 & 17.27 & 18.42 \\ 19.49 & 20.68 & 21.87 & 23.06 \end{bmatrix} </math>W′= 1.415.9310.4514.9719.492.447.0011.5616.1220.683.478.0712.6717.2721.874.509.1413.7818.4223.06
这个可以转化为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> W ′ = W + Δ W = [ 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 ] + [ 0.41 0.44 0.47 0.50 0.93 1.00 1.07 1.14 1.45 1.56 1.67 1.78 1.97 2.12 2.27 2.42 2.49 2.68 2.87 3.06 ] W' = W + \Delta W = \begin{bmatrix} 1.0 & 2.0 & 3.0 & 4.0 \\ 5.0 & 6.0 & 7.0 & 8.0 \\ 9.0 & 10.0 & 11.0 & 12.0 \\ 13.0 & 14.0 & 15.0 & 16.0 \\ 17.0 & 18.0 & 19.0 & 20.0 \end{bmatrix} + \begin{bmatrix} 0.41 & 0.44 & 0.47 & 0.50 \\ 0.93 & 1.00 & 1.07 & 1.14 \\ 1.45 & 1.56 & 1.67 & 1.78 \\ 1.97 & 2.12 & 2.27 & 2.42 \\ 2.49 & 2.68 & 2.87 & 3.06 \end{bmatrix} </math>W′=W+ΔW= 1.05.09.013.017.02.06.010.014.018.03.07.011.015.019.04.08.012.016.020.0 + 0.410.931.451.972.490.441.001.562.122.680.471.071.672.272.870.501.141.782.423.06
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> W ′ = W + Δ W = [ 1.41 2.44 3.47 4.50 5.93 7.00 8.07 9.14 10.45 11.56 12.67 13.78 14.97 16.12 17.27 18.42 19.49 20.68 21.87 23.06 ] W' = W + \Delta W = \begin{bmatrix} 1.41 & 2.44 & 3.47 & 4.50 \\ 5.93 & 7.00 & 8.07 & 9.14 \\ 10.45 & 11.56 & 12.67 & 13.78 \\ 14.97 & 16.12 & 17.27 & 18.42 \\ 19.49 & 20.68 & 21.87 & 23.06 \end{bmatrix} </math>W′=W+ΔW= 1.415.9310.4514.9719.492.447.0011.5616.1220.683.478.0712.6717.2721.874.509.1413.7818.4223.06
其中ΔW 可以分解为
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Δ W = A ⋅ B = [ 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 ] ⋅ [ 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 ] \Delta W = A \cdot B = \begin{bmatrix} 0.1 & 0.2 \\ 0.3 & 0.4 \\ 0.5 & 0.6 \\ 0.7 & 0.8 \\ 0.9 & 1.0 \end{bmatrix} \cdot \begin{bmatrix} 1.1 & 1.2 & 1.3 & 1.4 \\ 1.5 & 1.6 & 1.7 & 1.8 \end{bmatrix} </math>ΔW=A⋅B= 0.10.30.50.70.90.20.40.60.81.0 ⋅[1.11.51.21.61.31.71.41.8]
- 矩阵 ( A ):尺寸 ( 5 * 2 ),共10个参数
- 矩阵 ( B ):尺寸 ( 2 * 4 ),共8个参数
- LoRA总参数:( 10 + 8 = 18 ) 个
也就是说通过LoRA微调,调参对象从 W 变为 A、B,使得参数量从20个减少为18个,这是简化的例子。在实际案例中,参数量可以减少为0.01%~3%左右。
为什么需要LoRA
LoRA最早出现在2021年由微软研究院提出的一篇论文中(《LoRA: Low-Rank Adaptation of Large Language Models》),LoRA的核心思路是:与其每次都复制整个模型,不如只调整一小部分参数,把成本降下来。它的目标是解决大模型微调中的两大痛点:
-
资源消耗太大:大型语言模型动辄几亿甚至几千亿参数,全参数微调需要为每个新任务保存一份完整的模型副本。比如,一个10亿参数的模型,假设每个参数用4字节(float32),光存储就得4GB。多个任务下来,硬盘和显存都吃不消。
-
训练效率低下 :全参数微调不仅占空间,还需要大量计算资源和时间。每次训练都得更新所有参数。
LoRA的核心亮点
-
参数少
- 它只微调原始参数的1%甚至更少。
- 在GPT-3上,
r = 8
的LoRA参数量占全微调的0.01%-0.1%,性能却达到全微调的95%-99%。 - 在GLUE任务(BERT),
r = 16
的LoRA用0.1%参数,平均得分仅比全微调低0.5-1分。
- 在GPT-3上,
- 它只微调原始参数的1%甚至更少。
-
速度快
- 训练和部署都比全参数微调省时省力。
-
模块化
- 训练好的LoRA"插件"可以随时加载或卸载,不影响原始模型,特别适合多任务场景。
模块化设计的优点
- 避免灾难性遗忘
直接修改W
可能导致模型在新任务上表现良好,但在原始任务上性能下降(即"灾难性遗忘")。LoRA通过冻结核心W
,保留了原始模型的能力。 - 存储高效
一个大模型可以搭配多个LoRA模块,每个模块只占用MB级空间,相比全模型微调动辄几GB,节省显著。 - 快速切换任务
任务切换只需加载不同LoRA文件,几秒钟搞定,不用重新训练。 - 兼容性强
原始模型完全不动,多个团队可以共享同一个基础模型,只开发自己的LoRA模块。
为什么可以对增量权重 ΔW 低秩分解?
低秩分解的核心思想是:矩阵里的信息往往不是均匀分布的,很多维度是冗余的,只需要抓住"主要方向"就够了。
1. 什么是矩阵的秩(Rank)?
在线性代数中,一个矩阵的秩(rank)是它的线性独立行或列的数量。如果一个矩阵是"低秩"的,意味着它的信息可以用少量独立方向表达,而不是需要完整的维度。
比如下述矩阵,第5行 [1, 2, 0, 3, 0]
是第1行 [1, 0, 0, 2, 0]
和第2行 [0, 2, 0, 1, 0]
的线性组合(第5行=第1行+第2行),第5行没有提供更多的信息,理论上这个矩阵有前4行就能提供所有信息了,因此矩阵的行秩为4(列秩也为4,第5列全为0,没有信息增量)。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> S = [ 1 0 0 2 0 0 2 0 1 0 0 0 3 0 0 2 1 0 5 0 1 2 0 3 0 ] S = \begin{bmatrix} 1 & 0 & 0 & 2 & 0 \\ 0 & 2 & 0 & 1 & 0 \\ 0 & 0 & 3 & 0 & 0 \\ 2 & 1 & 0 & 5 & 0 \\ 1 & 2 & 0 & 3 & 0 \end{bmatrix} </math>S= 1002102012003002105300000
2. 低秩分解的原理
奇异值分解(SVD)可以把任意矩阵分解成三个矩阵的乘积。对于一个形状 ( d * k ) 的矩阵 ( W ),SVD可以写成:
- ( U ) 是 ( d * d ) 的正交矩阵
- (Σ ) 是 ( d * k ) 的对角矩阵(奇异值按降序排列)
- ( V^T ) 是 ( k * k ) 的正交矩阵(( V ) 的转置)
其中 ( r ) 是矩阵的秩(非零奇异值的数量)。通过保留前 ( r ) 个最大的奇异值(低秩近似),可以用更少的参数近似原矩阵 ( W )。
任意矩阵(无论是实数还是复数、方阵还是非方阵、满秩还是不满秩)都可以通过**奇异值分解(SVD)**精确拆分为三个特定矩阵的乘积
举个例子,针对上述矩阵 ( S ) 的SVD分解(计算过程略):
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> U ≈ [ 0.3 0 0.34 − 0.68 − 0.58 − 0.22 0 − 0.76 0.2 − 0.58 − 0.7 0 0.36 0.32 0 − 0.22 0 − 0.42 − 0.46 − 0.56 ] , Σ = [ 7.03 0 0 0 0 0 3 0 0 0 0 0 2.15 0 0 0 0 0 0.11 0 0 0 0 0 0 ] , V ≈ [ 0.34 − 0.32 0 − 0.89 0 0 0 − 1 0 0 0.3 − 0.93 0 0.22 0 − 0.89 − 0.19 0 0.41 0 0 0 0 0 1 ] \begin{aligned} U &\approx \begin{bmatrix} 0.3 & 0 & 0.34 & -0.68 & -0.58 \\ -0.22 & 0 & -0.76 & 0.2 & -0.58 \\ -0.7 & 0 & 0.36 & 0.32 & 0 \\ -0.22 & 0 & -0.42 & -0.46 & -0.56 \end{bmatrix}, \\ \Sigma &= \begin{bmatrix} 7.03 & 0 & 0 & 0 & 0 \\ 0 & 3 & 0 & 0 & 0 \\ 0 & 0 & 2.15 & 0 & 0 \\ 0 & 0 & 0 & 0.11 & 0 \\ 0 & 0 & 0 & 0 & 0 \end{bmatrix}, \\ V &\approx \begin{bmatrix} 0.34 & -0.32 & 0 & -0.89 & 0 \\ 0 & 0 & -1 & 0 & 0 \\ 0.3 & -0.93 & 0 & 0.22 & 0 \\ -0.89 & -0.19 & 0 & 0.41 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \end{aligned} </math>UΣV≈ 0.3−0.22−0.7−0.2200000.34−0.760.36−0.42−0.680.20.32−0.46−0.58−0.580−0.56 ,= 7.03000003000002.15000000.11000000 ,≈ 0.3400.3−0.890−0.320−0.93−0.1900−1000−0.8900.220.41000001
如果只保留前三个奇异值(),重构后的矩阵 ( S' ) 与原矩阵 ( S ) 几乎一致(三个矩阵分别取前三列,前三行&前三列,前三行):
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> S ′ = [ 0.93 − 0.01 0 2.03 0 0.02 2.00 0 0.99 0 0.00 0.00 3.00 0.00 0 2.05 1.01 0 4.98 0 0.95 1.99 0 3.02 0 ] S' = \begin{bmatrix} 0.93 & -0.01 & 0 & 2.03 & 0 \\ 0.02 & 2.00 & 0 & 0.99 & 0 \\ 0.00 & 0.00 & 3.00 & 0.00 & 0 \\ 2.05 & 1.01 & 0 & 4.98 & 0 \\ 0.95 & 1.99 & 0 & 3.02 & 0 \end{bmatrix} </math>S′= 0.930.020.002.050.95−0.012.000.001.011.99003.00002.030.990.004.983.0200000
结果对比原始矩阵和重构矩阵,直观上来看,基本保持一致,这就是说:如果只保留最大的几个奇异值,就能用更少的参数近似表示w。
3. 为什么可以对增量权重 ΔW 低秩分解?
研究发现:
- 信息集中性:微调后的权重变化 ( ΔW) 的奇异值分布中,前10-20个奇异值占据了90%以上的信息(LoRA论文在GPT-3上的实验结论)。
- 结构化特性:(ΔW ) 的变化不是随机的,而是集中在少数"任务相关方向"上(例如让模型学习法律术语只需调整少量语义方向)。
- 高效近似:直接用低秩矩阵 ( A * B ) 构造 (ΔW ),无需完整SVD计算,参数量从 ( d * k ) 降至 ( (d + k) * r )。
直观理解: 微调类似于让一个已学会"说话"的模型掌握某种"口音"。这种调整只需修改少数关键维度(如词汇选择),而非全部语言规则,因此低秩足够。
举个例子 :
对一个 ( 512 * 512 ) 的权重矩阵(262,144参数):
- 全微调:更新全部262,144个参数。
- LoRA(( r=8 )):仅需 ( 512 * 8 + 8 * 512 = 8,192 ) 个参数,即可捕捉主要变化。
4. 对原始权重 ( W ) 可以低秩分解吗?
不行。预训练模型的权重 ( W ) 通常接近满秩(奇异值分布平滑),低秩分解会丢失关键信息。而 ( ΔW ) 的秩天然较低,适合分解。
LoRA是如何更新参数的
本质上,LoRA仍然使用反向传播算法进行参数更新,但仅针对新增的低秩矩阵 ( A ) 和 ( B ),而保持原始权重 ( W ) 冻结。
参数更新过程
- 初始化
- ( W ) 使用预训练模型的权重,梯度计算被禁用(不更新)。
- ( A ) 用小的随机高斯分布初始化
- ( B ) 初始化为全零矩阵,确保训练开始时 ( ΔW = 0 ),避免干扰原始模型。
- 前向传播
- 输入数据 ( X ) 通过调整后的权重计算输出
- 根据任务目标 计算损失函数 ( L )(如交叉熵损失)。
- 反向传播
- 计算损失 ( L ) 对 ( A ) 和 ( B ) 的梯度
- 不计算 ( W ) 的梯度(因其被冻结)。
- 参数更新
- 使用优化器(如Adam)更新 ( A ) 和 ( B ):
。其中 ( η ) 是学习率。
- 使用优化器(如Adam)更新 ( A ) 和 ( B ):
- 迭代优化
- 重复步骤2-4,直到损失收敛或达到训练轮次。
- 训练完成后,( A ) 和 ( B ) 捕捉了任务特定的调整信息。
推理部署选项
- 合并权重:将 ( W' = W + A * B ) 合并为单一矩阵,直接用于推理(适合固定任务)。
- 动态加载:保持 ( W ) 和 ( A * B ) 分离,灵活切换不同任务的LoRA模块(适合多任务场景)。
关键特点
- 参数高效:仅训练 ( A ) 和 ( B ),参数量从 ( d * k ) 降至 ( (d + k) * r )。
- 内存节省:无需存储全参数微调的梯度,显存占用大幅降低。
- 兼容性:原始模型 ( W ) 保持不变,支持多任务共享。
LoRA可以用在Transformer的哪些层
LoRA是"好钢要用在刀刃上"。并非模型的所有参数都需要微调,选择关键层进行适配即可达到接近全参数微调的效果。LoRA目前主要可以应用在transformer中的以下两类层:
Transformer是谷歌在2017年推出的深度学习模型,专门处理序列数据。简单来说,序列数据就像排队的小朋友,每个小朋友都有自己的位置和信息,Transformer能把这些信息处理得明明白白。后面有空我会专门出一个系列讲解一下。
1. 注意力层(Self-Attention)
Transformer的核心是多头注意力机制,每个注意力头包含4个权重矩阵:
- ( W_q )(Query)
- ( W_k )(Key)
- ( W_v )(Value)
- ( W_o )(Output)
LoRA通常应用在:
-
( W_q ) 和 ( W_v )(最高优先级):
-
调整 ( W_q ) 可改变模型"关注哪些信息"。
-
调整 ( W_v ) 可影响"如何编码关注的信息"。
-
-
( W_o )(次优先级):
- 调整输出投影矩阵,但收益通常不如 ( W_q ) 和 ( W_v ) 显著。
实验结论(来自LoRA原论文):
- 仅微调 ( W_q ) 和 ( W_v ) 即可达到全参数微调效果的90%以上。
- 添加 ( W_o ) 的LoRA对性能提升有限(<2%),但会增加参数量。
2. 前馈网络层(FFN)
FFN包含两个线性变换:
- ( W_1 ):升维(通常放大4倍,如d_model → 4×d_model)
- ( W_2 ):降维(4×d_model → d_model)
适用场景:
- 大模型(如GPT-3):添加FFN层的LoRA可进一步提升性能。
- 复杂生成任务:调整FFN能增强任务特定的特征表达。
不推荐使用LoRA的层
- 嵌入层(Embedding) :
- 参数量大但微调收益低,冻结可节省资源。
- LayerNorm/Bias :
- 参数少,直接全参数微调成本低。
- LayerNorm的缩放因子和偏置本身具有低秩特性,无需LoRA。
实际配置建议
模型规模 | 推荐LoRA目标层 | 典型rank (r) |
---|---|---|
小模型(如BERT) | 仅 ( W_q ), ( W_v ) | 8-16 |
大模型(如GPT-3) | ( W_q ), ( W_v ), FFN的 ( W_1 ) | 32-64 |
复杂生成任务 | 所有注意力矩阵 + FFN | 64+ |
模块化设计优势
- 任务切换:不同任务可独立配置LoRA模块(如翻译任务用( W_q ), ( W_v ),摘要任务额外启用FFN)。
- 资源分配:对关键层分配更高秩(如( r=32 )),次要层用低秩(如( r=8 ))。
LoRA训练时需要调整哪些超参数
以 LLaMA-Factory 的配置为例,说明 LoRA 的关键超参数及其调参策略:
核心参数表
参数名 | 类型/范围 | 含义 | 建议值 | 默认值 |
---|---|---|---|---|
finetuning_type |
["full","freeze","lora"] |
微调类型选择 | 必须设为 "lora" |
"lora" |
lora_rank (r) |
正整数 | LoRA的秩,决定矩阵A/B的列数/行数 | 简单任务:8-16 中等任务:32 复杂任务:64+ | 8 |
lora_alpha (α) |
正整数 | 缩放系数,控制ΔW对原始权重W的影响强度 | 通常设为 lora_rank 的1-2倍(如r=16时α=32) |
None |
lora_dropout |
0.0-1.0 | LoRA层的Dropout概率 | 大数据集:0.0 小数据集:0.05-0.1(防过拟合) | 0.0 |
lora_target |
逗号分隔的字符串 | 应用LoRA的模块名称(需匹配模型层名) | 默认:"q_proj,v_proj" 复杂任务:"q_proj,k_proj,v_proj,o_proj" |
"all" |
additional_target |
逗号分隔的字符串 | 额外扩展的LoRA目标模块(如FFN层) | 通常留空,大模型可加"ffn.w1,ffn.w2" |
None |
调参技巧
-
秩(r)的选择
- 从小开始:优先尝试r=8或16,逐步增加直至性能饱和。
- 数据量关联 :
- 小数据集(<5K样本):r=8
- 大数据集(>50K样本):r=32+
-
目标层选择策略
python# 简单任务(如分类) lora_target = "q_proj,v_proj" # 复杂任务(如生成) lora_target = "q_proj,k_proj,v_proj,o_proj,ffn.w1,ffn.w2"
-
改进技术的适用场景
- LoRA+ :训练速度要求高时启用(设
lorapius_lr_ratio=8
)。 - DoRA :需要逼近全微调性能时开启(
use_dora=true
)。 - rsLoRA :当r≥32时更稳定(
use_rslora=true
)。
- LoRA+ :训练速度要求高时启用(设
参数影响对比
超参数 | 参数量影响 | 训练速度 | 性能影响 |
---|---|---|---|
lora_rank ↑ |
线性增加 | 略微下降 | 先升后平 |
lora_alpha ↑ |
无影响 | 无影响 | 调节强度 |
use_dora=true |
增加约10% | 下降10%-20% | 提升1%-3% |
pissa_init=true |
无影响 | 初始化耗时增加 | 收敛更快 |
经典配置示例
yaml
# GLUE任务(BERT-base)
lora_rank: 16
lora_alpha: 32
lora_target: "query,value"
lora_dropout: 0.1
# GPT-3文本生成
lora_rank: 64
lora_alpha: 128
use_rslora: true
lora_target: "q_proj,v_proj,ffn.w1"
总结
LoRA是一种高效的大模型微调技术,它通过低秩矩阵分解显著地减少了参数量和计算资源的需求,同时又能保持接近全模型微调的性能。在接下来的文章中,我们将从实战角度出发,借由Llama-Factory来进行模型微调。我希望能帮助读者从零开始,全面掌握模型微调的知识和技巧。
感兴趣的朋友可以关注我的微信公众号 AI 博物院,获取第一时间更新!