第十三期 常见的PEFT微调技术

一:LLM大语言模型

(一)阶段划分

LLM大语言模型训练一般有两个阶段

Step 1.预训练阶段

大模型首先在大量的无标签数据上进行训练,预训练的最终目的是让模型学习到语言的统计规律和一般知识。在这个过程中模型能够学习到词语的语义、句子的语法结构、以及文本的一般知识和上下文信息。
需要注意的是,预训练本质上是一个无监督学习过程;得到预训练模型(Pretrained Model), 也被称为基座模型(Base Model),模型具备通用的预测能力。如GLM-130B模型、OpenAI的A、B、C、D四大模型,都是基座模型。

Step 2.微调阶段

预训练好的模型然后在特定任务的数据上进行进一步的训练。这个过程通常涉及对模型的权重进行微小的调整,以使其更好地适应特定的任务;得到最终能力各异的模型,例如 gpt code系列、gpt text系列、 ChatGLM-6B等模型;

(二)微调划分

微调的含义,就是把已经训练好的模型(pretrained model)拿来,给它吃特定的下游任务数据,使得模型在预训练权重上继续训练,直至满足下游任务性能标准。预训练模型就像一个特征提取器,能够基于先前训练数据中学到的经验,为我们提取有效的特征,大大提升下游任务的训练效果和收敛速度。

1.全量微调

全量微调指的是,通过在预训练的大型模型基础上调整所有层和参数,使其适应特定任务。这一过程使用较小的学习率和特定任务的数据进行,可以充分利用预训练模型的通用特征,但可能需要更多的计算资源。例如图中,给出了Transformer的Q/K/V矩阵的全量微调示例,对每个矩阵来说,在微调时,其d*d个参数,都必须参与更新。
全量微调的显著缺点是,训练代价昂贵。例如GPT3的参数量有175B,我等单卡贵族只能望而却步,更不要提在微调中发现有bug时的覆水难收。
由于模型在预训练阶段已经吃了足够多的数据,收获了足够的经验,因此只要想办法给模型增加一个额外知识模块,让这个小模块去适配我的下游任务,模型主体保持不变(freeze)即可。那这样的知识小模块,具体要怎么添加呢?

2.参数高效微调PEFT(Parameter Efficient Fine-Tuning)

PEFT是一组技术或方法,用于以最计算和最高效的方式微调大型模型,而不会损失您可能从完全微调中看到的任何性能。在生产级应用程序中,微调和使用更大的模型已成为必要。PEFT 技术使您能够有效地微调模型,从而节省金钱和时间:
  • 通过仅微调神经网络中最重要和最相关的参数来完成的
  • 在网络中引入新的参数或冻结除某些部分之外的整个模型,以便更容易训练模型

(三)微调和迁移学习的区别补充

迁移学习是指我们获取模型的一些学习参数并将其用于其他任务。这听起来与微调类似,但又有所不同。
在微调中,我们重新调整模型的所有参数或冻结部分权重并调整其余参数,在微调时无法改变模型的架构,这在很多方面限制了我们。
迁移学习中,我们使用一些从模型中学习到的参数,并将它们用于其他网络中,当使用迁移学习时,我们仅使用经过训练的模型的一部分,然后我们可以将其附加到具有任何架构的任何其他模型。

二:PEFT参数高效微调

PEFT技术旨在通过最小化微调参数的数量和计算复杂度,来提高预训练模型在新任务上的性能,从而缓解大型预训练模型的训练成本。即使计算资源受限,也可以利用预训练模型的知识来迅速适应新任务,因此EFT技术可以在提高模型效果的同时,大大缩短模型训练时间和计算成本,让更多人能够参与到深度学习研究中来。
PEFT包括LORA、QLoRA、AdapterTuning、Prefix Tuning、Prompt Tuning、P-Tuning及P-Tuning v2等,下图示例了7个主流微调方法在Transformer网络架构的作用位置和简要说明

(一)输入嵌入层(Input Embedding)上的PEFT微调技术

Prompt Tuning、Prefix Tuning 和 P-Tuning/V2,这些方案都围绕 Token 做文章,并且在保持预训练语言模型(PLM)不变的情况下,实现了模型在不同任务上的适配。

1.Prompt Tuning提示调整

为了找到一个好的Prompt,有时候,我们常常要反反复复进行几十、上百遍修改才能找到理想的Prompt;还有些时候,即使进行了无数次的尝试,也总是找不到那个最优的Prompt。当我们穷尽了所有的Prompt技巧却依然找不到答案时,我们还有什么办法?Prompt Tuning就是对这一问题的答案
我们都知道LLM的能力很强,给出好的Prompt,它就能给出好的答案。那么我们能不能反过来,让LLM来帮我们来找到好的Prompt呢?这实际就是Prompt Tuning的基本思路。

Prompt Tuning流程及案例:

正常使用LLM的过程是:
而Prompt Tuning的工作过程是:

Prompt Tuning中生成额外token的LLM原理:

Prompt Tuning首先给LLM一些例子,告诉LLM碰到什么问题,应该给出什么结果,LLM在看了一些这样的例子之后,就会返回一段Prompt(咒语),之后当我们真正向LLM提问的时候,把这段Prompt作为前缀,拼接在我们自己的Prompt上,就可以让LLM给出好的结果了
https://mp.weixin.qq.com/s/C1lrOJUMkcngOnEE3qrEhA
通过少量数据对LLM进行训练,再将训练结果进行保存,使用时加载即可

Prompt Tuning和Few Shot的区别:

上面的第一个绿色LLM训练方式和Few Shot很像,从底层思路上两者有相似之处,都是通过让LLM学习例子,然后更好地回答问题,但是也有区别:
但Few Shot有一些天然的局限性:
  • 各种LLM的上下文(context)长度有限:我们只能在Prompt中给出有限几个例子,这在业务比较复杂的时候,并不足以教会LLM需要的知识。而Prompt Tuning则没有这个限制,我们可以对LLM进行充分训练
  • 上下文中样例过多开销太大:在向LLM提问时,过多的例子不但会降低LLM的响应速度,而且其费用是和问题长度成正比的,在业务中每天多次带着长长的例子提问,累积起来成本会大幅上升。而Prompt Tuning只需要在训练LLM的时候一次性给它看足够的例子,之后提问时,只需要带上一个短短的Prompt前缀(一般8~20个token)就可以了,一个例子都不需要再向它提供
  • Few Shot更大的问题是不稳定性,业界的研究已经发现,例子的顺序对结果有很大的影响,LLM总是倾向给出和最后一个例子接近的答案;研究还发现,例子的样本分布对结果也有很大影响,以上面判断一条tweet是否为吐槽的情况为例,如果给出的例子中大多数是吐槽,那么LLM给出的答案也会倾向于吐槽。而Prompt Tuning则可以完全避免这些问题,让LLM的输出稳定可控

2.Prefix Tuning前缀调整

与传统的微调范式不同,前缀调整提出了一种新的策略,即在预训练的语言模型(LM)输入序列前添加可训练、任务特定的前缀,从而实现针对不同任务的微调。这意味着我们可以为不同任务保存不同的前缀,而不是为每个任务保存一整套微调后的模型权重,从而节省了大量的存储空间和微调成本
这张图展示了两种不同的模型微调方法:Fine-tuning 和 Prefix-tuning。这两种方法都是为了调整预训练的Transformer模型以适应特定任务
  • 在 Fine-tuning 部分,图展示了三个不同任务的Transformer模型,分别用来做翻译、总结或将表格转换为文本(table-to-text)。每个任务都有自己的微调模型,这意味着模型的所有权重都在微调过程中针对特定任务进行了更新。这种方法通常需要大量的数据和计算资源,因为整个模型都在学习任务特定的知识。
  • 在 Prefix-tuning 部分,图展示了一种不同的微调策略,对于每个任务,都有一个特定的前缀被添加到输入序列的开始部分。这些前缀相当于任务特定的提示,可以是一组固定的词或是可训练的嵌入向量Prefix-tuning 的优势在于它不需要调整模型的全部权重,而是通过在输入中添加前缀来调整模型的行为,这样可以节省大量的计算资源,同时使得一个单一的模型能够适应多种不同的任务。前缀可以是固定的(即手动设计的静态提示)或可训练的(即模型在训练过程中学习的动态提示)

Prefix Tuning和Prompt Tuning对比:

  • 提示调整:旨在模仿自然语言中的提示形式,将可学习向量(通常称为提示标记)设计为模型针对特定任务生成特定类型输出的引导。这些向量通常被视为任务指导信息的一部分,倾向于使用较少的向量来模仿传统的自然语言提示。
  • 前缀调整:可学习前缀更多地用于提供输入数据的直接上下文信息,作为模型内部表示的一部分,可以影响整个模型的行为

3.P-Tuning

P-Tuning主要是为了解决这样一个问题:大模型的Prompt构造方式严重影响下游任务的效果。比如:GPT-3采用人工构造的模版来做上下文学习(in context learning),但人工设计的模版的变化特别敏感,加一个词或者少一个词,或者变动位置都会造成比较大的变化
P-Tuning通过将可训练的连续提示嵌入(continuous prompt embeddings)与离散提示(discrete prompts)结合使用,通过反向传播进行更新,以优化任务目标。通过减少不同离散提示之间的性能差距来稳定训练。

离散提示

离散提示通常是由人类专家手动编写的一系列自然语言词汇或模板,它们作为输入的一部分,用来引导语言模型更好地完成特定的NLU任务。这些提示是静态的,即它们在模型训练或推理过程中不会改变。例如,在一个问答任务中,离散提示可能是:
  • "What is the capital of [X]? The capital is [Y]."
在这个例子中,"[X]" 和 "[Y]" 是占位符,分别代表问题中询问的国家和预期的答案

连续提示(类似于bert,根据上下文预测中间mask的token)

与离散提示不同,连续提示是一组可训练的向量,它们与离散提示结合使用,并且在模型训练过程中会通过反向传播算法更新。连续提示提供了一种将学习性融入输入的方式,能够适应性地调整模型的行为以优化特定任务的性能。例如,在上述相同的问答任务中,连续提示可能被嵌入到离散提示中,形成如下结构:
  • "What is the [continuous prompt vector] capital of [X]? The [continuous prompt vector] capital is [Y]."
在这里,"[continuous prompt vector]" 是一个可学习的向量,它在模型训练过程中会被调整,以帮助模型更好地预测首都

离散/连续提示案例

假设我们有一个简单的NLU任务,即确定一个句子中的主题和评论对象。我们可以使用以下两种提示方法:
  • 离散提示:"The [MASK] is a great [MASK]." 这里的两个"[MASK]"是离散的占位符,分别代表句子中的主题和评论的属性。在实际应用中,它们将被具体词汇替换,例如:"The movie is a great masterpiece."
  • 连续提示如果我们在这个任务中使用P-Tuning,我们可能会在句子的某些部分添加连续提示,如:"The [continuous prompt vector] movie is a [continuous prompt vector] great [continuous prompt vector] masterpiece." 这些连续提示向量会在训练过程中学习到最优的表示,以帮助模型更好地理解句子结构和语义
总的来说,离散提示是固定的语言模式,而连续提示是动态的、可训练的向量,它们可以适应性地优化模型对特定任务的理解和输出。

P-Tuning本质

P-Tuning将连续提示嵌入与离散提示标记连接起来,并将它们作为输入提供给语言模型。连续提示通过反向传播进行更新,以优化任务目标连续提示将一定程度的可学习能力纳入输入中,这可能学会抵消离散提示中的微小变化,以提高训练稳定性。为了进一步提高性能,我们采用了使用LSTM或MLP的提示编码器来模拟连续提示嵌入之间的依赖性
设[Pi]是第i个连续提示嵌入。P-Tuning的提示模板如下:
P-Tuning利用一个额外的嵌入函数f : [Pi] → hi来将模板映射到
最终,我们更新嵌入的连续提示嵌入[Pi]以优化任务损失函数。
在上述框架中,采用一个映射函数f来将可训练的嵌入{Pi}映射到模型输入{hi}我们使用一个轻量级神经网络来构建函f,使用长短期记忆(LSTM)网络、多层感知器(MLPs)以及恒等映射函数来进行构建,得到对应的提示。

P-Tuning对比Prompt Tuning

P-Tuning则认为,我们不但可以在用户输入的前面附加信息,也可以在中间或者结尾附加信息,附加信息的位置可以更灵活。P-Tuning同样不需要修改LLM参数,但它比Prompt Tuning更加复杂,训练成本也要略高一些。

4.P-Tuning V2

P-Tuning中,连续提示被插入到输入序列的embedding里,除了语言模型的输入层之外,其他层的prompt embddding都来自于上一层。
这样的设计存在两个问题:
  1. 约束了要优化的参数量。由于模型的input text的长度是一定的,一般是512,那么prompt的长度就不能过于长。
  2. 当模型层数很深时,tuning时模型的稳定性难以保证;模型层数越深,在第一层输入的prompt对后面的影响是难以预估的,这会影响模型的稳定性。
P-Tuning v2的改进在于,将只在第一层插入连续提示修改为在许多层都插入连续提示,而不仅仅是输入层,层与层之间的连续提示是相互独立的。这样一来,在模型tuning时,可训练的参数就增多了,P-Tuning v2在应对复杂的NLU任务和小型模型方面,相比原始P-Tuning具有更出色的效能。

(二)编码器层(Encoder)上的PEFT微调技术

1.Adapter Tuning

适配器调整的方法是在模型的每个层或选定层之间插入小型神经网络模块,称为“适配器”这些适配器是可训练的,而原始模型的参数则保持不变。
图例中的左边是一层Transformer Layer结构,其中的Adapter就是我们说的“额外知识模块”;右边是Adatper的具体结构。在微调时,除了Adapter的部分,其余的参数都是被冻住的(freeze),这样我们就能有效降低训练的代价
这样的设计架构存在一个显著劣势:添加了Adapter后,模型整体的层数变深,会增加训练速度和推理速度,原因是:
  • 需要耗费额外的运算量在Adapter上
  • 当我们采用并行训练时(例如Transformer架构常用的张量模型并行),Adapter层会产生额外的通讯量,增加通讯时间

Adapter Tuning 的关键原理和步骤

预训练模型作为基础:开始时,我们有一个已经预训练好的大型模型,例如BERT或GPT。这个模型已经学习了大量的语言特征和模式。
插入适配器:在这个预训练模型的每一层或选定的层中,我们插入适配器。这些适配器是小型的神经网络,通常只包含几层,并且参数相对较少。
保持预训练参数不变:在微调过程中,原始预训练模型的参数保持不变。这意味着我们不直接调整这些参数,而是专注于训练适配器的参数。
训练适配器:适配器的参数会根据特定任务的数据进行训练。这样,适配器可以学习如何根据任务调整模型的行为。
任务特定的调整:通过这种方式,模型能够对每个特定任务做出微调,而不会影响到模型其他部分的通用性能。适配器可以帮助模型更好地理解和处理与特定任务相关的特殊模式和数据。
高效和灵活:由于只有一小部分参数被调整,这种方法比全模型微调更高效,同时也允许模型快速适应新任务。

2.Lora(Low-Rank Adaptation,低秩适配器)

总结一下,全参数微调太贵,Adapter Tuning存在训练和推理延迟,Prefix Tuning难训且会减少原始训练数据中的有效文字长度,那是否有一种微调办法,能改善这些不足呢?在这样动机的驱动下,作者提出了LoRA(Low-Rank Adaptation,低秩适配器)这样一种微调方法。

秩是什么?

秩表示的是矩阵的信息量。如果矩阵中的某一维,总可以通过其余维度线性推导而来,那么对模型来说,这一维的信息是冗余的,是重复表达的。
我们首先来看一个矩阵A:该矩阵中,row2 = row1 * 2,row3 = row1*3,也就是说,矩阵中的每一行,都可以通过第一行线性表示,因此A的秩是1。
A = [
[1, 2, 3],
[2, 4, 6],
[3, 6, 9]
]
我们再来看一个矩阵B:该矩阵中,任意一行,总可以用其他两行的线性组合来表示。所以只要掌握其中的任意两行,其余行都可以由这两行线性组合推导而来,因此B的秩是2。
B = [
[1, 2, 3],
[7, 11, 5],
[8, 13, 8]
]
我们最后再来看一个矩阵C:该矩阵中,任意一行,都不能从其余行的线性组合中推导而来。由于必须完全掌握三行,才能得到完整的C,因此C的秩是3。
C = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
]

图像中的秩

图像处理中,rank可以理解为图像所包含的信息的丰富程度,秩越大图像里面的信息越多(也可能是噪音越多),在现实生活中,一张图片中大部分成分是相似的,因此我们可以将一个二维图像转成两个低秩矩阵来存储图像的信息,虽然可能导致部分信息丢失,但是也可以通过低秩来消除图像噪音。最重要的是通过低秩分解,我们可以节约内存具体节省了多少内存呢?还需要取决于秩 r,秩 r 是一个超参数。例如,如果 ΔW 有 10,000 行和 20,000 列,则需存储 200,000,000 个参数。如果我们选择 r=8 的 A 和 B,则 A 有 10,000 行和 8 列,B 有 8 行和 20,000 列,即 10,000×8 + 8×20,000 = 240,000 个参数,比 200,000,000 个参数少约 830 倍。

Lora整体架构

图中左侧表示“全参数finetune”的场景。我们将参数分成了两个部分:
  • $$W∈R^{d∗d}$$ :预训练权重
  • $$ΔW∈R^{d∗d}$$ :finetune增量权重
之所以这么拆分,是因为全参数finetune可以理解成“冻住的预训练权重” + “微调过程中产生的权重更新量”。设输入为 x ,输出为 h ,则有:h=Wx+ΔWx 图中右侧表示“LoRA finetune”的场景。在LoRA中,我们用矩阵A和B来近似表达 ΔW :
  • $$A∈R^{r∗d}$$ :低秩矩阵 A ,其中 r 被称为“秩”,对 A 用高斯初始化
  • $$B∈R^{d∗r}$$:低秩矩阵 B ,对B采用零初始化
经过这样一番拆分,我们将 ΔW 改写成 ΔW=B 的形式,使得微调参数量从d*d降低至2*r*d,同时不改变输出数据的维度,即在LoRA下我们有: h=Wx+BAx
当然,A 和 B 无法捕捉到 ΔW 涵盖的所有信息,但这是 LoRA 的设计所决定的。在使用 LoRA 时,我们假设模型 W 是一个具有全秩的大矩阵,以收集预训练数据集中的所有知识。当我们微调 LLM 时,不需要更新所有权重,只需要更新比 ΔW 更少的权重来捕捉核心信息,低秩更新就是这么通过 AB 矩阵实现的。通过对已有的SD模型的部分权重进行调整,从而实现对生图效果的改善。(大部分LoRA模型是对Transformer中的注意力权重进行了调整)
  • A和B是矩阵,可以直接乘起来一步完成(代码实现是两个全连接层)
  • 只用LoRA微调了Transformer模块(Wq,Wk,Wv,Wo)
需要注意的是:
  • 对 A 采用高斯初始化,随机高斯分布就是根据高斯分布的规律随机生成数值,而这些数值的分布符合正态分布的特性。高斯初始化为了提供一种合理的初始值,使得模型能够更快地学习到合适的参数。
  • 对 B 采用零初始化的目的是,让训练刚开始时ΔW的值为0,这样不会给模型带来额外的噪声,从头开始微调。

3.AdaLora

https://blog.csdn.net/qq_29788741/article/details/132957760
在前面Lora中,选择对attention模块的$$W_q$$,$$W_k$$,$$W_v$$,$$W_o$$做低秩适配。但是有两个问题:
  • 对所有模块都采用同一个秩r,这是合理的吗?---在使用LoRA微调时,对模型的不同模块使用相同的秩,显然是不合理的不同的模块可以使用不同的秩
  • 在微调过程中,一直保持秩r不变,这是合理的吗?---前面说明了“对不同模块采用不同秩”的必要性,那么接下来势必就要解决“每个模块的秩到底要设成多少”的问题。解答这个问题最直接的办法,就是交给模型去学,在模型学习的过程,学会慢慢调整这个秩,直到最优。 所以,在微调step的更新过程中,秩也不会保持不变

AdaLora总体改进目标

找到一种办法,让模型在微调过程中,去学习每个模块参数对训练结果(以loss衡量)的重要性。然后,根据重要性,动态地调整不同模块的秩。

AdaLora技术原理:https://mp.weixin.qq.com/s?__biz=MzU0MDQ1NjAzNg==&mid=2247584691&idx=2&sn=1359f7fe2ac197b9739547601660e2b8&chksm=fad02b7123284b25a81dc2e7f0914cd45c71b34ec8585fc0795be12db54a2bc22f270551ba01&scene=27

AdaLoRA根据重要性评分动态分配参数预算给权重矩阵。具体做法如下:
  • 调整增量矩分配。AdaLoRA将关键的增量矩阵分配高秩以捕捉更精细和任务特定的信息,而将较不重要的矩阵的秩降低,以防止过拟合并节省计算预算。
  • 以奇异值分解的形式对增量更新进行参数化,并根据重要性指标裁剪掉不重要的奇异值,同时保留奇异向量。由于对一个大矩阵进行精确SVD分解的计算消耗非常大,这种方法通过减少它们的参数预算来加速计算,同时,保留未来恢复的可能性并稳定训练。
  • 在训练损失中添加了额外的惩罚项,以规范奇异矩阵P和Q的正交性,从而避免SVD的大量计算并稳定训练。

4.QLora

QLoRA结合了LoRA和量化的优势,将预训练模型量化为4位,并添加一组可学习的低秩适配器权重。这些权重通过量化权重的反向传播梯度进行微调,从而在保持性能的同时大幅降低内存使用量。
QLoRA的核心特点包括:

量化技术

https://zhuanlan.zhihu.com/p/419052103
https://zhuanlan.zhihu.com/p/671089942
QLoRA使用一种新颖的高精度技术将预训练模型量化为4-bit。这种技术包括一种低精度存储数据类型(4-bit NormalFloat,简写为NF4)和一种计算数据类型(16-bit BrainFloat)。这样做可以在保持整个模型精度损失极小的同时,减少存储需求
那么,量化具体怎么做呢?
4-bit量化意味着每个权重仅由4个比特表示,量化过程需要选择哪些值最重要并将它们映射到这16个可能的值上
首先确定量化的范围,比如从-1到1,然后将这个范围划分为16个区间,每个区间对应一个4-bit的值。
其次,将原始的32位浮点数值映射到最近的量化区间上。例如,如果原始值是0.85,且0.8和0.9是两个最近的量化点,根据舍入规则,0.85可能被量化为0.8或0.9。

微调过程

  • 4 位 NormalFloat,QLoRA 使用 NF4(Normal Float 4)bit 来量化压缩预训练模型。这是一种优化的 4 位量化方法,它针对神经网络权重通常遵循零中心正态分布的特性进行优化。使用标准正态分布函数将权重缩放到[-1, 1]的范围内。相比传统的 4 位量化,它的权重信息损失少,从而提高了模型量化的整体精度。
  • 双重量化,双重量化是一种内存优化策略,它对量化所使用的常数进行二次量化,进一步减小内存占用。这意味着我们可以在保持精度的同时,降低了内存需求。
  • Page Optimizer,这是一种内存管理技术,利用了 NVIDIA 的统一内存特性,在 CPU 和 GPU 之间进行自动 page 对 page 传输,它在 GPU 内存不足时,可以将一部分数据暂时移到 CPU 内存,需要时再移回。这降低了在大型模型训练时由于内存不足而造成的问题。
发现使用 LoRA 时可以节省 33% 的 GPU 内存。然而,由于 QLoRA 中预训练模型权重的额外量化和去量化,训练时间增加了 39%。
默认 LoRA 具有 16 bit 浮点精度:
  • 训练时长:1.85 小时
  • 内存占用:21.33GB
具有 4 位正常浮点数的 QLoRA
  • 训练时长为:2.79h
  • 内存占用为:14.18GB
此外,发现模型的性能几乎不受影响,这说明 QLoRA 可以作为 LoRA 训练的替代方案,更进一步解决常见 GPU 内存瓶颈问题。从 QLoRA 的名字可以看出,QLoRA 实际上是 Quantize+LoRA 技术,简单的说就是把大模型(Base Model)在训练的时候从 16bit 压缩到 4bit。从而降低训练的显存

LoRA、AdaLoRA和QLoRA对比

  • LoRA通过低秩分解减少了参数数量;
  • AdaLoRA通过自适应参数预算分配进一步提升了性能;
  • 而QLoRA则通过量化技术大幅降低了内存使用
 
posted @ 2024-10-14 14:41  山上有风景  阅读(117)  评论(0编辑  收藏  举报