06-LoRA参数高效微调

阶段 6:LoRA 参数高效微调

教学目标

  • 理解 LoRA(Low-Rank Adaptation)的核心原理和数学推导
  • 掌握 LoRA 的实现方式(A、B 矩阵分解)
  • 理解 LoRA 与全量微调的区别和各自适用场景
  • 能够运行 MiniMind 的原生 LoRA 训练(无需 peft 依赖)
  • 掌握 LoRA 权重合并与模型导出流程

课时安排

  • 理论讲解:1 课时(LoRA 原理 + 数学推导)
  • 实验对比:1 课时(LoRA vs 全量微调实验)

6.1 LoRA 的动机

6.1.1 全量微调的挑战

挑战 说明
显存需求大 需要存储所有参数的梯度(64M 模型需要约 0.5GB 额外显存存梯度)
训练速度慢 所有参数都需要更新
存储成本高 每个任务微调后需要保存完整的模型副本
灾难性遗忘 全量微调可能导致模型忘记预训练知识

6.1.2 LoRA 的核心假设

关键假设 :预训练模型在适配下游任务时,权重的变化量(ΔW)是低秩的。

即:虽然模型参数矩阵 W 很大,但在微调过程中的变化量 ΔW 可以用两个小矩阵的乘积近似:

plain 复制代码
ΔW = W_new - W_original ≈ B × A

其中:
  W ∈ ℝ^{d×d}      (原始权重矩阵,d=768)
  A ∈ ℝ^{d×r}      (降维矩阵,r << d)
  B ∈ ℝ^{r×d}      (升维矩阵,r << d)
  r = LoRA rank     (秩,通常 4-64)

6.1.3 LoRA 参数量对比

plain 复制代码
全量微调:
  可训练参数 = d × d = 768 × 768 = 589,824(每层一个线性层)

LoRA 微调(rank=8):
  可训练参数 = d × r + r × d = 768 × 8 + 8 × 768 = 12,288(每层)

节省比例:12,288 / 589,824 ≈ 2.1%(仅 2% 的参数需要训练!)

6.2 LoRA 数学原理

6.2.1 线性层的 LoRA 改造

原始线性层:

plain 复制代码
y = W × x
W ∈ ℝ^{out×in}

加入 LoRA 后:

plain 复制代码
y = W × x + (B × A) × x
  = (W + B × A) × x

其中:
  A ∈ ℝ^{r×in}      (初始化为随机高斯分布)
  B ∈ ℝ^{out×r}      (初始化为零矩阵)

因此,训练开始时:
  y = W × x + 0 × x = W × x   (与原始模型行为完全一致)

关键设计 :B 初始化为零,确保 LoRA 训练开始时模型行为不变,实现安全启动

6.2.2 LoRA 在 MiniMind 中的应用位置

LoRA 通常应用于 Attention 层的 Q、K、V、O 投影和 MLP 层的 W₁、W₂、W₃ 投影:

plain 复制代码
原始 Attention 层:
  Q = x × W_Q    →    Q = x × W_Q + x × (B_Q × A_Q)
  K = x × W_K    →    K = x × W_K + x × (B_K × A_K)
  V = x × W_V    →    V = x × W_V + x × (B_V × A_V)
  O = attn × W_O →    O = attn × W_O + attn × (B_O × A_O)

原始 MLP 层:
  h1 = x × W₁   →    h1 = x × W₁ + x × (B₁ × A₁)
  h2 = x × W₂   →    h2 = x × W₂ + x × (B₂ × A₂)

6.2.3 LoRA 代码实现

python 复制代码
import torch
import torch.nn as nn

class LoRALinear(nn.Module):
    def __init__(self, original_linear, rank=8, alpha=16):
        super().__init__()
        self.original = original_linear
        self.rank = rank
        self.alpha = alpha

        in_dim = original_linear.in_features
        out_dim = original_linear.out_features

        # 冻结原始权重
        self.original.weight.requires_grad = False

        # LoRA 矩阵
        self.lora_A = nn.Parameter(torch.randn(rank, in_dim) * 0.01)
        self.lora_B = nn.Parameter(torch.zeros(out_dim, rank))

        # 缩放因子
        self.scaling = alpha / rank

    def forward(self, x):
        # 原始输出 + LoRA 输出
        original_output = self.original(x)
        lora_output = (x @ self.lora_A.T @ self.lora_B.T) * self.scaling
        return original_output + lora_output

6.2.4 缩放因子(Scaling Factor)

plain 复制代码
LoRA 输出 = (B × A) × x × (α / r)

其中:
  α (alpha) = 缩放超参数(通常设为 rank 的 1-2 倍)
  r (rank) = LoRA 秩

作用:
  当增大 rank 时,B×A 的"原始幅度"也会增大。
  通过 α/r 的缩放,保持 LoRA 贡献的相对稳定。
  这样调整 rank 时不需要重新调整学习率。

6.3 MiniMind 的 LoRA 实现

6.3.1 原生实现(无 peft 依赖)

MiniMind 从零实现 LoRA ,不依赖 HuggingFace 的 peft 库。这样做的好处:

  • 代码透明,学生可以完全理解每一步
  • 无额外依赖
  • 与 MiniMind 的架构完美适配

6.3.2 LoRA 配置参数

参数 默认值 说明
lora_rank 8 LoRA 秩
lora_alpha 16 缩放因子
lora_dropout 0.05 LoRA Dropout
lora_target_modules "wq", "wk", "wv", "wo", "w1", "w2", "w3" 应用 LoRA 的模块

6.3.3 运行 LoRA 微调

bash 复制代码
cd minimind/trainer

# 从 SFT 模型开始 LoRA 微调
python train_lora.py

# 指定 LoRA rank
python train_lora.py --lora_rank 16

# 指定 LoRA alpha
python train_lora.py --lora_alpha 32

6.3.4 LoRA 训练输出

plain 复制代码
out/
├── full_sft_768.pth              # 基础模型权重(冻结)
├── lora_pretrain_768.pth         # LoRA 适配器权重(仅 A、B 矩阵)
└── ...

LoRA 权重文件非常小(仅包含 A、B 矩阵参数):

plain 复制代码
LoRA 权重大小 ≈ num_layers × num_targets × 2 × rank × dim
             ≈ 8 × 7 × 2 × 8 × 768
             ≈ 688KB

对比全量模型:64M × 4 bytes ≈ 256MB
节省:256MB / 688KB ≈ 370 倍!

6.4 LoRA 权重合并

6.4.1 为什么需要合并?

训练好的 LoRA 适配器是独立于基础模型的,推理时需要分别加载。为了部署方便,可以将 LoRA 权重合并回基础模型。

6.4.2 合并过程

plain 复制代码
合并前:
  推理: y = W × x + (B × A) × x    (需要分别加载 W 和 B、A)

合并后:
  W_merged = W + B × A
  推理: y = W_merged × x           (只需加载一个权重矩阵)

6.4.3 运行权重合并

bash 复制代码
cd minimind/scripts

# 合并 LoRA 权重到基础模型
python convert_model.py

# 输出: out/merged_model_768.pth(包含合并后的完整权重)

6.4.4 模型格式转换

MiniMind 支持在多种格式间转换:

bash 复制代码
cd scripts

# torch 格式 → transformers 格式(可被 vLLM、ollama 使用)
python convert_model.py --format transformers --input_dir ../minimind-3

# transformers 格式 → GGUF 格式(可被 llama.cpp 使用)
# 需要安装 llama-cpp-python 或使用 llama.cpp 的 convert脚本

6.5 LoRA vs 全量微调对比实验

6.5.1 实验设计

对比维度 全量微调 LoRA 微调
可训练参数 64M(全部) 1.4M(2%)
训练速度 基准 更快(反向传播计算量小)
显存占用 基准 更低(不需存储全部梯度)
效果 基准 通常接近(rank 足够时)
存储成本 每任务 256MB 每任务 ~1MB

6.5.2 实验步骤

bash 复制代码
# 实验1: 全量微调(已在阶段5完成)
python train_full_sft.py --max_epochs 5

# 实验2: LoRA rank=4
python train_lora.py --lora_rank 4

# 实验3: LoRA rank=8
python train_lora.py --lora_rank 8

# 实验4: LoRA rank=16
python train_lora.py --lora_rank 16

# 实验5: LoRA rank=32
python train_lora.py --lora_rank 32

# 对比各组在测试集上的 Loss 和回答质量

6.5.3 预期结果

plain 复制代码
效果趋势(通常):
  全量微调 > LoRA(r=32) ≈ LoRA(r=16) > LoRA(r=8) > LoRA(r=4)

但当 r ≥ 16 时,LoRA 效果通常非常接近全量微调。

6.6 LoRA 变体与扩展知识

6.6.1 常见 LoRA 变体

变体 核心思想 与标准 LoRA 的区别
QLoRA 量化 + LoRA 将基础模型量化为 4-bit,LoRA 部分保持 bf16
LoRA+ 自适应学习率 为 A 和 B 设置不同的学习率
DoRA 权重分解 将权重分解为方向和幅度,仅对方向做 LoRA
VeRA 共享随机矩阵 多个 LoRA 层共享相同的 A、B 随机矩阵

6.6.2 如何选择 LoRA rank?

rank 参数量 适用场景
1-4 极少 简单任务(如风格迁移)
8-16 较少 通用微调(推荐起点)
32-64 中等 复杂任务(如多轮对话、推理)
128+ 较多 接近全量微调的效果

经验法则:从 rank=8 开始,如果效果不够好,逐步增大到 16、32。


实践任务

任务 6.1:运行 LoRA 微调(必做)

  1. 使用 SFT 模型作为基础,运行 LoRA 微调
  2. 记录训练 Loss 曲线
  3. 合并 LoRA 权重
  4. 使用合并后的模型进行对话测试
bash 复制代码
# 完整流程
cd trainer && python train_lora.py --lora_rank 8
cd ../scripts && python convert_model.py
cd .. && python eval_llm.py --load_from ./out/merged_model_768.pth

任务 6.2:LoRA rank 对比实验(必做)

rank 训练 Loss 测试集回答质量 参数量 训练时间
4
8
16
32

思考题

  • rank 增大时,Loss 是否一定更低?有没有边际效应递减的点?
  • LoRA 模型在哪些类型的问题上表现与全量微调差距最大?

任务 6.3:LoRA 权重分析(选做)

python 复制代码
# 分析 LoRA 矩阵的奇异值分布
import torch

# 加载 LoRA 权重
lora_weights = torch.load('out/lora_pretrain_768.pth')

# 对每层的 A、B 矩阵做 SVD
for name, param in lora_weights.items():
    if 'lora_A' in name or 'lora_B' in name:
        U, S, Vh = torch.linalg.svd(param)
        print(f"{name}: top-3 singular values = {S[:3].tolist()}")

# 观察奇异值分布,判断 LoRA 是否"充分利用"了其秩容量

挑战任务(选做)

  • 实现一个简化版的 QLoRA(4-bit 量化 + LoRA)
  • 对比 LoRA 应用于不同层(仅 Attention vs Attention+MLP)的效果差异

下一阶段预告

在阶段 7 中,我们将学习**强化学习对齐(RLHF/RLAIF)**技术。理解 DPO、PPO、GRPO、CISPO 等算法的原理和区别,让模型学会区分"好回答"和"坏回答",进一步对齐人类偏好。

相关推荐
知几蜗牛3 分钟前
本地语音AI不等于零风险:VoiceStudio最值得看的三条边界
人工智能
知几蜗牛3 分钟前
Claude接入十余种金融系统后,真正稀缺的是可追溯的审批链
人工智能
天远API10 分钟前
零信任架构实战:基于天远单人婚姻查询构建自动化联合贷款审计网关
java·人工智能·架构·自动化
远航计算机10 分钟前
网站被 AI 收录要多久?从被抓取到被引用的完整时间表
大数据·人工智能·aigc
昨日之日200613 分钟前
AuK:多任务语音生成编辑,支持克隆、改词、换情绪与分离,内容编辑与增强全覆盖
人工智能·音视频
知几蜗牛20 分钟前
4万星的Agent技能提醒我们:答案太全也可能不可用
人工智能
智购科技无人售货机工厂20 分钟前
2026自动售货机AI智能体架构:从47个小模型到33.7亿Token的工程实践~YH
android·人工智能·驱动开发·单片机·pandas
Geek-Chow22 分钟前
09. LLM 接缝:把“厂商协议“关进一个可替换的盒子
人工智能
麻瓜code22 分钟前
【Agent】从 0 手写智能体框架:ReAct + ToolCall 分层设计
人工智能·spring
来了就不要走27 分钟前
亿数平台怎么样:亿象生态与数实融合路径观察
大数据·人工智能·区块链