第二章:主流微调方法概览——全参数微调、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 知识体系结构图

为了让你更直观地理解这些方法的关系,我画了一张图。这张图展示了微调方法的分类和核心逻辑。

微调方法知识体系 微调方法 暴力派:全参数微调 巧干派:PEFT 结构修改型 输入修改型 Adapter LoRA / QLoRA Prefix Tuning 核心逻辑:冻结原始模型,只训练少量参数 目标:在效果和效率之间找到最佳平衡点 推荐首选:LoRA

我的建议: 如果你是新手,我建议从LoRA开始。它效果好、显存占用低、推理无开销,是性价比最高的选择。如果你连消费级显卡都没有,那就用QLoRA。如果你要做多任务,Adapter是个好选择。至于全参数微调,除非你资源充足且数据量大,否则不推荐。

好了,这一章的内容就到这里。记住,没有最好的方法,只有最适合的方法。根据你的资源、数据和任务需求,灵活选择才是王道。

专注资料整理