登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

Transformers用LoRA做参数高效微调

来源:17golang原创

时间:2026-11-15 12:33:26 460浏览 收藏

全量微调需要为每个模型参数保存梯度和优化器状态,显存与检查点都会迅速变大。Transformers 配合 PEFT 使用 LoRA 时,可以冻结预训练底座,只在指定线性层旁训练低秩适配器;训练结束后保存的是一套轻量权重,而不是再复制一份完整模型。

官方文档:https://huggingface.co/docs/transformers/peft

要点速览
  • r 决定低秩更新的容量,target_modules 决定把适配器挂在哪里。
  • get_peft_model 会把底座改造成带适配器的模型,Trainer 只更新可训练参数。
  • 适配器要和兼容的底座一起加载;是否合并权重取决于部署时是否还要切换适配器。

先把训练边界定清楚:底座冻结,LoRA只改适配器

LoRA 的核心不是缩小原模型,而是在目标模块上增加两个低秩矩阵,用它们表达训练产生的增量。原始权重保持冻结,优化器只跟踪适配器参数,因此训练状态更轻,也便于为同一个底座保存多个领域适配器。

Transformers LoRA 结构说明图:冻结底座、注意力投影和可训练低秩适配器之间的关系
图1:结构说明图,展示冻结底座与 LoRA 适配器的边界关系,不是运行截图或训练结果。
参数作用判断思路
r低秩矩阵的秩越大容量越高,也会增加可训练参数
lora_alpha适配器缩放因子与 rank 一起观察更新幅度,不要脱离任务调参
lora_dropout适配器分支的 dropout小数据集可用来缓和过拟合
target_modules注入适配器的模块名必须匹配具体模型的层命名,不能照抄别的架构

用LoraConfig把训练范围写成代码

先以因果语言模型为例。下面的配置使用常见的 query/value 投影名;实际模型应先查看模块命名,再决定目标模块。get_peft_model 会原地改造模型对象,重复调用前要确认没有再次注入同一适配器。

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, TaskType, get_peft_model

model_id = "facebook/opt-350m"  # 示例底座;生产环境固定可复现的模型版本
tokenizer = AutoTokenizer.from_pretrained(model_id)  # 分词器必须与底座保持一致
model = AutoModelForCausalLM.from_pretrained(model_id)  # 先加载完整但尚未训练的底座

lora_config = LoraConfig(
    r=16,  # 用较小秩控制适配器容量
    lora_alpha=32,  # 控制 LoRA 更新的缩放
    lora_dropout=0.05,  # 小数据集上给适配器分支保留正则化
    target_modules=["q_proj", "v_proj"],  # 只在模型真实存在的投影层注入
    task_type=TaskType.CAUSAL_LM,  # 告诉 PEFT 当前任务是因果语言建模
    bias="none",  # 不额外训练底座 bias,减少更新范围
)

model = get_peft_model(model, lora_config)  # 注入低秩适配器并冻结底座
model.print_trainable_parameters()  # 用参数比例检查配置是否生效

这里最容易出错的是 target_modules。不同架构可能使用不同的投影命名;如果名称不存在,配置并没有覆盖你以为的注意力层。打印可训练参数只是第一层检查,还要在小批量数据上观察 loss 是否能正常下降。

交给Trainer训练,并只保存适配器

带适配器的模型可以像普通 Transformers 模型一样交给 Trainer。由于底座参数的 requires_grad 为关闭状态,Trainer 只会更新 LoRA 分支;检查点通常包含 adapter_model.safetensors 和 adapter_config.json,底座权重仍由部署侧单独提供。

Transformers Trainer 与 LoRA 适配器保存关系说明图:数据集、冻结底座、训练器和适配器检查点的静态关系
图2:关系说明图,展示 Trainer 训练后适配器权重与配置的保存边界,不是运行截图或实际指标。
from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir="./lora-output",  # 只把适配器检查点写入这个目录
    num_train_epochs=3,  # 训练轮数仍需结合数据规模和验证集调整
    per_device_train_batch_size=4,  # 显存不足时先降低单卡 batch
    save_strategy="steps",  # 保留可恢复训练的检查点
    save_steps=200,  # 生产任务应配合磁盘空间设置间隔
)

trainer = Trainer(
    model=model,  # 这里传入的是已经注入 LoRA 的模型
    args=training_args,
    train_dataset=dataset,  # dataset 需提前完成分词和标签整理
)
trainer.train()  # Trainer 只对可训练的适配器参数反向传播
model.save_pretrained("./lora-output/final-adapter")  # 保存最终适配器及配置

加载与合并的选择

恢复时必须使用兼容的底座,再从适配器目录加载参数。保留适配器的好处是可以在同一个底座上切换多个领域版本;只有部署链路不再需要切换、并且希望减少运行时组合逻辑时,才考虑合并权重。合并会改变交付物形态,不能把它当成训练成功的必要步骤。

from transformers import AutoModelForCausalLM
from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")  # 先恢复同一底座
model = PeftModel.from_pretrained(base, "./lora-output/final-adapter")  # 再加载适配器

# 只有部署不再切换适配器时才合并;合并后不便于继续管理独立适配器
# merged = model.merge_and_unload()

常见问题

LoRA 的 rank 越大效果一定越好吗?

不一定。rank 增大只代表更新容量和显存开销上升,数据规模、目标模块和训练步数同样影响结果。应从较小配置开始,用验证集比较。

为什么训练后的目录没有完整模型文件?

这是参数高效微调的预期行为。适配器目录记录增量权重和配置,推理时需要先准备对应底座,再加载适配器。

什么时候应该改用 QLoRA?

当底座模型本身难以放入显存时,可以把量化底座与 LoRA 结合,但量化 dtype、训练器和硬件约束需要单独核对,不能只替换一个参数。

判断这套方案是否合适,可以回到三个问题:目标层名称是否真实存在,训练数据是否足以区分领域增量,部署时是否需要同时保留多个适配器。三点都明确后,再调 rank 和缩放,通常比盲目扩大 LoRA 容量更稳。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>