Chapter 03

LoRA 原理:低秩矩阵分解

LoRA 让微调 7B 模型只需 2GB 显存。理解它的数学原理,才能正确设置超参数,而不是靠猜。

全参数微调的问题

微调一个 7B 参数的模型,如果更新全部参数:

LoRA(Low-Rank Adaptation,Hu et al. 2021)提供了一个优雅的解决方案:不改变原始权重,只训练两个小矩阵

低秩分解的数学直觉

假设我们要更新一个权重矩阵 W ∈ ℝ^(d×k)。全参数微调会直接更新这个矩阵。LoRA 的做法是:

全参数微调: W_new = W + ΔW 其中 ΔW ∈ ℝ^(d×k),参数量 = d × k LoRA 微调: W_new = W + ΔW = W + B × A 其中 A ∈ ℝ^(r×k),B ∈ ℝ^(d×r),r << min(d,k) 参数量 = r×k + d×r = r(d+k) << d×k 示例(d=4096, k=4096, r=16): 全参数:4096 × 4096 = 16,777,216 参数 LoRA: 16 × (4096+4096) = 131,072 参数 节省比例:99.2%!
低秩假设(Low-Rank Hypothesis)
论文中的关键假设:预训练模型已经具备了大量知识,微调时的权重变化 ΔW 内在维度很低(低秩的)。这意味着用低秩矩阵来近似 ΔW 是合理的。
初始化策略
训练开始时,A 用随机高斯初始化,B 用零初始化。这确保了初始的 ΔW = B×A = 0,即训练开始时 LoRA 对模型没有任何影响,从原始预训练权重出发。
缩放因子 α/r
实际应用中,ΔW 乘以缩放因子 α/r。这个设计让 α 成为一个类似学习率的超参,便于在不同 rank 之间迁移超参设置。

推理时的合并

训练完成后,LoRA 权重可以合并进原始权重,推理时没有额外开销:

# 训练时:W_effective = W + (alpha/r) * B @ A
# 推理前合并:
W_merged = W + (alpha / r) * B @ A
# 合并后的模型与原始模型推理速度完全相同

关键超参数详解

rank(r)— 最重要的超参

rank 决定了 LoRA 矩阵的"容量":

rank 值参数量(7B 模型)适用场景
r = 4~3M简单格式/风格调整
r = 8~6M多数指令微调任务(默认推荐)
r = 16~13M专业领域知识注入
r = 64~52M复杂任务,接近全参数效果
r = 128+~104M+极少需要,通常过拟合

alpha(α)— 缩放学习率

经验法则:alpha = 2 × rank。固定 alpha = 16(不随 rank 变化)也是常见做法。如果你不确定,先用 alpha = rank

target_modules — 应用到哪些层

from peft import LoraConfig

# 保守选择(只有 attention 的 q/v 投影)
config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],  # 原始 LoRA 论文做法
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# 激进选择(所有线性层,效果通常更好)
config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",  # attention
        "gate_proj", "up_proj", "down_proj"     # FFN
    ],
    lora_dropout=0.0,
    bias="none",
    task_type="CAUSAL_LM"
)
target_modules 选择经验 对于大多数任务,同时训练 attention 和 FFN 层(7个模块)比只训练 q/v 效果好 10-20%。代价是参数量增加约 3.5 倍,显存增加约 1GB。

完整 LoRA 训练代码

from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer

# 1. 加载基座模型
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Meta-Llama-3-8B-Instruct",
    torch_dtype="float16",
    device_map="auto"
)

# 2. 应用 LoRA 配置
lora_config = LoraConfig(
    r=16, lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 41,943,040 || all params: 8,072,495,104 || trainable%: 0.52%

# 3. 训练
trainer = SFTTrainer(
    model=model,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    args=TrainingArguments(
        output_dir="./lora-output",
        num_train_epochs=3,
        per_device_train_batch_size=4,
        gradient_accumulation_steps=4,
        learning_rate=2e-4,
        warmup_ratio=0.05,
        lr_scheduler_type="cosine",
        logging_steps=10,
        evaluation_strategy="steps",
        eval_steps=100,
        save_steps=500,
    )
)
trainer.train()
本章小结 LoRA 通过低秩分解把微调参数量从亿级降到百万级。关键超参:r 控制容量(推荐 8-16),alpha 控制缩放(推荐 = r 或 2r),target_modules 覆盖所有线性层效果最好。下一章引入 4-bit 量化,进一步降低显存。