← blog AI Agent · 2026-04-08

LLM Fine-Tuning

深入解析 LLM 微调技术,涵盖 SFT 监督微调和 LoRA 低秩适配两大核心方法,从数学原理到实践代码全面讲解。

25 min read

LLM Fine-Tuning 全面指南:SFT 与 LoRA 详解

引言

大语言模型(Large Language Model,LLM)的崛起标志着人工智能领域的一个转折点。以 GPT、LLaMA、Claude 为代表的预训练语言模型,通过在海量文本数据上进行自监督学习,获得了惊人的语言理解和生成能力。然而,预训练模型如同未经雕琢的璞玉,虽然掌握了语言的统计规律,却缺乏明确的任务导向和人类偏好对齐能力。

**微调(Fine-Tuning)**正是将预训练模型转化为实际应用的关键技术。微调是指在预训练模型的基础上,使用特定领域或任务的数据进行进一步训练,使模型获得执行特定任务的能力。根据是否更新全部参数,微调可分为全参数微调(Full Parameter Fine-Tuning)和参数高效微调(Parameter-Efficient Fine-Tuning,PEFT)两大类。

本文将深入探讨两种最重要的微调技术:SFT(监督微调)和LoRA(低秩适配),从理论原理到实践细节进行全面解析。


第一部分:SFT(监督微调)

1.1 什么是 SFT

**SFT(Supervised Fine-Tuning,监督微调)**是一种传统的模型微调方法,通过在标注数据上进行监督学习,使预训练模型适应特定任务。在 SFT 阶段,模型学习的是输入与输出之间的映射关系,这与预训练阶段的语言建模任务有本质区别。

预训练阶段,模型学习的是”给定前文,预测下一个token”,这是一种自监督学习范式,不需要人工标注。而 SFT 阶段则需要人类标注的问答对或多轮对话数据,模型学习的是”给定指令和问题,生成符合人类期望的回答”。

SFT 的核心目标是:

  1. 任务适配:让模型学会执行特定任务(如问答、摘要、翻译)
  2. 格式对齐:让模型学习符合人类期望的输出格式
  3. 能力激发:激活预训练模型中已经存在但未被充分激发的能力

1.2 SFT 与预训练的关系

理解 SFT 与预训练的关系对于把握微调的本质至关重要。

graph TD
    subgraph Stage["模型训练阶段对比"]
        P["预训练阶段<br/>语料:互联网大规模文本(数十亿到万亿token)<br/>任务:Next Token Prediction<br/>目标:学习通用语言知识和世界知识<br/>特点:自监督学习,不需要人工标注"]
        S["SFT 阶段<br/>语料:人类标注的指令-响应对(数千到数万条)<br/>任务:给定指令,生成期望回答<br/>目标:任务适配、格式对齐、能力激发<br/>特点:监督学习,需要高质量人工标注"]
        R["RLHF 阶段(可选)<br/>语料:人类偏好排序数据<br/>任务:学习人类偏好,优化生成质量<br/>目标:与人类价值观对齐 (Helpful, Honest, Harmless)<br/>特点:强化学习,结合奖励模型"]
    end
    P --> S --> R

关键认知:

  • 预训练模型已经包含了大量知识,SFT 的目的不是从头教授知识,而是激活和对齐
  • SFT 数据量虽小,但质量要求极高
  • SFT 可以看作是一种”教会模型遵循指令”的过程

1.3 SFT 数据格式

SFT 训练数据主要有两种格式:指令格式和对话格式。

1.3.1 指令格式(Instruction Format)

指令格式是最常用的 SFT 数据格式,包含三个核心组成部分:

┌─────────────────────────────────────────────────────────────┐
│                    指令格式数据示例                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入(Instruction):请将以下英文翻译成中文                 │
│                                                             │
│  输入(Input):The quick brown fox jumps over the lazy dog │
│                                                             │
│  输出(Output):敏捷的棕色狐狸跳过了懒惰的狗                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

完整的训练样本构建方式:

{
  "instruction": "请将以下英文翻译成中文",
  "input": "The quick brown fox jumps over the lazy dog",
  "output": "敏捷的棕色狐狸跳过了懒惰的狗"
}

在训练时,模型看到的序列是:

[INST] 请将以下英文翻译成中文
The quick brown fox jumps over the lazy dog
[/INST] 敏捷的棕色狐狸跳过了懒惰的狗

1.3.2 对话格式(Chat Format)

对话格式用于多轮对话场景,数据结构为消息列表:

{
  "messages": [
    {"role": "user", "content": "什么是大语言模型?"},
    {"role": "assistant", "content": "大语言模型是一类使用深度学习技术..."},
    {"role": "user", "content": "它和传统NLP模型有什么区别?"},
    {"role": "assistant", "content": "传统NLP模型通常是任务特定的..."}
  ]
}

常见的对话格式包括:

格式特殊标记应用场景
ChatML<|im_start|> / <|im_end|>Qwen、Yi
Llama 3[INST] / [/INST]Llama 3
Claude\n\nHuman: / \n\nAssistant:Claude
GPT-4system / user / assistantOpenAI API
# ChatML 格式示例
messages = [
    {"role": "system", "content": "你是一个有帮助的AI助手"},
    {"role": "user", "content": "你好,请介绍一下自己"},
    {"role": "assistant", "content": "你好!我是..."}
]

# 转换为训练文本
text = ""
for msg in messages:
    text += f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>\n"

1.4 SFT 训练过程与损失计算

1.4.1 训练流程

graph TD
    A["预训练模型"]
    B["添加特殊标记"]
    C["计算 Loss / 反向传播 / 更新权重"]
    D["微调模型"]
    A -- "加载预训练权重作为初始化" --> B
    B -- "添加对话格式需要的特殊token" --> C
    C -- "仅在 output 位置计算交叉熵损失" --> D

1.4.2 损失计算

SFT 采用标准的语言建模损失函数。给定训练样本 $(x_1, x_2, …, x_n)$,模型学习预测每个 token $x_t$($t > 1$),损失为负对数似然:

$$ \mathcal{L}{SFT} = -\sum{t=2}^{n} \log P_{\theta}(x_t | x_1, x_2, …, x_{t-1}) $$

关键点:在 SFT 训练中,我们仅在 output 区域计算损失,instruction 和 input 部分不参与损失计算。这是为了让模型学习的是”根据指令和问题生成回答”,而不是”根据回答生成回答”。

# SFT 损失计算示意
def compute_sft_loss(model, input_ids, attention_mask, labels):
    """
    Args:
        input_ids: 完整的输入序列token IDs
        labels: 与input_ids相同的序列,但在instruction位置被设置为-100(忽略)
    """
    outputs = model(
        input_ids=input_ids,
        attention_mask=attention_mask
    )
    
    # 仅计算 output 位置的损失(labels中非-100的位置)
    loss = outputs.loss
    
    return loss

# labels 构建示例
# input:  [INST] 指令 [INST] 回答开始...
# label:  [-100, -100, ..., -100, 输出token_ids, -100...]

#        instruction 部分     output 部分
#        ↓                   ↓
# labels = [-100, -100, ..., 1234, 5678, 9012, ...]

1.5 关键超参数

SFT 训练中有几个至关重要的超参数,需要根据数据规模和任务特点进行调优。

1.5.1 学习率(Learning Rate)

模型规模推荐学习率说明
7B 模型1e-5 ~ 2e-5较小模型需要相对较高的学习率
13B 模型5e-6 ~ 1e-5中等规模模型
70B 模型1e-6 ~ 5e-6大模型需要较小学习率
100B+ 模型5e-7 ~ 2e-6超大模型

学习率调度策略:

┌─────────────────────────────────────────────────────────────────┐
│                    学习率调度曲线                                │
│                                                                 │
│  lr                                                                │
│   │                                                                │
│   │         ┌──────────────┐                                     │
│   │         │              │                                     │
│   │         │    峰值学习率  │                                     │
│   │         │   (peak lr)  │                                     │
│   │         │              │                                     │
│   │─────────┘              └────────────                           │
│   │                                                        ────── │
│   │                                                    ──          │
│   │                                              ──                 │
│   │                                        ──                      │
│   │                                  ──                            │
│   └──────────────────────────────────────────────────────────→    │
│                       训练步数/迭代次数                              │
│                                                                 │
│   warmup    plateau (cosine decay)                               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

典型的学习率调度:

  • Warmup:前 1-3% 的步数从很小的学习率逐渐增加到峰值
  • Cosine Decay:之后余弦退火到最小学习率
  • Linear Decay:线性下降到最小学习率

1.5.2 训练轮数(Epochs)

数据规模推荐 Epochs说明
< 1万条3-10小数据集需要更多轮数以充分学习
1-10万条2-5中等规模数据
> 10万条1-3大规模数据,避免过拟合

警惕过拟合:SFT 阶段模型收敛很快,当验证损失开始上升时,应立即停止训练。

1.5.3 数据量与模型能力

┌─────────────────────────────────────────────────────────────────┐
│              数据量与模型能力关系示意图                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  能力水平                                                         │
│     │                                                            │
│     │                          ╭─── 最终能力上限                   │
│     │                      ╭───╯                                 │
│     │                  ╭───╯                                     │
│     │              ╭───╯                                         │
│     │          ╭───╯                                             │
│     │      ╭───╯        ←── 小数据集也能达到较高能力               │
│     │  ╭───╯                    但可能欠拟合某些模式              │
│     │──╯                                                        │
│     └────────────────────────────────────────────────────────→  │
│                         数据量                                    │
│                                                                 │
│     低质量大数据   ←————————→   高质量小数据                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

核心洞见:对于 SFT 而言,数据质量比数据数量更重要。1000条高质量标注数据往往比10000条低质量数据更有价值。

1.6 数据质量的重要性

SFT 的成功在很大程度上取决于训练数据的质量。低质量数据不仅无法提升模型性能,反而可能损害模型能力。

1.6.1 优质 SFT 数据的特征

特征描述重要性
准确性回答内容正确无误★★★★★
相关性回答与指令高度相关★★★★★
完整性回答覆盖问题的所有方面★★★★☆
清晰性表达清晰,逻辑连贯★★★★☆
多样性覆盖不同任务类型和表达方式★★★☆☆
格式一致性遵循统一的输出格式规范★★★☆☆

1.6.2 常见数据质量问题

问题类型负面影响解决方案
错误答案模型学到错误知识多轮质量审核
指令不匹配格式学习混乱重新标注或过滤
回答过于简短能力激发不足要求更详细的回答
风格不一致输出不稳定制定标注规范手册
领域偏差过大预训练知识遗忘混合通用数据

1.6.3 灾难性遗忘(Catastrophic Forgetting)

灾难性遗忘是 SFT 阶段面临的一个重要问题,指的是模型在学习新任务时,遗忘预训练阶段获得的知识和能力。

┌─────────────────────────────────────────────────────────────────┐
│                  灾难性遗忘示意图                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  能力水平                                                         │
│     │                                                            │
│     │     ╭───────────── 原始预训练能力                            │
│     │    ╱                                              ╲        │
│     │───╱                                                ╲───     │
│     │  ╱              ← 遗忘预训练知识                      ╲    │
│     │ ╱                                                    ╲    │
│     │╱                                                        ╲   │
│     └──────────────────────────────────────────────────────────→ │
│                         训练进程                                  │
│                                                                 │
│     [预训练知识]          [SFT学习]          [能力状态]           │
│         ↓                    ↓                  ↓              │
│      保留完整            学习新任务         部分知识丢失          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

应对策略:

  1. 混合预训练数据:在 SFT 数据中混入一定比例(通常 5-10%)的预训练语料
  2. 降低学习率:使用较预训练更小的学习率
  3. 早停法:监控验证集损失,及时停止
  4. 正则化:对权重更新施加约束
# 混合预训练数据的训练示例
def create_sft_data_loader(sft_data, pretrain_data, mix_ratio=0.1):
    """
    Args:
        sft_data: SFT 训练数据
        pretrain_data: 预训练语料数据
        mix_ratio: 预训练数据混合比例
    """
    # 每 N 条 SFT 数据中混入 1 条预训练数据
    combined = []
    for i, sft_sample in enumerate(sft_data):
        combined.append(sft_sample)
        if (i + 1) % int(1 / mix_ratio) == 0 and pretrain_data:
            combined.append(pretrain_data[len(combined) % len(pretrain_data)])
    return combined

1.7 SFT 与 RLHF 的关系

SFT 和 RLHF 是 LLM 对齐训练的两个关键阶段,它们既有联系又有区别。

graph TD
    P["预训练模型"]
    S["SFT<br/>学习格式和基本任务完成能力"]
    R["RM<br/>训练奖励模型学习人类偏好"]
    PP["PPO<br/>强化学习优化策略,最大化人类偏好"]
    A["对齐模型"]
    P --> S --> R --> PP --> A

SFT 的局限性:

  1. 依赖标注质量:SFT 的能力上限受限于标注者的水平和一致性
  2. 缺乏偏好学习:无法学习到”哪个回答更好”,只能学习”什么是正确的”
  3. 奖励信号单一:只有关键token的奖励,无法捕捉整体质量差异

RLHF 的优势:

  1. 偏好学习:通过对比学习理解人类偏好
  2. 连续奖励:为每个回答提供细粒度的质量信号
  3. 避免机械性:生成的回答更自然、更符合人类表达习惯

实践中的权衡:

方面SFTRLHF
计算成本较低高(需要训练RM和PPO)
数据需求需要高质量标注需要偏好排序数据
训练稳定性稳定复杂(需要KL约束)
最终效果良好更好(但收益递减)
适用场景资源受限、任务明确追求最优对齐效果

第二部分:LoRA(低秩适配)

2.1 LoRA 背景与动机

2.1.1 全参数微调的困境

随着 LLM 规模的爆发式增长(从7B到70B甚至1000B参数),全参数微调面临前所未有的挑战:

┌─────────────────────────────────────────────────────────────────┐
│                  全参数微调的三大困境                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 显存困境(VRAM Bottleneck)                                   │
│     ┌────────────────────────────────────────────────────────┐   │
│     │  7B 模型:fp16 精度下全参数训练需要 ~14GB VRAM          │   │
│     │  13B 模型:fp16 精度下全参数训练需要 ~26GB VRAM        │   │
│     │  70B 模型:fp16 精度下全参数训练需要 ~140GB VRAM       │   │
│     │  100B+ 模型:需要分布式多卡训练,硬件要求极高           │   │
│     └────────────────────────────────────────────────────────┘   │
│                                                                 │
│  2. 存储困境(Storage Bottleneck)                                │
│     每个任务需要保存一份完整模型权重                              │
│     N 个任务 = N × 模型大小的存储空间                            │
│                                                                 │
│  3. 效率困境(Efficiency Bottleneck)                             │
│     每个下游任务都需要重新训练全部参数                            │
│     训练时间长,能耗巨大                                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.1.2 参数高效微调的兴起

为了解决上述困境,**参数高效微调(Parameter-Efficient Fine-Tuning,PEFT)**技术应运而生。PEFT 的核心思想是:冻结预训练模型的全部参数,仅添加少量可训练参数,通过训练这些新增参数来实现任务适配。

主流 PEFT 方法对比:

方法添加参数位置可训练参数量性能
LoRALinear 层低(万级)接近全参数
AdapterFFN 层中(百万级)良好
Prefix TuningToken 前缀低(千级)良好
Prompt TuningEmbedding极低(百级)一般
IA³Attention极低良好

2.2 LoRA 数学原理

2.2.1 核心思想:低秩分解

LoRA 的数学基础是低秩分解(Low-Rank Decomposition)。其核心假设是:模型微调过程中权重的更新矩阵 $\Delta W$ 具有低秩特性,即可以用两个小矩阵的乘积来近似表示。

┌─────────────────────────────────────────────────────────────────┐
│                      LoRA 低秩分解示意图                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  全参数微调:                                                    │
│      W ∈ ℝ^{d×k}            ΔW = W' - W                        │
│                                                                 │
│      训练参数量 = d × k                                         │
│                                                                 │
│  LoRA 微调:                                                    │
│                      ┌───────────┐                               │
│      W ∈ ℝ^{d×k} ───│→ 冻结 ←──│─── + ΔW                       │
│                      └───────────┘        ↓                    │
│                                      ΔW = BA                     │
│                                      B ∈ ℝ^{d×r}                 │
│                                      A ∈ ℝ^{r×k}                 │
│                                                                 │
│      训练参数量 = d×r + r×k = r(d+k)                           │
│      当 r << min(d, k) 时,参数量大大减少                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.2.2 数学推导

对于预训练权重矩阵 $W_0 \in \mathbb{R}^{d \times k}$,LoRA 使用低秩分解来近似更新:

$$ W’ = W_0 + \Delta W = W_0 + BA $$

其中:

  • $B \in \mathbb{R}^{d \times r}$:降维矩阵
  • $A \in \mathbb{R}^{r \times k}$:升维矩阵
  • $r \ll \min(d, k)$:秩(Rank),控制低秩近似的维度

前向传播时: $$ h = W’x = (W_0 + BA)x = W_0x + BAx $$

梯度更新时,仅更新 $A$ 和 $B$: $$ \frac{\partial \mathcal{L}}{\partial A} = \frac{\partial \mathcal{L}}{\partial (BA)} \cdot B^T $$ $$ \frac{\partial \mathcal{L}}{\partial B} = \frac{\partial \mathcal{L}}{\partial (BA)} \cdot A^T $$

2.2.3 秩的选择与影响

┌─────────────────────────────────────────────────────────────────┐
│                    秩 r 对模型性能的影响                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  性能                                                            │
│     │                                                            │
│     │        ╭─────────────── 高秩                               │
│     │      ╭─╯                                                     │
│     │    ╭─╯ ←── 中等秩(推荐起点)                               │
│     │  ╭─╯                                                        │
│     │╭─╯ ←── 低秩(可能欠拟合)                                   │
│     │                                                             │
│     └──────────────────────────────────────────────→             │
│                          秩 r                                     │
│                    小 ←————————→ 大                               │
│                   r=2,4,8          r=32,64                       │
│                                                                 │
│  推荐选择策略:                                                   │
│  - 通用任务:r = 4~8                                              │
│  - 复杂任务:r = 16~32                                            │
│  - 追求效果:r = 64~128(接近全参数效果)                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.3 LoRA 超参数详解

2.3.1 核心超参数

超参数说明推荐值影响
rank (r)低秩维度4~64越大越接近全参数,但增加参数量
alpha (α)缩放因子1~2×r控制 LoRA 层的影响强度
dropoutLoRA 参数 dropout0~0.1防止过拟合
target_modules应用 LoRA 的层Q, K, V, O影响微调效果

2.3.2 Alpha 缩放

LoRA 的输出会乘以一个缩放因子 $\frac{\alpha}{r}$:

$$ h = W_0x + \frac{\alpha}{r} BAx $$

这个设计的意义在于:当 $r$ 变化时,可以通过调整 $\alpha$ 来保持 LoRA 层的影响大致一致。通常设置 $\alpha = 2r$ 或 $\alpha = r$。

# LoRA 缩放示意
output = base_output + (lora_weight @ lora_input) * (alpha / rank)

2.4 LoRA 应用位置

2.4.1 Transformer 中的 LoRA 应用

在 Transformer 架构中,LoRA 通常应用在 Attention 层的 $Q$、$K$、$V$、$O$ 四个投影矩阵上:

graph TD
    X["输入 x"]
    subgraph MHA["Multi-Head Attention"]
        QKV["Q = W_q x ← LoRA 位置 1<br/>K = W_k x ← LoRA 位置 2<br/>V = W_v x ← LoRA 位置 3"]
        AT["Attention Score = QK^T"]
        O["O = W_o h ← LoRA 位置 4"]
    end
    H["输出 h"]
    X --> QKV --> AT --> O --> H

2.4.2 各位置效果对比

应用位置参数量效果说明
仅 Q最低★★★☆☆效果有限
Q + K低★★★★☆平衡选择
Q + V中★★★★☆常用配置
Q + K + V中高★★★★★最佳效果
Q + K + V + O最高★★★★★接近全参数

推荐配置:对于大多数场景,使用 Q + V 或 Q + K + V 即可获得良好效果。

2.5 LoRA 变体

2.5.1 QLoRA(量化 LoRA)

QLoRA 由 Tim Dettmers 等人提出,是一种结合量化技术的高效微调方法,可以在极低显存下微调大模型。

核心创新:

  1. 4-bit NormalFloat (NF4) 量化:一种针对正态分布权重优化的4位量化
  2. 双重量化:对量化常数本身也进行量化
  3. 分页优化器:处理内存峰值
┌─────────────────────────────────────────────────────────────────┐
│                  QLoRA vs LoRA 显存对比                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  70B 模型全参数微调:                                            │
│      FP16: ~140GB VRAM(不可行)                                 │
│                                                                 │
│  LoRA:                                                          │
│      FP16: ~80GB VRAM(需要8×A100 80GB)                        │
│                                                                 │
│  QLoRA:                                                         │
│      NF4 + 双量化: ~40GB VRAM(4×A100 80GB 可行)                │
│      INT4 + 分页优化器: ~24GB VRAM(2×A100 80GB 可行)           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

QLoRA 的量化流程:

graph TD
    A["1. 模型加载 (4-bit NF4)<br/>FP16 权重 → 4-bit NF4 量化 → 存储在内存中"]
    B["2. 训练时反量化<br/>4-bit NF4 → FP16 → 计算"]
    C["3. LoRA 更新<br/>仅在 LoRA 参数上计算梯度,更新"]
    A --> B --> C
    note["关键洞察:量化损失由 LoRA 低秩适配来弥补"]

2.5.2 DoRA(权重分解微调)

DoRA(Weight-Decomposed Fine-Tuning) 将预训练权重分解为幅度(magnitude)和方向(direction)两部分:

$$ W = m \cdot \frac{W}{|W|} = m \cdot \hat{W} $$

其中 $m$ 是可学习的幅度标量,$\hat{W}$ 是方向矩阵。对两者分别应用 LoRA:

  • 方向更新:$W’ = W + \Delta W = W + BA$
  • 幅度更新:$m’ = m + \Delta m$
# DoRA 示意
def dora_forward(x, W, m, lora_A, lora_B, alpha, rank):
    # 方向:标准 LoRA 更新
    direction = W + (lora_B @ lora_A) * (alpha / rank)
    
    # 幅度:可学习标量
    magnitude = m
    
    # 归一化方向
    normalized = direction / (direction.norm(dim=-1, keepdim=True) + 1e-8)
    
    # 结合幅度
    output = magnitude * normalized @ x.T
    
    return output.T

DoRA 的优势在于更好地利用了预训练权重的结构信息,在某些任务上取得了比 LoRA 更好的效果。

2.5.3 AdaLoRA(自适应 LoRA)

AdaLoRA 根据各层的重要性动态分配不同的秩,重要程度高的层分配更大的秩。

┌─────────────────────────────────────────────────────────────────┐
│                    AdaLoRA 动态秩分配                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Layer 1: r=4   ████                                            │
│  Layer 2: r=64  ████████████████████████████████                 │
│  Layer 3: r=32  ████████████████                                 │
│  Layer 4: r=8   ████                                            │
│  ...                                                            │
│                                                                 │
│  重要性高的层(如 Layer 2)获得更大的秩                           │
│  重要性低的层(如 Layer 1, 4)获得更小的秩                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

AdaLoRA 通过奇异值分解(SVD)来评估各层的重要性,并迭代调整秩的分配。

2.5.4 LoRA+ 与 VeRA

变体核心改进特点
LoRA+A/B 使用不同学习率B 使用更大的学习率
VeRA随机投影共享进一步减少参数量
LoRA-FAFreezing A仅训练 B,节省一半参数
LoftQ量化感知训练量化与 LoRA 联合优化

2.6 LoRA 训练与部署

2.6.1 训练配置示例

# 使用 transformers 和 peft 库配置 LoRA
from peft import LoraConfig, get_peft_model, TaskType

# LoRA 配置
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,      # 任务类型
    r=16,                               # 秩
    lora_alpha=32,                      # 缩放因子
    lora_dropout=0.05,                  # Dropout
    target_modules=[                    # 应用 LoRA 的模块
        "q_proj",
        "k_proj", 
        "v_proj",
        "o_proj"
    ],
    bias="none",                        # 不训练 bias
    inference_mode=False,               # 训练模式
)

# 将 LoRA 应用到模型
model = get_peft_model(base_model, lora_config)

# 查看可训练参数
model.print_trainable_parameters()
# 输出: trainable params: 4,194,304 || all params: 6,738,415,616 || trainable%: 0.0622

2.6.2 训练流程

graph TD
    A["1. 加载预训练模型(冻结)<br/>AutoModelForCausalLM.from_pretrained(...)<br/>param.requires_grad = False"]
    B["2. 应用 LoRA 适配器<br/>get_peft_model(base_model, lora_config)"]
    C["3. 标准训练循环(仅更新 LoRA 参数)<br/>outputs = model(**batch) → loss.backward() → optimizer.step()"]
    D["4. 保存 LoRA 权重<br/>model.save_pretrained('lora_weights')"]
    A --> B --> C --> D

2.6.3 推理部署

# 方式1:合并权重后推理(适合一次性部署)
from peft import PeftModel

# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained("base_model_path")

# 加载 LoRA 权重并合并
model = PeftModel.from_pretrained(base_model, "lora_weights")
model = model.merge_and_unload()

# 推理
output = model.generate(**inputs)

# 方式2:动态加载 LoRA(适合多任务切换)
from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained("base_model_path")

# 任务A的 LoRA
model_a = PeftModel.from_pretrained(base_model, "lora_task_a")
output_a = model_a.generate(**inputs)

# 切换到任务B的 LoRA
model_b = PeftModel.from_pretrained(base_model, "lora_task_b")
output_b = model_b.generate(**inputs)

2.7 LoRA 代码实战

2.7.1 完整训练示例

import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    Trainer
)
from peft import LoraConfig, get_peft_model, TaskType

# 1. 加载模型和 tokenizer
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# 2. 配置 LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type=TaskType.CAUSAL_LM
)

# 3. 应用 LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# 4. 准备数据集
def format_example(example):
    return f"### 指令:\n{example['instruction']}\n\n### 回答:\n{example['output']}"

# 5. 训练参数
training_args = TrainingArguments(
    output_dir="./lora_llama2",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine",
    logging_steps=10,
    save_steps=100,
    fp16=True,
    optim="adamw_torch"
)

# 6. Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator,
)
trainer.train()

# 7. 保存
model.save_pretrained("./lora_weights")

2.7.2 合并与推理

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto"
)

# 方式1:直接使用 LoRA 推理(不合并)
peft_model = PeftModel.from_pretrained(base_model, "./lora_weights")

def chat_with_model(prompt):
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
    outputs = peft_model.generate(**inputs, max_new_tokens=256)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

# 方式2:合并后推理
merged_model = peft_model.merge_and_unload()

def chat_with_merged(prompt):
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
    outputs = merged_model.generate(**inputs, max_new_tokens=256)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

2.8 LoRA 最佳实践

2.8.1 超参数选择指南

场景rankalphatarget_modules说明
简单任务4-88-16q, v如分类、实体识别
复杂任务16-3232-64q, k, v, o如问答、对话
追求效果64+2×rankq, k, v, o接近全参数
资源受限2-44-8q, v最低资源消耗

2.8.2 训练技巧

┌─────────────────────────────────────────────────────────────────┐
│                    LoRA 训练技巧汇总                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 数据预处理                                                   │
│     - 使用与预训练相同的 tokenizer                               │
│     - 添加适当的特殊 token(如对话格式)                          │
│     - 数据量不在多,在于质量                                     │
│                                                                 │
│  2. 学习率设置                                                   │
│     - LoRA 通常使用 1e-4 ~ 3e-4                                 │
│     - 高于全参数微调的学习率                                     │
│     - 使用 warmup + cosine decay                                │
│                                                                 │
│  3. 防止过拟合                                                   │
│     - 增加 dropout (0.05-0.1)                                    │
│     - 减少 rank                                                  │
│     - 减少训练 epochs                                            │
│                                                                 │
│  4. 多任务学习                                                   │
│     - 可以同时训练多个任务的 LoRA                                │
│     - 或为每个任务单独训练一个 LoRA                               │
│     - 推理时动态切换                                              │
│                                                                 │
│  5. 推理优化                                                    │
│     - 合并权重到基础模型(减少延迟)                              │
│     - 使用量化(INT8/INT4)减少显存                              │
│     - 批量推理提高吞吐率                                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.8.3 常见问题与解决方案

问题原因解决方案
效果不佳rank 过低增加 r 到 16 或 32
过拟合数据量小或 rank 过高减小 r,增加 dropout
训练不稳定学习率过高使用 warmup,降低学习率
推理慢未合并权重合并权重或使用动态加载
显存不足模型太大使用 QLoRA 或进一步量化

第三部分:SFT 与 LoRA 的结合

3.1 SFT + LoRA 的典型范式

在实际应用中,SFT 和 LoRA 通常结合使用,以兼顾效果和效率:

graph TD
    P["预训练模型"]
    S["阶段1:SFT(全参数或 LoRA)<br/>学会遵循指令、格式对齐"]
    R["阶段2:RLHF(可选,通常用 LoRA)<br/>奖励模型 + PPO 优化"]
    P --> S
    P --> R
    note["推荐配置:<br/>SFT: LoRA r=16~32, Q + K + V + O<br/>RLHF: LoRA r=4~8, 仅 Q + V"]

3.2 训练策略对比

策略描述适用场景资源需求
纯 SFT全参数监督微调任务明确、数据充足高
纯 LoRA仅 LoRA 训练资源受限、多任务低
SFT+LoRA先 SFT 后 LoRA标准流程中
QLoRA量化 + LoRA超大模型极低

总结

SFT 与 LoRA 核心要点

┌─────────────────────────────────────────────────────────────────┐
│                    SFT vs LoRA 对比总结                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  SFT(监督微调)                                                  │
│  ├─ 原理:全参数监督学习,使用标注数据训练                         │
│  ├─ 优势:效果最好,能充分激发模型能力                            │
│  ├─ 劣势:资源消耗大,存在遗忘风险                                │
│  └─ 关键:数据质量 > 数据数量                                    │
│                                                                 │
│  LoRA(低秩适配)                                                │
│  ├─ 原理:冻结原模型,仅训练低秩分解的增量                        │
│  ├─ 优势:参数量小、训练快、可动态切换                            │
│  ├─ 劣势:效果略逊于全参数(但差距在减小)                         │
│  └─ 关键:选择合适的 rank 和 target_modules                      │
│                                                                 │
│  实践建议:                                                      │
│  - 资源充足 → 全参数 SFT + RLHF                                  │
│  - 资源中等 → LoRA SFT + RLHF                                   │
│  - 资源受限 → QLoRA                                             │
│  - 多任务场景 → LoRA + 动态加载                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

未来展望

LLM 微调技术仍在快速发展,几个值得关注的方向:

  1. 更高效的微调方法:Beyond LoRA,探索更高效的参数更新机制
  2. 多模态微调:将微调技术扩展到视觉-语言模型
  3. 持续学习:避免微调过程中的灾难性遗忘
  4. 自动化微调:AutoML + PEFT,自动搜索最优微调配置

参考资源

  • LoRA 原始论文:Hu et al., “LoRA: Low-Rank Adaptation of Large Language Models” (2021)
  • QLoRA 论文:Dettmers et al., “QLoRA: Efficient Finetuning of Quantized LLMs” (2023)
  • DoRA 论文:Liu et al., “DoRA: Weight-Decomposed Low-Rank Adaptation” (2024)
  • AdaLoRA 论文:Zhang et al., “AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning” (2023)
  • Hugging Face PEFT:https://github.com/huggingface/peft
  • trl 库:https://github.com/huggingface/trl

Sources

No external sources for this entry.

Related