图片来源:Leonardo.ai
大型语言模型在Common Crawl等组织的帮助下,基于大量数据集进行训练,能够通过零样本到少量样本提示执行多种任务。随着检索增强生成 (RAG) 方法的兴起,这些通用模型越来越多地被组织用于各种应用,从简单的聊天机器人到更复杂的代理自动化 (角色)。尽管已经开发了GraphRAG等技术来根据实体提取文档之间的关系,但由于基础模型缺乏实质性背景,它们可能无法完全满足每个特定领域的需求。这一限制导致每月都会发布大量新模型。
对于这些特定领域的模型,可以采用现有的 LLM 架构并调整其权重以学习特定领域的上下文,这一过程称为微调。在本文中,我们将探索语言模型的微调过程,研究各种类型、所涉及的关键考虑因素以及无代码(几乎)开源工具的示例。让我们深入研究并揭开微调语言模型的复杂性!
微调:一个简单的类比
为了理解微调,我们来打个比方。假设你是一名准备参加科学考试的学生。你从课堂上打下坚实的基础开始。随着考试的临近,你会专注于将要测试的特定主题。你会解答练习题来评估你的理解,然后根据你在这些问题上的表现复习材料。你也可以向朋友寻求指导,查阅在线资源,或重新回顾关键主题。
这个过程反映了微调:我们采用预先训练好的模型(具有扎实基础的学生),将其引导到特定任务(复习特定主题),评估其性能(通过练习测试),并反复进行此过程,直到我们获得最佳结果(基于性能指标)。正如学生可以精通特定领域一样,我们可以开发一个在某个领域/多个领域的所有任务中表现出色的语言模型。最终,此过程的有效性取决于所选模型、特定任务以及所用训练数据的质量。
常见的微调用例
在深入研究微调之前,让我们通过分析几个场景来检验为什么微调是必要的。
语言学习
让我们比较一下用泰米尔语回答问题的两种版本的Llama 。
Llama-2–7b-chat模型的响应
Mozhi.ai HuggingFace 空间的回应(模型的微调版本来自这里)
如上例所示,Llama 的基本变体很难理解所请求的语言,而经过微调的模型能够流利地用该语言做出响应。这种能力源于微调过程,它允许模型学习和识别新语言中的模式。相比之下,简单的检索增强生成 (RAG) 应用程序受到限制,因为它们无法有效地将新上下文与现有知识联系起来。在模型必须获取和整合不同上下文的场景中,微调变得至关重要。
有效保障LLMs
人工智能开发面临的一个重大挑战是为模型建立有效的护栏。想象一下,一个税务助理人工智能突然开始回答有关心理健康的问题。虽然人工智能能够处理各种主题令人印象深刻,但这也可能存在风险。并非所有模型都经过适当的数据训练,特别是在心理健康等敏感领域。
即使我们指示模型不回答某些问题,也会出现两个主要问题:提示黑客攻击和上下文窗口。当用户操纵输入以绕过限制时,就会发生提示黑客攻击。此外,虽然较大的模型(例如具有 128k 上下文窗口的 Llama )为指令提供了更多空间,但这并不能完全解决问题。虽然上下文窗口可以容纳更多信息,但如果使用太多标记来设置上下文,则会减少可用于实际内容的空间。
有效的提示模板可以提供帮助,但它们无法解释所有可能的细微差别。虽然较大的上下文窗口很有用,但它并不是一个全面的解决方案,因此微调是一个更可靠的选择。甚至像 Meta 这样的大型参与者也推出了LlamaGuard,这是 Llama 模型的微调版本,旨在加强聊天护栏并防止有害响应。
人工智能角色
新闻媒体经常报道相同的新闻,但每个媒体都从独特的角度呈现它们。想象一下一个聊天助手,它通过从各种来源收集信息来帮助撰写文章。如果您的组织使用像 ChatGPT 这样的预训练模型,有效的提示(包括用户和系统指令)可以生成有用的新闻片段。但是,这些片段可能并不总是符合您组织的特定风格或指导方针。
为了确保一致性并遵守组织的基调和标准,您可以使用自己团队撰写的新闻文章来微调模型。这种方法可以创建更加个性化的人工智能,准确反映组织的声音,并且可以依靠它提供一致且准确的内容。此外,一些初创公司现在正专注于开发企业级人工智能角色,以简化手动活动。
更智能、更小的模型
您并不总是需要一个庞大的模型来实现出色的结果。与非常大的语言模型相比,具有十亿(甚至几百万)个参数的较小模型通常可以更高效、更经济地满足您的特定需求。这种方法大大降低了运行和维护这些模型的相关成本。
在本文中,我们将探讨一种称为参数高效微调 (PEFT) 的技术。此方法采用矩阵分解,以更小、更易于管理的形式表示大型模型。这意味着您不需要利用模型的所有参数来实现目标 - 尽管性能可能会略有下降。因此,您可以在消费级硬件上使用强大的模型,而无需承担过高的成本。因此,非常大的模型并不总是必要的。
在微调之前,请考虑以下因素:
- 充足的数据:您是否有足够的数据来有效地训练模型?
- 硬件可用性:是否有可用的硬件来训练和运行模型?
- RAG 策略:可以使用 RAG 策略和现有的 LLM API 解决您的问题吗?
- 上市时间:您需要多快让该服务投入运营?
- 您可以组合来自不同服务提供商的 API 来构建统一的产品。这些模型质量上乘,并不断更新以满足最新标准。彻底探索可以帮助您确定最适合您特定需求的模型。
微调过程
现在我们了解了微调是什么及其应用,让我们来探索不同的类型以及每种类型的功能。根据学习方法,有三种流行的方法可以微调大型语言模型 (LLM):
- 监督学习— 在这种方法中,模型通过对输入输出对进行训练来获得新概念。诸如指令微调之类的技术就是这种方法的典型代表,我们教模型对特定指令做出精确的响应。
想象一个教室里,一个学生正在学习写论文。最初,学生就各种主题撰写论文,但他们的作品并不完美。老师会审阅论文,提供详细的反馈并提出改进建议。随着时间的推移,学生会根据这些反馈修改论文,并成为一名更好的作家。
在大型语言模型 (LLM) 的监督微调中,模型从一般知识开始,然后通过类似的过程进行“教学”:它使用具有正确输出和反馈的特定示例进行训练,以提高其在特定任务上的表现,就像学生提高写作技巧一样。 - 自监督学习——这种强大的方法用于语言模型调整,帮助模型理解语言建模数据的细微差别。它利用数据的固有结构来生成监督信号,从而无需手动标记数据。
想象一个教室里,学生们收到不完整或杂乱的笔记,必须自己推断出缺失的部分。这种情况与人工智能中的自监督学习相似。就像学生使用上下文和自己的知识来填补空白一样,自监督模型通过预测数据的隐藏部分并通过这些预测来完善其理解。它代表了一种从数据本身学习的方式,而不需要明确的标签或直接的答案。一些流行的策略包括掩蔽语言建模(BERT)、自回归语言建模(GPT)、对比学习(SimCLR)、下一句预测(BERT) 和排列语言建模 ( XLNet ) 等,并且经常组合使用。 - 强化学习— 语言模型的强化学习 (RL) 涉及通过评估其输出的奖励系统训练它们产生更好的响应。该模型根据提示生成响应,并对高质量答案获得积极奖励,对差劲答案受到惩罚。通过这种反馈,模型会调整其参数以随着时间的推移提高其性能。用于最大化奖励(理想情况下)的模板称为策略,各种策略优化策略可帮助 RL 实现更接近人类输出的结果。
想象一下课堂上学习解决数学问题的学生。每当学生正确解决问题时,老师都会给他们一颗金星作为奖励,鼓励他们进一步努力和进步。有时,老师也会对错误提供建设性的反馈,帮助学生调整他们的方法。随着时间的推移,学生会了解哪些策略可以产生更多金星和更少的错误。
在这个比喻中,金星和反馈代表强化学习中的奖励和惩罚,引导学生通过反复试验来学习和优化他们的解决问题的能力。惩罚的严重程度可能因老师的政策而异。目标是找到能够实现最佳结果的正确策略。
接下来,我们需要根据手头的任务来决定垂直和水平微调策略。
水平微调涉及调整模型,使其在一系列相似的任务或领域中表现良好。该模型针对跨多个相关领域的数据进行微调,而不专注于任何一个领域。这种方法的一个显著优势是它能够处理各种任务,同时保留基础模型的通用性。
而垂直微调则侧重于调整模型,使其在特定任务或领域中表现出色。该模型使用高度专业化或特定领域的数据进行微调,使其能够更好地理解和生成与已微调的特定领域相关的响应,从而产生高度准确的结果。
我们还需要确定微调模型所需的参数数量。主要关注的是调整和推理所需的计算资源,因为成本可能会迅速上升。此外,我们还应该考虑具体任务以及我们希望基础模型适应的数据类型。涉及三种不同的策略:完全参数再训练、参数高效微调(PEFT) 和迁移学习。在本次讨论中,我们将重点介绍在消费者 GPU 上微调模型的各种 PEFT 策略。
这些只是关键的考虑因素,还有一些策略,例如决定采用自上而下还是自下而上的方法、在各个层上进行训练还是对它们进行批处理等。因此,关键的考虑因素以及适当的微调模块选择将为我们提供正确的基础基线。由于微调本身就是一个海洋,让我们将重点放在实现部分的一个较小的子集(最常见)上,这将是参数高效的垂直微调。您可以参考上一篇文章,了解HuggingFace上看到的各种量化模型格式。
PEFT 用于微调
参数高效微调 (PEFT) 是一种利用以下理念的技术:大型语言模型中并非所有参数都需要更新才能实现最佳性能。通过冻结大多数参数并专注于较小的子集,我们可以显著减少微调所需的计算资源和时间。
想象一下,在一个课堂上,一个学生在许多科目上表现出色,但在几个特定领域需要改进。老师不会彻底改变整个课程,而是针对这些领域提供有针对性的额外练习。这种方法是有效的,因为它建立在学生现有的知识基础上,将资源集中在最需要的地方。同样,在 PEFt 中,我们只关注优化有影响力的权重。
通过冻结大多数参数、继续使用残差连接并应用适当的正则化技术,这些模型保留了先验知识,从而避免了灾难性遗忘。GaLore 等方法使得在个人计算机上微调大型模型(如 Llama-3 )成为可能,从而使高级语言建模更加容易实现。
让我们探索一些 PEFT 技术,注意这些技术并不互相排斥。
参数有效微调技术
(参数高效微调方法分类)图片来源:从缩小到扩大:参数高效微调指南(ArXiv)
如上图所示,PEFT 方法分为 3 大类,即:基于添加(添加新的可训练参数)、基于选择(从基础模型中选择参数子集)和基于重新参数化(具有替代表示)。在本文中,我们将仅查看每个过程的示例代码片段。您可以从其官方文档中找到 PEFT 的所有 HuggingFace Transformer 实现。
适配器
适配器属于附加类。适配器是添加到现有 Transformer 架构的前馈模块,用于减少全连接层之间的参数空间,如下图所示。
图片来源:NLP 的参数高效迁移学习(ArXiv)
如果全连接层在一个层中缩小维度,然后在下一个层中将其重新缩放回输入维度,那么这会如何缩小特征空间?例如,假设第一个全连接层将 维输入缩小到 维,而第二层将其恢复到 维。这总共会产生 x + x = 8, 个权重参数。相比之下,将 维输入映射到 维输出的单个全连接层将具有 x = , 个参数。在适配器调整中,只有适配器、层规范和最终头在下游数据上进行训练,从而使调整更快、更高效。根据以下观察,该方法被证明是成功的:使用适配器方法训练的 BERT 模型达到了与完全微调的 BERT 模型相当的建模性能,而只需要训练 % 的参数。一个简单的适配器块看起来像这样。
import torch
import torch.nn as nn
class AdapterBlock(nn.Module):
def __init__(self, input_dim, adapter_dim=):
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):
# Apply the adapter block
return x + self.up_proj(self.activation(self.down_proj(x)))
还有另一个独特的适配器称为Llama-Adapter,它具有独特的适配器架构,专门用于将 Llama 转变为指令遵循模型。
Prompt Tuning
提示调整又是一种利用软提示(使用损失反馈进行动态提示更新)的力量来代替明确的人类静态提示/硬提示的附加方法。这种 PEFT 方法旨在仅通过输入而不是改变模型权重来实现模型性能的提高。如果是这样,那么为什么不进行提示工程呢?提示工程需要付出很多努力来设计理想的提示,并且存在上下文窗口长度的问题。即使提示在测试场景中被证明有效,但它可能不适用于其他场景,因为有很多方法可以提出同样的问题。
图片来源:参数高效快速调优的规模力量(ArXiv)
如上图所示,我们向输入嵌入向量添加了额外的可训练标记(与输入向量大小相同)。这些不是嵌入空间中的固定点,因此可以表示任何单词。目标是不断找到这些可训练标记的最佳表示,以指导完成模型的输出。根据基础论文,我们可以在 个标记内实现此目标(超过此标记只会产生边际收益),用于分类任务。如上所示,我们只处理与任务提示相关的 20K 个参数,而不是处理 11B 个参数。此方法具有黑盒性质,因为它可以采用嵌入空间中的任何表示,并且难以控制。经过最近邻分析发现,这些标记采用语义词表示,这意味着该方法不能用于高度专业化的任务。其次,如研究观察所示,这种方法被证明在模型尺寸增加时是有效的,但对于较小的模型则不那么有效。一个简单的提示调整块如下。
图片来源:参数高效快速调优的规模力量(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)
# A simple linear layer for output
self.fc = nn.Linear(hidden_dim + prompt_length * input_dim, output_dim)
def forward(self, input_ids):
# Get input embeddings
input_embeds = self.input_embedding(input_ids)
# Concatenate prompt embeddings
combined_embeds = torch.cat([input_embeds, self.prompt_embeddings.mean(dim=0).unsqueeze(0)], dim=1)
# Pass through the linear layer
output = self.fc(combined_embeds)
前缀调整
这是提示调整的增强版本,我们向每个转换器块(在位置编码之前)添加软提示,而不是单独添加输入嵌入。这样,前缀标记就成为跨层的唯一可训练参数,并且对输出有更好的影响。它与提示调整有何不同?前缀调整通过向输入序列添加特定于任务的前缀来增强模型的多个层,这需要对更多参数进行微调。相比之下,提示调整仅专注于调整输入提示嵌入,从而导致更新的参数更少,并且可能提高参数效率,尽管它可能会限制对目标任务的适应性。虽然前缀调整可能由于其更大的参数集而产生更好的性能,但它也可能需要更多的计算资源并增加过度拟合的风险。可以安全地假设,尽管提示调整效率更高,但由于微调参数的数量减少,其性能可能不如前缀调整。一个简单的前缀调整块如下所示。
图片来源:前缀调整:优化连续提示以进行生成(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=) for _ in range(num_layers)])
def forward(self, input_ids):
# Create a tensor of prefix tokens
prefix_tokens = self.prefix_tokens.unsqueeze(0).expand(input_ids.size(0), -1, -1)
# Concatenate input_ids and prefix_tokens
input_ids = torch.cat([prefix_tokens, input_ids], dim=1)
# Pass the concatenated input through the transformer layers
output = input_ids
for layer in self.transformer_layers:
output = layer(output)
return output
大型语言模型的低秩自适应( LoRA 系列)
这是一种重新参数化方法,它对语言模型的替代表示进行微调。该技术通过将注意力层中的大型权重矩阵分解为两个较小的矩阵来简化它,从而大大减少了微调期间需要调整的参数数量。它不是直接分解矩阵,而是从分解后的表示(伪分解)中进行学习。
我们专注于这种替代表示,而不是向模型添加新参数。普遍的共识是将等级 r 与训练数据量和模型大小成比例,以减轻过度拟合问题并有效管理模型预算。据观察,LoRA 学习得更少,遗忘得更少,这是意料之中的。
让我们通过一个例子来理解分解。
图片来源:Sebastian Raschka 撰写的《使用 LoRA 进行参数高效 LLM 微调》文章
假设我们有 2 个矩阵 A 和 B,分别具有 个和 个参数。因此 ΔW 中的参数总数为 AXB = 100X500 = ,。现在让我们假设等级为 5。现在新的权重矩阵 WA 和 WB 变为 100X5 = 和 500X5 = 2,。新的 ΔW = WA + WB = = 个参数,减少了 %。示例 LoRA 块如下所示。
import torch
import torch.nn as nn
import math
class LoRA(nn.Module):
def __init__(self, input_dim, output_dim, rank=8, alpha=):
super().__init__()
self.input_dim = input_dim
self.output_dim = output_dim
self.rank = rank
self.alpha = alpha
# Create LoRA weight matrices
self.W_A = nn.Parameter(torch.empty(input_dim, rank))
self.W_B = nn.Parameter(torch.empty(rank, output_dim))
# Initialize LoRA weights
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
# Apply LoRA
h += self.alpha * x @ (self.W_A @ self.W_B)
return h
图片来源:Vinija Jain 的博客(在 QLoRA 方法中,原始模型的权重被量化为 4 位精度。新添加的 LoRA 权重未量化;它们保持更高的精度并在训练过程中进行微调。)
LoRA 有多种风格,例如 DoRA、QLoRA(一种流行的混合精度策略)、LoHA 等。您可以在这篇文章中找到一些流行的风格,也可以在HuggingFace 文档中找到变压器实现。
通过抑制和放大内部激活注入适配器(IA3)
这是另一种加法方法,涉及三个阶段,向量的添加、重新缩放(抑制/放大)和对下游数据进行调整。 三个添加的向量是关键重新缩放向量(此向量与自注意层中的键相乘)、值重新缩放向量(此向量与自注意层和编码器-解码器注意层中的值相乘)和中间激活重新缩放向量(此向量与位置前馈网络中的中间激活相乘)。 然后使用学习到的向量重新缩放模型中的相应元素。 这种重新缩放可以抑制(减少)或放大(增加)激活,具体取决于学习到的向量中的值。 最后,对模型在下游任务上进行微调。 在微调过程中更新学习到的向量,以优化模型的性能。 该模型被提出作为小样本提示策略(情境学习)的更好替代方案。
图片来源:少样本参数高效微调比上下文学习更好、更便宜(ArXiv)
通过蝴蝶分解 (BOFT) 进行正交微调
这是另一种重新参数化策略,我们使用蝴蝶分解对权重矩阵进行正交变换。让我们了解蝴蝶分解。目标是将给定的权重矩阵表示为两个矩阵的乘积,即对角矩阵和置换矩阵,如下图所示。
图片来源:作者
通过在微调损失和预训练损失的梯度之间引入正交性因子,我们保持了结构约束。如 HuggingFace概念指南中所述,它有助于在微调期间保持超球面能量不变。能量可以被视为超球面上某个点与原点的距离。在微调过程中保持超球面能量可确保学习到的表示保持接近其原始位置,从而降低忘记先前学习到的信息的风险。这种稀疏表示也有助于更好的模型泛化。正如作者所说“蝴蝶结构在 OFT 中充当不同块数超参数之间的平滑插值,使正交微调框架更加灵活,更重要的是,参数效率更高”。这种方法主要在图像模型上进行训练和测试,并且可以优先用于文本到图像模型。
图片来源:通过蝴蝶分解实现参数有效的正交微调
LoRA 的其他流行替代方案是跨层共享相同的低秩矩阵,并使用原始模型权重的主奇异值和奇异向量初始化适配器层。这是一个不断寻求的问题,因为主要目标是在智能手机和 PC(移动芯片)等本地硬件上拟合和计算模型。
LlamaFactory(微调框架)
让我们使用 PEFT 策略对预训练的 Llama 模型进行微调,以学习 Docker 查询。LlamaFactory 是一个统一的框架,用于通过一套先进的高效训练方法对所有大型语言模型 (LLM) 进行微调。我们将探索用户界面 (UI) — 虽然这也可以通过命令行界面 (CLI) 完成 — 包括如何从 Hugging Face 添加新模型和数据集、微调术语表以及导出微调模型的过程。
安装
可以按照这些 GitHub repo 说明进行安装。建议在单独的虚拟环境中构建这样的特定工具。
图片来源:作者
让我们看一下上图中的各个字段。
Lang —设置 UI 的语言。目前支持英语、俄语、中文和韩语。
模型名称— 设置微调的基础模型。它已经支持所有流行的模型。如果我们需要添加自定义模型怎么办?将您的模型添加到src/llamafactory/extras/constants.py中的模型模板中,并添加到相应的模型注册表中,如下图所示。下载源也可以是包含所有模型信息的本地目录。
图片来源:作者(从 HuggingFace 添加 Mistral-Small-Instruct 模型)
模型路径- 从中加载模型的路径
微调方法——它支持完整、冻结和 LoRA 调整方法。这些是完整微调、迁移学习和 PEFT 调整方法。
检查点路径— 一旦我们对模型进行微调,所有模型信息都会被检查点(例如,微调模型的核心属性的元信息,稍后可以加载并用于数据任务)。只有当所选模型的微调变体可用时才会显示(通过 LlamaFactory 训练或在 LlamaFactory 模板中更新的本地目录路径)
高级配置— 由于本地硬件可能无法处理调整和全精度或半精度,我们需要量化模型(从此处了解更多信息)以使其在我们的 PC 上运行。所有量化参数都可以从此选项卡进行调整。
- 量化位 (QLoRA) — 支持 2 个选项:4 位和 8 位。这是分配用于表示模型权重的位数,因此精度越低,存储的信息就越少,因此准确度就越低。这是理论上的,但有量化方法(如 GGUF 和 bitsandbytes)有助于在 4 位精度下保留大部分模型推理,从而可以在 CPU 上调整和运行模型。
- 量化方法— 用于量化的方法。有关量化的更详细文章可以在我之前的文章中找到。支持的方法是半二次量化 (hqq)、eetq 和 bitsandbytes。Bitsandbytes 已成为将模型量化为 4 位和 8 位的最知名工具/方法。您可以从HuggingFace 文档中了解更多信息。
- 提示模板— 有大量现成的模板可用。我们可以通过在src/llamafactory/data/template.py中的_register_template部分下添加一个模板来注册一个新模板,如下图所示。
图片来源:作者(用新的_register_template方法添加新的提示)
- RoPE 缩放— RoPE 缩放涉及调整旋转位置嵌入 (RoPE) 的参数,以增强大型语言模型 (LLM) 在其原始训练上下文长度之外的外推能力。通过修改 RoPE 计算中的基值,该技术允许 LLM 有效地管理比训练期间遇到的更长的文本序列。通过使用较小或较大的基值对 RoPE 进行微调,模型可以更好地捕获扩展上下文中的位置信息,最终提高其在涉及长文本的任务上的性能。动态 NTK 和线性是 LlamaFactory 支持的两种外推策略。从本文中了解有关 RoPE 缩放的更多信息。
- Booster — 顾名思义,这些技术旨在加速 LLM 的训练和推理过程。它目前支持 flash 注意力 2、unsloth 和 liger 内核(基于 triton 语言设计的内核)。您可以从资源中了解有关这些内容的更多信息。
图片来源:作者
让我们看一下上图中提到的用于微调 LLM 的基本训练策略和超参数。
阶段— 有多种选项可供选择,包括监督调优、预训练和强化学习策略。每种策略的代码库都可以在src/llamafactory/train 下的各自文件夹中找到。让我对每种策略给出一句话介绍。
- 监督微调- 使用序列到序列训练器。您可以从此处找到概念概述,并从此处找到要微调的参数列表。
- 奖励模型——你可以从这个视频中找到概述,并从这里找到需要微调的参数列表。
- 近端策略优化——你可以在HuggingFace的 RL 课程中阅读有关 PPO 的内容。这是最基本的 RL 策略,使用策略逐步提高模型性能。
- 直接偏好优化——这是一种有效的策略,用交叉熵损失函数取代单独的奖励模型,从而最大限度地减少强化学习部分,从而减少相关的计算和时间。您可以从这里了解 HunggingFace DPO 训练器。
- Kahneman-Tversky 优化— 这会像上述两个步骤一样消除数据集中对响应的成对偏好。我们改为使用二进制标签(真或假)来指导模型。您可以从HuggingFace 文档中了解 KTO 训练器。
- 预训练——你可以从这篇速读中找到预训练、监督微调和强化学习之间的区别。预训练的目标是让模型在没有任何明确指令的情况下从数据中学习所有细微差别。
数据目录——包含训练数据/数据模板的文件夹路径。
数据集——HuggingFace 已经为各种模型引用了相当多的数据集。为了添加自定义数据集,我们可以修改data/dataset_info.json中的数据集模板,如下图所示。
图像来源:作者(添加自定义数据集,可以是 HF URL 或任何本地或云目的地)
超参数
学习率——它有助于确定在每次训练迭代中模型权重的调整程度。它本质上控制了优化过程中的步长。值越低,学习速度越慢。
周期数— 周期数是指对整个训练数据集进行一次完整的遍历。周期数决定了模型读取整个数据的时间。周期数越多,就越容易出现过度拟合。在进入下一个周期之前,使用早期停止等策略来监控模型在验证集上的表现。
最大梯度范数——梯度裁剪是一种用于防止梯度在训练期间变得过大的技术。范数值作为裁剪过程的阈值。范数值越大,裁剪越频繁 => 训练越慢。
最大样本——每次迭代的样本大小,因为我们无法在大型数据集上在一次迭代中完成一个时期。
计算类型— 这决定了在训练期间是否使用混合精度。一些量化策略依赖于这样的想法:通过保留具有实际精度的最有影响力的权重集,并量化相对影响较小的权重,可以产生更接近原始的性能。这是bf16 的 WikiPedia 链接。LlamaFactory 支持 bf16(混合)、fp32、fp16 和纯 bf_16 格式。
截止长度- 输入的最大标记长度(根据模型而变化)
批次大小——一次处理的样本数量。
梯度累积— 从基础训练批次中分割出的子批次数量。这对于在内存和计算能力较低的消费级 GPU 上训练模型是必需的。
Val Size — 用于验证的数据百分比。
LR 调度器——学习率调度器用于在每次迭代期间动态调整学习率。
额外配置
图片来源:作者(LlamaFactory 中的附加通用配置)
记录步骤- 每次记录的迭代次数(如果为 5,则每 5 步记录一次)
保存步骤—两个检查点之间的步骤数决定了保存检查点的频率。数字越大,保存检查点的频率越低,数字越低,保存检查点的频率越高。这有助于应对训练过程中断的情况。
热身步骤——是指一种训练技巧,其中学习率在指定的步骤数内从较小的初始值逐渐增加到目标学习率。
NEFTune Alpha — NEFTune 为语言模型的输入嵌入添加了噪声,从而将随机性引入训练过程。您可以在HuggingFace 文档中了解更多信息。在分布良好的数据集上应用 NEFTune 可能只会带来边际收益(有时甚至没有)。
优化器——顾名思义,它用于优化模型以获得最小的损失函数值。它通过迭代调整模型的参数(权重和偏差)来实现这一点,以找到最佳值集,从而最小化模型预测值与真实值之间的误差。支持的优化器列表包括adamw_torch、adamw_8bit 或 adafactor。您可以在PyToch 文档中了解更多信息。
打包序列——打包将不同长度的序列组合成一个张量,从而消除了不必要的填充。PyTorch 允许我们打包序列,内部打包序列是两个列表的元组。一个包含序列的元素,另一个包含每个步骤的批处理大小。
使用整齐的打包——使用打包序列时,通常建议避免打包张量内不同序列之间的交叉注意力。
按提示进行训练——禁用所有标签掩码,允许模型在训练期间关注目标序列中的所有标记,包括未来的标记。
调整标记嵌入的大小——调整标记器词汇表和嵌入层的大小以适应生成的新标记。
启用 s^2 注意力— 您可以从这里了解 LongLoRA 。转移短注意力就像将教科书分解成更小的章节或部分。您可以专注于理解每个章节,而无需尝试一次性记住它们之间的所有联系。
启用外部记录器— 可以激活训练过程的 tensorboard 监控。默认情况下,您可以使用 LlamaFactory UI 或 cli 的底部部分监控训练活动。
具体配置
图片来源:作者(特定阶段的配置)
冻结调整配置
可训练层数——如描述中所示,需要训练的隐藏层数。
可训练和额外模块— 我们可以指定要训练的 LLM 模块,如果指定为“全部”,则所有内容都经过训练。您可以从src/llamafactory/model/adapter.py中找到相应的代码。您可以从 pretrained transformers 的 named_parameters() 函数中找到支持的模块。
图片来源:作者(可训练模块(隐藏模块)和额外模块(non_hidden_modules))
RLHF 配置
Beta 值— 用于控制人类反馈和奖励信号之间平衡的超参数。如果 Beta 值较高,则模型会优先考虑人类反馈,而 Beta 值较低则更侧重于奖励结构。
Ftx gamma — 表示强化学习中的折扣因子。它决定了未来奖励与即时奖励相比的估值。接近 1 的 gamma 表示未来奖励的价值较高,而接近 0 的 gamma 则优先考虑即时奖励。
损失类型— 损失函数。支持的类型列表为 sigmoid、hinge、IPO、KTO_pair、ORPO 和 SimPO。
奖励模型——奖励模型的路径。
分数标准化和奖励白化— 分数标准化是指将奖励或优势估计调整到标准范围的过程。这种技术有助于稳定和改善学习过程。通过奖励白化,我们将其标准化为平均值为零,标准差为一。
GaLore(梯度低秩投影)配置
这是一种内存高效的低秩训练策略,允许全参数学习,但比常见的低秩自适应方法(例如 LoRA)更节省内存。这是第一种允许在 28GB VRAM 上预训练 llama-2-7B 模型而无需使用模型并行性、检查点或卸载的方法。
GaLore 等级——更高的等级需要更多的内存和更高的准确性,反之亦然。
更新间隔— 更新频率会显著影响 GaLore 的性能。如果更新过于频繁,内存节省可能会受到限制。如果更新频率过低,近似值可能会过时并降低模型的性能。
Galore Scale — 缩放系数越大,更新次数越多。
BAdam配置
这是商用 GPU 上的全参数优化技术。作者能够使用单个 RTX3090 结合 Adam 的更新规则和混合精度训练来微调 Llama 2–7b 和 Llama 3–8B。这使用了一种称为块坐标优化的技术,该技术将模型的参数划分为较小的块并迭代更新每个块,同时保持其他块固定,从而显着减少训练所需的内存占用,同时保持良好的性能,如下所示。
图片来源:BAdam GitHub repo
BAdam 模型— 我们可以进行逐层(导入 BlockOptimizer 类)或按比例(导入 BlockOptimizerRatio 类)BAdam 优化器。逐层训练需要安装DeepSpeed才能运行。
更新比率——稀疏掩码的比率。
切换模式——在不同块之间切换的模式。
图片来源:BAdam GitHub
切换间隔——切换到下一个块之前的优化步骤数。
您可以按照此视频教程使用 LlamaFactory 进行微调。
结论
在本文中,我们探讨了各种微调策略,每种策略都根据项目的具体要求提供独特的优势。从迁移学习等传统方法到适配器和快速调整等更先进的技术,了解这些方法可以让您为用例选择最有效的策略。