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

身为一名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的核心思路是:与其每次都复制整个模型,不如只调整一小部分参数,把成本降下来。它的目标是解决大模型微调中的两大痛点:

  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,没有信息增量)。
<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 低秩分解?

研究发现:

  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
复杂生成任务 所有注意力矩阵 + 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
调参技巧
  1. 秩(r)的选择

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

    python 复制代码
    # 简单任务(如分类)
    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 无影响 初始化耗时增加 收敛更快
经典配置示例
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 博物院,获取第一时间更新!

相关推荐
写bug写bug28 分钟前
如何正确地对接口进行防御式编程
java·后端·代码规范
不超限1 小时前
Asp.net core 使用EntityFrame Work
后端·asp.net
豌豆花下猫1 小时前
Python 潮流周刊#105:Dify突破10万星、2025全栈开发的最佳实践
后端·python·ai
忆雾屿2 小时前
云原生时代 Kafka 深度实践:06原理剖析与源码解读
java·后端·云原生·kafka
Undoom4 小时前
🔥支付宝百宝箱新体验!途韵归旅小帮手,让高铁归途变旅行
后端
不超限4 小时前
Asp.net Core 通过依赖注入的方式获取用户
后端·asp.net
啊哈灵机一动4 小时前
Node.js 进程间通信与自定义消息的核心知识点解析
后端
pengyu4 小时前
【Java设计原则与模式之系统化精讲:零】 | 编程世界的道与术(理论篇)
java·后端·设计模式
Aurora_NeAr4 小时前
深入浅出Docker
后端