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 等算法的原理和区别,让模型学会区分"好回答"和"坏回答",进一步对齐人类偏好。

相关推荐
Dola_Zou1 小时前
以CmASIC重塑AI+边缘设备的安全与商业复利
人工智能·安全·软件工程·软件加密
蓝星空20001 小时前
GPT-image-2 模型国内免费使用教程:零门槛直出商业级 AI 大片
人工智能·gpt·aigc·image2·imagen
直接冲冲冲1 小时前
两层神经网络与三层神经网络的区别
人工智能·深度学习·神经网络
Elastic 中国社区官方博客1 小时前
4 个英伟达人工智能任务,1 个 Elasticsearch 接口:嵌入、聊天、completion 和重排序
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai
铅笔侠_小龙虾1 小时前
Rust 学习(6)-所有权规则、移动语义、Clone 与 Copy
python·学习·rust
Tom·Ge2 小时前
【AI前沿】2026年7月第三周:GPT-5.6开启递归自我改进、Claude惊现“意识结构“、中国模型周调用量全球第一
人工智能·gpt
码云骑士2 小时前
65-AI-Agent基础原理-AutoGPT-四大架构全景解读
人工智能·python·架构
染指11102 小时前
56.llama_index-查询引擎
人工智能·llama·rag·llama_index·llamaindex
chatblog2 小时前
OpenCode 完全指南:120k Stars 的开源终端 AI 编程代理
人工智能·开源·ai工具