Post

How to use Large Language Model

大模型调优方法

  • 这三种技术在性能、效率和适用范围上各有千秋。选择哪种方法取决于具体的应用需求。

提示工程(Prompt Engineering)

  • 输入prompt给large language model,大语言模型就根据这个输入来生成回应。然后得到我们想要的结果。这实际上是一种精确的输入方法,旨在引导模型产生相应的输出。大多数情况下prompts指的是文本,尤其是以自然语言来作为描述的文本。
  • 当AI应用需求不涉及大量外部知识,且主要依赖模型现有知识库时,提示工程是一个简单有效的选择。提示工程通过设计合适的提示,引导模型生成更符合预期的回答。由于这类应用场景中,模型已经具备足够的知识储备,因此只需通过优化提示,便可实现更好的性能。这种方法适用于各种通用场景,如自然语言生成、对话系统等。
    • 但是它的缺点也非常明显。因为通常大模型的实现原理,都会对输入序列的长度有限制,Prompt Engineering 的方式会把Prompt搞得很长。越长的Prompt,大模型的推理成本越高,因为推理成本是跟Prompt长度的平方正向相关的。另外,Prompt太长会因超过限制而被截断,进而导致大模型的输出质量打折口,这也是一个非常严重的问题。对于个人使用者而言,如果是解决自己日常生活、工作中的一些问题,直接用Prompt Engineering的方式,通常问题不大。但对于对外提供服务的企业来说,要想在自己的服务中接入大模型的能力,推理成本是不得不要考虑的一个因素,微调相对来说就是一个更优的方案。
    • Prompt Engineering的效果达不到要求,企业又有比较好的自有数据,能够通过自有数据,更好的提升大模型在特定领域的能力。这时候微调就非常适用。要在个性化的服务中使用大模型的能力,这时候针对每个用户的数据,训练一个轻量级的微调模型,就是一个不错的方案。
    • 数据安全的问题。如果数据是不能传递给第三方大模型服务的,那么搭建自己的大模型就非常必要。通常这些开源的大模型都是需要用自有数据进行微调,才能够满足业务的需求,这时候也需要对大模型进行微调。

检索增强生成(Retrieval Augmented Generation)

  • RAG技术通常将客户的原有文档切分成很多片段,理解每一个片段的语义并将其保存在数据库中,通过大语言模型理解用户的查询需求,并将相关的片段从数据库中检索出来,将提示工程与数据库查询相结合以获得上下文丰富的答案。达到提升生成内容的精准度。
  • 在需要引入和利用大量外部知识的场景中,RAG能提供更加丰富和准确的输出,RAG更多的是关于改变知识,而且利用外部知识库来生成答案。通过结合外部知识库,RAGs能够为模型提供更多上下文信息,从而生成更精确、详细的回答。这类方法适用于知识密集型场景,如问答系统、信息抽取等。

微调(Fine-tuning)

  • 微调和前两个不同的地方在于,前面两种技术,不管是提示工程还是RAG,大模型本身是没有任何的变化的,但是如果做微调,其实是要去通过一个训练的过程来修改大模型它本身的参数,使模型能更加专业化。比如,一个语言模型可以在医学文献上进行微调,从而更擅长回答健康护理相关的问题。
  • 当AI应用需要在特定领域内表现出高度专业化时,微调是最佳选择,微调主要是关于改变模型的行为,通过训练特定领域的数据,使模型更好地适应特定应用场景。微调能够让模型在特定领域具备更高的准确性和专业性,从而满足领域内复杂、专业的要求。这类方法适用于医疗、金融、法律等特定领域。
  • 如果你的应用既需要定制知识又需要改变模型的行为,那么采用混合方案(RAGs + 微调)将是更佳选择。通过结合RAGs和微调,可以在特定领域内实现丰富的知识生成和高度专业化的模型性能。这种混合方案充分发挥了两种方法的优势,使得AI应用在定制知识和行为改变方面达到更高水平。

    从参数规模的角度,大模型的微调分成两条技术路线:

  • 一条是对全量的参数,进行全量的训练,这条路径叫全量微调FFT(Full Fine Tuning)。

PEFT

  • 区别比较
  • 一条是只对部分的参数进行训练,这条路径叫PEFT(Parameter-Efficient Fine Tuning)。

    • FFT的原理,就是用特定的数据,对大模型进行训练,将W变成W,W相比W ,最大的优点就是上述特定数据领域的表现会好很多。
    • 但FFT也会带来一些问题,影响比较大的问题,主要有以下两个:
    • 一个是训练的成本会比较高,因为微调的参数量跟预训练的是一样的多的;
    • 一个是叫灾难性遗忘(Catastrophic Forgetting),用特定训练数据去微调可能会把这个领域的表现变好,但也可能会把原来表现好的别的领域的能力变差。
    • PEFT主要想解决的问题,就是FFT存在的上述两个问题,PEFT也是目前比较主流的微调方案。
    • 从训练数据的来源、以及训练的方法的角度,大模型的微调有以下几条技术路线:
    • 一个是监督式微调SFT(Supervised Fine Tuning),这个方案主要是用人工标注的数据,用传统机器学习中监督学习的方法,对大模型进行微调;
    • 一个是基于人类反馈的强化学习微调RLHF(Reinforcement Learning with Human Feedback),这个方案的主要特点是把人类的反馈,通过强化学习的方式,引入到对大模型的微调中去,让大模型生成的结果,更加符合人类的一些期望;
    • 还有一个是基于AI反馈的强化学习微调RLAIF(Reinforcement Learning with AI Feedback),这个原理大致跟RLHF类似,但是反馈的来源是AI。这里是想解决反馈系统的效率问题,因为收集人类反馈,相对来说成本会比较高、效率比较低。
    • 不同的分类角度,只是侧重点不一样,对同一个大模型的微调,也不局限于某一个方案,可以多个方案一起。
    • 微调的最终目的,是能够在可控成本的前提下,尽可能地提升大模型在特定领域的能力。

      一些比较流行的PEFT方案

  • 从成本和效果的角度综合考虑,PEFT是目前业界比较流行的微调方案。接下来介绍几种比较流行的PEFT微调方案。

Prompt Tuning

  • 出发点,是基座模型(Foundation Model)的参数不变,为每个特定任务,训练一个少量参数的小模型,在具体执行特定任务的时候按需调用。
  • Prompt Tuning的基本原理是在输入序列X之前,增加一些特定长度的特殊Token,以增大生成期望序列的概率。
  • 具体来说,就是将X = [x1, x2, …, xm]变成,X = [x1, x2, ..., xk; x1, x2, …, xm], Y = WX`。
  • 如果将大模型比做一个函数:Y=f(X),那么Prompt Tuning就是在保证函数本身不变的前提下,在X前面加上了一些特定的内容,而这些内容可以影响X生成期望中Y的概率。
  • Prompt Tuning的具体细节,可以参见:The Power of Scale for Parameter-Efficient Prompt Tuning

Prefix Tuning

  • 感来源是,基于Prompt Engineering的实践表明,在不改变大模型的前提下,在Prompt上下文中添加适当的条件,可以引导大模型有更加出色的表现。
  • Prefix Tuning的出发点,跟Prompt Tuning的是类似的,只不过它们的具体实现上有一些差异。
  • Prompt Tuning是在Embedding环节,往输入序列X前面加特定的Token。
  • 而Prefix Tuning是在Transformer的Encoder和Decoder的网络中都加了一些特定的前缀。
  • 具体来说,就是将Y=WX中的W,变成W = [Wp; W],Y=WX。
  • Prefix Tuning也保证了基座模型本身是没有变的,只是在推理的过程中,按需要在W前面拼接一些参数。
  • Prefix Tuning的具体细节,可以参见:Prefix-Tuning: Optimizing Continuous Prompts for Generation

LoRA模型

  • LoRA是跟Prompt Tuning和Prefix Tuning完全不相同的另一条技术路线。
    • LoRA背后有一个假设:我们现在看到的这些大语言模型,它们都是被过度参数化的。而过度参数化的大模型背后,都有一个低维的本质模型。
    • 通俗讲人话:大模型参数很多,但并不是所有的参数都是发挥同样作用的;大模型中有其中一部分参数,是非常重要的,是影响大模型生成结果的关键参数,这部分关键参数就是上面提到的低维的本质模型。
    • LoRA的基本思路,包括以下几步:
    • 首先, 要适配特定的下游任务,要训练一个特定的模型,将Y=WX变成Y=(W+∆W)X,这里面∆W主是我们要微调得到的结果;
    • 其次,将∆W进行低维分解∆W=AB (∆W为m * n维,A为m * r维,B为r * n维,r就是上述假设中的低维);
    • 接下来,用特定的训练数据,训练出A和B即可得到∆W,在推理的过程中直接将∆W加到W上去,再没有额外的成本。
    • 另外,如果要用LoRA适配不同的场景,切换也非常方便,做简单的矩阵加法即可:(W + ∆W) - ∆W + ∆W`。
    • 关于LoRA的具体细节,可以参见LoRA: Low-Rank Adaptation of Large Language Models
  • 什么是LoRA模型 LoRA的全称是LoRA: Low-Rank Adaptation of Large Language Models,由于GPT参数量超过千亿,训练成本太高,因此LoRA采用了一个办法,仅训练低秩矩阵(low rank matrics),使用时将LoRA模型的参数注入(inject)大模型,从而改变大模型的生成风格。
  • 用数据公式表达如下,其中$W_0$是初始SD模型的参数(Weights), $BA$为低秩矩阵也就是LoRA模型的参数, $W$代表被LORA模型影响后的最终大模型参数。整个过程是一个简单的线性关系,可以认为是原模型叠加LORA模型后,得到一个全新效果的模型。
    • \(W = W_0 +BA\)

      针对的问题

    • 全量参数 Fine-tune 需要调整模型全部参数,随着预训练模型规模的不断扩大(GPT-3,175B),全量 Fine-tune 的资源压力也倍增。高效、快速对模型进行领域或任务的微调,在大模型时代极其重要。

      替代解决方案

    • 针对全量 Fine-tune 的昂贵问题,目前主要有两种解决方案:Adapt Tuning。即在模型中添加 Adapter 层,在微调时冻结原参数,仅更新 Adapter 层。具体而言,其在预训练模型每层中插入用于下游任务的参数,即 Adapter 模块,在微调时冻结模型主体,仅训练特定于任务的参数。
      • 每个 Adapter 模块由两个前馈子层组成,第一个前馈子层将 Transformer 块的输出作为输入,将原始输入维度 d 投影到 m,通过控制 m 的大小来限制 Adapter 模块的参数量,通常情况下 m « d。在输出阶段,通过第二个前馈子层还原输入维度,将 m 重新投影到 d,作为 Adapter 模块的输出(如上图右侧结构)。
      • LoRA 事实上就是一种改进的 Adapt Tuning 方法。但 Adapt Tuning 方法存在推理延迟问题,由于增加了额外参数和额外计算量,导致微调之后的模型计算速度相较原预训练模型更慢。
    • Prefix Tuning。该种方法固定预训练 LM,为 LM 添加可训练,任务特定的前缀,这样就可以为不同任务保存不同的前缀,微调成本也小。具体而言,在每一个输入 token 前构造一段与下游任务相关的 virtual tokens 作为 prefix,在微调时只更新 prefix 部分的参数,而其他参数冻结不变。
      • 也是目前常用的微量微调方法的 Ptuning,其实就是 Prefix Tuning 的一种改进。但 Prefix Tuning 也存在固定的缺陷:模型可用序列长度减少。由于加入了 virtual tokens,占用了可用序列长度,因此越高的微调质量,模型可用序列长度就越低。

        LoRA 的思路

    • LoRA文中指出,现有的预训练模型通常是过参数化的(the learned over-parametrized models in fact reside on a low intrinsic dimension),在对这些模型进行微调时,参数的更新主要在低维子空间中。换而言之,很多高维子空间的参数在微调前后根本就没动。基于这一点,微调所学的 $\Delta W$ 其实也就不需要那么高的维度(秩),我们可以将其降低到一个更低的维度进行优化。当然从这里也可以注意到,如果参数的更新也会大量发生在高维子空间中,此时进行低秩分解会遗漏信息,导致LoRA失效。
    • 如果一个大模型是将数据映射到高维空间进行处理,这里假定在处理一个细分的小任务时,是不需要那么复杂的大模型的,可能只需要在某个子空间范围内就可以解决,那么也就不需要对全量参数进行优化了,我们可以定义当对某个子空间参数进行优化时,能够达到全量参数优化的性能的一定水平(如90%精度)时,那么这个子空间参数矩阵的秩就可以称为对应当前待解决问题的本征秩(intrinsic rank)。
    • 预训练模型本身就隐式地降低了本征秩,当针对特定任务进行微调后,模型中权重矩阵其实具有更低的本征秩(intrinsic rank)。同时,越简单的下游任务,对应的本征秩越低。Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tunin因此,权重更新的那部分参数矩阵尽管随机投影到较小的子空间,仍然可以有效的学习,可以理解为针对特定的下游任务这些权重矩阵就不要求满秩。我们可以通过优化密集层在适应过程中变化的秩分解矩阵来间接训练神经网络中的一些密集层,从而实现仅优化密集层的秩分解矩阵来达到微调效果。
    • 例如,假设预训练参数为 $\theta_{0}^{D} $ ,在特定下游任务上密集层权重参数矩阵对应的本征秩为 $\theta_d$, 对应特定下游任务微调参数为 $\theta^D$ ,那么有:$\theta^D = \theta_{0}^{D} + \theta^d M$. 这个 M 即为 LoRA 优化的秩分解矩阵。

      为什么矩阵B被初始化为0,而矩阵A正常高斯初始化

    • 这里讨论另外两种设置的缺点:
      1. 如果B,A全都初始化为0,那么缺点与深度网络全0初始化一样,很容易导致梯度消失(因为此时初始所有神经元的功能都是等价的)。
      1. 如果B,A全部高斯初始化,那么在网络训练刚开始就会有概率为得到一个过大的偏移值公W从而引入太多噪声,导致难以收敛。 因此,一部分初始为0,一部分正常初始化是为了在训练开始时维持网络的原有输出(初始偏移为0),但同时也保证在真正开始学习后能够 更好的收敛。

        如何理解低维子空间/高维子空间特征

    • 这里笔者给出一个可能不正确的类比。比如在计算机视觉中,无论是做分割,检测,医学等各种不同下游任务,都可以基于ImageNet上 的预训练模型(如ResNet进行微调。预训练模型中的纹理,边缘,轮廓等特征,一般是无论做哪种任务都需要的,那么这种任务无关特征 就类似于上面所提到的高维子空间特征,在下游任务微调时基本上不发生变化。反之,对于一些下游任务中自有的先验特征(比如特有的 光照条件,目标位置分布),则可以被视为上面所提到的低维子空间特征。模型想要刷点到SOTA则必须对这些任务相关特征进行有效的利 用。

LoRA和Adapter的区别

  • 主要的区别个人认为有如下几点:
  • 插入位置。LoRA是以残差连接的形式”并联”在Transformer的Q,K,V,O矩阵上,而Adapter是插入在Feed-forward Layer后面。
  • 推理延迟。LoRA在训练完后其参数可以与原有预训练模型直接合并,变回单分支结构,不会引入额外的延迟;而Adapter由于引入了
  • 额外的串联网络层,因此会带来额外的延迟。
  • 参数存储。使用LoRA进行微调,在训练完毕后只需要保存LoRA本身的参数;而使用Adapter则要保存整个原有模型的参数。

    LoRA 的优势

  • 可以针对不同的下游任务构建小型 LoRA 模块,从而在共享预训练模型参数基础上有效地切换下游任务。
  • LoRA 使用自适应优化器(Adaptive Optimizer),不需要计算梯度或维护大多数参数的优化器状态,训练更有效、硬件门槛更低。
  • LoRA 使用简单的线性设计,在部署时将可训练矩阵与冻结权重合并,不存在推理延迟。
  • LoRA 与其他方法正交,可以组合。

    LoRA 的原理

    低秩参数化更新矩阵

  • LoRA 假设权重更新的过程中也有一个较低的本征秩,对于预训练的权重参数矩阵 $W_0 \in R^{d*k}$ (d 为上一层输出维度,k 为下一层输入维度),使用低秩分解来表示其更新:
  • $W= W_0 + \Delta W = W_0 + BA$ where,
  • $B \in R^{dr}$ and $A\in R^{r k}$, 在训练过程中,$W_0$ 冻结不更新,
  • $A、B$ 包含可训练参数。 一般来说,r ≪ m i n ( d , k ) ,就是 $\Delta W$ 被分解为两个低秩矩阵相乘
  • 因此,LoRA 的前向传递函数为:
  • $h=W_0x+ \Delta W x=W_0x+BAx $
  • 在开始训练时,对 A 使用随机高斯初始化,对 B 使用零初始化,然后使用 Adam 进行优化。

    应用于 Transformer

  • 在 Transformer 结构中,LoRA 技术主要应用在注意力模块的四个权重矩阵:$W_q$, $W_k$, $W_v$, $W_0$, 而冻结 MLP 的权重矩阵。
  • 通过消融实验发现同时调整 $W_q$ 和 $W_v$ 会产生最佳结果。
  • 在上述条件下,可训练参数个数为:
  • $\Theta = 2 * L_{LoRA} d_{model} r$ where $L_{LoRA}$ 为应用 LoRA 的权重矩阵的个数, $d_{model}$为 Transformer 的输入输出维度,r 为设定的 LoRA 秩。一般情况下,r 取到 4、8、16。

    代码实现

  • 目前一般通过 peft 库来实现模型的 LoRA 微调。peft 库是 huggingface 开发的第三方库,其中封装了包括 LoRA、Adapt Tuning、P-tuning 等多种高效微调方法,可以基于此便捷地实现模型的 LoRA 微调。
  • 本文简单解析 peft 库中的 LoRA 微调代码,简单分析 LoRA 微调的代码实现。

    实现流程

  • LoRA 微调的内部实现流程主要包括以下几个步骤:
  • 确定要使用 LoRA 的层。peft 库目前支持调用 LoRA 的层包括:nn.Linear、nn.Embedding、nn.Conv2d 三种。
  • 对每一个要使用 LoRA 的层,替换为 LoRA 层。所谓 LoRA 层,实则是在该层原结果基础上增加了一个旁路,通过低秩分解(即矩阵 A 和矩阵 B)来模拟参数更新。
  • 冻结原参数,进行微调,更新 LoRA 层参数。

    确定 LoRA 层

  • 在进行 LoRA 微调时,首先需要确定 LoRA 微调参数,其中一个重要参数即是 target_modules。target_modules 一般是一个字符串列表,每一个字符串是需要进行 LoRA 的层名称,例如:
    1
    
    target_modules = ["q_proj","v_proj"]
    
  • 这里的 q_proj 即为注意力机制中的 $W_q$, “v_proj” 即为注意力机制中的 $W_v$, 我们可以根据模型架构和任务要求自定义需要进行 LoRA 操作的层。
  • 在创建 LoRA 模型时,会获取该参数,然后在原模型中找到对应的层,该操作主要通过使用 re 对层名进行正则匹配实现:
    1
    2
    3
    
    # 找到模型的各个组件中,名字里带"q_proj","v_proj"的
    target_module_found = re.fullmatch(self.peft_config.target_modules, key)
    # 这里的 key,是模型的组件名
    

    替换 LoRA 层

  • 对于找到的每一个目标层,会创建一个新的 LoRA 层进行替换。
  • LoRA 层在具体实现上,是定义了一个基于 Lora 基类的 Linear 类,该类同时继承了 nn.Linear 和 LoraLayer。LoraLayer 即是 Lora 基类,其主要构造了 LoRA 的各种超参:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    class LoraLayer:
    def __init__(
      self,
      r: int, # LoRA 的秩
      lora_alpha: int, # 归一化参数
      lora_dropout: float, # LoRA 层的 dropout 比例
      merge_weights: bool, # eval 模式中,是否将 LoRA 矩阵的值加到原权重矩阵上
    ):
      self.r = r
      self.lora_alpha = lora_alpha
      # Optional dropout
      if lora_dropout > 0.0:
          self.lora_dropout = nn.Dropout(p=lora_dropout)
      else:
          self.lora_dropout = lambda x: x
      # Mark the weight as unmerged
      self.merged = False
      self.merge_weights = merge_weights
      self.disable_adapters = False
    
  • nn.Linear 就是 Pytorch 的线性层实现。Linear 类就是具体的 LoRA 层,其主要实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    class Linear(nn.Linear, LoraLayer):
      # LoRA 层
      def __init__(
          self,
          in_features: int,
          out_features: int,
          r: int = 0,
          lora_alpha: int = 1,
          lora_dropout: float = 0.0,
          fan_in_fan_out: bool = False, 
          merge_weights: bool = True,
          **kwargs,
      ):
          # 继承两个基类的构造函数
          nn.Linear.__init__(self, in_features, out_features, **kwargs)
          LoraLayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout, merge_weights=merge_weights)
    
          self.fan_in_fan_out = fan_in_fan_out
          # Actual trainable parameters
          if r > 0:
              # 参数矩阵 A
              self.lora_A = nn.Linear(in_features, r, bias=False)
              # 参数矩阵 B
              self.lora_B = nn.Linear(r, out_features, bias=False)
              # 归一化系数
              self.scaling = self.lora_alpha / self.r
              # 冻结原参数,仅更新 A 和 B
              self.weight.requires_grad = False
          # 初始化 A 和 B
          self.reset_parameters()
          if fan_in_fan_out:
              self.weight.data = self.weight.data.T
    
  • 替换时,直接将原层的 weight 和 bias 复制给新的 LoRA 层,再将新的 LoRA 层分配到指定设备即可。

    训练

  • 实现了 LoRA 层的替换后,进行微调训练即可。由于在 LoRA 层中已冻结原参数,在训练中只有 A 和 B 的参数会被更新,从而实现了高效微调。训练的整体过程与原 Fine-tune 类似,此处不再赘述。由于采用了 LoRA 方式,forward 函数也会对应调整:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    def forward(self, x: torch.Tensor):
      if self.disable_adapters:
          if self.r > 0 and self.merged:
              self.weight.data -= (
                  transpose(self.lora_B.weight @ self.lora_A.weight, self.fan_in_fan_out) * self.scaling
              )
              self.merged = False
    
          return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
      '''主要分支'''
      elif self.r > 0 and not self.merged:
          result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
          if self.r > 0:
              result += self.lora_B(self.lora_A(self.lora_dropout(x))) * self.scaling
          return result
      else:
          return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
    
  • 上述代码由于考虑到参数合并问题,有几个分支,此处我们仅阅读第二个分支即 elif 分支即可。基于 LoRA 的前向计算过程如前文公式所示,首先计算原参数与输入的乘积,再加上 A、B 分别与输入的乘积即可。

    使用 peft 实现大模型微调

  • peft 进行了很好的封装,支持我们便捷、高效地对大模型进行微调。此处以开源大模型 ChatGLM2-6B 为例,简要介绍如何使用 peft 对大模型进行微调。此处我们假设数据集已处理完成,不再介绍数据处理过程。
    1
    2
    3
    4
    
    import torch.nn as nn
    from transformers import AutoTokenizer, AutoModel
    from peft import get_peft_model, LoraConfig, TaskType, PeftModel
    from transformers import Trainer
    
  • 首先需要加载原模型与原 tokenizer,此处我们使用 transformers 进行加载:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    # 加载底座模型
    tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
    model = AutoModel.from_pretrained(
      MODEL_PATH, load_in_8bit=False, trust_remote_code=True, device_map="auto"
    )
    # 对底座模型做一些设置
    model.gradient_checkpointing_enable()
    model.enable_input_require_grads()
    model.is_parallelizable = True
    model.model_parallel = True
    model.config.use_cache = (
      False  # silence the warnings. Please re-enable for inference!
    )
    
  • 接着,设定 peft 参数:
    1
    2
    3
    4
    5
    6
    7
    
    peft_config = LoraConfig(
      task_type=TaskType.CAUSAL_LM,
      inference_mode=False,
      r=8,
      lora_alpha=32,
      lora_dropout=0.1,
    )
    
  • 注意,对不同的模型,LoRA 参数可能有所区别。例如,对于 ChatGLM,无需指定 target_modeules,peft 可以自行找到;对于 BaiChuan,就需要手动指定。task_type 是模型的任务类型,大模型一般都是 CAUSAL_LM 即传统语言模型。
  • 然后获取 LoRA 模型:
    1
    
    model = get_peft_model(model, peft_config)
    
  • 此处的 get_peft_model 的底层操作,即为上文分析的具体实现。
  • 最后使用 transformers 提供的 Trainer 进行训练即可:
    1
    2
    3
    4
    5
    6
    7
    
    trainer = Trainer(
      model=model,
      train_dataset=dataset,
      args=training_args,
      data_collator=lambda x : data_collator_glm(x, tokenizer),
    )
    trainer.train()
    

    QLoRA

  • LoRA 效果已经非常好了,可以媲美全量微调的效果了,那为什么还要有个QLoRA呢?
  • 这里先简单介绍一下,量化(Quantization)。
  • 量化,是一种在保证模型效果基本不降低的前提下,通过降低参数的精度,来减少模型对于计算资源的需求的方法。
  • 量化的核心目标是降成本,降训练成本,特别是降后期的推理成本。
  • QLoRA就是量化版的LoRA,它是在LoRA的基础上,进行了进一步的量化,将原本用16bit表示的参数,降为用4bit来表示,可以在保证模型效果的同时,极大地降低成本。
  • 论文中举的例子,65B的LLaMA 的微调要780GB的GPU内存;而用了QLoRA之后,只需要48GB。效果相当惊人!
  • 关于QLoRA的具体细节,可以参见:QLoRA: Efficient Finetuning of Quantized LLMs

References

This post is licensed under CC BY 4.0 by the author.