第二章:主流微调方法概览——全参数微调、Adapter、Prefix Tuning、LoRA、QLoRA的原理对比
大家好,我是你们的老朋友。今天咱们来聊聊微调方法这个大杂烩。说实话,我刚入行那会儿,面对一堆微调术语,头都大了。什么全参数、Adapter、LoRA……感觉像在背菜谱。但别急,我带你捋一遍,你会发现其实没那么玄乎。
说白了,微调就是让预训练模型学会干新活。但怎么干,效率差很多。我个人习惯把方法分成两类:暴力派和巧干派。暴力派就是全参数微调,巧干派就是各种参数高效微调(PEFT)。
核心观点: 选对方法,能省下90%的显存和训练时间。我见过太多人一上来就全参数微调,结果显存爆了,项目延期。嗯,这里要注意,不是所有场景都需要全参数。
2.1 全参数微调(Full Fine-tuning)
全参数微调,顾名思义,就是把模型的所有参数都拿来更新。你想想看,一个7B的模型,所有参数都参与训练,那显存消耗是巨大的。我曾在一次项目中,用4张A100微调一个13B模型,结果训练到一半,显存直接飙到80GB,吓得我赶紧停了。
优点:
- 理论上效果最好,因为所有参数都能适应新任务。
- 适合数据量大的场景,比如你有百万级标注数据。
缺点:
- 显存消耗巨大,一般需要多卡并行。
- 训练时间长,成本高。
- 每个任务都要保存一份完整模型副本,存储成本高。
避坑指南: 我曾经在微调一个对话模型时,全参数微调后,模型在原有任务上的能力大幅下降,这就是灾难性遗忘。所以,如果你不是土豪,或者数据量不大,建议慎用全参数微调。
2.2 Adapter(适配器)
Adapter的思路很简单:在Transformer的每一层里插入一个小型神经网络模块(Adapter),只训练这些模块,冻结原始模型参数。说白了,就是给模型加个“外挂”,让它学会新技能,但不动原来的脑子。
我记得第一次用Adapter时,觉得这玩意儿真巧妙。它像给模型穿了一件“马甲”,只训练马甲,不训练身体。这样,每个任务只需要保存一个很小的Adapter模块,存储成本极低。
Adapter的结构:
# 伪代码示例
class AdapterLayer(nn.Module):
def __init__(self, hidden_size, bottleneck_size=64):
super().__init__()
self.down = nn.Linear(hidden_size, bottleneck_size)
self.up = nn.Linear(bottleneck_size, hidden_size)
self.activation = nn.ReLU()
def forward(self, x):
return self.up(self.activation(self.down(x))) + x # 残差连接
优点:
- 显存占用小,只训练Adapter参数。
- 支持多任务,每个任务一个Adapter,切换方便。
- 不容易灾难性遗忘。
缺点:
- 推理时会有额外计算开销(虽然很小)。
- 效果略逊于全参数微调,尤其在数据量大的时候。
2.3 Prefix Tuning(前缀微调)
Prefix Tuning的思路更“野”:它不在模型结构上动手脚,而是在输入序列前面加一段可学习的“前缀”向量。这些前缀向量会参与注意力计算,从而引导模型生成符合任务需求的输出。
你想想看,这就像给模型一个“提示词”,但这个提示词是可学习的,不是人写的。我刚开始觉得这方法有点“玄学”,但试过之后发现,效果还真不错,尤其适合生成任务。
核心原理:
- 在每一层Transformer的Key和Value上拼接可学习的前缀向量。
- 只训练这些前缀向量,冻结原始模型。
- 前缀长度一般设为10-100个token。
小技巧: 我个人习惯把前缀长度设为20,效果和计算量的平衡点比较好。太长会增加计算量,太短效果不明显。
优点:
- 参数效率极高,只需要训练很少的参数。
- 适合生成任务,如文本摘要、对话生成。
缺点:
- 对序列长度敏感,前缀太长会影响性能。
- 在分类任务上效果一般。
2.4 LoRA(低秩适配)
LoRA可以说是目前最火的微调方法了。它的核心思想是:模型权重矩阵的更新量是低秩的。说白了,就是用一个低秩矩阵来近似权重更新量,而不是直接更新整个权重矩阵。
我记得第一次看到LoRA论文时,觉得这数学推导真漂亮。它把权重更新量分解成两个小矩阵的乘积,训练时只训练这两个小矩阵。这样,参数量从d×d变成了d×r + r×d,其中r远小于d。
LoRA的数学形式:
# 原始权重更新
W_new = W_old + ΔW
# LoRA近似
ΔW = A @ B # A: d×r, B: r×d, r << d
实际应用:
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=8, # 秩
lora_alpha=32, # 缩放因子
target_modules=["q_proj", "v_proj"], # 只对Q和V矩阵应用LoRA
lora_dropout=0.1
)
model = get_peft_model(base_model, lora_config)
经验之谈: 我建议r值一般设为8或16,效果和效率的平衡最好。r太大,参数量增加,效果提升有限;r太小,表达能力不足。
优点:
- 参数效率极高,通常只训练原始参数的0.1%-1%。
- 推理时无额外开销,可以将LoRA权重合并到原始权重中。
- 支持热插拔,多个LoRA模块可以动态切换。
缺点:
- 需要手动选择目标模块(如Q、V、K、O等)。
- 在极低资源场景下,效果可能不如Adapter。
2.5 QLoRA(量化LoRA)
QLoRA是LoRA的“升级版”,它把模型权重量化到4-bit或8-bit,然后在此基础上应用LoRA。说白了,就是先把模型“压缩”一下,再微调,这样显存占用更小。
我曾在一次项目中,用QLoRA在单张RTX 3090上微调了一个13B模型,显存只用了12GB左右。这在以前想都不敢想。嗯,这里要注意,量化会带来一定的精度损失,但QLoRA通过一些技巧(如NF4量化、双重量化)把损失降到了最低。
QLoRA的关键技术:
- NF4量化: 一种4-bit量化方法,比普通4-bit量化精度更高。
- 双重量化: 对量化常数再进行一次量化,进一步减少显存占用。
- 分页优化器: 利用CPU内存来缓解显存压力。
避坑指南: 我曾经用QLoRA微调一个代码生成模型,发现量化后的模型在生成复杂代码时,偶尔会出现语法错误。所以,如果你对精度要求极高,建议还是用LoRA或全参数微调。
优点:
- 显存占用极低,可以在消费级显卡上微调大模型。
- 训练速度快,因为量化后的计算更快。
缺点:
- 精度略有损失,尤其在低bit量化时。
- 推理时需要反量化,会有额外计算开销。
2.6 方法对比总结
说了这么多,咱们来做个对比总结。我整理了一个表格,方便你快速选择。
| 方法 | 可训练参数量 | 显存占用 | 效果 | 推理开销 | 适用场景 |
|---|---|---|---|---|---|
| 全参数微调 | 100% | 极高 | 最好 | 无 | 数据量大、资源充足 |
| Adapter | 1%-5% | 低 | 较好 | 略有增加 | 多任务、存储受限 |
| Prefix Tuning | 0.1%-1% | 极低 | 中等 | 略有增加 | 生成任务 |
| LoRA | 0.1%-1% | 低 | 接近全参数 | 无(可合并) | 通用场景、推荐首选 |
| QLoRA | 0.1%-1% | 极低 | 接近LoRA | 略有增加 | 资源受限、消费级显卡 |
2.7 知识体系结构图
为了让你更直观地理解这些方法的关系,我画了一张图。这张图展示了微调方法的分类和核心逻辑。
我的建议: 如果你是新手,我建议从LoRA开始。它效果好、显存占用低、推理无开销,是性价比最高的选择。如果你连消费级显卡都没有,那就用QLoRA。如果你要做多任务,Adapter是个好选择。至于全参数微调,除非你资源充足且数据量大,否则不推荐。
好了,这一章的内容就到这里。记住,没有最好的方法,只有最适合的方法。根据你的资源、数据和任务需求,灵活选择才是王道。