Chapter 05

LoRA 深挖:每个超参在干什么

📌 commit a106ff4c7061 r / alpha / target_modules / init_lora_weights / rsLoRA / DoRA 全面拆解

LoRA 看似只有 r 和 alpha 两个超参,实际 LoraConfig 里有 25+ 个字段。 本章按"对效果影响"的顺序排列,告诉你每个开关在调什么、什么时候该动它。

5.1LoRA 公式回顾

$$h = W_0 x + \Delta W \cdot x,\quad \Delta W = \frac{\alpha}{r}\, B A,\quad B\in\mathbb R^{d\times r},\;A\in\mathbb R^{r\times d}$$

$W_0$ 冻结、$A, B$ 可训。关键事实

5.2r(rank)

$r$ 直接决定容量。经验起点:

场景推荐 r典型 trainable%
风格调整、轻微微调 4–8 0.05–0.1%
通用 SFT(指令跟随) 8–16 0.1–0.3%
领域增强(医疗 / 法律 / 代码) 16–32 0.5–1%
多任务 / 多语言增强 32–64 1–2%
追求接近全参效果 64–128 2–5%

r 翻倍后参数量翻倍、显存翻倍,但边际效果递减。一般 r=16 是甜点。

5.3lora_alpha 和 scaling

scaling = $\alpha/r$。LoRA 论文设 $\alpha = r$,即 scaling = 1;社区惯例 $\alpha = 2r$,即 scaling = 2。理由:

调试时建议先固定 alpha = 2r,调 r 找甜点,最后再微调 alpha。

rsLoRA:让 scaling 更稳

大 r(> 32)时 $\alpha/r$ 会很小,update 信号减弱。rsLoRA 改成 $\alpha / \sqrt{r}$:

$$\Delta W = \frac{\alpha}{\sqrt{r}}\, BA$$

启用:LoraConfig(use_rslora=True, ...)。当 r > 16 时建议开。

5.4target_modules

选哪些 layer 上 LoRA 是最影响效果的参数。常见配方:

策略配置trainable%效果
QKV 三件套 ["q_proj","v_proj"] ~0.1% 论文原版,最小
QKV + O ["q_proj","k_proj","v_proj","o_proj"]~0.2%稍好
QKV + MLP + gate_proj/up_proj/down_proj~0.5%常见 SFT
全部 Linear "all-linear" ~1% 效果最好
+ embedding + modules_to_save=["embed_tokens","lm_head"]视词表扩词表必加

实战建议:SFT 用 "all-linear" 起步;嫌慢再砍 MLP。

modules_to_save

这些模块不上 LoRA,但仍然可训且会被保存。典型场景:扩了词表后让 embedding/lm_head 全参微调。

LoraConfig(
    target_modules="all-linear",
    modules_to_save=["embed_tokens", "lm_head"],
)

5.5init_lora_weights

LoraConfig.init_lora_weights

取值初始化方式用途
True(默认)$A$ 用 Kaiming uniform、$B$ 零大多数场景
"gaussian"$A$ 高斯、$B$ 零 等价 PyTorch 默认
False $A, B$ 都随机非零 谨慎:起点 $\Delta W \ne 0$
"loftq" LoftQ 初始化(用 SVD 近似量化误差)QLoRA 长跑场景
"pissa" 用 base weight 的 top-r 奇异向量初始化 BA快速收敛
"pissa_niter_N"PiSSA 但用 niter 步 randomized SVD(更快)大模型上 PiSSA 必备
"olora" OLoRA:用 QR 分解的正交矩阵初始化正交方向训练
"eva" EVA:用激活的 PCA 初始化需先跑 calibration

5.6lora_dropout

对 LoRA 的 input 做 dropout,防止过拟合。默认 0.0,SFT 数据少时设 0.05–0.1。位置:

$$h = W_0 x + (\alpha/r) B A\,\text{Dropout}(x)$$

注意 dropout 在 input 上而非 output——这样不会破坏 B 零初始化的对称性。

5.7bias 怎么处理

LoraConfig.bias 三档:

通常 "none" 够用;调不上时可尝试 "all"

5.8DoRA:把 magnitude 和 direction 分开

DoRA(Weight-Decomposed LoRA)把 $W = m \cdot \hat W$ 拆成方向 $\hat W$(单位列向量)和模长 $m$。LoRA 只更新方向:

$$W' = m \cdot \frac{W_0 + (\alpha/r) BA}{\lVert W_0 + (\alpha/r) BA \rVert_c}$$

启用:LoraConfig(use_dora=True, ...)。比 LoRA 多约 0.01% 参数(每列一个 m),但效果常比 LoRA 强 1-2%。代价:训练慢 30%。

实现:src/peft/tuners/lora/dora.py

5.9训练时机:什么时候用什么变体

场景推荐 LoRA 变体
第一次跑、想稳 原版 LoRA(r=16, alpha=32)
已稳,追加 1-2% 效果 DoRA
r 需要 ≥ 64 rsLoRA
QLoRA 长跑(数千 step) LoftQ 初始化
想快速收敛 PiSSA 初始化
有 calibration 数据 EVA 初始化

5.10对照其他低秩方法

LoRA 是基线,PEFT 还内置了一堆"分解方式不同"的变体(具体细节看后续章节):

方法分解路径
LoRA $\Delta W = BA$(rank-r) tuners/lora/
LoHa $\Delta W = (B_1 A_1) \odot (B_2 A_2)$(Hadamard) tuners/loha/
LoKr $\Delta W = B \otimes A$(Kronecker) tuners/lokr/
OFT $W' = R W_0$,$R$ 是正交矩阵(保结构) tuners/oft/
BOFT OFT 的 butterfly 分解版(更省参数) tuners/boft/
HRA Householder 反射 tuners/hra/
VeRA 共享 A, B 矩阵,只学缩放 vector tuners/vera/
FourierFT用 FFT 频域的稀疏系数表达 $\Delta W$ tuners/fourierft/

实战中 95% 用 LoRA / DoRA / rsLoRA 就够了,其他变体多是论文实验。

5.11init_lora_weights 10 种选项详解

LoraConfig 的 init_lora_weights 字段(tuners/lora/config.py:411–470)不只是 True/False。完整可选值:

取值实现位置含义
True(默认) layer.py:225 Kaiming uniform A + 零 B;训练前等价于 base
False 随机 A 和 B;仅 debug,训练前 base 行为已坏
"gaussian" A ~ N(0, 1/r) + 零 B
"pissa" config.py:421 / layer.py:225SVD-based init:A B 取 base 权重 top-r 奇异分量(2-4× 收敛)
"pissa_niter_[N]" fast-SVD PiSSA,niter_4 / 8 / 16 控制精度
"loftq" config.py:461 / layer.py:234量化感知 init,4bit base 不掉精度(见 ch03.10)
"olora" layer.py:231 正交 LoRA:A 与 base 行向量正交
"eva" config.py:465 / layer.py:237EVA:基于数据特征值的 init
"corda" config.py:298 / layer.py:228Context-Oriented Decomposition:用 forward 激活的 SVD
"orthogonal" layer.py:239 A、B 都初始化为正交矩阵

"该选哪个?"实战经验:

5.12rank_pattern / alpha_pattern:每层不同 rank

不是所有层都需要 r=16。LoraConfig 提供两个 dict 字段(config.py:441–446)实现层级异构 adapter:

cfg = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules="all-linear",
    rank_pattern={                       # ★ 正则匹配层名 → 自定义 r
        ".*q_proj": 32,                  # query 用大 rank
        ".*v_proj": 32,                  # value 用大 rank
        ".*\.mlp\..*": 4,                # MLP 用小 rank(参数量大,rank 小省内存)
    },
    alpha_pattern={                       # ★ 同样可以差异化 alpha
        ".*q_proj": 64,
    },
)

用途:

5.13layers_to_transform + layers_pattern

"我只想动后 N 层" → LoraConfig.layers_to_transform

cfg = LoraConfig(
    r=16,
    target_modules="all-linear",
    layers_to_transform=[28, 29, 30, 31],    # ★ 只在第 28-31 层加 LoRA
    layers_pattern=None,                       # 自动检测 layer 命名规律
)

"模型里怎么判断'第 i 层'?" → layers_pattern 是 layer 命名的正则(如 model\.layers\.(\d+)),不填则自动检测。这个字段配合 freeze_trainable_layers 风格的 freeze 微调用很方便(见 LLaMA-Factory ch06.13)。

5.14这章你需要带走的