显存直降 96%!深入浅出 LoRA 原理并微调 DINOv3 视觉大模型
LORA实战:10 分钟上手 DINOv3 编码器 LoRA 微调(附 PEFT 注入代码)
1.lora 原理
针对 Transformer 自注意力机制中的特征投影(Projection)阶段,也就是计算 QKV 的那一步,调整其投影权重
⭐Wq,k,v→W+ΔW=W+ABW_{q,k,v} \rightarrow W + \Delta W = W + ABWq,k,v→W+ΔW=W+AB
(1)标准的 Transformer 中计算 QKV采用以下投影方式( Linear(x) ):
Q=X⋅Wq,K=X⋅Wk,V=X⋅WvQ = X \cdot W_q, \quad K = X \cdot W_k, \quad V = X \cdot W_vQ=X⋅Wq,K=X⋅Wk,V=X⋅Wv
统一简化为:
Y=X⋅WY = X \cdot WY=X⋅W
(2)在全量微调时,模型训练的目标是更新这个大矩阵,也就是寻找一个增量矩阵 ΔW:
Wnew=W+ΔWW_{new} = W + \Delta WWnew=W+ΔW
(此时 W0W_0W0 和 ΔW\Delta WΔW 的尺寸一模一样,如果 W0W_0W0 有 1 亿个参数,你要训练的增量也有 1 亿个参数)。
(3)⭐⭐⭐LoRA 的核心思想来了:
LoRA 不直接训练庞大的 ΔWΔWΔW,而是把 ΔW 拆解成两个小矩阵 A 和 B 的乘积:
ΔW=A⋅B\Delta W = A \cdot BΔW=A⋅B
- 假设原始权重 W0W_0W0 的维度是 1000X10001000X10001000X1000(含 1,000,000 个参数)。
- 我们设定一个很小的秩(Rank,通常用 r 表示),比如 r=16r = 16r=16。
- 那么矩阵 AAA 的维度就是 1000∗161000*161000∗16。
- 矩阵 BBB 的维度就是 16∗100016*100016∗1000。
⭐ 此时,新的前向传播公式变成了:
Y=X⋅W+X⋅A⋅BY = X \cdot W + X \cdot A \cdot BY=X⋅W+X⋅A⋅B
算一笔账:
原来要训练的参数量:1000*1000 = 1000000
LoRA 要训练的参数量:(100016)+(161000) = 32000
参数量直接减少了 96.8%! 这也就是为什么你在单张家用显卡上也能微调大模型的原因。
(4)A 和 B 矩阵在做什么?
你可以把 X⋅A⋅BX \cdot A \cdot BX⋅A⋅B 这个过程理解为一个**“信息压缩再解压”**的过程:
- 降维(矩阵 A): 把高维的输入特征 X(1000 维) 压缩到一个极低的维度 r(比如 16 维)。这迫使模型提取出当前任务最核心、最关键的特征。
- 升维(矩阵 B): 将这 16 维的浓缩特征,重新映射回原来的高维空间(1000 维),以便与原始模型的输出 (X∗W0)(X*W0)(X∗W0)相加。
(这也是为什么我们之前提到,矩阵 A 用随机初始化提取特征,而矩阵 B 必须全 0 初始化,以保证初始状态下 A *B=0,不影响原始预训练模型输出。)
2.使用 peft 库进行 lora 注入微调
例如以下是对 DINOV3 模型的 LoRA 注入:
import torch
import torch.nn as nn
import timm
from peft import LoraConfig, get_peft_model
#==============================带有LoRA的DINOV3编码器===================================
class DINOV3_SAT_LORA(nn.Module):
def __init__(self, pre_ckpt_path,
image_size=512,
model_name='vit_large_patch16_dinov3.sat493m',
r=16,
lora_alpha=16):
super(DINOV3_SAT_LORA, self).__init__()
self.pre_ckpt_path = pre_ckpt_path
# 1. 创建基础模型
self.model = timm.create_model(
model_name = model_name,
pretrained=False,
features_only=True,
img_size=image_size,
dynamic_img_size=True,
out_indices=[4, 11, 17, 23]
)
# 2. 加载你的预训练权重 (必须在应用LoRA之前加载)
if self.pre_ckpt_path is not None:
self.load_weights()
#############-----------注入LoRA------------###########
# 3. 配置 LoRA
# timm 的 ViT 实现中,自注意力层的投影通常被命名为 'qkv'
# 如果你想进一步提升拟合能力,可以在 target_modules 中加入 "proj", "fc1", "fc2"
lora_config = LoraConfig(
r=r, # LoRA 的秩,通常设置为 8, 16, 32
lora_alpha=lora_alpha, # LoRA 的缩放系数,通常等于 r 或 2*r
target_modules=["qkv"], # 我们想要注入 LoRA 模块的具体网络层名称
lora_dropout=0.1, # 防止过拟合的 Dropout
bias="none", # 是否微调 bias 参数
modules_to_save=[] # 如果有额外的分类头需要全量微调,写在这里,这里做特征提取置空即可
)
# 4. 使用 PEFT 包装模型
# !!!get_peft_model 会自动冻结基础模型的参数,并只解冻 LoRA 相关的参数
self.model = get_peft_model(self.model, lora_config)
# 打印可训练参数量,确认 LoRA 是否注入成功
self.model.print_trainable_parameters()
更多推荐


所有评论(0)