LoRA 深挖:每个超参在干什么
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$ 可训。关键事实:
- $A$ 高斯初始化、$B$ 初始为零 → 训练开始时 $\Delta W = 0$,等价于 base 模型;
- scaling $\alpha/r$ 单独控制 update 的强度,不动 $A, B$ 本身的方差;
- 可学参数量 = $2dr$(一个 Linear),相对全参 $d \times d$ 比例 $2r/d$。
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$ 时,调 r 只改容量、不改 update 幅度;
- 固定 $\alpha = 2r$ 时,update 幅度跟 r 同步增大,效果通常更稳。
调试时建议先固定 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"(默认):bias 全冻结;"all":所有 bias 都训;"lora_only":只有 LoRA layer 自己的 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:225 | SVD-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:237 | EVA:基于数据特征值的 init |
"corda" | config.py:298 / layer.py:228 | Context-Oriented Decomposition:用 forward 激活的 SVD |
"orthogonal" | layer.py:239 | A、B 都初始化为正交矩阵 |
"该选哪个?"实战经验:
- 普通 SFT:True(默认 Kaiming)就行;
- 追求快速收敛:pissa(需要给 base 模型权重做 SVD,启动慢但训练快);
- 4bit QLoRA:loftq(必备,否则精度掉);
- 有训练数据 calibration:corda(用 forward 激活算 init,比 pissa 更贴合任务);
- 研究复现:eva / olora / orthogonal 看论文。
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,
},
)
用途:
- attention 投影对小 LoRA 更敏感,MLP 反过来 → 给 attention 高 rank、MLP 低 rank;
- AdaLoRA 用同样字段的结果,只是它训练时动态调;
- 跟
target_modules配合:先匹配 target,再用 pattern 覆盖具体 r/alpha。
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这章你需要带走的
- r 是容量旋钮,alpha 是 update 强度旋钮,默认 $\alpha = 2r$;
- target_modules 用
"all-linear"起步,扩词表时配modules_to_save; - 大 r(≥ 32)开
use_rslora=True; - 想刷 1-2% 效果开
use_dora=True(慢 30%); - QLoRA 长跑用
init_lora_weights="loftq"; - 40+ LoRA 变体本质都是"换 $\Delta W$ 的分解方式"。