通俗理解大模型的各大微调方法:从LoRA、QLoRA到P-Tuning V1/V2

前言

PEFT 方法仅微调少量(额外)模型参数,同时冻结预训练 LLM 的大部分参数

第一部分 高效参数微调的发展史

1.1 Google之Adapter Tuning:嵌入在transformer里 原有参数不变 只微调新增的Adapter

谷歌的研究人员首次在论文《Parameter-Efficient Transfer Learning for NLP》提出针对 BERT 的 PEFT 微调方式,拉开了 PEFT 研究的序幕。他们指出

  • 在面对特定的下游任务时,如果进行 Full-fintuning(即预训练模型中的所有参数都进行微调),太过低效
  • 而如果采用固定预训练模型的某些层,只微调接近下游任务的那几层参数,又难以达到较好的效果

于是他们设计了如下图所示的 Adapter 结构

image.png

  1. 如上图左侧所示,将其嵌入 Transformer 的结构里面,在训练时,固定住原来预训练模型的参数不变,只对新增的 Adapter 结构进行微调
  2. 如上图右侧所示,同时为了保证训练的高效性(也就是尽可能少的引入更多参数),他们将 Adapter 设计为这样的结构:首先是一个 down-project 层将高维度特征映射到低维特征,然后过一个非线形层之后,再用一个 up-project 结构将低维特征映射回原来的高维特征;同时也设计了 skip-connection 结构,确保了在最差的情况下能够退化为 identity

从实验结果来看,该方法能够在只额外对增加的3.6%参数规模(相比原来预训练模型的参数量)的情况下取得和Full-finetuning接近的效果(GLUE指标在0.4%以内)

1.2 斯坦福之Prefix Tuning

第二部分 LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

2.1 什么是LoRA

如此文《LLaMA的解读与其微调:Alpaca-LoRA/Vicuna/BELLE/中文LLaMA/姜子牙/LLaMA 2》中的2.2.3节Alpaca-LoRA:通过PEFT库在消费级GPU上微调「基于LLaMA的Alpaca」所述,在神经网络模型中,模型参数通常以矩阵的形式表示。对于一个预训练好的模型,其参数矩阵已经包含了很多有用的信息。为了使模型适应特定任务,我们需要对这些参数进行微调

LoRA的核心思想是用一种低秩的方式来调整这些参数矩阵。在数学上,低秩意味着一个矩阵可以用两个较小的矩阵相乘来近似,通过论文《LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS》可知

  1. 选择目标层:首先,在预训练神经网络模型中选择要应用LoRA的目标层。这些层通常是与特定任务相关的,如自注意力机制中的查询Q和键K矩阵
  2. 初始化映射矩阵和逆映射矩阵:为目标层创建两个较小的矩阵A和B
    \rightarrow  A是映射矩阵(一般用随机高斯分布初始化,当然实际代码实现时,比如微软的deepspeed chat在用到LoRA时,一开始通过0矩阵占位,然后调用搭配ReLU激活函数的kaiming均匀分布初始化,虽与LoRA原始定义所用的正态分布初始化不同,但此两种初始化方式都可以工作,更多介绍见下面deepspeed chat的代码 ),维度上是降维
    \rightarrow  B是逆映射矩阵(用0矩阵初始化),维度上是升维
    其中,矩阵的大小由LoRA的秩(rank)和alpha值确定
  3. 参数变换:将目标层的原始参数矩阵W通过映射矩阵A和逆映射矩阵B进行变换。计算公式为:W' = W + A * B,这里W'是变换后的参数矩阵
  4. 微调模型:使用新的参数矩阵W'替换目标层的原始参数矩阵W,然后在特定任务的训练数据上对模型进行微调
  5. 梯度更新:在微调过程中,计算损失函数关于映射矩阵A和逆映射矩阵B的梯度,并使用优化算法(如Adam、SGD等)对A和B进行更新
    注意,在更新过程中,原始参数矩阵W保持不变,说白了,训练的时候固定原始PLM的参数,只训练降维矩阵A与升维矩阵B
  6. 重复更新:在训练的每个批次中,重复步骤3-5,直到达到预定的训练轮次(epoch)或满足收敛条件

总之,LoRA的详细步骤包括选择目标层、初始化映射矩阵和逆映射矩阵、进行参数变换和模型微调。在微调过程中,模型会通过更新映射矩阵U和逆映射矩阵V来学习特定任务的知识,从而提高模型在该任务上的性能

2.2 微软DeepSpeed-Chat中对LoRA微调的实现

继续说一下,这个LoRA的应用还是挺广的,比如后续微软推出的DeepSpeed-Chat便用了这个方法

DeepSpeed-Chat的实现中,当设置LoRA的低秩维度lora_dim(如lora_dim=128)时,即认为启用了LoRA训练,则将原始模型中名称含有“deoder.layers.”且为线性层修改为LoRA层,具体操作为:

  1. 将原始结构的weight参数冻结;
  2. 新引入了2个线性层lora_right_weight和lora_left_weight (分别对应上图中的降维矩阵A、升维矩阵B ),可实现先降维至lora_dim再升维回原维度;
  3. LoRA层主要实现了两分支通路,一条分支为已被冻结weight参数的原始结构、另一条分支为新引入的降维再升维线性层组
# applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py
# 判断是否启用LoRA模式
if args.lora_dim > 0:
"""
如果启用,则对名称中含有“decoder.layers.”且为线性层的结构部分引入LoRA旁路(实现先降维后升维的2个线性层),
这类结构基本都是attention、信息交互用的inner线性层,
这类结构的Weight参数将被冻结,转而优化LoRA旁路的参数。
"""
    args.lora_module_name = "decoder.layers."
    model = convert_linear_layer_to_lora(model, args.lora_module_name,
                                         args.lora_dim)

# applications/DeepSpeed-Chat/training/utils/module/lora.py
def convert_linear_layer_to_lora(model,
                                 part_module_name,
                                 lora_dim=0,
                                 lora_scaling=1,
                                 lora_droppout=0):
    """
	将名称中带有"decoder.layers."的线性层转换为lora层
	"""
	"""取出模型中参数名含有decoder.layers.的线性层"""
    repalce_name = []
    for name, module in model.named_modules():
        if isinstance(module, nn.Linear) and part_module_name in name:
            repalce_name.append(name)

    for name in repalce_name:
    	"""recursive_getattr实现了从model中根据属性名取出对应原始结构"""
        module = recursive_getattr(model, name)

        """纳入原始结构的参数,实例化lora层"""
        tmp = LinearLayer_LoRA(
            module.weight, lora_dim, lora_scaling, lora_droppout,
            module.bias).to(module.weight.device).to(module.weight.dtype)

        """recursive_getattr实现了将model对应属性的结构换成lora层实例"""
        recursive_setattr(model, name, tmp)
    return model
    
# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):
	"""具体的lora层"""
	def __init__(...):
		...
		"""此处的weight和bias即为原始结构中的参数"""
		self.weight = weight
		self.bias = bias
		···

		"""冻结weight部分的参数"""
		self.weight.requires_grad = False
		···
		self.lora_right_weight = nn.Parameter(torch.zeros(columns, lora_dim))
	    self.lora_left_weight = nn.Parameter(torch.zeros(lora_dim, rows))
        ...

	    """初始化LoRA线性层的参数"""
	    self.reset_parameters()

    # 调用reset_parameters(self)做初始化
    def reset_parameters(self):
    	# 降维矩阵与LoRA原始定义所用的(0,\sigma^2)正态分布初始化不同,而是使用的kaiming均匀分布初始化
    	# kaiming服从均匀分布U(-\sqrt{1/in_feature}, +\sqrt{1/in_feature})
        # f_i是矩阵的输入维度,就是nn.Linear(in_features, out_features)中的in_features
        # 对应上面代码中的columns,而这个columns相当于基座模型的hidden_size
        nn.init.kaiming_uniform_(self.lora_right_weight, a=math.sqrt(5))

        # 升维矩阵使用全0初始化
        nn.init.zeros_(self.lora_left_weight)

    def forward(self, input):
    	"""LoRA的正向传播"""
    	···
    	else:
            # F.linear(input, self.weight, self.bias)是使用给定的权重self.weight和偏差self.bias对输入数据input进行线性变换
            # 这个操作等价于input @ self.weight.t() + self.bias,其中@表示矩阵乘法,.t()表示矩阵转置
	    	return F.linear(input, self.weight, self.bias) 
                    # 1,self.lora_dropout(input)对输入进行了随机的dropout操作,这是一种正则化手段
                    # 2,对结果进行两次线性变换,一次是@ self.lora_right_weight,然后是@ self.lora_left_weight
                    # 3,乘法部分* self.lora_scaling是对加号后面部分的结果进行缩放
	    			+ (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling

再额外分析下 这段代码的最后部分

# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):
	"""具体的lora层"""
	···
    def forward(self, input):
    	"""LoRA的正向传播"""
    	···
    	else:
	    	return F.linear(
	                input, self.weight,
	                self.bias) + (self.lora_dropout(input) @ self.lora_right_weight
	                              @ self.lora_left_weight) * self.lora_scaling

常规部分的正向传播由transformers所定义,而LoRA部分的正向传播则由LinearLayer_LoRA(nn.Module)的forward()所定义,即“LoRA层的两条分支结果进行加和”,如下图所示『图源:LoRA,相当于在训练期间,较小的权重矩阵(下图中的A和B)是分开的,但一旦训练完成,权重可以合并到一个新权重矩阵中 

 在代码中体现为

F.linear(input, self.weight, self.bias) + (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling

加号左侧为原结构支路,加号右侧为新增支路,self.lora_right_weight self.lora_left_weight 分别为两个新引入线性层的参数

2.3 Huggingface上PEFT库对LoRA、Prefix Tuning、P-Tuning的封装

而Huggingface公司推出的PEFT(Parameter-Efficient Fine-Tuning,即高效参数微调之意) 库也封装了LoRA这个方法,PEFT库可以使预训练语言模型高效适应各种下游任务,而无需微调模型的所有参数,即仅微调少量(额外)模型参数,从而大大降低了计算和存储成本

ModelFull FinetuningPEFT-LoRA PyTorchPEFT-LoRA DeepSpeed with CPU Offloading
bigscience/T0_3B (3B params)47.14GB GPU / 2.96GB CPU14.4GB GPU / 2.96GB CPU9.8GB GPU / 17.8GB CPU
bigscience/mt0-xxl (12B params)OOM GPU56GB GPU / 3GB CPU22GB GPU / 52GB CPU
bigscience/bloomz-7b1 (7B params)OOM GPU32GB GPU / 3.8GB CPU18.1GB GPU / 35GB CPU

且PEFT库 (peft/src/peft/peft_model.py at main · huggingface/peft · GitHub)支持以下流行的方法

  1. LoRA,PEFT对LoRA的实现封装见:peft/src/peft/tuners/lora.py at main · huggingface/peft · GitHub,比如对权重的合并代码 (和上面DSC对LoRA权重合并的实现,在本质上是一致的)
    def merge(self): 
        # 检查当前激活的适配器是否在lora_A的键中,如果不在则终止函数
        if self.active_adapter not in self.lora_A.keys():  
            return  
    
        if self.merged:  
            warnings.warn("Already merged. Nothing to do.")
            return  
    
        # 如果激活适配器的r值大于0,表示有可以合并的权重
        if self.r[self.active_adapter] > 0: 
            # 在当前的权重上加上计算得到的新权重
            self.weight.data += (  
                # 转置运算
                transpose(  
                    # 通过矩阵乘法计算新的权重
                    self.lora_B[self.active_adapter].weight @ self.lora_A[self.active_adapter].weight, 
     
                    # 这是转置运算的维度参数
                    self.fan_in_fan_out,  
                )
    
                # 然后将计算得到的权重乘以对应的缩放因子
                * self.scaling[self.active_adapter]  
            )
            self.merged = True
  2. Prefix Tuning: Prefix-Tuning: Optimizing Continuous Prompts for Generation, P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks
  3. P-Tuning: GPT Understands, Too
  4. Prompt Tuning: The Power of Scale for Parameter-Efficient Prompt Tuning

第三部分 QLoRA

// 待更

第四部分 P-Tuning V1/V2

4.1 P-Tuning V1

// 待更

4.2 P-Tuning V2:其关键所在在于引入Prefix-tuning

// 待更

参考文献与推荐阅读

  1. Google关于Adapter Tuning的论文《Parameter-Efficient Transfer Learning for NLP》
  2. 让天下没有难Tuning的大模型-PEFT技术简介
  3. PEFT:在低资源硬件上对十亿规模模型进行参数高效微调
  4. LLaMA的解读与其微调:Alpaca-LoRA/Vicuna/BELLE/中文LLaMA/姜子牙/LLaMA 2
  5. P-Tuning v2大幅提升小模型性能,NER也可promp tuning了
  6. P-tuning:自动构建模版,释放语言模型潜能
  7. Prompt-Tuning——深度解读一种新的微调范式
  8. ..

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/60117.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ输出日志配置

参考地址rabbitmq启用日志功能记录消息队列收发情况_rabbitmq开启日志_普通网友的博客-CSDN博客 启用日志插件命令 # 设置用户权限 rabbitmqctl set_user_tags mqtt-user administrator rabbitmqctl set_permissions -p / mqtt-user ".*" ".*" ".*&…

《命运》阅读笔记

《命运》阅读笔记 2023年5月17号在杭州的小屋读完,我读完后,脑海里经常把余华的《活着》和这本《命运》的故事情节搞混淆,几乎都是讲着生活的苦难。全文以阿太(外婆的妈妈)的视角,在她九十九岁的人生里&…

利用PostGIS自带工具导入shp数据

一、shapefile导入PostGIS 1、利用PostGIS自带工具导入 开始程序搜索如下工具 打开工具界面如下图,点击View conncetion details进行数据库连接,点击Add File进行Shapefile所在路径加载,点击Option进行编码设置,设置完成后点击Im…

数据安全与可追溯:PDM系统的信息保护锦囊

在当今数字化时代,数据安全与可追溯是企业管理中至关重要的环节。PDM系统(Product Data Management,产品数据管理)作为一款强大的数字化工具,为企业提供了全方位的信息保护锦囊。让我们一同深入探讨,看看PD…

半导体制造工艺流程

本资料仅用于学习和讨论,如有侵权请反应 1、半导体制造工艺流程-要求 1.1 英特尔50亿纳米的制作工艺 2、第一步 晶圆加工 2.1 第一步 晶圆加工 2.2 第二步 氧化 2.3 第三步 光刻 2.4第四步 刻蚀 2.5 第五步 薄膜沉积 2.6 第六步 互连 2.7 第七步 测试 2.8…

【网络基础实战之路】基于MGRE多点协议的实战详解

系列文章传送门: 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 PS:本要求基于…

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小 核心代码完整代码在线示例 之前由于误解遇到一个特殊的需求:想要把三维球上叠加倾斜摄影进行自由放大缩小,跟随地图的缩放进行缩放。 后来经过搜索、尝试,终于实现了需求。 但是,后…

ClickHouse SQL与引擎--基本使用(一)

1.查看所有的数据库 show databases; 2.创建库 CREATE DATABASE zabbix ENGINE Ordinary; ATTACH DATABASE ck_test ENGINE Ordinary;3.创建本地表 CREATE TABLE IF NOT EXISTS test01(id UInt64,name String,time UInt64,age UInt8,flag UInt8 ) ENGINE MergeTree PARTI…

【单调栈part03】| 84.柱状图中的最大矩形

🎈LeetCode84.柱状图中的最大矩形 链接:84.柱状图中的最大矩形 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积 public static in…

金融学复习博迪(第1-5章)

第一部分 金融和金融体系 第1章 金融学 金融:资金的流通,即储蓄,信贷、汇兑、股票和证券交易等经济活动的总称。 金融学:研究货币流通的学问。 传统的金融学研究领域大致有两个方向: >宏观层面的金融市场运行理论…

【C++】数据结构与算法:常用排序算法

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍常用排序算法。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路&#x1…

2023年C++面试宝典

目录 第一章:C基础知识1.1 C语言起源与发展1.2 C的重要特点和优点1.3 C的数据类型和变量1.4 函数和命名空间1.5 运算符和表达式 第二章:面向对象编程2.1 类与对象的概念2.2 封装、继承和多态2.3 构造函数和析构函数2.4 静态成员和常量成员2.5 虚函数和纯…

服务器的shell脚本

shell脚本语句可以执行linux的操作语句。 linux相当于网页,shell相当于java。可以解释编写执行逻辑。 shell的开头以:#!bin/sh 定义解析方式,不同的linuxe内核解释方式不同。大多数内核支持sh(bash)方式。 执行sh文件可…

Intellij IDEA运行报Command line is too long的解决办法

想哭,vue前端运行起来,对应的后端也得起服务。 后端出的这个bug,下面的博客写的第二种方法,完整截图是下面这个。 ​​​​​​​​​​​​​​​​​​​​Intellij IDEA运行报Command line is too long的解决办法 - 知乎 (zh…

Jmeter组件作用域及执行顺序

目录 一、Jmeter八大可执行元件 二、组件执行顺序 三、组件作用域 四、特殊说明 一、Jmeter八大可执行元件 配置元件---Config Element 用于初始化默认值和变量,以便后续采样器使用。配置元件大其作用域的初始阶段处理,配置元件仅对其所在的测试树分…

【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)

[导读]本系列博文内容链接如下: 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

【云原生】K8S二进制搭建二:部署CNI网络组件

目录 一、K8S提供三大接口1.1容器运行时接口CRI1.2云原生网络接口CNI1.3云原生存储接口CSI 二、Flannel网络插件2.1K8S中Pod网络通信2.2Overlay Network2.3VXLAN2.4Flannel 三、Flannel udp 模式的工作原理3.1ETCD 之 Flannel 提供说明 四、vxlan 模式4.1Flannel vxlan 模式的工…

Total Variation loss

Total Variation loss 适合任务 图像复原、去噪等 处理的问题 图像上的一点点噪声可能就会对复原的结果产生非常大的影响,很多复原算法都会放大噪声。因此需要在最优化问题的模型中添加一些正则项来保持图像的光滑性,图片中相邻像素值的差异可以通过…

ardupilot 中坐标变换矩阵和坐标系变换矩阵区别

目录 文章目录 目录摘要1.坐标变换矩阵与坐标系变换矩阵摘要 本节主要记录ardupilot 中坐标变换矩阵和坐标系变换矩阵的区别,这里非常重要,特别是进行姿态误差计算时,如果理解错误,很难搞明白后面算法。 1.坐标变换矩阵与坐标系变换矩阵 坐标变换矩阵的本质含义:是可以把…

【visual studio2019】如何打开即时窗口

在 Visual Studio2019 中打开即时窗口,有两种方法: 1、可以通过“调试”菜单,然后选择“窗口”下的“即时窗口”选项 2、直接使用快捷键“Ctrl Alt I” 此时即时窗口将显示在 Visual Studio2019 的底部。在即时窗口中,可以执…
最新文章