参数高效微调(PEFT)总论
1. 背景与动机
1.1 全参数微调的代价
给定预训练模型参数
梯度更新需对全部参数计算并存储:
- 显存:梯度
与优化器状态(如 Adam 的 )需要占用 显存。对 7B 参数的模型,仅 Adam 状态就需要约 56 GB(FP32)。 - 算力:前向+反向均需遍历全部参数,计算量与参数量线性成正。
- 存储:每次微调需保存一套完整模型权重,7B 模型在 FP32 下约 28 GB。
- 部署:每个下游任务需独立部署一套完整权重,无法共享底座。
1.2 PEFT 的核心思想
PEFT(Parameter-Efficient Fine-Tuning)的基本假设是:下游任务的适配信息可以被压缩到一小簇参数中。具体做法是:
- 冻结(freeze)大部分预训练参数
,使其在训练中保持不变。 - 只训练少量新增参数
,或对部分参数做低秩改造。
最终优化目标变为:
这使显存占用、计算量、存储成本均大幅下降,同时保留了对新任务的有效适配能力。
2. 从统一视角理解 PEFT
所有主流 PEFT 方法都可以看作是对模型架构的某个局部结构进行参数化改造,然后只训练这些新引入的结构。我们用下表统一刻画各方法修改的”位置”与”形式”:
| 方法 | 修改的模型部位 | 新增/修改参数的数学形式 | 训练参数量级(以 7B 模型为例) |
|---|---|---|---|
| LoRA / QLoRA | 注意力权重矩阵 | 几 MB( | |
| Adapter | FFN 层之间插入瓶颈层 | 几十 MB | |
| Prefix Tuning | 每层注意力机制的键/值序列 | 可学习向量 | 几 MB~几十 MB |
| Prompt Tuning | 输入 embedding 层 | 可学习向量 | 几 MB |
| IA³ | 激活值逐通道缩放 | < 1 MB |
3. 核心方法详解
3.1 LoRA(Low-Rank Adaptation)
数学形式
设原始权重矩阵
其中
为什么低秩有效?
直觉来自文献中观察到的现象:梯度矩阵在微调过程中往往本征维度较低。即:
这意味着参数更新方向可以由少量正交方向张成。从优化角度看,用低秩矩阵逼近真实梯度方向,是合理且高效的。
QLoRA
QLoRA(Quantized LoRA)在 LoRA 基础上引入NF4(4-bit NormalFloat)量化的基座模型,结合 LoRA 微调。典型配置:
- 基座模型以 4-bit NF4 量化存储(约节省 4 倍空间)
- LoRA 的
以 16-bit 或 32-bit 全精度训练 - 精度对比:7B 模型全精度 ~28 GB → 4-bit QLoRA ~4 GB 加载 + LoRA adapter ~几百 MB
这使单卡(24 GB)微调 65B 模型成为可能。
3.2 Adapter
Adapter 在 Transformer 每层的 FFN 之后(有时在注意力之后)插入一个瓶颈结构:
其中瓶颈维度
- 逐层独立:每个 Adapter 独立训练,推理时要么串行计算(增加延迟),要么吸收进主网络。
- 残差设计:恒等映射保证训练稳定性。
3.3 Prefix Tuning
Prefix Tuning 在每层注意力计算前,向
每个 Transformer 层独立学习一组前缀。可学习参数总数约为
物理含义:在每层为模型注入”任务专属的上下文先验”,使后续 token 的注意力机制能以任务感知的方式运行。
3.4 Prompt Tuning
仅在输入层添加可学习的软提示向量:
与 Prefix Tuning 的区别在于:Prompt Tuning 只在第一层注入提示,而不修改每一层的
3.5 其他方法简述
- IA³(Infused Adapter by Inhibiting and Amplifying Inner Activations):学习逐通道缩放向量
,修改 。参数量极小(约 0.1%),适合极端资源受限场景。 - LoRA+:为
设置不同的学习率,缓解收敛速度不匹配的问题。 - AdaLoRA / QLoRA+:自适应调整不同层的 rank 分配,优先在更重要/高敏感度的权重上分配更多参数。
- VeRA(Vector-based Random Matrix):用随机投影向量代替可学习矩阵,大幅减少可训练参数。
- LoRA-FA(LoRA with Fisher):基于 Fisher 信息确定哪些层更重要,动态调整 rank。
4. 数学视角:为什么少量参数也能有效适配?
4.1 从参数更新角度的推导
全参数微调的参数更新为
这要求任务相关的参数更新方向落在低维子空间中。大量实验和理论分析表明,当预训练模型足够大时,下游任务的适应方向确实呈现出低维结构(” intrinsically low-dimensional”)。
4.2 渐进式表征理论
从 representation 的角度看:预训练模型已经学到了丰富的语言知识和世界知识(存储在参数中)。PEFT 只负责在冻结的基础表示之上,添加任务特定的轻微扰动。
这个
4.3 灾难性遗忘的缓解机制
全参数微调时,由于所有参数都参与梯度更新,模型容易覆盖预训练阶段获得的知识(灾难性遗忘)。PEFT 通过以下机制缓解:
- 冻结即保护:预训练知识存储在
中,永不更新。 - 低维约束:即使训练目标有噪声,有限的自由度也约束了破坏性更新的能力。
- 正则化隐式:LoRA 的
可以看作是对 的贝叶斯后验估计中加入了先验( 接近原始值),具有隐式正则化效果。
5. 能力边界与任务适配
5.1 PEFT 适合的任务类型
| 任务类型 | PEFT 适用性 | 说明 |
|---|---|---|
| 文本分类 / 情感分析 | 非常适合 | 简单序列标注,LoRA/Adapter 通常优于全参 |
| 对话 / 指令微调 | 适合 | 需要对齐时通常配合 RLHF 使用 PEFT |
| 领域自适应(法律、医疗) | 适合 | 知识迁移型任务 |
| 复杂推理 / 长链推理 | 效果偏弱 | 复杂推理可能需要更多参数灵活性 |
| 超大规模多任务 | 部分适合 | 若任务间差异极大,多任务 PEFT 可能不如全参 |
5.2 PEFT 与全参微调的能力边界
- 小模型(< 1B):PEFT 效果显著弱于全参微调。小模型本征表达能力有限,冻结大部分参数会导致容量不足。
- 中模型(1B ~ 7B):PEFT 与全参差距较小,多数任务 PEFT 效果可达到全参的 90%~95%。
- 大模型(> 7B):PEFT 效果与全参几乎持平(如 LLaMA-2 7B 以上),且大模型本身过参数化,低秩适应足以捕获任务信息。
- 极端情况:当目标任务与预训练数据分布差异极大时(如预训练为英文,任务为中文古诗),全参微调仍更优。
6. 工程流程
6.1 如何选择 PEFT 方法
决策流程:
1. GPU 显存 ≤ 8 GB?
→ QLoRA(4-bit 量化 + LoRA)
2. 训练速度优先?
→ LoRA(几乎零推理延迟开销,训练/推理均快)
3. 需要同时运行多个任务(多 Adapter 切换)?
→ LoRA / Adapter(各 Adapter 可独立保存,按需加载)
4. 只需要极少量参数(< 1MB)?
→ IA³
5. 任务较简单(分类、NER)且模型较大(> 7B)?
→ Prompt Tuning / Prefix Tuning
6. 内存 + 精度双约束?
→ QLoRA
6.2 如何冻结/解冻参数
以 HuggingFace PEFT 库为例:
from peft import get_peft_model, LoraConfig, TaskType
# 冻结全部参数
for name, param in model.named_parameters():
param.requires_grad = False
# LoRA 配置:只解冻 A, B 矩阵
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"], # 只在 Q/V 上加 LoRA
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出: "trainable params: 4,194,304 || all params: 6,738,415,616 || trainable%: 0.062%"target_modules 选择注意:若选错(如选了模型不支持的层名),PEFT 将无效,模型不会学到任何东西。常用 target_modules:
- LLaMA/Mistral:
["q_proj", "k_proj", "v_proj", "o_proj"] - ChatGLM:
["query_key_value"] - Bloom:
["query_layer", "key_layer", "value_layer", "dense"]
6.3 训练流程
# 优化器配置:只优化 PEFT 参数
optimizer = torch.optim.AdamW(
filter(lambda p: p.requires_grad, model.parameters()),
lr=2e-4,
weight_decay=0.01
)
# 训练循环
for batch in dataloader:
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()6.4 保存 Adapter 与 Merge
保存 Adapter(不合并):
# 只保存 adapter 权重(推荐用于多任务切换场景)
model.save_pretrained("./lora_adapter")
# 会在 ./lora_adapter 下保存:adapter_config.json, adapter_model.safetensors合并权重(推理时使用):
from peft import PeftModel, LoraConfig
# 加载基座模型 + adapter,合并后保存
base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b")
peft_model = PeftModel.from_pretrained(base_model, "./lora_adapter")
merged_model = peft_model.merge_and_unload()
merged_model.save_pretrained("./merged_model")注意:合并后模型文件大小恢复为完整基座模型大小,但推理时无任何额外计算开销。
6.5 部署推理
# 方式1:加载合并后的完整模型(单卡推理推荐)
model = AutoModelForCausalLM.from_pretrained("./merged_model")
# 方式2:加载基座 + adapter,实时合并(节省存储,按需加载)
base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b")
peft_model = PeftModel.from_pretrained(base_model, "./lora_adapter")
# 合并到基座后再推理(见上方 merge_and_unload)6.6 多 PEFT 方法组合
PEFT 方法之间可组合使用,但需注意:
# 多 LoRA 叠加(如多个不同任务的 adapter)
from peft import PeftModel
model = AutoModelForCausalLM.from_pretrained("base_model")
for adapter_path in ["task_a_adapter", "task_b_adapter"]:
model = PeftModel.from_pretrained(model, adapter_path)
model.merge_and_unload() # 依次合并# LoRA + Prefix Tuning 组合(更灵活但更复杂)
from peft import get_peft_model, LoraConfig, PrefixTuningConfig
config = LoraConfig() + PrefixTuningConfig() # 需框架支持7. 性能对比
7.1 显存占用
以 LLaMA-2 7B 为例:
| 配置 | 加载基座(GB) | 梯度/优化器(GB) | 总显存(GB) | 相对全参 |
|---|---|---|---|---|
| 全参微调 | 14(FP16) | ~28(Adam状态) | ~42 | 1.0× |
| LoRA(r=16, 2层) | 14 | ~0.4 | ~15 | 0.36× |
| QLoRA(4-bit NF4) | ~3.5 | ~0.4 | ~4 | 0.10× |
| Adapter(bottleneck=64) | 14 | ~0.5 | ~15 | 0.36× |
| Prompt Tuning(l=20) | 14 | ~0.002 | ~14 | 0.33× |
7.2 训练速度
| 方法 | 相对全参速度 | 原因 |
|---|---|---|
| 全参微调 | 1.0× | 全部参数参与前反向传播 |
| LoRA | ~0.9× | 主要计算仍在主干,仅额外少量矩阵乘(Q, V 的低秩分解) |
| QLoRA | ~0.5×(相对全参) | 4-bit 推理本身较慢,但计算量少 |
| Adapter | ~0.85× | 插入层带来额外前向计算 |
| Prefix Tuning | ~0.95× | 仅注意力 K/V 扩展,计算量增加较少 |
7.3 可移植性
| 方法 | 可移植性 | 说明 |
|---|---|---|
| LoRA | 极高 | adapter 权重文件(几十 MB)可在同架构不同基座间复用 |
| QLoRA | 高 | 需基座量化方式一致(NF4 vs. Int8) |
| Adapter | 高 | 但推理时需动态加载,计算开销稍大 |
| Prefix Tuning | 中 | 前缀向量与模型层强绑定,迁移需相同结构 |
| Prompt Tuning | 低 | 极度依赖输入 embedding,对不同 tokenizer 敏感 |
7.4 可组合性
| 方法 | 可组合性 | 说明 |
|---|---|---|
| LoRA | 非常高 | 可同时加载多个 adapter 并 merge,组合灵活 |
| Adapter | 高 | 同一层可叠加多个 adapter(串行或并行) |
| Prefix Tuning | 中 | 多任务前缀可拼接,但扩展性有限 |
| Prompt Tuning | 低 | 软提示间相互干扰,多任务效果较差 |
8. 常见坑与避坑指南
8.1 target_module 选错
表现:训练损失几乎不变,model.print_trainable_parameters() 显示参数为 0。
检查步骤:
- 打印模型所有层名,确认存在目标模块:
for name, _ in model.named_modules(): print(name) - 对比 PEFT 配置文件中的
target_modules,确保命名完全匹配(大小写敏感)。 - 部分模型(如 ChatGLM)使用统一层名
query_key_value,而非分开的q_proj, k_proj, v_proj。
8.2 rank 过小
表现:训练收敛慢,最终精度不足。
经验值:
- 简单任务(分类、NER):
足够 - 中等复杂度(对话、翻译):
- 高复杂度(代码生成、多跳推理):
- 若
过大(>128)且数据量少,容易过拟合。
8.3 学习率不匹配
LoRA 的最佳学习率通常比全参微调高 10~100 倍。原因是 LoRA 参数空间小,需要更大的步长来有效更新。
| 微调方式 | 学习率范围 |
|---|---|
| 全参微调 | 1e-5 ~ 5e-5 |
| LoRA(r=8~64) | 1e-4 ~ 3e-3 |
| QLoRA | 3e-4 ~ 1e-2 |
| Adapter | 5e-5 ~ 1e-3 |
8.4 合并后精度变化
现象:merge_and_unload() 后 perplexity 或下游指标轻微下降(0.5%~2%)。
原因:合并过程存在数值精度问题,尤其在
缓解方法:
- 使用
merge_and_unload()前先将 adapter 转为与基座相同的精度。 - 若精度损失敏感,考虑在推理时使用
PeftModel动态合并,而非预合并。
8.5 数据量与 PEFT 方法的匹配
| 数据规模 | 推荐方法 |
|---|---|
| < 1k 条 | Prompt Tuning / IA³(极低过拟合风险) |
| 1k ~ 50k | LoRA(r=8~32)或 Adapter |
| 50k ~ 500k | LoRA(r=16~64)或全参微调 |
| > 500k | 全参或 LoRA + 全参混合 |
9. PEFT 方法对比表
| 方法 | 可训练参数量 | 推理延迟 | 多任务支持 | 实现难度 | 显存优化 | 适用场景 |
|---|---|---|---|---|---|---|
| LoRA | 0.1%~1%(r 决定) | 无额外延迟 | 非常高(多个 adapter 合并) | 低 | 好(可 QLoRA) | 通用场景,对话/指令微调,领域适配 |
| QLoRA | 0.1%~0.5% | 轻微增加(4-bit 推理) | 高(需同量化格式) | 中 | 极好(4-bit) | 单卡微调大模型(>7B) |
| Adapter | 0.5%~2% | 增加(逐层串行计算) | 中(动态加载) | 中 | 中等 | 需要逐层控制的中等规模模型 |
| Prefix Tuning | 0.1%~2%(取决于前缀长度) | 轻微增加(K/V 序列更长) | 中(前缀拼接) | 中 | 好 | 需要逐层任务感知的场景 |
| Prompt Tuning | < 0.1%(仅 embedding) | 几乎无 | 低(软提示相互干扰) | 低 | 极好 | 超大模型 + 小数据量(分类为主) |
| IA³ | < 0.1%(仅缩放向量) | 无额外延迟 | 中 | 低 | 极好 | 极端资源受限 + 简单任务 |
10. 选择 PEFT 方法的决策建议
决策树
开始
│
├─ GPU 显存 ≤ 8 GB
│ └─ 是 → QLoRA(4-bit NF4 + LoRA)
│ └─ 若仍显存不足 → 降低 r 或进一步量化
│
├─ 需要部署多任务(按需切换 Adapter)?
│ └─ 是 → LoRA(adapter 体积小,切换灵活,合并后零推理开销)
│
├─ 任务非常简单(分类、NER)且数据量少(< 1k)?
│ └─ 是 → Prompt Tuning(参数最少,不易过拟合)
│
├─ 需要极低延迟推理(实时系统)?
│ └─ 是 → LoRA 或 IA³(无推理额外计算)
│
├─ 模型规模 > 30B?
│ └─ 是 → QLoRA(其他方法显存难以承受)
│
├─ 需要多层任务感知(前缀影响每一层)?
│ └─ 是 → Prefix Tuning(比 Prompt Tuning 表达力更强)
│
└─ 以上都不满足 → LoRA(r=16~32)作为默认选择
快速参考
- 入门首选:LoRA r=16, alpha=32, lr=2e-4, target_modules 覆盖 Q/K/V/O
- 单卡微调 7B+:QLoRA 4-bit NF4 + LoRA r=64
- 极致参数节省:IA³ 或 Prompt Tuning
- 多任务切换:LoRA,每个任务一个 adapter 目录,推理时按需 load + merge
- 生产部署:合并后的模型(merged_model),无 PEFT 运行时依赖
文档版本:2026-05-17