从原理到调参,小白也能读懂的大模型微调算法Lora

379 阅读12分钟

身为一名AI工程师,我过去的工作主要集中在应用层开发,对算法的理解并不深入。然而,近期我开始对算法产生了浓厚的兴趣,并转向研究模型微调。在众多微调算法中,Lora以其普遍应用引起了我的关注,我计划在本文中对它进行详细介绍。将Lora仅仅视为一种算法可能并不准确,它更像是一种精妙的技巧或策略。下文将围绕几个核心问题,全面探讨和解析Lora技术,希望这些内容能为对模型微调感兴趣的你提供有用的参考和帮助。

Lora是什么

假设大模型的原始的权重矩阵w是:

W=[1.02.03.04.05.06.07.08.09.010.011.012.013.014.015.016.017.018.019.020.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}

全量微调需要更新 5 * 4 = 20个参数,假设微调后的参数是:

W=[1.412.443.474.505.937.008.079.1410.4511.5612.6713.7814.9716.1217.2718.4219.4920.6821.8723.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}

这个可以转化为:

W=W+ΔW=[1.02.03.04.05.06.07.08.09.010.011.012.013.014.015.016.017.018.019.020.0]+[0.410.440.470.500.931.001.071.141.451.561.671.781.972.122.272.422.492.682.873.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}
W=W+ΔW=[1.412.443.474.505.937.008.079.1410.4511.5612.6713.7814.9716.1217.2718.4219.4920.6821.8723.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}

其中ΔW 可以分解为

ΔW=AB=[0.10.20.30.40.50.60.70.80.91.0][1.11.21.31.41.51.61.71.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}
  • 矩阵 ( 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的核心思路是:与其每次都复制整个模型,不如只调整一小部分参数,把成本降下来。它的目标是解决大模型微调中的两大痛点:

  1. 资源消耗太大:大型语言模型动辄几亿甚至几千亿参数,全参数微调需要为每个新任务保存一份完整的模型副本。比如,一个10亿参数的模型,假设每个参数用4字节(float32),光存储就得4GB。多个任务下来,硬盘和显存都吃不消。

  2. 训练效率低下 :全参数微调不仅占空间,还需要大量计算资源和时间。每次训练都得更新所有参数。

LoRA的核心亮点

  1. 参数少

    • 它只微调原始参数的1%甚至更少。
      • 在GPT-3上,r = 8的LoRA参数量占全微调的0.01%-0.1%,性能却达到全微调的95%-99%。
      • 在GLUE任务(BERT),r = 16的LoRA用0.1%参数,平均得分仅比全微调低0.5-1分。
  2. 速度快

    • 训练和部署都比全参数微调省时省力。
  3. 模块化

    • 训练好的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,没有信息增量)。

S=[1002002010003002105012030]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}

2. 低秩分解的原理

奇异值分解(SVD)可以把任意矩阵分解成三个矩阵的乘积。对于一个形状 ( d * k ) 的矩阵 ( W ),SVD可以写成: SVD Formula

  • ( U ) 是 ( d * d ) 的正交矩阵
  • (Σ ) 是 ( d * k ) 的对角矩阵(奇异值按降序排列)
  • ( V^T ) 是 ( k * k ) 的正交矩阵(( V ) 的转置)

其中 ( r ) 是矩阵的秩(非零奇异值的数量)。通过保留前 ( r ) 个最大的奇异值(低秩近似),可以用更少的参数近似原矩阵 ( W )。

任意矩阵(无论是实数还是复数、方阵还是非方阵、满秩还是不满秩)都可以通过**奇异值分解(SVD)**精确拆分为三个特定矩阵的乘积

举个例子,针对上述矩阵 ( S ) 的SVD分解(计算过程略):

U[0.300.340.680.580.2200.760.20.580.700.360.3200.2200.420.460.56],Σ=[7.03000003000002.15000000.11000000],V[0.340.3200.890001000.30.9300.2200.890.1900.41000001]\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}

如果只保留前三个奇异值(奇异值),重构后的矩阵 ( S' ) 与原矩阵 ( S ) 几乎一致(三个矩阵分别取前三列,前三行&前三列,前三行):

S=[0.930.0102.0300.022.0000.9900.000.003.000.0002.051.0104.9800.951.9903.020]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}

结果对比原始矩阵和重构矩阵,直观上来看,基本保持一致,这就是说:如果只保留最大的几个奇异值,就能用更少的参数近似表示w

3. 为什么可以对增量权重 ΔW 低秩分解?

研究发现:

  1. 信息集中性:微调后的权重变化 ( ΔW) 的奇异值分布中,前10-20个奇异值占据了90%以上的信息(LoRA论文在GPT-3上的实验结论)。
  2. 结构化特性:(ΔW ) 的变化不是随机的,而是集中在少数"任务相关方向"上(例如让模型学习法律术语只需调整少量语义方向)。
  3. 高效近似:直接用低秩矩阵 ( 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 ) 冻结。

参数更新过程

  1. 初始化
    • ( W ) 使用预训练模型的权重,梯度计算被禁用(不更新)。
    • ( A ) 用小的随机高斯分布初始化
    • ( B ) 初始化为全零矩阵,确保训练开始时 ( ΔW = 0 ),避免干扰原始模型。
  2. 前向传播
    • 输入数据 ( X ) 通过调整后的权重计算输出
    • 根据任务目标 计算损失函数 ( L )(如交叉熵损失)。
  3. 反向传播
    • 计算损失 ( L ) 对 ( A ) 和 ( B ) 的梯度
    • 不计算 ( W ) 的梯度(因其被冻结)。
  4. 参数更新
    • 使用优化器(如Adam)更新 ( A ) 和 ( B ):梯度更新公式。其中 ( η ) 是学习率。
  5. 迭代优化
    • 重复步骤2-4,直到损失收敛或达到训练轮次。
    • 训练完成后,( A ) 和 ( B ) 捕捉了任务特定的调整信息。

推理部署选项

  • 合并权重:将 ( W' = W + A * B ) 合并为单一矩阵,直接用于推理(适合固定任务)。
  • 动态加载:保持 ( W ) 和 ( A * B ) 分离,灵活切换不同任务的LoRA模块(适合多任务场景)。

关键特点

  1. 参数高效:仅训练 ( A ) 和 ( B ),参数量从 ( d * k ) 降至 ( (d + k) * r )。
  2. 内存节省:无需存储全参数微调的梯度,显存占用大幅降低。
  3. 兼容性:原始模型 ( 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的层

  1. 嵌入层(Embedding)
    • 参数量大但微调收益低,冻结可节省资源。
  2. 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
复杂生成任务所有注意力矩阵 + FFN64+

模块化设计优势

  • 任务切换:不同任务可独立配置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_dropout0.0-1.0LoRA层的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
调参技巧
  1. 秩(r)的选择

    • 从小开始:优先尝试r=8或16,逐步增加直至性能饱和。
    • 数据量关联
      • 小数据集(<5K样本):r=8
      • 大数据集(>50K样本):r=32+
  2. 目标层选择策略

    # 简单任务(如分类)
    lora_target = "q_proj,v_proj"
    
    # 复杂任务(如生成)
    lora_target = "q_proj,k_proj,v_proj,o_proj,ffn.w1,ffn.w2"
    
  3. 改进技术的适用场景

    • LoRA+:训练速度要求高时启用(设lorapius_lr_ratio=8)。
    • DoRA:需要逼近全微调性能时开启(use_dora=true)。
    • rsLoRA:当r≥32时更稳定(use_rslora=true)。
参数影响对比
超参数参数量影响训练速度性能影响
lora_rank线性增加略微下降先升后平
lora_alpha无影响无影响调节强度
use_dora=true增加约10%下降10%-20%提升1%-3%
pissa_init=true无影响初始化耗时增加收敛更快
经典配置示例
# 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 博物院,获取第一时间更新!