LoRA / QLoRA 高效微调
高效微调(PEFT)让大模型微调变得触手可及——不再需要 8×A100。
为什么需要高效微调
全参数微调(Full Fine-Tuning)的成本非常高,主要体现在以下几个方面:
- 显存占用:需要存储模型参数、梯度、优化器状态(Adam 需 2 倍参数量的额外显存),以 LLaMA 7B 为例,仅梯度 + 优化器状态就需要约 56GB 显存。
- 通信开销:分布式训练中各 GPU 间同步梯度,参数量越大通信压力越大。
- 存储开销:每个微调任务都需保存一份完整的模型副本(7B~70B),难以大规模部署多任务。
PEFT(Parameter-Efficient Fine-Tuning) 只更新一小部分参数,冻结其余全部预训练权重,显著降低显存和存储需求。
LoRA(Low-Rank Adaptation)
核心原理
LoRA(Hu et al., 2021)的核心思想是用低秩矩阵来近似微调过程中的权重更新量。
- 冻结预训练权重矩阵 W ∈ ℝ^(d×k)
- 插入低秩分解矩阵 B ∈ ℝ^(d×r) 和 A ∈ ℝ^(r×k),其中 r ≪ min(d, k)
- 前向传播时:h = Wx + BAx
- 初始化策略:B 初始化为零矩阵,A 初始化为随机高斯分布
这样训练时只更新 B 和 A,参数量从 d×k 降为 d×r + r×k ≈ O(d×r)。
为什么有效
- 低内在维度(Intrinsic Dimension):预训练模型在微调时的参数变化空间实际上是低秩的,即 ΔW 的有效秩远小于其原始维度。
- 泛化性假设:预训练权重已处于一个良好的损失盆地(Loss Basin)中;微调只需在低维子空间中找到邻域最优解。
秩(Rank)选择
- r 是 LoRA 的核心超参数,控制低秩分解的秩大小。
- r = 8 通常已有不错的效果,是大多数任务的默认起点。
- 不同模块可能需要不同的秩——Attention 的 W^Q / W^V 常用较小 r,W^O 可能需要稍大 r。
- 高秩(r = 64+)带来的效果提升通常很微小,但显存和参数量线性增加。
- 经验法则:从 r = 8 开始,验证集效果饱和后尝试 r = 16 或 32。
应用位置
- Attention 模块:Q、K、V、O 四个投影矩阵是最常见的应用目标。
- FFN 模块:较少使用,通常在 Attention 上应用 LoRA 已经足够。
- Embedding 层:一般不应用 LoRA。
- 实践中,仅在 Q 和 V 上应用 LoRA 就能达到不错的效果;增加 K 和 O 通常有边际收益。
LoRA 的合并
训练完成后,LoRA 权重可以合并到原始权重中:
- W' = W + BA
- 合并后推理时无需额外计算,零推理延迟增加。
- 也可不合并,保持 LoRA 权重分离,方便在不同任务间切换(Adapter 范式)。
QLoRA(Quantized LoRA)
核心创新
QLoRA(Dettmers et al., 2023)在 LoRA 的基础上引入量化技术,进一步降低显存需求:
- 4-bit NormalFloat(NF4):对预训练权重进行 4-bit 量化,比 INT4 更适配正态分布的模型权重。
- 双重量化(Double Quantization):对量化常数再进行一次量化,进一步压缩存储。
- Paged Optimizer:利用 CPU 内存和 GPU 显存之间的分页管理,处理梯度 checkpoint 时的显存峰值。
NF4 数据类型
NF4 是 QLoRA 的核心贡献,它是一种信息最优的 4-bit 量化方法:
- 假设权重服从零均值正态分布。
- 将正态分布的分位数映射到 16 个量化区间(4-bit)。
- 相比均匀量化的 INT4,NF4 在正态分布数据上能保留更多信息。
- 实际效果接近于 4-bit 的理论下界(信息论意义上最优)。
QLoRA 显存节省
| 模型 | 全量微调 | LoRA(8-bit) | QLoRA(4-bit) |
|---|---|---|---|
| LLaMA 7B | 112 GB | 18 GB | 6 GB |
| LLaMA 13B | 208 GB | 32 GB | 10 GB |
| LLaMA 65B | 780 GB | 140 GB | 48 GB |
- 在单张 48GB A6000 上即可微调 65B 模型。
- 在单张 24GB RTX 3090 上即可微调 13B 模型。
P-Tuning 与 Prefix Tuning
Prefix Tuning
Prefix Tuning(Li & Liang, 2021)是在 Transformer 的每一层中插入可学习的前缀向量:
- 在每层的 Key 和 Value 前拼接可学习的前缀向量。
- 冻结所有预训练参数,只更新前缀参数。
- 前缀长度影响效果,通常 10~200 tokens。
- 前缀参数量级约为全模型参数的 0.1%~0.5%。
优势:参数量小,适合生成任务(NLG)。 劣势:前缀占用序列长度,影响有效上下文窗口。
P-Tuning(v1 & v2)
- P-Tuning v1:仅在输入层添加可学习的连续 Prompt 嵌入(Continuous Prompt Embedding),通过 LSTM/MLP 生成。
- P-Tuning v2:在 Transformer 每一层都添加可学习的 Prefix/Prompt,结构上更接近 Prefix Tuning。
- 适用场景:P-Tuning v1 更适合 NLU 任务(分类、序列标注等),P-Tuning v2 在 NLU 和 NLG 上均有不错表现。
各种 PEFT 方法对比
| 方法 | 可训练参数量 | 显存需求 | 推理延迟影响 | NLU 效果 | NLG 效果 | 代码复杂度 |
|---|---|---|---|---|---|---|
| LoRA | 0.1%~1% | 低 | 无(可合并) | ★★★★★ | ★★★★★ | 低 |
| QLoRA | 0.1%~1% | 极低 | 无(可合并) | ★★★★☆ | ★★★★☆ | 低 |
| Prefix Tuning | 0.1%~0.5% | 低 | 增加 KV Cache | ★★★☆☆ | ★★★★★ | 中 |
| P-Tuning v1 | 0.01%~0.1% | 极低 | 无 | ★★★★☆ | ★★☆☆☆ | 低 |
| P-Tuning v2 | 0.1%~1% | 低 | 增加 KV Cache | ★★★★★ | ★★★★☆ | 中 |
| Adapter | 1%~5% | 中 | 增加少量计算 | ★★★★★ | ★★★★★ | 中 |
| IA³ | 0.01%~0.1% | 极低 | 无 | ★★★★☆ | ★★★★☆ | 低 |
注:各方法的效果评级为相对比较,具体表现因任务、模型大小和超参数而异。
实战:使用 Hugging Face PEFT 进行 LoRA 微调
python
from peft import LoraConfig, get_peft_model, TaskType
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 可训练参数占比通常 <1%QLoRA 实战示例
python
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
# 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)
# LoRA 配置同上
model = get_peft_model(model, lora_config)训练与保存
python
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./lora-llama",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
num_train_epochs=3,
logging_steps=10,
save_strategy="epoch",
optim="paged_adamw_8bit", # QLoRA 推荐
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
# 保存 LoRA 权重(仅几 MB)
model.save_pretrained("./lora-llama-adapter")
# 加载时只需
from peft import PeftModel
model = PeftModel.from_pretrained(base_model, "./lora-llama-adapter")常见问题
r 的选择原则
- 小任务(分类、情感分析):r = 4~8 即可。
- 复杂任务(摘要、对话):r = 16~32 更稳妥。
- 超大模型(70B+):即使 r = 8 也足够,因为模型本身已有很强的泛化能力。
- 建议先用小 r 快速实验,再根据验证集结果逐步增大。
target_modules 如何选择
- 默认推荐:
["q_proj", "v_proj"],效果好且参数量适中。 - 进阶推荐:
["q_proj", "k_proj", "v_proj", "o_proj"],覆盖完整 Attention,参数量约翻倍。 - 全量 Attention + FFN:参数量较大,收益有限,不推荐。
- 不同模型家族的投影矩阵命名不同(如 LLaMA 为
q_proj,GPT-2 为c_attn),需查阅对应模型配置。
lora_alpha 和 lora_dropout 的作用
- lora_alpha:缩放因子,用于调整 LoRA 权重的更新幅度。实际权重为
(alpha / r) * BA。常见范围为 8~64,通常设为 r 的 2 倍。 - lora_dropout:LoRA 权重的 Dropout 概率,用于防止过拟合。小数据集(<10K 样本)推荐 0.1,大数据集可设为 0.05 或 0。
多 GPU 训练注意事项
- 使用
device_map="auto"让 accelerate 自动分配模型层到各 GPU。 - QLoRA 的 Paged Optimizer 在显存不足时会自动使用 CPU 内存作为交换空间。
- DeepSpeed ZeRO-3 与 QLoRA 结合使用时,需设置
bnb_4bit_use_double_quant=False。 - 建议使用 FSDP(Fully Sharded Data Parallelism)替代 DeepSpeed 以获得更好的兼容性。
总结
- LoRA 是最广泛使用的 PEFT 方法,兼顾效果、效率和易用性,几乎成为业界标配。
- QLoRA 通过 4-bit 量化和双重量化,让大模型微调走进了消费级 GPU(RTX 3090/4090)。
- 选择 PEFT 方法时,根据任务类型(NLU vs NLG)、可用显存、推理延迟要求综合权衡。
- 对于大多数场景,优先尝试 LoRA;显存受限时切换到 QLoRA。
高效微调的意义不仅在于省钱——它让更多研究者和开发者能够参与到大模型的定制化中来,推动了整个生态的繁荣。