AI参数高效微调折腾手记
前一阵子在做一个垂域知识库问答项目时,遇到个现实问题:基础模型用的是 7B 参数量级,单张 24G 显卡勉强能装下,但要微调时就彻底不行了。
全量微调的问题有几个方面:
- 显存占用高:除了模型权重,优化器状态(Adam 的两个动量)和梯度都要占一份。
背景与限制
先说清楚当时的限制条件,避免后面有人问"为什么不用 4×A100":
- 单机单卡环境,GPU 只有 24G 显存
- 目标是让模型掌握行业术语和写作风格,不是从零训练
- 数据量大概 5K 条高质量问答对,不算多也不算少
- 训练时间有限,不能等上一周再看到结果
这些条件其实挺典型的,很多中小企业或个人开发者都是这个级别。全量微调在资源足够时确实效果最好,但现实就是这样。
为什么需要 PEFT
全量微调的问题有几个方面:
显存占用高:除了模型权重,优化器状态(Adam 的两个动量)和梯度都要占一份。对于 7B 模型,仅模型权重就占 14G(fp16),加上优化器状态和梯度,总共可能要 50G+ 显存
训练慢:所有参数都要参与前向和反向传播,计算量大
存储开销大:每个微调任务都要保存一份完整模型权重
PEFT 的思路很简单:大部分参数保持冻结,只训练一小部分参数或引入少量可训练适配器。这样既能保持模型能力,又能大幅降低资源需求。

这张图直接说明了问题:全量微调的显存需求是 PEFT 的 2-3 倍,这也是为什么在资源有限时 PEFT 几乎是唯一可行方案。
主要 PEFT 方法对比
当时看了几种主流方法,各有特点。
LoRA(Low-Rank Adaptation)
LoRA 的想法是:在权重矩阵的更新量上做低秩分解。假设原始权重是 $W$,训练时只更新 $\Delta W$,LoRA 把 $\Delta W$ 分解成两个小矩阵 $A$ 和 $B$ 的乘积:$\Delta W = BA$。
具体实现时,在前向传播中这样计算:
$$W_{new} = W + BAx$$
其中 $A \in \mathbb{R}^{r \times d}$,$B \in \mathbb{R}^{d \times r}$,$r$ 是秩,通常取 4、8、16 这些小值。
这个方法的好处是:训练时只优化 $A$ 和 $B$,参数量极少;推理时可以把 $BA$ 融合进 $W$,不增加推理开销。
Prefix Tuning
Prefix Tuning 的思路是在每层注意力机制前面加一段可训练的"前缀向量"。这些前缀向量会影响模型对输入的处理方式,相当于在内部"打标签"。
但这个方法有个问题:前缀向量要占用 KV Cache 的空间,推理时没优势。另外调参比较玄学,前缀长度少了不够用,多了又占资源。
Adapter
Adapter 是在 Transformer 层之间插入小型神经网络模块,通常是两层 MLP,中间有个瓶颈层。原始层权重冻结,只训练这些 Adapter。

设计上挺合理,但问题也很明显:推理时每一层都要多做一次前向传播,延迟增加明显。对实时场景不友好。
其他方法
还有 Prompt Tuning(只优化 prompt 嵌入)、LoRA 变体(LoRA+、DoRA)、AdaLoRA(动态调整秩)等,但核心思想都是"少改一点,多学一点"。
实际选择与实现
综合考虑后,选了 LoRA 作为主要方案。理由简单粗暴:
- 推理零开销,这点对线上服务很重要
- 参数量可控,显存压力小
- 实现成熟,社区支持好
- 调参相对直观
环境配置
先装必要依赖,这里用 HuggingFace 的 PEFT 库:
pip install peft transformers datasets accelerate bitsandbytes
代码里这样配置 LoRA:
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
# 4bit 量化,进一步降低显存占用
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
"model-path",
quantization_config=bnb_config,
device_map="auto"
)
# LoRA 配置
lora_config = LoraConfig(
r=16, # 秩,越大表达能力越强,但参数量也多
lora_alpha=32, # 缩放因子,通常设为 2×r
target_modules=["q_proj", "v_proj"], # 只微调注意力层
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
这里有个小坑:target_modules 选择很重要。只微调 q_proj、v_proj 是常见做法,但有些模型架构不同,需要改成 ["q_proj", "k_proj", "v_proj", "o_proj"] 或其他组合。第一次训练时要检查模型实际结构。
训练参数
训练时主要调整这些参数:
training_args = TrainingArguments(
output_dir="./output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
warmup_steps=100,
logging_steps=10,
save_steps=500,
fp16=True,
optim="paged_adamw_32bit", # 显存不够时用这个
)
gradient_accumulation_steps=4 意味着每 4 个 batch 才更新一次参数,相当于 batch size 是 16。这是个常用技巧,用时间换空间。
踩坑记录
实际跑起来后,遇到了几个值得记一下的问题。
1. rank 太小效果差
刚开始设了 $r=4$,参数量确实很少,但训练完后模型输出几乎没有变化,还是原始模型的调性。调到 $r=16$ 后才明显感受到风格变化。
这里有个规律可以参考:任务越简单,rank 可以越小;需要大幅改写风格或学习新知识,rank 要适当大。但大到一定程度(比如超过 64)后,收益就不明显了,反而增加训练时间。

2. 学习率过大导致不稳定
LoRA 的参数量少,一开始用了全量微调常用的 1e-4,结果训练到一半 loss 直接爆炸。降到 2e-4 后才稳定,但还是要配合梯度裁剪。
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
data_collator=data_collator,
max_grad_norm=0.3, # 梯度裁剪
)
3. 数据质量比数量重要
刚开始贪多,把所有能找到的领域数据都灌进去,结果模型学会了些奇怪的表述。后来人工清洗了一遍,只保留高质量样本,效果反而好了。
这是个老生常谈的问题,但在 PEFT 场景下尤其明显——因为本身可训练参数就少,如果数据质量不行,那点参数很快就学偏了。
4. 推理时忘合并权重
LoRA 训练完后得到的是适配器权重,推理前要合并回原始模型,否则推理时多一次计算,而且可能没适配好。
from peft import PeftModel
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained("model-path")
# 加载 LoRA 适配器
model = PeftModel.from_pretrained(base_model, "checkpoint-dir")
# 合并权重
merged_model = model.merge_and_unload()
# 保存合并后的模型
merged_model.save_pretrained("merged-model")
效果对比
最后对比一下几种方法的实际表现(以 7B 模型为例):
| 方法 | 可训练参数量 | 显存占用 | 训练时间 | 效果 |
|---|---|---|---|---|
| 全量微调 | 7B | ~50G | 慢 | 最好 |
| LoRA (r=16) | ~40M | ~16G | 快 | 接近全量 |
| Adapter | ~60M | ~20G | 中等 | 接近全量 |
| Prefix Tuning | ~30M | ~18G | 中等 | 较差 |
这里的效果评估是主观的,主要看领域问答的准确性和风格适配程度。LoRA 在这个任务上基本能达到全量微调 90% 的效果,但资源需求只有 1/3。
写在后面
PEFT 不是银弹,但在资源有限时确实是个实用方案。它解决的核心问题是:在模型能力保持和资源消耗之间找个平衡点。
实践中,选哪个方法要考虑具体场景。如果推理延迟敏感,LoRA 优先;如果训练资源更紧,可以考虑更激进的方法;如果效果要求极高,那就只能上全量微调,或者换更贵的硬件。
最后提醒一点:PEFT 的参数量少了,调参时反而更要小心,稍微偏一点模型就学不到东西。多观察训练曲线,早点发现异常,能省不少时间折腾。
版权声明: 本文首发于 指尖魔法屋-AI参数高效微调折腾手记(https://blog.thinkmoon.cn/post/383-ai-peft-full-efficient-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。