大型语言模型(LLM)微调策略

2024/9/29 21:02:38

本文主要是介绍大型语言模型(LLM)微调策略,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

解锁精准:定制您的大型语言模型以完美适应您的需求!

图片来源: Leonardo.ai

大型语言模型得益于像Common Crawl这样的组织提供的大规模数据集训练,能够通过零样本或少量样本提示来执行多种任务。随着检索增强生成(RAG)方法的兴起,这些通用模型被组织广泛应用于各种应用,从简单的聊天机器人到更复杂的代理自动化(角色)。尽管已经开发了诸如GraphRAG这样的技术来基于实体从文档中提取关系,但由于基础模型缺乏足够的上下文,它们可能无法完全满足每个领域的特定需求。这一局限性导致了每月都有新的模型被发布。

对于这些特定领域的模型,可以对现有的大型语言模型架构进行调整,使其权重适应特定领域的上下文,这一过程被称为微调。本文将探讨语言模型的微调过程,分析各种类型、涉及的关键考虑因素以及一个无代码(几乎)开源工具的实例。让我们深入探讨,揭开微调语言模型的奥秘吧!

微调:一个简单的类比

为了理解微调,我们可以用一个类比来说明。想象你是一名准备科学考试的学生。你从课堂上打下了坚实的基础。随着考试的临近,你开始专注于将要测试的具体主题。你通过解答练习题来评估自己的理解,并根据这些题目中的表现来复习相关材料。你可能还会向朋友寻求指导,查阅在线资源,或者重新回顾关键主题。

这个过程类似于微调:我们从一个预训练的模型(相当于一个基础扎实的学生)开始,将其导向特定的任务(修订特定的主题),评估其表现(通过练习测试),并不断迭代这一过程,直到我们达到最佳结果(基于性能指标)。就像学生可以在特定领域变得熟练一样,我们也可以开发一个在某个领域/多个领域所有任务中都表现出色的语言模型。最终,这个过程的有效性取决于所选的模型、特定的任务以及所使用的训练数据的质量。

常见的微调应用场景

在我们深入了解微调之前,让我们通过分析几个场景来探讨微调为何必要。

语言学习

让我们比较两个版本的Llama,它们的任务是用泰米尔语回答问题。

来自Llama-2–7b-chat模型的回复

来自Mozhi.ai HuggingFace空间的回复(基于这里的模型微调版本)

如上例所示,基础版本的Llama难以理解请求的语言,而经过微调的模型则能够流利地用该语言回应。这种能力源于微调过程,使模型能够学习并识别新语言中的模式。相比之下,简单的检索增强生成(RAG)应用存在局限性,因为它们无法有效地将新上下文与现有知识联系起来。在模型必须获取和整合多种上下文的场景中,微调变得至关重要。

有效保护LLM

在AI开发中,一个重要的挑战是为模型建立有效的防护措施。想象一下,一个税务助手AI意外地开始回答关于心理健康的问题。虽然AI能够处理多样的话题令人印象深刻,但这也会带来风险。并不是所有的模型都经过了适当的数据训练,特别是在像心理健康这样敏感的领域。

即使我们指示模型不要回答某些问题,仍然会出现两个主要问题:提示破解和上下文窗口。提示破解是指用户通过操纵输入来绕过限制的情况。此外,虽然更大的模型,如拥有128k上下文窗口的Llama 3.1,提供了更多的空间来容纳指令,但这并不能完全解决问题。尽管上下文窗口可以容纳更多信息,但如果用于设置上下文的token过多,就会减少实际内容的空间。

有效的提示模板可以有所帮助,但它们无法涵盖所有可能的细微差别。虽然较大的上下文窗口是有益的,但这并不是一个全面的解决方案,使得微调成为一个更可靠的选择。即使是像Meta这样的主要玩家也推出了LlamaGuard,这是Llama模型的一个微调版本,旨在执行聊天防护措施并防止有害的响应。

AI 角色

新闻媒体经常报道相同的新闻故事,但每个媒体机构都有其独特的视角。想象一下,有一个聊天助手可以帮助从多个来源收集信息来撰写文章。如果你的组织使用像ChatGPT这样的预训练模型,有效的提示——包括用户和系统指令——可以生成有用的新闻片段。然而,这些片段可能并不总是符合你组织的具体风格或指导方针。

为了确保与您组织的语气和标准保持一致,您可以使用您团队撰写的新闻文章来微调模型。这种方法可以创建一个更个性化的AI,准确反映您组织的声音,并且可以依赖它来生成一致且精确的内容。此外,现在许多初创公司正专注于开发企业级AI角色,以简化手动活动。

更小、更智能的模型

你并不总是需要一个庞大的模型来获得出色的结果。一个较小的模型,拥有数十亿(甚至几百万)参数,通常在效率和成本效益方面比非常大的语言模型更适合你的特定需求。这种方法显著降低了运行和维护这些模型的成本。

在这篇文章中,我们将探讨一种称为参数高效微调(PEFT)的技术。该方法通过矩阵分解将大型模型表示为更小、更易管理的形式。这意味着你不需要使用模型的所有参数就能达到你的目标——尽管可能会在性能上有一些微小的妥协。因此,你可以在消费级硬件上使用强大的模型而不必承担过高的成本。因此,并非总是需要非常大的模型。

在微调之前,考虑以下因素:

  • 数据充足: 您是否有足够的数据来有效训练模型?
  • 硬件可用性: 是否有必要的硬件来训练和运行模型?
  • RAG策略: 您的问题能否通过现有的LLM API使用RAG策略来解决?
  • 上市时间: 您需要服务多快时间投入运营?
  • 您可以结合不同服务提供商的API来构建统一的产品。这些模型质量高,并且会持续更新以满足最新的标准。彻底的探索可以帮助您找到最适合您特定需求的模型。

微调过程

图片来源 — 博客

现在我们了解了微调是什么及其应用,让我们来探索不同的微调类型及其各自的功能。根据学习方法,微调大型语言模型(LLMs)主要有三种流行的方法:

  1. 监督学习 — 在这种方法中,模型通过输入-输出对的训练来学习新概念。例如,指令微调就是这种方法的一个例子,我们教模型如何对特定指令提供精确的响应。
    想象一下,一个学生在学习写作文的情景。最初,学生会写各种主题的作文,但他们的作品并不完美。老师会审阅这些作文,提供详细的反馈并提出改进建议。随着时间的推移,学生根据这些反馈修改他们的作文,从而变得更好的作家。
    在大型语言模型(LLM)的监督微调中,模型开始时具有通用知识,然后通过类似的过程进行“学习”:它在特定示例上进行训练,这些示例具有正确的输出和反馈,以增强其在特定任务上的表现,就像学生通过反馈提高写作技能一样。

  2. 自监督学习 — 这种强大的方法用于语言模型的调优,帮助模型理解数据的语言模型细微差别。它利用数据的内在结构来生成监督信号,从而不需要手动标注的数据。
    想象一个学生收到不完整或打乱的笔记,必须自己推断缺失的部分。这种情况类似于AI中的自监督学习。就像学生利用上下文和自己的知识填补空白一样,自监督模型通过预测数据的隐藏部分并根据这些预测来完善其理解。它代表了一种从数据本身学习的方法,而无需明确的标签或直接答案。一些流行的战略包括掩码语言建模(BERT)、自回归语言建模(GPT)、对比学习(SimCLR)、下一句预测(BERT)和置换语言建模(XLNet)等,这些策略经常被组合使用。

  3. 强化学习 — 语言模型的强化学习(RL)涉及通过奖励系统来训练模型生成更好的响应。模型根据提示生成响应,并因高质量的回答而获得正奖励,因低质量的回答而受到惩罚。通过这种反馈,模型调整其参数以随着时间的推移提高其性能。用于最大化奖励(理想情况下)的模板称为策略,各种策略优化策略有助于RL实现更接近人类输出的结果。
    想象一个学生在课堂上学习解数学题的情景。每当学生正确解答一个问题时,老师会给他们一个金星作为奖励,鼓励他们进一步努力和改进。偶尔,老师也会提供有关错误的建设性反馈,帮助学生调整他们的方法。随着时间的推移,学生学会了哪些策略能获得更多金星和更少错误。
    在这个类比中,金星和反馈代表强化学习中的奖励和惩罚,指导学生通过试错学习和优化他们的解题技能。惩罚的严重程度取决于老师的策略。目标是找到一种最佳策略,以实现最佳结果。

接下来,我们需要根据手头的任务决定采用纵向还是横向的微调策略。

横向微调 涉及将模型调整为在一系列相似的任务或领域中表现良好。模型会在涵盖多个相关领域的数据上进行微调,而不专注于任何单一领域。这种方法的一个显著优势是能够在处理多种任务的同时保持基础模型的通才性质。

纵向微调 则侧重于将模型调整为在特定任务或领域中表现出色。通过使用高度专业化或特定领域的数据对模型进行微调,使其能够更好地理解和生成与特定领域相关的响应,从而获得高度准确的结果。

我们也需要确定微调模型所需的参数数量。主要关注点将是微调和推理所需的计算资源,因为成本可能会迅速增加。此外,我们还应考虑具体任务以及希望基础模型适应的数据类型。涉及三种不同的策略:全参数重新训练、参数高效微调(PEFT)和迁移学习。在本次讨论中,我们将重点介绍各种PEFT策略,以在消费级GPU上微调模型。

这些只是关键考虑因素,还有诸如决定采用自上而下或自下而上的方法、单独训练每一层或批量训练等策略。因此,关键考虑因素加上适当的微调模块选择将为我们提供正确的基础设施基线。由于微调本身是一个庞大的领域,我们将专注于一个较小的子集(最常见的)来实现参数高效的垂直微调。你可以参考之前的文章来了解在HuggingFace上看到的各种量化模型格式。

PEFT微调技术

参数高效的微调(PEFT)是一种技术,它利用了这样一个理念:大型语言模型中的所有参数并不都需要更新就能达到最佳性能。通过冻结大部分参数并专注于一小部分参数,我们可以显著减少微调所需的计算资源和时间。

想象一个班级,其中一名学生在许多科目上表现出色,但在某些特定领域需要改进。老师没有重新设计整个课程,而是针对这些领域提供了有针对性的额外练习。这种方法高效,因为它利用了学生现有的知识,将资源集中在最需要的地方。同样,在PEFT中,我们只专注于优化那些有影响力的权重。

通过冻结大部分参数、继续使用残差连接以及应用适当的正则化技术,这些模型保留了先前的知识,从而避免了灾难性遗忘。像GaLore这样的方法使得在个人电脑上对大型模型(如Llama-3)进行微调成为可能,从而使高级语言建模更加易于访问。

让我们来探索一些PEFT技术,需要注意的是,这些技术并非互斥的。

参数高效微调的技术

(参数高效微调方法分类)图片来源:从缩小到放大:参数高效微调指南(ArXiv)

如上图所示,PEFT方法主要分为三大类:Addition(添加新的可训练参数)、Selection(从基础模型中选择一部分参数)和Reparametrization(采用替代表示)。本文仅提供每种方法的代码示例。您可以在HuggingFace Transformer的官方文档中找到所有PEFT的实现。

适配器

适应器属于添加类。适应器是在现有Transformer架构中添加的前向模块,用于减少全连接层之间的参数空间,如下面的图所示。

图片来源:Parameter-Efficient Transfer Learning for NLP(ArXiv)

如果全连接层在一层中缩小维度,然后在下一层中再将其维度恢复到输入维度,那么这如何减少特征空间呢?例如,假设第一个全连接层将一个256维的输入减少到16维,而第二层再将其恢复到256维。这将总共产生256 x 16 + 16 x 256 = 8,192个权重参数。相比之下,一个将256维输入映射到256维输出的单个全连接层将有256 x 256 = 65,536个参数。在适配器调优中,只有适配器、层归一化和最终头部在下游数据上进行训练,使得调优更快更高效。这种方法的成功基于以下观察:使用适配器方法训练的BERT模型达到了与完全微调的BERT模型相当的建模性能,但只需要训练3.6%的参数。一个简单的适配器模块看起来像这样。

    import torch  
    import torch.nn as nn  

    class AdapterBlock(nn.Module):  
        def __init__(self, input_dim, adapter_dim=64):  
            super(AdapterBlock, self).__init__()  
            self.down_proj = nn.Linear(input_dim, adapter_dim)  
            self.activation = nn.ReLU()  
            self.up_proj = nn.Linear(adapter_dim, input_dim)  

        def forward(self, x):  
            # 应用适配器块  
            return x + self.up_proj(self.activation(self.down_proj(x)))

还有一个独特的适配器叫做Llama-Adapter,它具有专门设计用于将Llama转变为指令跟随模型的独特适配器架构。

提示调整

提示调整再次是一种附加方法,利用软提示(通过损失反馈动态更新提示)的力量,而不是明确的人工静态提示/硬提示。这种参数高效微调(PEFT)方法旨在仅通过输入来提高模型性能,而不是改变模型权重。那么为什么不用提示工程呢?提示工程需要花费大量精力来设计理想的提示,并且存在上下文窗口长度的问题。即使提示在测试场景中有效,它们可能在其他场景中不起作用,因为提问的方式有很多。

图片来源:参数高效提示调整的规模力量 (ArXiv)

如上图所示,我们在输入嵌入向量中添加了额外的可训练令牌(与输入向量大小相同)。这些令牌不是嵌入空间中的固定点,因此可以表示任何单词。目标是不断寻找这些可训练令牌的最佳表示,以指导模型输出的完成。根据基础论文,对于分类任务,我们可以在20个令牌内(超过这个数量只会带来边际收益)实现这一点。如上所示,我们不再处理110亿个参数,而是只处理与任务提示相关的2万个参数。这种方法具有黑盒性质,因为它可以在嵌入空间中采用任何表示,难以控制。通过最近邻分析发现,这些令牌采用了语义单词表示,这意味着此方法不适合用于高度专业化的任务。其次,根据研究观察,随着模型规模的增加,这种方法证明是有效的,但对于较小的模型则不然。一个简单的提示调整块如下所示。

图片来源:参数高效提示调优的规模效应 (ArXiv)

    import torch  
    import torch.nn as nn  

    class PromptTuningBlock(nn.Module):  
        def __init__(self, input_dim, prompt_length, hidden_dim, output_dim):  
            super(PromptTuningBlock, self).__init__()  
            self.prompt_length = prompt_length  
            self.prompt_embeddings = nn.Parameter(torch.randn(prompt_length, input_dim))  
            self.input_embedding = nn.EmbeddingBag(input_dim, hidden_dim)  
            # 一个简单的线性层用于输出  
            self.fc = nn.Linear(hidden_dim + prompt_length * input_dim, output_dim)  

        def forward(self, input_ids):  
            # 获取输入嵌入  
            input_embeds = self.input_embedding(input_ids)  
            # 拼接提示嵌入  
            combined_embeds = torch.cat([input_embeds, self.prompt_embeddings.mean(dim=0).unsqueeze(0)], dim=1)  
            # 通过线性层传递  
            output = self.fc(combined_embeds)

前缀调整

这是提示调整的一种增强变体,我们在每个Transformer块的位置编码之前添加了软提示(soft-prompts),而不是仅仅在输入嵌入上添加。这样,前缀标记就成为了跨层的唯一可训练参数,并且对输出有更大的影响。_它与提示调整有何不同?_前缀调整通过在输入序列中添加特定任务的前缀来增强模型的多个层,这需要更多的参数进行微调。相比之下,提示调整仅专注于调整输入提示嵌入,导致更新的参数更少,可能更高效,但可能会限制对目标任务的适应性。虽然前缀调整由于其更大的参数集可能表现更好,但它也可能需要更多的计算资源并增加过拟合的风险。可以假设,尽管提示调整更高效,但由于微调参数较少,其表现可能不如前缀调整。一个简单的前缀调整块如下所示。

图片来源:Prefix-Tuning: 生成连续提示的优化(ArXiv)

    import torch  
    import torch.nn as nn  

    class PrefixTuningBlock(nn.Module):  
        def __init__(self, num_prefix_tokens, hidden_size, num_layers):  
            super(PrefixTuningBlock, self).__init__()  
            self.prefix_tokens = nn.Parameter(torch.randn(num_prefix_tokens, hidden_size))  
            self.transformer_layers = nn.ModuleList([nn.TransformerEncoderLayer(d_model=hidden_size, nhead=8, dropout=0.1) for _ in range(num_layers)])  

        def forward(self, input_ids):  
            # 创建一个前缀 token 的张量  
            prefix_tokens = self.prefix_tokens.unsqueeze(0).expand(input_ids.size(0), -1, -1)  
            # 将 input_ids 和 prefix_tokens 拼接起来  
            input_ids = torch.cat([prefix_tokens, input_ids], dim=1)  
            # 将拼接后的输入通过 transformer 层  
            output = input_ids  
            for layer in self.transformer_layers:  
                output = layer(output)  
            return output

大型语言模型的低秩适应(LoRA 家族)

这是一种重新参数化方法,它在语言模型的另一种表示上进行微调操作。该技术通过将注意力层中的大型权重矩阵分解为两个较小的矩阵,从而简化了该矩阵,显著减少了微调过程中需要调整的参数数量。它不是直接分解矩阵,而是从分解后的表示(伪分解)中学习。

与其在模型中添加新的参数,我们更侧重于这种替代表示方法。一般认为,将秩 r 设置为与训练数据量和模型大小成比例,可以有效缓解过拟合问题并合理管理模型预算。还观察到,LoRA 学习得更少,遗忘得也更少,这是意料之中的。

让我们通过一个例子来理解这个分解。

图片来源:Parameter-Efficient LLM Finetuning With LoRA 文章 由 Sebastian Raschka 撰写

假设我们有两个矩阵A和B,分别包含100和500个参数。因此,ΔW中的总参数数量为AXB=100X500=50,000。现在假设秩为5。那么新的权重矩阵WA和WB变为100X5=500和500X5=2,500。新的ΔW=WA+WB=500+2500=3000个参数,这相当于减少了94%。一个LoRA块的示例如下所示。

    import torch  
    import torch.nn as nn  
    import math  

    class LoRA(nn.Module):  
        def __init__(self, input_dim, output_dim, rank=8, alpha=1.0):  
            super().__init__()  
            self.input_dim = input_dim  
            self.output_dim = output_dim  
            self.rank = rank  
            self.alpha = alpha  
            # 创建LoRA权重矩阵  
            self.W_A = nn.Parameter(torch.empty(input_dim, rank))  
            self.W_B = nn.Parameter(torch.empty(rank, output_dim))  
            # 初始化LoRA权重  
            nn.init.kaiming_uniform_(self.W_A, a=math.sqrt(5))  
            nn.init.zeros_(self.W_B)  

        def forward(self, x, W):  
            h = x @ W  
            # 应用LoRA  
            h += self.alpha * x @ (self.W_A @ self.W_B)  
            return h

图片来源:博客 由 Vinija Jain 撰写 (在 QLoRA 方法中,量化为 4 位精度的是原始模型的权重。新添加的 LoRA 权重不会被量化;它们保持在较高的精度,并在训练过程中进行微调。)

有各种各样的LoRA变体,比如DoRA、QLoRA(一种流行的混合精度策略)、LoHA等。你可以在这篇文章中找到一些流行的变体,也可以从HuggingFace文档中获取transformers的实现。

*通过抑制和放大内部激活来注入适应器(IA3)

这是另一种累加方法,涉及三个阶段:向量加法、重缩放(抑制/放大)和在下游数据上的调优。这三个加法向量分别是键重缩放向量(此向量与自注意力层中的键相乘),值重缩放向量(此向量与自注意力和编码器-解码器注意力层中的值相乘)和中间激活重缩放向量(此向量与位置感知前馈网络中的中间激活相乘)。学习到的向量随后用于重缩放模型中的相应元素。这种重缩放可以抑制(减少)或放大(增加)激活,具体取决于学习到的向量中的值。最后,模型在下游任务上进行微调。在微调过程中更新学习到的向量,以优化模型的性能。该模型被提出作为少数样本提示策略(上下文学习)的更好替代方案。

图片来源:少量样本参数高效微调比上下文学习更好且更经济 (ArXiv)

通过蝴蝶分解实现正交微调(BOFT)

这是另一种重新参数化策略,我们使用正交变换对权重矩阵进行重新参数化,具体方法是采用蝴蝶因子分解。让我们来理解一下蝴蝶因子分解。目标是将给定的权重矩阵表示为两个矩阵的乘积,即一个对角矩阵和一个置换矩阵,如下面的图所示。

图片来源:作者

通过引入微调损失和预训练损失之间梯度正交性的因素,我们保持了结构约束。正如HuggingFace 概念指南所述,这有助于在微调过程中保持双曲球面能量不变。能量可以被视为双曲球面上一个点到原点的距离。在微调过程中保持双曲球面能量不变,确保学习到的表示保持在原来的位置附近,减少了遗忘先前学习信息的风险。这种稀疏表示还有助于提高模型的泛化能力。正如作者所说,“蝴蝶结构在OFT中作为不同块数超参数之间的平滑插值,使正交微调框架更加灵活,更重要的是,更加参数高效”。这种方法主要在图像模型上进行训练和测试,也可以用于文本到图像模型。

图片来源:Parameter-Efficient 正交微调通过蝴蝶因子分解

其他流行的LoRA替代方案是在各层之间共享相同的低秩矩阵,并用原始模型权重的主要奇异值和奇异向量初始化适配器层。这一问题始终备受关注,因为主要目标是在智能手机和PC(移动芯片)等本地硬件上拟合和计算模型。

LlamaFactory(微调框架)

让我们使用PEFT策略对预训练的Llama模型进行微调,以学习Docker查询。LlamaFactory是一个统一的框架,用于使用一系列前沿高效的训练方法对所有大型语言模型(LLMs)进行微调。我们将探索用户界面(UI)——尽管也可以通过命令行界面(CLI)进行操作——包括如何从Hugging Face添加新的模型和数据集、微调术语词汇表以及导出微调模型的过程。

安装

安装可以按照这些GitHub仓库说明进行。建议在单独的虚拟环境中构建此类特定工具。

图片来源:作者

让我们来看看上述图片中的各个字段。

Lang — 设置UI的语言。当前支持英语、俄语、中文和韩语。

模型名称 — 设置微调的基础模型。它已经支持所有流行模型。如果我们需要添加自定义模型怎么办? 将您的模型添加到 src/llamafactory/extras/constants.py 文件中的相应模型注册表中,如下图所示。下载源也可以是一个包含所有模型信息的本地目录。

图片来源: 作者(从HuggingFace添加Mistral-Small-Instruct模型)

模型路径 — 加载模型的路径

微调方法 — 它支持全量微调、冻结微调和LoRA微调方法。这些方法包括全量微调、迁移学习和参数高效微调(PEFT)方法。

检查点路径 — 一旦我们微调了一个模型,所有模型信息都会被保存为检查点(类似于微调模型核心属性的元信息,以后可以加载并用于数据任务)。这仅在存在微调版本的选定模型时显示(通过LlamaFactory训练或在LlamaFactory模板中更新本地目录路径)。

高级配置 — 由于本地硬件可能无法处理调优和全精度或半精度的计算,我们需要对模型进行量化(了解更多请参阅这里),以便使其能在我们的电脑上运行。所有量化参数都可以在这个选项卡中进行调整。

  • 量化位数 (QLoRA) — 支持两种选项,4位和8位。这是分配给表示模型权重的位数,因此位数越低,存储的信息越少,精度也越低。理论上是这样,但像GGUF和bitsandbytes这样的量化方法可以帮助在4位精度下保留大部分模型推理,使得在CPU上调整和运行模型成为可能。
  • 量化方法 — 用于量化的量化方法。有关量化的更多详细信息,请参阅我之前的文章。支持的方法包括半二次量化(hqq)、eetq和bitsandbytes。Bitsandbytes已成为将模型量化到4位和8位的最知名工具/方法。您可以从HuggingFace文档中了解更多。
  • 提示模板 — 有许多现成的模板可供选择。我们可以通过在src/ llamafactory/ data /template.py下的__registertemplate部分添加一个新的模板来注册一个新模板,如下面的图所示。

图片来源:作者(添加一个新的提示,使用新的 _register_template 方法)

  • RoPE 扩展 — RoPE 扩展涉及调整旋转位置嵌入(RoPE)的参数,以增强大型语言模型(LLMs)在超出其原始训练上下文长度的情况下进行外推的能力。通过修改 RoPE 计算中的基础值,该技术允许 LLM 更有效地处理比训练中遇到的更长的文本序列。通过使用较小或较大的基础值对 RoPE 进行微调,模型可以更好地捕捉扩展上下文中的位置信息,从而在涉及长文本的任务中提高其性能。LlamaFactory 支持动态 NTK 和线性两种外推策略。了解更多关于 RoPE 扩展的信息,请参阅这篇文章。

  • 加速器 — 正如其名,这些技术旨在加速 LLM 的训练和推理过程。目前支持的技术包括 flash attention 2、unsloth 和 liger 核心(在 Triton 语言上设计的核心)。您可以从相关资源中了解更多。

图片来源:作者

让我们来看一下上述图片中提到的用于微调大型语言模型(LLM)的基本训练策略和超参数。

阶段 — 可用的选项包括监督微调、预训练和强化学习策略。每个策略的代码库可以在 src/llamafactory/train 目录下的相应文件夹中找到。让我简单介绍一下每种策略。

  • 监督微调 — 使用序列到序列的训练器。你可以从这里找到概念概述,从这里找到微调参数列表。
  • 奖励建模 — 你可以从这个视频了解概述,从这里找到微调参数列表。
  • 近端策略优化 — 你可以从HuggingFace的RL课程中了解PPO。这是最基本的RL策略,通过使用策略逐步提高模型性能。
  • 直接偏好优化 — 这是一种高效的策略,用交叉熵损失函数替代单独的奖励模型,从而最小化强化学习部分,因此减少了相关的计算和时间。你可以从这里了解HunggingFace DPO训练器。
  • 卡内曼-特沃斯基优化 — 这种方法摒弃了数据集中对响应的成对偏好,而是为模型提供一个二元标签(真或假)。你可以从HuggingFace文档了解KTO训练器。
  • 预训练 — 你可以从这篇快速阅读中了解预训练、监督微调和强化学习之间的区别。预训练的目标是让模型从数据中学习所有细微差别,而无需任何明确的指令。

数据目录 — 训练数据或数据模板所在文件夹的路径。

数据集 — 目前HuggingFace已经为各种模型提供了不少数据集。若要添加自定义数据集,可以修改位于_data/datasetinfo.json中的数据集模板,如下面的图片所示。

图片来源:作者(添加自定义数据集,可以是HuggingFace URL或任何本地或云端位置)

超参数

学习率 — 它有助于决定模型权重在每次训练迭代中调整的程度。它本质上控制了优化过程中所采取的步长。值越低,学习速度越慢。

Epochs — 一个Epoch指的是模型完整地遍历一次整个训练数据集的过程。Epoch的数量决定了模型读取整个数据集的次数。Epoch数量越大,模型过拟合的风险越高。可以通过使用如早停(early stops)等策略,在进入下一个Epoch之前,用验证集监测模型性能。

最大梯度范数 — 梯度裁剪是一种在训练过程中防止梯度变得过大的技术。范数值作为裁剪过程中的阈值。范数的值越大 === 裁剪的频率越高 => 训练速度越慢。

最大样本数 — 在每次迭代中使用的样本数量,因为我们无法在一次迭代中完成大型数据集的一个完整轮次。

计算类型 — 这决定了在训练过程中是否使用混合精度。一些量化策略依赖于保留最具影响力的权重集并对其使用实际精度,而对相对影响较小的权重进行量化,从而获得接近原始性能的效果。这里有一个关于bf16的WikiPedia链接。LlamaFactory支持bf16(混合)、fp32、fp16和纯bf_16格式。

截断长度 — 输入的最大 token 长度。(根据模型的不同而变化)

批量大小 — 每次处理的样本数量。

梯度累积 — 将基础训练批次拆分成子批次的数量。这在使用内存和计算资源较低的消费级GPU训练模型时是必需的。

验证集大小 — 用于验证的数据比例。

学习率调度器 — 学习率调度器用于在每次迭代过程中动态调整学习率。

额外配置

图片来源:作者(LlamaFactory 中的额外通用配置)

日志记录步骤 — 每次记录的日志迭代次数(如果为5,则每5步记录一次)

保存步骤两个检查点之间的步数 决定了检查点保存的频率。步数越高,保存检查点的频率越低;步数越低,保存检查点的频率越高。这有助于在训练过程被中断时恢复训练。

预热步骤 — 它是指一种训练技术,其中学习率从一个小的初始值逐渐增加到目标学习率,持续一个指定的步数。

NEFTune Alpha — NEFTune 向语言模型的输入嵌入添加噪声,从而在训练过程中引入随机性。您可以在HuggingFace 文档中了解更多信息。在分布良好的数据集上应用 NEFTune 可能只会带来边际收益(有时甚至没有)。

优化器 — 如其名,它用于优化模型,使其损失函数值最小化。通过迭代调整模型的参数(权重和偏差),找到一组最佳值,从而最小化模型预测与真实值之间的误差。支持的优化器有 _adamw_torch, adamw8bit 或 adafactor。您可以在 PyTorch 文档 中了解更多。

序列打包 — 打包将不同长度的序列合并为一个张量,消除不必要的填充。PyTorch 允许我们将序列打包,内部打包后的序列是一个包含两个列表的元组。一个列表包含序列的元素,另一个列表包含每一步的批量大小。

使用紧凑打包 — 在使用打包序列时,通常建议避免在打包张量内的不同序列之间进行跨注意力操作

基于提示训练 — 禁用所有标签掩码,使模型在训练过程中能够关注目标序列中的所有标记,包括未来的标记。

调整 token 嵌入大小 — 调整分词器词汇表和嵌入层的大小,以适应新生成的 token。

启用s²注意力 — 您可以在这里了解LongLoRA:这里。移除短注意力 就像是将教科书拆分成更小的章节或部分。您会专注于理解每个章节,而不会试图一次性记住它们之间的所有联系。

启用外部日志记录器 — 可以激活 tensorboard 来监控训练过程。默认情况下,你可以使用 LlamaFactory UI 或 CLI 的底部部分来监控训练活动。

具体配置

图片来源:作者(特定阶段的配置)

冻结微调配置

可训练的层 — 可训练的隐藏层数量,如描述中所述。

可训练和额外模块 — 我们可以指定要训练的LLM模块,如果设置为‘all’,则所有模块都将被训练。你可以从 src/llamafactory/model/adapter.py 查找相应的代码。你可以通过PreTrained transformers的named_parameters()函数找到支持的模块。

图片来源:作者(可训练模块(隐藏模块)和额外模块(非隐藏模块))

RLHF配置

Beta值 — 控制人类反馈与奖励信号之间平衡的超参数。如果Beta值较高,模型会优先考虑人类反馈;如果Beta值较低,则更侧重于奖励结构。

Ftx gamma — 它表示强化学习中的折扣因子。它决定了对未来奖励和即时奖励的重视程度。接近1的gamma值意味着对未来奖励的重视程度较高,而接近0的gamma值则更重视即时奖励。

损失函数类型 — 损失函数的类型。支持的类型包括 sigmoid、hinge、IPO、KTO_pair、ORPO 和 SimPO。

奖励模型 — 奖励模型的路径。

评分归一化和奖励白化 — 评分归一化是指将奖励或优势估计调整到标准尺度的过程。这一技术有助于稳定和改进学习过程。通过奖励白化,我们将奖励标准化为均值为零、标准差为一。

GaLore(梯度低秩投影)配置

这是一种内存高效的低秩训练策略,允许进行全参数学习,但比常见的低秩适应方法(如LoRA)更节省内存。这是第一个能够在仅使用28GB显存且不使用模型并行、检查点或卸载的情况下,对llama-2-7B模型进行预训练的方法。

GaLore 排行 — 更高的排行需要更多的内存和更高的精度,反之亦然。

更新间隔 — 更新的频率会显著影响GaLore的性能。如果更新过于频繁,节省内存的效果可能会有限。如果更新过于稀少,近似值可能会过时并降低模型的性能。

Galore Scale — 规模因子越大,更新越多。

BAdam配置

这是一项在商用GPU上进行的全参数优化技术。作者使用单个RTX3090显卡,结合Adam的更新规则和混合精度训练,成功对Llama 2–7b和Llama 3–8B进行了微调。这项技术称为Block Coordinate Optimization(块坐标优化),它将模型的参数划分为更小的块,并迭代更新每个块,同时保持其他块固定,从而显著减少了训练所需的内存占用,但仍能保持良好的性能,如下面所示。

图片来源:BAdam GitHub 仓库

BAdam 模型 — 我们可以按层(导入 BlockOptimizer 类)或按比例(导入 BlockOptimizerRatio 类)进行 BAdam 优化。按层训练需要安装 DeepSpeed 才能运行。

更新比例 — 稀疏掩码的比例。

切换模式 — 用于在不同模块之间切换的模式。

图片来源:BAdam GitHub

切换间隔 — 切换到下一个块之前的优化步骤数量。

你可以跟随这个视频教程学习如何使用LlamaFactory进行微调。

结论

在本文中,我们探讨了各种微调策略的广阔领域,每种策略根据项目的具体需求提供了独特的优势。从传统的迁移学习方法到更先进的技术,如适应器和提示调整,理解这些方法使您能够为您的应用场景选择最有效的策略。

过渡到实际操作中,我们深入研究了LlamaFactory工具,该工具简化了微调过程。通过分析各种参数和超参数,我们强调了学习率、批量大小和dropout等选择如何显著影响模型性能。这些实际见解使您能够有效地调整微调工作。

希望这篇概述能增强你对大型语言模型微调领域的理解,并赋能你成功实施这些策略。

资源
  1. 慕尼黑大学现代自然语言处理方法,研讨会手册
  2. 深度强化学习,OpenAI,书籍
  3. 参数高效微调,Vinija Jain,入门笔记
  4. Sebastian Raschka 的 AI 博客,Sebastian Raschka,博客


这篇关于大型语言模型(LLM)微调策略的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程