hugging face参数高效微调peft源码解析

news2024/9/20 17:01:19

大模型参数高效微调(PEFT) - 知乎

让天下没有难Tuning的大模型-PEFT技术简介 - 知乎 

大模型参数高效微调技术原理综述(三)-P-Tuning、P-Tuning v2 - 知乎 

你似乎来到了没有知识存在的荒原 - 知乎 

大模型参数高效微调技术原理综述(六)-MAM Adapter、UniPELT - 知乎 

PEFT:Parameter Efficient Fine-Tuning技术旨在通过最小化微调参数的数量和计算复杂度,来提高预训练模型在新任务上的性能,从而缓解大型预训练模型的训练成本。 

Prefix-Tuning(软提示/连续提示)

  1. 在每一层的token之前构造一段任务相关的tokens作为Prefix,训练时只更新Prefix部分的参数,而Transformer中的其他部分参数固定
  2. 一个Prefix模块就是一个可学习的id到embedding映射表,可以在多层分别添加Prefix模块
  3. 为了防止直接更新Prefix的参数导致训练不稳定的情况,在Prefix层前面加了MLP结构
  4. 作用阶段:所有transformer block的Attention注意力计算

Prompt-Tuning(软提示/连续提示)

  1. 可看做是Prefix-Tuning的简化版本,只在输入层加入prompt tokens,并不需要加入MLP进行调整
  2. 提出 Prompt Ensembling 方法来集成预训练语言模型的多种 prompts
  3. 在只额外对增加的3.6%参数规模(相比原来预训练模型的参数量)的情况下取得和Full-finetuning接近的效果
  4. 作用阶段:第一层transformer block的Attention注意力计算

P-Tuning(软提示/连续提示)

  1. P-Tuning只是在输入的时候加入Embedding,并通过LSTM+MLP对prompt embedding序列进行编码
  2. 根据人工设计模板动态确定prompt token的添加位置,可以放在开始,也可以放在中间
  3. 作用阶段:第一层transformer block的Attention注意力计算

P-Tuning V2(软提示/连续提示)

  1. 可看做是Prefix-Tuning的优化版本。在模型的每一层都添加连续的 prompts
  2. P-Tuning v2在不同规模和任务中都可与微调效果相媲美
  3. 移除重参数化的编码器(如:Prefix Tuning中的MLP、P-Tuning中的LSTM)、针对不同任务采用不同的提示长度、引入多任务学习等
  4. 作用阶段:所有transformer block的Attention注意力计算

 

Adapter(变体:AdapterFusion、AdapterDrop)

  1. 在Transformer Block中加入两层MLP,固定住原来预训练模型的参数不变,只对新增的Adapter结构进行微调
  2. 在只额外对增加的3.6%参数规模(相比原来预训练模型的参数量)的情况下取得和Full-finetuning接近的效果
  3. 作用阶段:Transformer Block主体

LoRA(变体:AdaLoRA、QLoRA)

  1. 在训练阶段,预训练参数W固定,只更新A和B参数,A和B模拟W的变化量。
  2. 在推理阶段,用A、B参数与原预训练参数相加替换原有预训练模型的参数。推理过程没有额外的参数量和计算量
  3. 相当于是用LoRA去模拟Full-finetune的过程,几乎不会带来效果损失
  4. 由于没有使用Prompt方式,避免了Prompt方法的一系列问题(Prompt的问题:难训练、序列长度受限、效果不如finetune),在推理阶段也没有引入额外的参数
  5. 作用阶段:Transformer Block主体query、value

 

BitFit

  1. 不需要对预训练模型做任何改动,只微调训练指定神经网络中的偏置Bias
  2. 参数量只有不到2%,但是效果可以接近全量参数
  3. 作用阶段:模型Backbone主体

PEFT方法总结

  1. 增加额外参数,如:Prefix Tuning、Prompt Tuning、Adapter Tuning及其变体
  2. 选取一部分参数更新,如:BitFit
  3. 引入重参数化,如:LoRA、AdaLoRA、QLoRA
  4. 混合高效微调,如:MAM Adapter、UniPELT

 


         下面看一下hugging face peft开源代码中对于上述几种方法的实现。tuners目录下实现了PrefixTuning、PromptTuning、PTuning、Adapter、LoRA、AdaLoRA这些方法配置文件的构造、解析,新增训练参数模型的构造,各种PEFT方法配置文件类之间的继承关系,如下:

PeftConfig -> PromptLearningConfig -> (PrefixTuningConfig、PromptEncoderConfig、PromptTuningConfig)

PeftConfig -> LoraConfig -> AdaLoraConfig

PushToHubMixin -> PeftConfigMixin -> PeftConfig

        下面先看一下PrefixTuning、PromptTuning、PTuningV1模块的输入、输出情况:

Prefix-Tuning

from peft import PrefixEncoder, PrefixTuningConfig
import torch

config = PrefixTuningConfig(
    peft_type="PREFIX_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    encoder_hidden_size=768,
    prefix_projection=False
)
print(config)

# 初始化PrefixEncoder
prefix_encoder = PrefixEncoder(config)
print(prefix_encoder)
"""
PrefixEncoder(
  (embedding): Embedding(20, 18432)
)
# 2 * layers * hidden = 2 * 12 * 768 = 18432,在每一层transformer block的key和value的前面都加上virtual embedding
"""

Prompt-Tuning

from peft import PromptTuningConfig, PromptEmbedding
import torch

word_embedding = torch.nn.Embedding(num_embeddings=100, embedding_dim=768)
config = PromptTuningConfig(
    peft_type="PROMPT_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    # encoder_hidden_size=768
)
print(config)

# 初始化PromptEncoder
prompt_encoder = PromptEmbedding(config, word_embedding)
print(prompt_encoder)
"""
PromptEmbedding(
  (embedding): Embedding(20, 768)
)
只在输入层的原始序列上面添加prompt embedding
"""

PTuningV1

from peft import PromptEncoderConfig, PromptEncoder
import torch

config = PromptEncoderConfig(
    peft_type="PREFIX_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    encoder_hidden_size=768,
    # prefix_projection=False
)
print(config)

# 初始化PrefixEncoder
p_encoder = PromptEncoder(config)
print(p_encoder)
"""
PromptEncoder(
  (embedding): Embedding(20, 768)
  (mlp_head): Sequential(
    (0): Linear(in_features=768, out_features=768, bias=True)
    (1): ReLU()
    (2): Linear(in_features=768, out_features=768, bias=True)
    (3): ReLU()
    (4): Linear(in_features=768, out_features=768, bias=True)
  )
)
只在输入层的原始序列上面添加prompt embedding
"""

 PTuningV2

from peft import PrefixEncoder, PrefixTuningConfig
import torch

config = PrefixTuningConfig(
    peft_type="PREFIX_TUNING",
    task_type="SEQ_2_SEQ_LM",
    num_virtual_tokens=20,
    token_dim=768,
    num_transformer_submodules=1,
    num_attention_heads=12,
    num_layers=12,
    encoder_hidden_size=768,
    prefix_projection=True
)
print(config)

# 初始化PrefixEncoder
prefix_encoder = PrefixEncoder(config)
print(prefix_encoder)
"""
PrefixEncoder(
  (embedding): Embedding(20, 768)
  (transform): Sequential(
    (0): Linear(in_features=768, out_features=768, bias=True)
    (1): Tanh()
    (2): Linear(in_features=768, out_features=18432, bias=True)
  )
)
# 2 * layers * hidden = 2 * 12 * 768 = 18432,在每一层transformer block的key和value的前面都加上virtual embedding
"""

        PrefixTuning和PTuningV在实现上基本上是一样的,其实就是一样的。

 


 

        下面以peft_model.py文件中PeftModelForSequenceClassification的forward函数实现为例,看一下在推理阶段如何对于PrefixTuning、PromptTuning、PTuningV1、PTuningV2、Adapter、LoRA进行操作。

        1、首先通过配置文件的所继承的父类类型来判断PEFT方法是否属于Prompt相关的,如果不是,就表示使用的是Adapter、LoRA等方法,直接执行推理。LoRA的具体推理计算过程后面再补充

        2、如果通过配置文件的所继承的父类类型判断PEFT方法属于Prompt相关的,因为要在transformer block的序列开始位置添加虚拟token的embedding,所以也要补全attention mask

        3、通过配置文件的类型来判断PEFT方法到底是PrefixTuning/PTuningV2,还是PromptTuning/PTuningV1。如果是PromptTuning/PTuningV1,则将虚拟token的embedding直接concat到原始输入序列的前面,送入base model模型进行推理。如果是PrefixTuning/PTuningV2,由于涉及到给每一个transformer block的key和value添加虚拟token的embedding,还需要使用_prefix_tuning_forward函数进行额外的处理。

 

PromptTuning/PTuningV1源码

        PromptTuning和PTuningV1的共同点都是使用了浅层的Prompt,只在输入层使用。所以PromptTuning和PTuningV1在使用时的方式基本相同。

        PromptTuning的源码详见src/peft/tuners/prompt_tuning.py,里面包含了相关的配置项以及Encoder的创建过程。

        PTuningV1的源码详见src/peft/tuners/p_tuning.py,里面包含了相关的配置项以及Encoder的创建过程。

        PromptTuning/PTuningV1的推理过程就是将虚拟token的embedding直接concat到原始输入序列的前面,并对attention mask进行扩充,送入base model模型进行推理。对应上面讲到的2和3。

PrefixTuning/PTuningV2源码

        PrefixTuning和PTuningV2的共同点都是使用了深层的Prompt,在每层Transformer Block都使用,所以PrefixTuning和PTuningV2的实现放在一起。

        上面说到当执行的PEFT类型是PrefixTuning/PTuningV2时,由于要给每个Transformer Block的Key和Value前面都加上可以学习的virtual token embedding,需要使用_prefix_tuning_forward函数进行额外的处理。

        在看_prefix_tuning_forward函数之前先了解一些相关知识。自然语言处理任务可以分为Auto-Encoding(也叫NLU、自然语言理解、Masked Language Model)和Auto-Regressive(也叫NLG、自然语言生成、Language Model)。对于Auto-Encoding类型的任务,在模型的训练和预测阶段,self-attention都可以并行计算。对于Auto-Regressive类型的任务,在模型训练阶段通过使用attention mask也能够进行并行计算,但是在模型预测阶段,由于是时序生成任务,只能一步一步的生成,没办法并行。我们来看一下在生成阶段每个时间步有哪些计算:

        时间步t:

        Query:来自前一个时间步t-1时刻的输出,使用query矩阵(就是一个全连接层)转换之后得到

        Key:来自前t-1个时间步[1,……,t-1]的输出,使用key矩阵(就是一个全连接层)转换之后得到

        Value:来自前t-1个时间步[1,……,t-1]的输出,使用value矩阵(就是一个全连接层)转换之后得到

        时间步t + 1:

        Query:来自前一个时间步t时刻的输出,使用query矩阵(就是一个全连接层)转换之后得到

        Key:来自前t个时间步[1,……,t-1,t]的输出,使用key矩阵(就是一个全连接层)转换之后得到

        Value:来自前t个时间步[1,……,t-1,t]的输出,使用value矩阵(就是一个全连接层)转换之后得到

        可以看到,每个时间步的Query都和上一个时间步的输出相关,每一步都需要重新计算Query,但是key和value来自前t-1个时间步的输出相关,所以t+1时刻的key和value与t时刻的key和value在[1,……,t-1]时刻上的计算结果是相同的,也就是这些结果是可以复用的,在每个时刻可以复用前一时刻计算的key和value,然后追加上当前时刻新增的key和value,构成完整的key和value。

        在hugging face实现的self-attention模块中,为了复用decode生成阶段的key和value,会传入一个past_key_values参数,如果past_key_values不是None,表示前面时间步已经有计算结果了,直接复用上一步的结果,然后将当前时间步的key和value拼接上去,更新后的past_key_values将继续传递到下一个时间步。

        有了上面的背景知识,对于_prefix_tuning_forward函数中关于PrefixTuning/PTuningV2方法的实现就很好理解了,就是将生成的virtual token embedding通过past_key_values参数带入到transformer block的每一层,放在每一层key和value的前面。

 

关于LoRA相关的源码部分后续补充。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

由LM386构成的3W简易OCL功放电路/由TDA2009构成的1W高保真BTL功率放大器

由LM386构成的3W简易OCL功放电路 该电路是使用低功耗集成功率放大器 LM386 构成的 OCL 功放电路,电路结构简单,容易调试,非常适于自制。 一、电路工作原理 电路原理如图 31 所示。 图中IC1和IC2是两片集成功放LM386,接成OCL电路…

【编程的多线程学习-前章】什么是进程,PCB进程控制块抽象,cup分配,内存分配,虚拟地址,进程的通信,进程的意义

什么是进程 什么是进程/任务(Process/Task)进程就是一个运行起来的程序PCB 进程控制块抽象(PCB Process Control Block)pcb就是一个进程PCB具体包含的信息 CPU 分配 —— 进程调度(Process Scheduling)内存分配 —— 内存管理&…

Linux进度条小程序

文章目录 🪅1. 回车换行♥1.1 回车♥1.2 换行 🪆2. 缓冲区现象🧸3. 进度条实现♟3.1 逻辑♟3.2 进度条样式♟3.3 代码实现 🃏4. 场景使用 🪅1. 回车换行 在学习C语言的时候,我们输出的时候,通常…

Linux 安装elasticsearch,kibana,Logstash

1、Elasticsearch 安装 cd /usr/localwget \ https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.7-linux-x86_64.tar.gz \ https://artifacts.elastic.co/downloads/kibana/kibana-7.17.7-linux-x86_64.tar.gz \ https://artifacts.elastic.co/downlo…

BM77-最长的括号子串

题目 给出一个长度为 n 的,仅包含字符 ( 和 ) 的字符串,计算最长的格式正确的括号子串的长度。 例1: 对于字符串 "(()" 来说,最长的格式正确的子串是 "()" ,长度为 2 .例2:对于字符串 ")()…

AI Chat 设计模式:5. 策略模式

本文是该系列的第五篇,采用问答式的方式展开,问题由我提出,答案由 Chat AI 作出,灰色背景的文字则主要是我的旁白。 问题列表 Q.1 我想学习一下策略模式A.1Q.2 你先给我简单讲解一下吧A.2Q.3 你举得这个电商平台例子不错&#xf…

青岛大学_王卓老师【数据结构与算法】Week04_09_线性表的应用2_学习笔记

本文是个人学习笔记,素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享,另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权,请留言作删文处理。 课程视频链接: 数据结构与算法基础–…

使用 tail -f 实时观测服务器日志输出

在开发阶段, 有 console 端的输出, 总是可以方便实时地看到应用的日志. 可一旦应用部署到服务器上之后呢, 日志被输出到文件中, 在某些情景下需要不停地查看日志文件的输出以定位某些问题, 此时是否还能像开发那样实时查看日志呢? 答案是可以的! 这个命令就是 tail -f . tail…

vue2 element-ui 2.9.1版不支持抽屉el-drawer

一个老项目中想使用抽屉 el-drawer,死活没有反应。 查了下element-ui的版本是2.9.1 再看其他一个正常使用抽屉的项目的element-ui版本。 但是 2.14.1 的element-ui版本又存在菜单管理无法折叠菜单项的问题。 看来还是得抽空解决在2.14.1菜单无法折叠的问题了。

一文了解潜力黑马Infiblue:借力Web3,释放元宇宙价值

2013 年,JDN Dionisio 曾发表了一篇名为《3D Virtual Worlds and the Metaverse: Current Status and Future Possibilities》的论文,深入探讨与归纳了虚拟世界的几个发展阶段,可以简单的归纳为: 第一阶段:基于计算机文…

零代码量化投资:用ChatGPT获取新浪财经上的股票实时行情

现在很多免费的股票数据库,比如akshare,其实是从新浪财经或者东方财富网站上爬取下来的。如果能直接从新浪财经或者东方财富网站上爬取数据,可以获取更全面更即时的信息。 可以在ChatGPT中输入提示词如下: 写一段Python代码&…

探索stable-diffusion技术乐园:活学活用界面参数

开篇 嗨!欢迎踏入我们充满有趣和创新的stable-diffusion技术乐园,让我们一起走进stable-diffusion界面参数的世界,看看怎样如行家袋里取物般自在地活用这些参数! 看了这么多大V、大卡和群粉们使用的英文,提起来有点沉,别急,我会尽量使用轻松的语气带你一起探索这些小秘…

MySQL视图、索引、导入导出、执行计划

目录 一、前言 1.导读 2.学习的好处 二、视图 1.什么是视图 2.视图与数据表的区别 3.使用视图的优点 4.视图的语法 1.创建视图(CREATE VIEW) 2.查询视图数据 3.更新视图数据 4.修改视图定义(ALTER VIEW) 5.删除视图(DROP VIEW&a…

VSCode中打开NodeJS项目自动切换对应版本的配置

这几年搞了不少静态站点,有的是Hexo的,有的是VuePress的。由于不同的主题对于NodeJS的版本要求不同,所以本机上不少NodeJS的版本。 关于如何管理多个NodeJS版本,很早之前就写过用nvm来管理的相关文章,这里就不赘述了&a…

第四范式携「式说」亮相世界人工智能大会 大模型落地进入加速时刻

7月6日-8日,以“智联世界 生成未来”为主题的2023世界人工智能大会(WAIC)在上海召开,本届大会聚焦通用人工智能发展。第四范式携「式说」大模型亮相现场,更有第四范式「大模型之城」首秀,汇聚了大模型在金融…

Elasticsearch【全文检索、倒排索引、应用场景、对比Solr、数据结构】(一)-全面详解(学习总结---从入门到深化)

目录 Elasticsearch介绍_全文检索 Elasticsearch介绍_倒排索引 Elasticsearch介绍_Elasticsearch的出现 Elasticsearch介绍_Elasticsearch应用场景 Elasticsearch介绍_Elasticsearch对比Solr Elasticsearch介绍_Elasticsearch数据结构 Elasticsearch介绍_全文检索 Elasti…

休闲时光:最近上映的电影与爬虫世界,带您彻底放松!

大家好,我是安果! 周末是与亲朋好友相聚的好时机,可以选择一部大家都喜欢的电影,彻底放松,共同度过一个愉快而难忘的周末 本篇文章将介绍如何使用 Scrapy 爬取最新上映的电影 目标对象: aHR0cHM6Ly93d3cubW…

深入浅出讲解Python中的可变类型与不可变类型,以及赋值,浅拷贝与深拷贝的区别

文章目录 1、不可变数据类型2、可变数据类型3、赋值,浅拷贝与深拷贝3.1 赋值操作符 3.2 浅拷贝copy()3.3 深拷贝copy() 小结 在Python中,数据类型主要分为可变数据类型和不可变数据类型。主要的区别在于可变数据类型的值可以改变,而不可变数据…

【网络系统集成】路由器实验

1.实验名称:路由器RIP协议配置 2.实验目的 在PacketTracer中进行模拟实验,配置RIP协议,验证RIP协议更新时间及路由状态变化,加深对路由器RIP协议相关知识的理解与掌握。 3.实验内容 (1)拓扑结构图 (2)ip地址分配与端口分配

Mybatis基础总结1

Mybatis快速入门 一.Mybatis快速入门1.1 框架介绍1.2 ORM介绍1.3 原始jdbc操作(查询数据)1.4原始jdbc操作(插入数据)1.5 原始jdbc操作的分析1.6 什么是Mybatis1.7 Mybatis的快速入门1.7.1 环境搭建1.7.2编写测试代码 1.8 知识小结…