6847 words
34 minutes
LLM Fine-Tuning

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 与预训练的关系对于把握微调的本质至关重要。

┌─────────────────────────────────────────────────────────────────┐
│ 模型训练阶段对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 预训练阶段 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 语料:互联网大规模文本(数十亿到万亿token) │ │
│ │ 任务:Next Token Prediction(下一个token预测) │ │
│ │ 目标:学习通用语言知识和世界知识 │ │
│ │ 特点:自监督学习,不需要人工标注 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ SFT 阶段 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 语料:人类标注的指令-响应对(数千到数万条) │ │
│ │ 任务:给定指令,生成期望回答 │ │
│ │ 目标:任务适配、格式对齐、能力激发 │ │
│ │ 特点:监督学习,需要高质量人工标注 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ RLHF 阶段(可选) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 语料:人类偏好排序数据 │ │
│ │ 任务:学习人类偏好,优化生成质量 │ │
│ │ 目标:与人类价值观对齐(Helpful、Honest、Harmless) │ │
│ │ 特点:强化学习,结合奖励模型 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

关键认知

  • 预训练模型已经包含了大量知识,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
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 训练流程#

┌─────────────────────────────────────────────────────────────────┐
│ SFT 训练流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ 预训练模型 │ ← 加载预训练权重作为初始化 │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 添加特殊标记 │ ← 添加对话格式需要的特殊token │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 计算 Loss │ ← 仅在 output 位置计算交叉熵损失 │
│ │ 反向传播 │ │
│ │ 更新权重 │ │
│ └──────────────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 微调模型 │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

1.4.2 损失计算#

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

LSFT=t=2nlogPθ(xtx1,x2,...,xt1)\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 对齐训练的两个关键阶段,它们既有联系又有区别。

┌─────────────────────────────────────────────────────────────────┐
│ LLM 对齐训练管线 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 预训练模型 │
│ │ │
│ ↓ │
│ ┌─────────┐ │
│ │ SFT │ ← 学习格式和基本任务完成能力 │
│ └────┬────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ │
│ │ RM │ ← 训练奖励模型学习人类偏好 │
│ └────┬────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ │
│ │ PPO │ ← 强化学习优化策略,最大化人类偏好 │
│ └────┬────┘ │
│ │ │
│ ↓ │
│ 对齐模型 │
│ │
└─────────────────────────────────────────────────────────────────┘

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)。其核心假设是:模型微调过程中权重的更新矩阵 ΔW\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 数学推导#

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

W=W0+ΔW=W0+BAW' = W_0 + \Delta W = W_0 + BA

其中:

  • BRd×rB \in \mathbb{R}^{d \times r}:降维矩阵
  • ARr×kA \in \mathbb{R}^{r \times k}:升维矩阵
  • rmin(d,k)r \ll \min(d, k)秩(Rank),控制低秩近似的维度

前向传播时:

h=Wx=(W0+BA)x=W0x+BAxh = W'x = (W_0 + BA)x = W_0x + BAx

梯度更新时,仅更新 AABB

LA=L(BA)BT\frac{\partial \mathcal{L}}{\partial A} = \frac{\partial \mathcal{L}}{\partial (BA)} \cdot B^TLB=L(BA)AT\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 的输出会乘以一个缩放因子 αr\frac{\alpha}{r}

h=W0x+αrBAxh = W_0x + \frac{\alpha}{r} BAx

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

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

2.4 LoRA 应用位置#

2.4.1 Transformer 中的 LoRA 应用#

在 Transformer 架构中,LoRA 通常应用在 Attention 层的 QQKKVVOO 四个投影矩阵上:

┌─────────────────────────────────────────────────────────────────┐
│ Transformer Attention 层与 LoRA │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Multi-Head Attention │
│ │ │
│ 输入 x ───────────────┼──────────────→ │
│ │ │
│ ↓ │
│ ┌───────────────┐ │
│ │ Q = W_q x │ ← LoRA 应用位置 1 │
│ │ K = W_k x │ ← LoRA 应用位置 2 │
│ │ V = W_v x │ ← LoRA 应用位置 3 │
│ └───────┬───────┘ │
│ │ │
│ ↓ │
│ ┌───────────────┐ │
│ │ Attention │ │
│ │ Score = QK^T │ │
│ └───────┬───────┘ │
│ │ │
│ ↓ │
│ ┌───────────────┐ │
│ │ O = W_o h │ ← LoRA 应用位置 4 │
│ └───────┬───────┘ │
│ │ │
│ ↓ │
│ 输出 h │
│ │
└─────────────────────────────────────────────────────────────────┘

2.4.2 各位置效果对比#

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

推荐配置:对于大多数场景,使用 Q + VQ + 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 的量化流程:

┌─────────────────────────────────────────────────────────────────┐
│ QLoRA 量化流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 模型加载(4-bit NF4) │
│ FP16 权重 → 4-bit NF4 量化 → 存储在内存中 │
│ │
│ 2. 训练时反量化 │
│ 4-bit NF4 → FP16 → 计算 │
│ │
│ 3. LoRA 更新 │
│ 仅在 LoRA 参数上计算梯度,更新 │
│ │
│ 关键洞察:量化损失由 LoRA 低秩适配来弥补 │
│ │
└─────────────────────────────────────────────────────────────────┘

2.5.2 DoRA(权重分解微调)#

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

W=mWW=mW^W = m \cdot \frac{W}{\|W\|} = m \cdot \hat{W}

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

  • 方向更新W=W+ΔW=W+BAW' = W + \Delta W = W + BA
  • 幅度更新m=m+Δmm' = 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 训练流程#

┌─────────────────────────────────────────────────────────────────┐
│ LoRA 训练流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 加载预训练模型(冻结) │
│ base_model = AutoModelForCausalLM.from_pretrained(...) │
│ for param in base_model.parameters(): │
│ param.requires_grad = False │
│ │
│ 2. 应用 LoRA 适配器 │
│ model = get_peft_model(base_model, lora_config) │
│ │
│ 3. 标准训练循环(仅更新 LoRA 参数) │
│ for batch in dataloader: │
│ outputs = model(**batch) │
│ loss = outputs.loss │
│ loss.backward() │
│ optimizer.step() │
│ │
│ 4. 保存 LoRA 权重 │
│ model.save_pretrained("lora_weights") │
│ │
└─────────────────────────────────────────────────────────────────┘

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 通常结合使用,以兼顾效果和效率:

┌─────────────────────────────────────────────────────────────────┐
│ LLM 微调最佳实践 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 预训练模型 │
│ │ │
│ ├─── 阶段1:SFT(全参数或 LoRA) │
│ │ │ │
│ │ └── 学会遵循指令、格式对齐 │
│ │ │
│ └─── 阶段2:RLHF(可选,通常用 LoRA) │
│ │ │
│ └── 奖励模型 + PPO 优化 │
│ │
│ 推荐配置: │
│ - SFT: LoRA r=16~32, Q + K + V + O │
│ - 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 PEFThttps://github.com/huggingface/peft
  • trl 库https://github.com/huggingface/trl
LLM Fine-Tuning
https://sgjki547.top/posts/llm-fine-tuning/
Author
SGJki
Published at
2026-04-08
License
CC BY-NC-SA 4.0