【Lora微调详解】一篇文章带你搞懂Lora微调中的A、B矩阵,参数r,参数Alpha的详细解释与调参经验

这篇文章会用 “原理 + 数学 + 代码 + 直观类比 + 最佳实践” 的方式,带你彻底掌握 LoRA 中的:

AB 矩阵
r(秩)
α(alpha)
α/r 缩放比
初始化策略
其他关键参数(dropout、bias、target_modules)


一、LoRA 核心思想回顾

我们有一个大模型,其某层权重为 $ W \in \mathbb{R}^{d \times k} $(例如 4096×4096),全量微调要更新这个 WWW,成本极高。

LoRA 的想法是:不直接改 WWW,而是加一个小的低秩增量:

ΔW=B⋅A其中 A∈Rd×r, B∈Rr×k, r≪d \Delta W = B \cdot A \quad \text{其中 } A \in \mathbb{R}^{d \times r},\ B \in \mathbb{R}^{r \times k},\ r \ll d ΔW=BA其中 ARd×r, BRr×k, rd

最终前向传播变为:

h=x⋅(W+ΔW)=x⋅W+x⋅B⋅A⏟LoRA 增量 h = x \cdot (W + \Delta W) = x \cdot W + \underbrace{x \cdot B \cdot A}_{\text{LoRA 增量}} h=x(W+ΔW)=xW+LoRA 增量xBA

  • 原始 WWW冻结不动
  • A,BA, BA,B可训练的小矩阵

这就是“参数高效微调”的本质:只训练极少量参数,就能让大模型适应新任务。


二、A 和 B 矩阵详解


一句话总结(

在 LoRA 中:

  • 矩阵 A:负责“捕捉输入中的新特征”(可理解为“激活路径”)
  • 矩阵 B:负责“把这些特征映射回输出空间”(可理解为“重组输出”)

它们共同构成一个低秩增量:
ΔW=B⋅A \Delta W = B \cdot A ΔW=BA
其中 A 先学“怎么提取”,B 再学“怎么组合”,两者协同完成对原始权重的微调。


一、背景回顾:LoRA 是干嘛的?

我们有一个大模型,比如 Qwen2.5-VL-7B,它的某个线性层是:

h = x @ W   # W 是原始权重,比如 [4096 → 4096]

全参数微调要更新这个 WWW,但参数太多(千万级),显存爆炸。

LoRA 的想法是:我不动 WWW,而是加一个小的增量 ΔW\Delta WΔW

h=x@(W+ΔW) h = x @ (W + \Delta W) h=x@(W+ΔW)

而这个 ΔW\Delta WΔW 不是随便学的,它是两个小矩阵的乘积:

ΔW=B⋅A其中 A∈Rd×r, B∈Rr×k, r≪d \Delta W = B \cdot A \quad \text{其中 } A \in \mathbb{R}^{d \times r},\ B \in \mathbb{R}^{r \times k},\ r \ll d ΔW=BA其中 ARd×r, BRr×k, rd

这就是 LoRA 的核心公式。


二、A 和 B 到底长什么样?有什么区别?

我们以一个具体的例子来说明:

假设有一个 q_proj 层:

  • 输入维度 d=4096d = 4096d=4096
  • 输出维度 k=4096k = 4096k=4096
  • LoRA 秩 r=64r = 64r=64

那么:

矩阵形状作用
AAA[4096 × 64]将高维输入压缩到低维(64维“隐空间”)
BBB[64 × 4096]将低维表示扩展回原始输出空间

所以:

  • x⋅Ax \cdot AxA:把输入 xxx 投影到一个 64 维的“LoRA 特征空间”
  • (x⋅A)⋅B(x \cdot A) \cdot B(xA)B:再从这个特征空间投影回最终输出空间

A 是“降维器”(encoder),B 是“升维器”(decoder)

这就像你在做 PCA 或 AutoEncoder:

  • A:编码器(encoder)→ 压缩信息
  • B:解码器(decoder)→ 重构输出

只是这里不是为了压缩,而是为了高效地学习一个微小的增量变化


三、A 和 B 的初始化方式不同(关键区别!)

这是很多人忽略但极其重要的点!

#在 peft 库中,默认初始化是:

# 矩阵 A:从正态分布初始化
A = torch.randn(in_features, r) * std   # 例如 N(0, 0.02)

# 矩阵 B:全零初始化
B = torch.zeros(r, out_features)        # 初始为 0

为什么这么做?

因为:

  • 初始时我们希望 ΔW=B⋅A=0\Delta W = B \cdot A = 0ΔW=BA=0,这样模型行为完全由原始 WWW 决定(保持预训练能力)
  • 如果 B=0B=0B=0,不管 AAA 是什么,ΔW=0\Delta W = 0ΔW=0
  • 所以 B 初始化为 0,A 初始化为小随机数

这样做的好处是:训练开始时模型输出不变,训练稳定;随着训练进行,B 逐渐学到有效的增量


举个形象的比喻

想象你在教一个大师画画(预训练模型),他已经很厉害了。

现在你想让他学会画猫:

  • 你不能直接改他的大脑(冻结 WWW
  • 你给他戴一副“智能眼镜”(LoRA)
    • 这副眼镜有两个镜片:
      • A 镜片(前镜):观察画面,提取“耳朵、胡须”等猫的特征
      • B 镜片(后镜):把这些特征融合,告诉大脑“该怎么改笔触”

刚开始,B 镜片是透明的(B=0B=0B=0),所以不影响作画。
训练过程中,B 学会如何利用 A 提取的特征来调整输出。

A 学“看什么”,B 学“怎么用”


四、数学表达:前向传播过程

给你完整的数学流程:

原始输出:h=x⋅WLoRA 增量:Δh=x⋅(B⋅A)总输出:hnew=x⋅W+x⋅B⋅A=x⋅(W+B⋅A) \begin{align*} \text{原始输出:} &\quad h = x \cdot W \\ \text{LoRA 增量:} &\quad \Delta h = x \cdot (B \cdot A) \\ \text{总输出:} &\quad h_{\text{new}} = x \cdot W + x \cdot B \cdot A \\ &\quad = x \cdot (W + B \cdot A) \end{align*} 原始输出:LoRA 增量:总输出:h=xWΔh=x(BA)hnew=xW+xBA=x(W+BA)

注意顺序:是 x→A→Bx \to A \to BxAB,所以:

  • AAA 是第一个变换,必须能接收原始输入 xxx
  • BBB 是最后一个变换,必须能输出到原输出空间

五、代码层面:A 和 B 是怎么创建的?

peft 源码中(简化版):

class LoraLayer:
    def __init__(self, in_features, out_features, r=64):
        self.lora_A = nn.Linear(in_features, r, bias=False)
        self.lora_B = nn.Linear(r, out_features, bias=False)
        
        # 初始化
        nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5))  # 小随机数
        nn.init.zeros_(self.lora_B.weight)                            # 全零

    def forward(self, x):
        return x @ self.lora_B.weight @ self.lora_A.weight  # 注意顺序:先A后B,但矩阵乘法反过来

注意!虽然逻辑上是 x⋅A⋅Bx \cdot A \cdot BxAB,但在 PyTorch 中写成:

delta = x @ (lora_B.weight @ lora_A.weight)

因为矩阵乘法结合律:
(x⋅A)⋅B=x⋅(A⋅B)(x \cdot A) \cdot B = x \cdot (A \cdot B)(xA)B=x(AB),但 A 和 B 的权重要先相乘


六、训练时谁更重要?A 还是 B?

两者都重要,但角色不同:

维度矩阵 A矩阵 B
作用特征提取(输入侧)输出调制(输出侧)
敏感性对输入变化敏感对任务目标敏感
梯度流动接收来自上一层的梯度直接连接 loss,梯度更强
训练速度通常收敛较慢通常先更新(因靠近输出)

实践建议:

  • 不要冻结 A 或 B
  • 保持两者都可训练
  • 使用相同的优化器(如 AdamW)

七、缩放系数 αr\frac{\alpha}{r}rα 的作用

你还记得这个公式吗?

ΔW=αr⋅B⋅A \Delta W = \frac{\alpha}{r} \cdot B \cdot A ΔW=rαBA

它的作用是控制 LoRA 更新的强度

比如:

  • r=64,α=16r=64, \alpha=16r=64,α=16 → 缩放 = 0.25 → 增量较小,保守
  • r=64,α=64r=64, \alpha=64r=64,α=64 → 缩放 = 1.0 → 增量较大,激进

相当于:你让 B 的输出乘以一个系数,控制它对最终结果的影响程度

所以即使 B 学得很快,也可以用 α/r\alpha/rα/r 把它“压一压”,防止破坏原始模型能力。


八、图解:A 和 B 的数据流动

      x (输入, 4096)
       │
       ▼
   ┌─────────┐
   │   A     │   ← LoRA_A: [4096×64],提取低维特征
   │ (随机初值)│
   └─────────┘
       │
       ▼
    z (64维)
       │
       ▼
   ┌─────────┐
   │   B     │   ← LoRA_B: [64×4096],初始为0
   │ (零初始化) │
   └─────────┘
       │
       ▼
   Δh (输出增量, 4096)
       │
       ├───→ 加到原始输出 x@W 上
       ▼
   h_new = x@W + Δh

初始状态:B=0 → Δh=0 → 模型行为不变
训练中:B 逐渐非零 → Δh 生效 → 模型微调


九、常见误区澄清

误区正确认知
“A 和 B 是对称的”❌ 不对!A 是 encoder,B 是 decoder,角色不同
“可以交换 A 和 B”❌ 不行!形状不匹配,且初始化策略不同
“只训练 B 就够了”❌ 必须一起训练,A 提供特征,B 使用特征
“A 应该初始化为 0”❌ 不行!那样 ΔW\Delta WΔW 梯度也为 0,无法训练

总结:A 与 B 的核心区别表

特性矩阵 A矩阵 B
形状[in_dim × r][r × out_dim]
作用特征提取(降维)输出调制(升维)
初始化小随机数(如 N(0,0.02))全零
训练起点有梯度但变化慢初始为0,逐步激活
类比眼睛(看输入)手(改输出)
是否可省略❌ 不可❌ 不可

三、参数 r:LoRA 秩(Rank)

含义:

  • rrr 是低秩分解的“中间维度”
  • 控制 LoRA 模型的容量(表达能力)

参数量计算:

每个 LoRA 层参数数:
Params=r×(in+out) \text{Params} = r \times (\text{in} + \text{out}) Params=r×(in+out)

例如:in=4096, out=4096, r=6464×(4096+4096)=524,28864 \times (4096 + 4096) = 524,28864×(4096+4096)=524,288

如果一个模型有 40 层,每层 7 个模块(q,k,v,o,gate,up,down)→ 总 LoRA 参数 ≈ 1.47 亿

但实际上由于共享结构,通常在 100万~300万 可训练参数之间。

如何选择 r

r 值显存训练速度表达能力推荐场景
8~16极低快速实验、极小数据
32较快平衡选择
64正常多数推荐
128+接近全微调数据量大、追求 SOTA

📌 Qwen 官方推荐:r=64r=128


四、参数 α(lora_alpha):缩放系数

含义:

控制 LoRA 增量 ΔW\Delta WΔW 的强度。

最终公式是:

ΔW=αr⋅B⋅A \Delta W = \frac{\alpha}{r} \cdot B \cdot A ΔW=rαBA

所以 α\alphaα 不是独立作用,而是和 rrr 一起决定缩放比例。


为什么需要 α\alphaα

因为:

  • rrr 很小时,B⋅AB \cdot ABA 的数值范围可能太小 → 影响太弱
  • 我们想让 LoRA 的更新“力度”可控

👉 举个例子:

  • r=8,α=16r=8, \alpha=16r=8,α=16 → 缩放 = 16/8=2.016/8 = 2.016/8=2.0 → 增量被放大 2 倍
  • r=64,α=16r=64, \alpha=16r=64,α=16 → 缩放 = 16/64=0.2516/64 = 0.2516/64=0.25 → 增量被缩小

也就是说:α\alphaα 可以补偿 rrr 太小带来的表达力不足


推荐的 α/r\alpha/rα/r 比值(经验法则)

α/r\alpha/rα/r特点推荐场景
0.1 ~ 0.25保守,更新弱小数据、防止过拟合
0.5平衡多数情况推荐
1.0激进,更新强大数据、快速收敛
>2.0太强,易破坏原始知识❌ 不推荐

最佳实践:保持 α/r=0.5\alpha/r = 0.5α/r=0.5 或 1.0

例如:

  • r=64, alpha=32 → 比值 0.5 (推荐)
  • r=64, alpha=64 → 比值 1.0

建议尝试 alpha=3264,提升学习效率


五、lora_dropout=0.05:防过拟合

作用:

在 LoRA 的 A 输出上加 dropout,防止过拟合。

x → A → Dropout → B → Δh
  • 训练时启用
  • 推理时自动关闭

推荐值:

数据量dropout
<1k 样本0.1 ~ 0.3
1k~10k0.05 ~ 0.1
>10k0.0 ~ 0.05

六、bias="none":是否微调偏置项

选项:

  • "none":不训练任何 bias( 推荐)
  • "all":训练所有 bias
  • "lora_only":只训练 LoRA 层内部的 bias

为什么用 "none"

  • bias 参数量小

  • 微调 bias 效果不明显

  • 增加复杂度,容易过拟合

    标准做法就是 bias="none"


七、target_modules:哪些模块加 LoRA?

你在代码中写了:

target_modules=["q_proj", "k_proj", "v_proj", "o_proj", 
                "gate_proj", "up_proj", "down_proj"]

这是针对 Qwen 类模型(基于 Transformer) 的最佳选择。

各模块含义:

模块所属结构说明
q_projAttentionQuery 投影
k_projAttentionKey 投影
v_projAttentionValue 投影
o_projAttention输出投影
gate_proj, up_proj, down_projFFN (SwiGLU)Qwen 使用门控 FFN

Qwen 的 FFN 是 SwiGLU 结构:

FFN(x)=down_proj(Swish(gate_proj(x))⊗up_proj(x)) \text{FFN}(x) = \text{down\_proj}\left( \text{Swish}(\text{gate\_proj}(x)) \otimes \text{up\_proj}(x) \right) FFN(x)=down_proj(Swish(gate_proj(x))up_proj(x))

所以这三个都必须微调!


八、总结:LoRA 参数最佳实践表

参数推荐值说明
r(秩)64 或 128越大表达力越强,显存越高
lora_alpha2×rr保持 α/r=0.5\alpha/r = 0.5α/r=0.5~1.0
lora_dropout0.05 ~ 0.1小数据集可更高
bias"none"不微调 bias
target_modulesQ/K/V/O + Gate/Up/Down覆盖主要路径
init for Bzero确保初始 ΔW=0\Delta W = 0ΔW=0
init for A小随机数如 Kaiming Uniform

你的配置:

r=64, alpha=16, dropout=0.05, bias="none"

→ 唯一建议:alpha 改成 32 或 64,让学习更强一些。


九、你可以做的实验

实验 1:对比不同 alpha

实验ralphaα/r观察
A64160.25收敛慢?
B64320.5更快?
C64641.0是否过拟合?

实验 2:可视化 A 和 B 的权重分布

for name, param in peft_model.named_parameters():
    if "lora_A" in name and "weight" in name:
        print(f"{name}: mean={param.mean():.6f}, std={param.std():.6f}")
    if "lora_B" in name and "weight" in name:
        print(f"{name}: mean={param.mean():.6f}, std={param.std():.6f}")

你应该看到:

  • A:有非零均值
  • B:训练初期接近 0,后期逐渐非零
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值