引言

随着预训练语言模型规模越来越大(从几亿到上千亿参数),全量微调(Full Fine-tuning)的成本变得令人望而却步——需要存储完整的梯度、优化器状态,并且在每个下游任务上保存一份完整的模型副本。参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)应运而生,它只训练极少量的额外参数,却能取得接近甚至超越全量微调的效果。

本文将系统介绍 6 类最常用的微调方法:FreezePrefix-TuningPrompt TuningP-Tuning v1/v2LoRA 以及 QLoRA。我们会剖析原理、给出关键参数、对比优劣,并用表格和图示帮助记忆。


1. Freeze 方法 – 最简单的“冻结”思路

Freeze 即冻结模型的大部分参数,只训练最后几层(或指定层)。例如在 BERT 上做文本分类时,冻结全部 Transformer 层,只微调最后的分类头。

  • 原理:保持预训练权重不变,仅更新靠近输出层的少量参数。

  • 典型配置

    • 冻结所有 bert.encoder 层

    • 训练 classifier 层

  • 优点:实现简单,显存占用极低。

  • 缺点:表达能力有限,除非任务与预训练数据非常相似,否则效果常不如其他 PEFT 方法。

python

# 伪代码示例
for name, param in model.named_parameters():
    if "classifier" not in name:
        param.requires_grad = False

2. P-tuning 系列:让提示变得“连续可微”

P-tuning 家族包含 Prefix-TuningPrompt TuningP-Tuning v1 和 P-Tuning v2。它们核心思想相同:在输入前后添加可学习的连续向量(soft prompt),而不改变原始模型权重。

2.1 Prefix-Tuning (2021)

  • 提出场景:自然语言生成(NLG),如摘要、对话。

  • 做法:在每个 Transformer 层的 Key 和 Value 之前拼接可学习的前缀向量(prefix)。每一层有自己的前缀,长度为 prefix_length

  • 特点:参数量较大(层数 × 每层前缀长度 × 隐藏维度),但在生成任务中效果很好。

2.2 Prompt Tuning (2021)

  • 提出场景:NLU 任务(如分类、蕴含)。

  • 做法:仅在 输入嵌入层 前添加一个可学习的连续提示向量(长度为 num_virtual_tokens),模型其余部分完全冻结。

  • 特点:参数量极小(仅 20K~100K),显存几乎不增加。当模型规模大于 10B 时,效果可媲美全量微调。

2.3 P-Tuning v1 (2021)

  • 改进点:Prompt 的位置不限于开头,可以插入到输入序列的任意位置。同时为了考虑 prompt 之间的相关性,使用 双向 LSTM 或 MLP 对 prompt embedding 进行重参数化。

  • 适用:NLU 与少样本场景。

  • 缺陷:在大模型上不稳定,且无法很好地处理序列标注等复杂任务。

2.4 P-Tuning v2 (2022)

  • 核心变化:将 Prefix-Tuning 的思路移植到 NLU 并做了深度优化。

    • 每一层 Transformer 前都加入可学习的连续提示(deep prompt)。

    • 移除重参数化模块(不再需要 LSTM),直接用可学习向量。

    • 适配序列标注、多项选择等复杂任务。

  • 效果:在 10B 模型上超越全量微调,且推理速度无明显损失。

关系小结
Prompt Tuning (仅输入层) ← 更早
P-Tuning v1(任意位置 + LSTM)
Prefix-Tuning(每一层的 K/V 前缀) → 启发
P-Tuning v2(每一层前缀 + 无重参数化) = 融合前两者优势的 NLU 版

方法 可训练位置 是否重参数化 擅长任务 参数量级
Prefix-Tuning 各层 K/V NLG(生成) 中(Layer×L×d)
Prompt Tuning 仅输入层 大模型 NLU 极小(L×d)
P-Tuning v1 任意位置 是(LSTM) 小/中模型 NLU
P-Tuning v2 各层前缀 全任务(尤其 NLU) 中(Layer×L×d)

3. LoRA – 低秩适配的典范

LoRA (Low-Rank Adaptation) 是目前最流行的 PEFT 方法之一。其核心假设是:参数更新矩阵 ∆W 是低秩的

3.1 原理

对于预训练权重矩阵 W0∈Rd×kW0​∈Rd×k,LoRA 不直接更新 W0W0​,而是引入两个低秩矩阵 A∈Rr×kA∈Rr×k,B∈Rd×rB∈Rd×r,其中 r≪min⁡(d,k)r≪min(d,k):

W′=W0+αr⋅BAW′=W0​+rα​⋅BA

训练时只更新 AA 和 BB,W0W0​ 完全冻结。前向传播时,h=W0x+αrBAxh=W0​x+rα​BAx。

3.2 关键参数

参数名 含义 典型值 说明
r (rank) 低秩矩阵的秩 4, 8, 16 越大则拟合能力越强,参数量线性增加
alpha 缩放系数 16, 32 调整 LoRA 输出的强度,实际学习率与 alpha/r 相关
dropout Dropout 概率 0.1 防止过拟合
target_modules 应用 LoRA 的模块名 ["q_proj","v_proj"] 一般作用于注意力层的 Q 和 V 矩阵

3.3 LoRA 的优势(图表示意)

  • 显存节省:只存储低秩矩阵的梯度,减少约 10,000 倍可训练参数。

  • 无推理延迟:可以将 BABA 合并到 W0W0​ 中,实现 zero-cost 推理。

  • 快速任务切换:一个基座模型保存多个 LoRA 模块,按需加载。


4. QLoRA – 量化 + LoRA 的极致压缩

QLoRA 在 LoRA 基础上引入 4-bit NormalFloat (NF4) 量化 + 双重量化 + 分页优化器,使得在 48GB 显存上能微调 65B 模型。

4.1 核心技术

  1. NF4 量化:一种信息论上最优的 4-bit 数据类型,适用于正态分布的权重。

  2. 双重量化:对量化常数再做一次量化,额外节省内存。

  3. 分页优化器:当显存不足时自动将梯度页到 CPU 内存,避免 OOM。

4.2 关键参数(HuggingFace bitsandbytes 配置)

python

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,                      # 启用4bit加载
    bnb_4bit_quant_type="nf4",              # 使用NF4量化
    bnb_4bit_use_double_quant=True,         # 双重量化
    bnb_4bit_compute_dtype=torch.bfloat16,  # 计算精度
)
参数 作用
load_in_4bit 将基座模型以 4-bit 加载
bnb_4bit_quant_type 可选 "fp4" 或 "nf4",推荐 "nf4"
bnb_4bit_use_double_quant 是否对量化常数再量化,节省约 0.4% 显存
bnb_4bit_compute_dtype 进行 LoRA 运算时使用的精度(通常是 bfloat16)

4.3 对比实验效果

在众多评测中,QLoRA 训练出的模型与全量微调或标准 LoRA 性能几乎持平,但显存占用降低 3~4 倍。例如:

方法 模型大小 显存占用 (训练) 推理性能
全量微调 33B 约 120GB 100%
LoRA (16bit) 33B 约 45GB 99.2%
QLoRA (4bit) 33B 约 21GB 99.0%

5. 所有方法综合对比表格

方法 可训练参数占比 典型显存节省 是否改变推理架构 典型适用场景 参数调节要点
Freeze 0.1%~5% 简单分类 选择正确的解冻层
Prompt Tuning <0.01% 极高 否(加前缀token) >10B模型的NLU 虚拟token长度
Prefix-Tuning ~0.1% 否(每层加前缀) 生成任务 prefix长度,作用于K/V
P-Tuning v1 ~0.1% 中小模型少样本 prompt插入位置、LSTM
P-Tuning v2 ~0.1%~0.5% 全任务(尤其序列标注) 深度提示层数
LoRA 0.1%~1% 可合并(无延迟) 通用,最流行 rank r,alpha,target modules
QLoRA <0.5% + 4bit 极高 可合并(无延迟) 超大模型消费级硬件 同上 + 4bit配置

显存节省:相比全量微调(基线),通常 Freeze 节省 50%~70%,P-tuning / LoRA 节省 70%~90%,QLoRA 节省 90%+。


6. 如何选择?一张决策图

经验法则

  • 追求通用性 + 社区生态:LoRA(首选)

  • 要在单卡 24GB 微调 70B 模型:QLoRA

  • 任务为 NLG 且想尝鲜:Prefix-Tuning

  • 使用 T5 或 ChatGLM 等原生支持 P-Tuning v2 的模型:直接用官方实现


7. 代码片段示例(LoRA + QLoRA)

LoRA 配置 (peft 库)

python

from peft import LoraConfig, get_peft_model

lora_config = LoraConfig(
    r=8,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.1,
    bias="none",
    task_type="CAUSAL_LM",
)
peft_model = get_peft_model(base_model, lora_config)

QLoRA 完整训练流程

python

from transformers import BitsAndBytesConfig, AutoModelForCausalLM
from peft import prepare_model_for_kbit_training, LoraConfig, get_peft_model

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
)

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b", quantization_config=bnb_config)
model = prepare_model_for_kbit_training(model)

lora_config = LoraConfig(r=8, lora_alpha=32, target_modules=["q_proj","v_proj"])
model = get_peft_model(model, lora_config)
# ... 正常训练

结语

从简单的 Freeze 到精巧的 LoRA,再到极致的 QLoRA,参数高效微调让大模型落地不再遥不可及。希望本文的讲解和对比表格能帮助你快速选出最适合自己任务与资源的方法。下次微调时,不妨先问自己:我真的需要更新全部参数吗?

如果觉得有用,欢迎分享、收藏。有任何疑问欢迎评论区讨论~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐