PEFT (Parameter-Efficient Fine-Tuning) —— 参数高效微调
Important原论文:LoRA: Low-Rank Adaptation of Large Language Models、QLoRA: Efficient Finetuning of Quantized LLMs、Prefix-Tuning: Optimizing Continuous Prompts for Generation
优秀博客:Understanding Parameter-Efficient LLM Finetuning: Prompt Tuning And Prefix Tuning、LoRA fine-tuning Hyperparameters Guide、HuggingFace LoRA
PEFT库:HuggingFace PEFT
引言:大模型微调的困境
近年来,大语言模型的规模呈指数级增长——从BERT的1亿参数到GPT-3的1750亿参数,再到今天动辄万亿参数的模型——全量微调(Full Fine-Tuning)的成本已高到令人望而却步。以GPT-3 175B为例,单次全参数微调需要TB级显存和数万GPU小时。与此同时,绝大多数下游任务并不需要调整模型的全部参数——我们往往只需要让模型 “学会”某个特定领域的知识或能力。
正是在这样的背景下,参数高效微调(Parameter-Efficient Fine-Tuning, PEFT) 技术应运而生。PEFT的核心思想是冻结预训练模型的大部分参数,仅更新一小部分参数或引入少量新增参数,从而以极低的计算成本实现与全量微调相当的性能。
本文将从数学原理、方法分类、核心算法和工程实践四个维度,系统性地解析PEFT技术体系。
一、PEFT的分类体系
根据参数更新策略的不同,PEFT方法可分为三大范式:
-
选择性微调(Selective Fine-Tuning):仅更新模型中的部分已有参数,其余参数全部冻结。代表性方法包括BitFit(仅微调偏置项)和DiffPruning(通过可微分掩码选择重要参数)。
-
附加式微调(Additive Fine-Tuning):在模型中新增可训练的参数模块,原始模型参数完全冻结。这是目前最主流的一类方法,包括:
- Adapter Tuning:在Transformer层间插入小型适配器模块
- Prefix Tuning:在输入序列前添加可训练的前缀向量
- Prompt Tuning:在输入层添加可训练的软提示
- LoRA:在权重矩阵旁添加低秩分解矩阵
-
重参数化微调(Reparameterized Fine-Tuning):通过数学变换将参数更新重参数化为更紧凑的形式。LoRA本质上也是一种重参数化方法。
二、Adapter Tuning:最早的PEFT范式
Adapter Tuning是PEFT领域最早的经典方法之一。其核心思路是在Transformer的每一层中插入小型神经网络模块(称为Adapter),训练时仅更新这些Adapter的参数。
一个典型的Adapter模块是一个瓶颈结构 —— 先通过降维层将特征维度压缩,再通过升维层恢复:
1import torch2import torch.nn as nn3
4class Adapter(nn.Module):5 def __init__(self, d_model: int, reduction_factor: int = 16):6 super().__init__()7 self.down = nn.Linear(d_model, d_model // reduction_factor)8 self.up = nn.Linear(d_model // reduction_factor, d_model)9 self.activation = nn.ReLU()10
11 def forward(self, x):12 # 残差连接:原始输出 + Adapter输出13 return x + self.up(self.activation(self.down(x)))Adapter Tuning的优势在于结构简单、易于实现,但缺点是引入了额外的推理延迟——每个Adapter模块都增加了前向传播的计算量。以7B模型为例,Adapter方法通常需要新增约3%-5%的参数。
三、Prefix Tuning与Prompt Tuning:用“提示”替代“调参”
3.1 Prefix Tuning
Prefix Tuning的核心思想是在每一层的输入序列前添加可训练的前缀向量(Prefix Vectors)。这些前缀向量本质上是连续的虚拟令牌,在训练过程中被优化以引导模型生成合适的输出。
Prefix Tuning的形式为 [PREFIX; x; y],其中PREFIX是可训练的前缀参数。训练时,原始模型参数完全冻结,仅更新前缀向量。
3.2 Prompt Tuning
Prompt Tuning是Prefix Tuning的轻量版——仅在输入层添加可训练的提示向量(soft prompts),而非每一层。因此,Prompt Tuning的参数规模更小——以7B模型为例,新增参数占比通常小于0.1%。
1# Prompt Tuning的简化示意2class PromptTuning(nn.Module):3 def __init__(self, num_virtual_tokens: int, embed_dim: int):4 super().__init__()5 # 可训练的软提示嵌入6 self.prompt_embeddings = nn.Parameter(7 torch.randn(num_virtual_tokens, embed_dim)8 )9
10 def forward(self, input_embeds):11 # 将软提示拼接到输入嵌入前面12 return torch.cat([self.prompt_embeddings, input_embeds], dim=0)Prefix Tuning和Prompt Tuning的共同优势是参数效率极高——仅需更新0.1%以下的参数即可获得不错的性能。但缺点也同样明显:性能上限有限,在复杂任务上往往不及LoRA等方法。
四、LoRA:低秩适配的典范
低秩适配(Low-Rank Adaptation, LoRA) 是目前最流行、应用最广泛的PEFT方法。
4.1 关键假设
LoRA的核心洞察基于一个关键假设:模型在适应下游任务时,权重更新矩阵 ΔW 本质上是低秩的。也就是说,模型适应新任务时,权重的变化并不需要满秩矩阵来描述,核心变化可以通过低秩矩阵来近似。
这个发现意味着如果微调时的权重更新本质上是低秩的,那可以用两个小矩阵的乘积来近似这个更新,而不是去更新整个庞大的权重矩阵。
在原论文中,LoRA主要应用于Transformer的注意力权重矩阵(特别是 Wq 和 Wv)。实验表明,在查询(Query)和值(Value)投影上应用LoRA效果最好,而在键(Key)投影上应用的效果相对有限。
4.2 数学原理
对于预训练模型中的一个线性层,其原始计算为:
y=W0x
其中 W0∈Rd×k 是预训练的权重矩阵。
全参数微调会直接更新 W0,而LoRA的做法是冻结 W0,转而学习一个更新矩阵 ΔW∈Rd×k,使得微调后的输出为:
y=W0x+ΔWx
LoRA的核心创新在于将 ΔW 分解为两个低秩矩阵的乘积:
ΔW=B⋅A
其中 B∈Rd×r,A∈Rr×k,且秩 r≪min(d,k)。
这样一来,原本需要学习 d×k 个参数,现在只需要学习 r×(d+k) 个参数。参数量从 O(dk) 降至 O(r(d+k))。
前向传播的计算变为:
y=W0x+ΔWx=W0x+BAx
在训练过程中,W0 被冻结(不计算梯度),仅更新 A 和 B。
引入缩放系数后,完整的前向计算为:
y=W0x+rα⋅BAx
其中 α 是缩放因子,用于控制LoRA更新的强度。实际更新幅度为 α/r。在实际调参中,α 通常设置为 r 的2倍。这个缩放机制允许我们在改变 r 时保持更新幅度的相对稳定。
4.3 梯度传播
反向传播时,梯度仅流向低秩矩阵:
∂A∂L=(∂ΔW∂L)T⋅BT,∂B∂L=∂ΔW∂L⋅AT
这种设计使得基础模型参数的梯度恒为零,反向传播的计算复杂度从 O(d2) 降至 O(dr)。
4.4 初始化策略
LoRA采用精心设计的初始化策略以保证训练稳定性:
- 矩阵 A 使用随机高斯分布初始化
- 矩阵 B 使用全零初始化
- 这样,在训练开始时 ΔW=BA=0,模型的行为与原始预训练模型完全一致,避免了训练初期的剧烈扰动。
4.5 秩的选择策略
秩 r 是LoRA最核心的超参数,需要在表达能力与计算效率之间权衡:
| 秩的范围 | 适用场景 | 特点 |
|---|---|---|
| r≤8 | 特定任务适配(风格迁移、领域适配) | 参数极少,泛化性有限 |
| 16<r<64 | 通用任务微调(指令跟随、问答) | 效率与效果的黄金平衡点 |
| r≥64 | 接近全参数微调效果 | 参数效率下降 |
实验表明,在LLaMA-7B上微调代码生成任务时,r=16 即可达到全参数微调 92% 的效果,而参数量减少了 98%。
五、LoRA算法代码实现
5.1 Python代码实现LoRA
1import torch2import torch.nn as nn3
4class LoRALinear(nn.Module):5 def __init__(self, in_features, out_features, rank=8, alpha=16):6 super().__init__()7 self.rank = rank8 self.scaling = alpha / rank # 缩放系数9
10 # 冻结原始权重(不可训练)11 self.weight = nn.Parameter(12 torch.randn(out_features, in_features),13 requires_grad=False14 )15 self.bias = nn.Parameter(16 torch.zeros(out_features),17 requires_grad=False18 )19
20 # 可训练的低秩矩阵21 # A矩阵:随机初始化22 self.lora_A = nn.Parameter(torch.randn(rank, in_features) * 0.01)23 # B矩阵:零初始化,确保训练初期 ΔW = 024 self.lora_B = nn.Parameter(torch.zeros(out_features, rank))25
26 def forward(self, x):27 # 原始输出(冻结权重)28 base_output = x @ self.weight.T + self.bias29 # LoRA增量30 # 注意:实际计算顺序是 x @ A^T @ B^T31 lora_output = (x @ self.lora_A.T) @ self.lora_B.T * self.scaling32 return base_output + lora_output5.2 使用HuggingFace PEFT库
在实际工程中,我们通常使用HuggingFace的PEFT库,它封装了LoRA及其变体的完整实现:
1from transformers import AutoModelForCausalLM2from peft import LoraConfig, get_peft_model, TaskType3
4# 加载预训练模型5model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-3B-Instruct")6
7# 配置LoRA8peft_config = LoraConfig(9 r=16, # 秩10 lora_alpha=32, # 缩放因子11 task_type=TaskType.CAUSAL_LM,12 target_modules=["q_proj", "v_proj"] # 指定要应用LoRA的模块13)14
15# 包装模型16model = get_peft_model(model, peft_config)17model.print_trainable_parameters()18# 输出:trainable params: 3,686,400 || all params: 3,089,625,088 || trainable%: 0.11935.3 推理时的权重合并
在部署阶段,可以将LoRA权重合并回原始模型,消除额外的计算开销:
1# 加载LoRA适配器2from peft import PeftModel3model = PeftModel.from_pretrained(base_model, "path/to/lora-adapter")4# 合并权重5model = model.merge_and_unload() # 现在 ΔW 已合并到 W0 中六、LoRA的改进与优化
6.1 QLoRA:当量化遇上LoRA
QLoRA(Quantized Low-Rank Adaptation) 是LoRA的量化增强版,其核心思想用公式概括为QLoRA = 4-bit量化基座 + 全精度LoRA适配器
QLoRA将预训练模型的权重从32位或16位浮点数量化为4位精度,同时保持LoRA适配器为全精度。这种“双重优化”策略带来了显著的显存节省——QLoRA可以在单张48GB显存的GPU上微调65B参数的模型。
1from transformers import BitsAndBytesConfig2from peft import LoraConfig, get_peft_model3
4# 4-bit量化配置5bnb_config = BitsAndBytesConfig(6 load_in_4bit=True,7 bnb_4bit_quant_type="nf4", # NormalFloat 4-bit8 bnb_4bit_compute_dtype=torch.bfloat16,9)10
11# 加载量化模型12model = AutoModelForCausalLM.from_pretrained(13 "meta-llama/Llama-2-70b",14 quantization_config=bnb_config,15 device_map="auto",16)17
18# 应用LoRA(适配器保持全精度)19lora_config = LoraConfig(r=8, lora_alpha=16, target_modules=["q_proj", "v_proj"])20peft_model = get_peft_model(model, lora_config)QLoRA的工程价值在于极大地降低了微调大模型的硬件门槛,使得在消费级GPU上进行百亿参数模型的微调成为可能。
6.2 AdaLoRA:自适应秩分配
标准LoRA在所有层和所有模块上均匀分配参数预算。然而,不同层、不同模块对微调的重要性是不同的。AdaLoRA(Adaptive LoRA) 通过奇异值分解(SVD) 参数化增量更新,并根据各权重矩阵的重要性动态分配秩预算。
AdaLoRA采用三阶段训练调度,在训练过程中动态调整各模块的秩,从而在相同参数预算下获得更好的性能。
6.3 IA3:通过抑制与放大内部激活进行微调
IA3(Infused Adapter by Inhibiting and Amplifying Inner Activations) 是一种极简的PEFT方法,通过在注意力机制的Key和Value以及前馈模块中注入可学习的缩放向量来调整激活值。
IA3的新增参数极少,但性能在某些场景下会显著下降。它代表了PEFT在极端参数效率方向上的探索。
七、方法对比与选型指南
| 方法 | 新增参数占比(7B模型) | 推理延迟 | 适用场景 |
|---|---|---|---|
| Adapter | 3% ~ 5% | +5% ~ 15% | 需要结构解耦的场景 |
| Prefix Tuning | 0.1% ~0.3% | +10% ~ 30%显存 | 低数据量场景 |
| Prompt Tuning | <0.1% | 轻微增加 | 极简适配,参数最少 |
| LoRA | 0.5% ~ 2% | 无额外延迟(可融合) | 通用首选,效果与效率最佳平衡 |
| QLoRA | 0.5% ~ 2% | 无额外延迟 | 显存受限场景,大模型微调 |
NotePEFT技术彻底改变了我们与大模型交互的方式——从“重新训练整个模型”到“轻量级适配”,从“需要数百张GPU”到“单张消费级显卡即可完成”。LoRA及其衍生方法(如QLoRA、AdaLoRA)已成为这一领域的基石。
理解PEFT不仅仅是学会调用几个API——它背后蕴含着低秩假设、矩阵分解、参数重参数化等深刻的数学思想。当你下次微调一个大模型时,不妨想一想:你真正需要更新的是哪些参数?有多少参数是可以“借”而不是“买”的?
这或许正是PEFT带给我们最宝贵的思维方式——在资源有限的世界里,学会用最小的代价撬动最大的价值。
Some information may be outdated