当前位置: 首页 > news >正文

【模型学习】LoRA的原理,及deepseek-vl2下LoRA实现

【模型学习】LoRA的原理,及deepseek-vl2下LoRA实现

LoRA(Low-Rank Adaptation)是一种针对大型语言模型的微调技术,旨在降低微调过程中的计算和内存需求。其核心思想是通过引入低秩矩阵来近似原始模型的全秩矩阵,从而减少参数数量和计算复杂度。

基本原理

在LoRA中,原始模型的全秩矩阵被分解为低秩矩阵的乘积。

具体来说,对于一个全秩矩阵W,LoRA将其分解为两个低秩矩阵A和B的乘积,即W ≈ A * B。

其中,A和B的秩远小于W的秩,从而显著减少了参数数量。

在这里插入图片描述

其实现流程为:

  • 在原始预训练语言模型旁边增加一个旁路,做降维再升维的操作来模拟内在秩
  • 用**随机高斯分布初始化 A,**用零矩阵初始化B,训练时固定预训练模型的参数,只训练矩阵 A 与矩阵 B
  • 训练完成后,将 B 矩阵与 A 矩阵相乘后合并预训练模型参数作为微调后的模型参数

在这里插入图片描述

W是原始的权重矩阵,A是一个尺寸为dr的矩阵,B是一个尺寸为rd’的矩阵,r是低秩矩阵的秩

通过这种分解,原始矩阵W的更新仅由A和B的乘积决定

LoRA引入了一个缩放因子α,使得更新公式为

在这里插入图片描述

DeepSeek_vl2中LoRA的参数在

class DeepseekV2Config(PretrainedConfig):kv_lora_rank=512,  # 键值对LoRA的秩q_lora_rank=1536,  # 查询LoRA的秩

LoRA 的核心思想是通过对预训练模型中的特定层进行低秩矩阵插入,实现参数高效微调而无需修改原始权重

对于语言模型,通常选择影响权重更新较大的模块,例如q_projk_proj(负责查询和键的变换),v_proj(值的变换),以及o_proj(输出投影)等。

Rotary Embedding:虽然在一些实现中会对嵌入进行微调,但通常LoRA不会直接用于rotary_emb,因为它通常是固定的。


MLP层中的Gate、Up和Down投影

  • gate_proj:控制门投影 -

  • up_proj:上升投影 -

  • down_proj:下降投影 -

    MLP层的Gate、Up和Down投影通常涉及大量的可训练参数,因此对这些投影进行LoRA微调,可以在不显著增加计算负担的情况下优化模型表现。

    通过低秩适应,LoRA能够在减少参数量的同时,增强模型对复杂模式的适应能力。

    这些曾在处理非线性变换时起到重要作用,通常也是LoRA微调的目标。


在这里插入图片描述

DeepSeek_vl2 LoRA实现

class DeepseekVLV2ForCausalLM(DeepseekVLV2PreTrainedModel):def __init__(self, config):# 1. 视觉编码器 - SigLIP ViT (无LoRA)self.vision = VisionTransformer(...)# 2. 投影器 - 连接视觉和语言self.projector = MlpProjector(...)# 3. 语言模型 - DeepSeek-V2 (有LoRA)self.language = DeepseekV2ForCausalLM(language_config)

LoRA只在语言模型中实现,并且Deepseekvl2的语言模型是用的llama框架(虽然我也没接触过)

Copied from transformers.models.llama.modeling_llama.LlamaAttention with Llama->DeepseekV2

from transformers.models.llama.modeling_llama import (LlamaAttention,LlamaFlashAttention2
)ATTENTION_CLASSES = {# MLA (Multi-Latent Attention) - DeepSeek-V2的默认注意力"eager": DeepseekV2Attention,                    # 标准MLA实现"flash_attention_2": DeepseekV2FlashAttention2,  # MLA + Flash Attention 2# MLA的别名"mla_eager": DeepseekV2Attention,"mla_flash_attention_2": DeepseekV2FlashAttention2,# MHA (Multi-Head Attention) - 传统注意力"mha_eager": LlamaAttention,                     # 标准MHA实现"mha_flash_attention_2": LlamaFlashAttention2   # MHA + Flash Attention 2
}# 在SS-lora.py中,可以这样配置:
lora_config = {'enable_lora': True,'q_lora_rank': 256,'kv_lora_rank': 128,'_attn_implementation': 'flash_attention_2'  # 选择注意力类型
}# 在模型初始化时
config = DeepseekV2Config(**language_config)
config._attn_implementation = lora_config.get('_attn_implementation', 'eager')

deepseek_vl2下完整的LoRA调用层次

DeepseekVLV2ForCausalLM
├── self.language = DeepseekV2ForCausalLM(language_config)├── DeepseekV2ForCausalLM.__init__(config)├── self.model = DeepseekV2Model(config)├── DeepseekV2Model.__init__(config)├── self.embed_tokens = nn.Embedding(...)├── self.layers = nn.ModuleList([...])├── DeepseekV2DecoderLayer(config, layer_idx)├── self.self_attn = ATTENTION_CLASSES[attn_implementation]├── DeepseekV2Attention(config, layer_idx)  # 包含LoRA│   ├── q_a_proj, q_b_proj (LoRA)│   ├── kv_a_proj, kv_b_proj (LoRA)│   └── 其他注意力组件└── 或者 DeepseekV2FlashAttention2├── self.mlp = DeepseekV2MLP/MoE└── 层归一化└── self.norm = DeepseekV2RMSNorm(...)└── self.lm_head = nn.Linear(...)

具体的看下DeepseekV2Attention(config, layer_idx)中的调用代码

class DeepseekV2Attention(nn.Module):def __init__(self, config, layer_idx):# LoRA参数self.q_lora_rank = config.q_lora_rank      # 1536self.kv_lora_rank = config.kv_lora_rank    # 512# LoRA层if self.q_lora_rank is not None:self.q_a_proj = nn.Linear(hidden_size, q_lora_rank)self.q_b_proj = nn.Linear(q_lora_rank, output_dim)self.kv_a_proj_with_mqa = nn.Linear(self.hidden_size,config.kv_lora_rank + config.qk_rope_head_dim,bias=config.attention_bias,)self.kv_a_layernorm = DeepseekV2RMSNorm(config.kv_lora_rank)self.kv_b_proj = nn.Linear(config.kv_lora_rank,self.num_heads* (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim),bias=False,)   

到这里为止我们也就确定了A和B,但我们需要知道q和KV为什么需要生成两组A,B。缩放因子α和参数r又在代码中哪里体现了

这里进行补充说明理解

本文信息在完整的输入进来

实际上,本文信息所有token都经过相同的输入hidden_states = self.embed_tokens(input_ids)  # [batch, seq_len, hidden_size]# 然后通过不同的投影层生成Q、K、V
q = self.q_proj(hidden_states)  # 查询投影
k = self.k_proj(hidden_states)  # 键投影  
v = self.v_proj(hidden_states)  # 值投影在DeepSeek-VL2中,K和V被合并处理:
# 合并的KV投影 (DeepseekV2Attention中可以找到对应的处理方式)
self.kv_a_proj_with_mqa = nn.Linear(self.hidden_size,config.kv_lora_rank + config.qk_rope_head_dim,  # 512 + 64 = 576bias=config.attention_bias,
)def forward(self, hidden_states):# 1. 生成Q(查询)if self.q_lora_rank is None:q = self.q_proj(hidden_states)  # 标准投影else:q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(hidden_states)))  # LoRA投影# 2. 生成合并的KVcompressed_kv = self.kv_a_proj_with_mqa(hidden_states)  # 4096 → 576# 3. 分离K和Vcompressed_kv, k_pe = torch.split(compressed_kv, [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1)# compressed_kv: [batch, seq_len, 512] - 用于生成K和V# k_pe: [batch, seq_len, 64] - RoPE位置编码# 4. 通过B矩阵生成最终的K和Vkv_b_proj = self.kv_b_proj.weight.view(self.num_heads, -1, self.kv_lora_rank)# 从512维度扩展到最终的K和V维度通过kv_lora_b分离出K和V,也就是B矩阵
k = kv_lora_b_k(compressed_kv) → [batch, seq_len, k_dim]
v = kv_lora_b_v(compressed_kv) → [batch, seq_len, v_dim]

通过以上细读代码,其实我们可以注意到DeepSeek-VL2中的LoRA实现没有显式的缩放因子α

DeepSeek-VL2的LoRA实现是"替换式"而非"加法式"

# 标准LoRA(加法式)
output = original_projection(x) + α * lora_projection(x)# DeepSeek-VL2 LoRA(替换式)
output = lora_projection(x)  # 直接替换原始投影对相应的步骤进行简单实现
# 查询LoRA
if self.q_lora_rank is None:q = self.q_proj(hidden_states)  # 原始投影
else:q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(hidden_states)))  # LoRA投影# 键值LoRA
compressed_kv = self.kv_a_proj_with_mqa(hidden_states)
compressed_kv = self.kv_a_layernorm(compressed_kv)

这里的r也对用的表示为LoRA他降维的维度

lora_config = {'q_lora_rank': 256,    # r参数'kv_lora_rank': 128,   # r参数# 没有α参数,因为DeepSeek-VL2不使用
}# q_lora_rank = 256 表示:
q_a_proj: 4096 → 256    # A矩阵:降维到256
q_b_proj: 256 → 6144    # B矩阵:从256升维到输出维度# kv_lora_rank = 128 表示:
kv_a_proj: 4096 → 128   # A矩阵:降维到128  
kv_b_proj: 128 → 8192   # B矩阵:从128升维到输出维度# 原始全连接层
W: [6144, 4096]  # 完整权重矩阵# LoRA分解
A: [256, 4096]   # r=256,降维矩阵
B: [6144, 256]   # r=256,升维矩阵# 数学表示
W ≈ B * A  # 低秩近似

总结来说,如果我们在调用DeepSeek_vl2的LoRA时,其实只需要关心Q_r和KV_r这两组参数值就可以了,值得注意的kv_lora_rank的A和B矩阵是架构必需的,q_lora_rank才是可选的!

def _freeze_backbone_parameters(self):for name, param in self.backbone.named_parameters():# 只训练LoRA相关参数if 'q_a_proj' in name or 'q_b_proj' in name or 'kv_a_proj' in name or 'kv_b_proj' in name:param.requires_grad = True  # 可训练else:param.requires_grad = False  # 冻结# 可训练参数(requires_grad = True)
✅ q_a_proj.weight, q_a_proj.bias
✅ q_b_proj.weight
✅ kv_a_proj.weight, kv_a_proj.bias  # KV的A矩阵
✅ kv_b_proj.weight                   # KV的B矩阵# 冻结参数(requires_grad = False)
❌ q_proj.weight, q_proj.bias(如果存在)
❌ o_proj.weight, o_proj.bias
❌ vision编码器的所有参数
❌ projector的所有参数
❌ language模型的其他参数# 在原始DeepSeek-VL2中,所有参数默认都是可训练的:
# ✅ vision编码器的所有参数
# ✅ projector的所有参数  
# ✅ language模型的所有参数
# ✅ 包括q_proj, kv_proj, o_proj等# 没有内置的冻结机制
http://www.xdnf.cn/news/1436311.html

相关文章:

  • 力扣242:有效的字母异位词
  • JetBrains 2025 全家桶 11合1 Windows直装(含 IDEA PyCharm、WebStorm、DataSpell、DataGrip等
  • C++类和对象(中)- 默认成员函数
  • 什么是数据库管理系统(DBMS)?RDBMS和NoSQL又是什么?
  • 第 2 讲:Kafka Topic 与 Partition 基础
  • Qwen3-Embedding-0.6B 模型结构
  • Go结构体详解:核心概念与实战技巧
  • Redis-底层数据结构篇
  • MySQL-表的约束(上)
  • 开发中使用——鸿蒙本地存储之收藏功能
  • LLM 能不能发展为 AGI?
  • 开源模型应用落地-模型上下文协议(MCP)-构建AI智能体的“万能插座”-“mcp-use”高级用法(十三)
  • 3.2-C++基础组件
  • 重新审视信任基石:公网IP证书对网络安全生态的影响
  • 【Go语言入门教程】 Go语言的起源与技术特点:从诞生到现代编程利器(一)
  • Cursor 教我学 Python
  • 英伟达Jetson Orin NX-YOLOv8s目标检测模型耗时分析
  • 深度集成Dify API:企业级RAG知识库管理平台解决方案
  • ts,js文件中使用 h函数渲染组件
  • 美国服务器连接速度变慢时应该着重做哪些检查?
  • 双Token实战:从无感刷新到安全防护,完整流程+代码解析
  • PostgreSQL(1) FETCH用法
  • 【MySQL体系结构详解:一条SQL查询的旅程】
  • 《一篇拿下!C++:类和对象(中)构造函数与析构函数》
  • Java 21 虚拟线程 + 分布式调度深度实战:从原理到落地,大促日志同步效率提升 367%
  • 基于SpringBoot的校园资料分享平台
  • Mysql数据库基础(上)
  • 第1章:VisualVM 简介与安装
  • 东土科技战略升级:成立半导体子公司,赋能国产半导体智能化升级
  • 基于 HTML、CSS 和 JavaScript 的智能图像锐化系统