(最全)入门级-LLM调教指北

Transformer架构

变换器架构是由Vaswani等人在其著名的论文《Attention Is All You Need》中引入的,需要了解细节的可以参考[原始论文](https://arxiv.org/abs/1706.03762) Transformer使我们能够训练具有惊人推理能力的大型语言模型(LLMs),同时保持架构的简洁,足以让机器学习新手开始训练/尝试。

对于语言建模来说,最常见的架构就是transformer架构。Transformer允许我们以一种非常高效的方式处理数据,它使用注意力机制来加强模型对输入数据序列中不同部分之间关系的学习能力。这种机制特别适用于语言,因为语言本质上是一系列相互关联的标记(例如单词或字符)。

在变换器模型中,每个输入标记都会通过一个嵌入层,将标记转换为固定大小的向量。然后,这些向量通过一系列的层进行处理,每一层都会对数据进行转换,同时模型会学习如何更好地预测序列中的下一个标记。

变换器架构的关键创新之一是自注意力机制,它允许模型在生成每个预测时同时关注序列中的多个位置,这与传统的循环神经网络(RNN)不同,后者按顺序处理序列中的每个元素。

基础概念

预训练

预训练涉及几个步骤。首先,收集大量的文本数据集,通常以TB为单位。接下来,选择或特别为手头任务创建一个模型架构。此外,训练一个分词器来适当处理数据,确保它能够有效地编码和解码文本。然后,使用分词器的词汇表对数据集进行预处理,将原始文本转换为适合训练模型的格式。这一步包括将标记映射到它们相应的ID,并加入任何必要的特殊标记或注意力掩码。预处理数据集后,它就准备好用于预训练阶段了。

在预训练期间,模型学习预测句子中的下一个词或者填补缺失的词,这通过利用大量可用数据来完成。这个过程涉及通过迭代训练过程优化模型的参数,以最大化生成正确单词或单词序列的可能性,给定上下文。

为了实现这一点,预训练阶段通常采用自监督学习技术的一种变体。模型呈现部分掩蔽的输入序列,其中某些标记被有意隐藏,它必须基于周围上下文预测这些缺失的标记。通过以这种方式在大量数据上训练,模型逐渐发展出对语言模式、语法和语义关系的丰富理解。这种特定方法是用于掩蔽语言建模的。然而,今天最常用的方法是因果语言建模。与掩蔽语言建模不同,其中某些标记被掩蔽,模型预测这些缺失的标记,因果语言建模侧重于根据前面的上下文预测句子中的下一个词。

这个初始的预训练阶段旨在捕获一般语言知识,使模型成为一个熟练的语言编码器。但不足为奇的是,它缺乏特定任务或领域的知识。为了弥合这一差距,随后的微调阶段跟在预训练之后。

微调

在初始的预训练阶段之后,模型学习了一般的语言知识,微调允许我们专门化模型的能力,并优化其在更狭窄、特定任务数据集上的表现。

微调过程涉及几个关键步骤。首先,收集特定任务的数据集,包括与所需任务相关的标记示例。例如,如果任务是指令调整,将收集指令-响应对的数据集。微调数据集的大小比通常用于预训练的数据集要小得多。

接下来,使用之前学到的参数初始化预训练模型。然后,在特定任务的数据集上训练模型,优化其参数以最小化特定任务的损失函数(即模型与期望结果的“偏差”)。

在微调期间,使用基于梯度的优化算法调整预训练模型的参数,例如随机梯度下降(SGD)或Adam。梯度是通过反向传播损失来计算的,允许模型从错误中学习并相应地更新其参数。

为了增强微调过程,可以采用额外的技术,如学习率调度、像dropout或权重衰减这样的正则化方法,或提前停止以防止过拟合。这些技术有助于优化模型的泛化能力,并防止模型过于紧密地记忆训练数据集。

微调在计算上是昂贵的,需要数百GB的VRAM来训练数十亿参数的模型。为了解决这个特定问题,提出了一种新的方法:低秩适应。与使用Adam微调OPT-175B相比,LoRA可以将可训练参数的数量减少10,000倍,并将GPU内存需求降低3倍以上。请参阅LoRA论文:大型语言模型的低秩适应,以及HuggingFace关于在低资源硬件上对十亿规模模型进行参数高效微调的博客文章。

训练成本

根据模型和数据集大小,内存要求会有所不同。您可以参考 EleutherAI 的 Transformer Math 101 博客文章,了解详细但易于理解的计算。一般来说您将需要微调至少 7B 类的模型。一些流行的选项是 Llama-2 7B 和 Mistral 7B 等。此大小级别通常需要 160~192GB 范围内的内存。

收集数据集

毫无疑问,数据集收集是您微调过程中最重要的部分。质量和数量都很重要 - 尽管质量更重要。首先,想想你希望微调后的模型做什么。写故事?角色扮演?为您写电子邮件?也许你想创建你的 AI waifubot。为了这个条目的目的,让我们假设你想训练一个聊天和角色扮演模型,比如Pygmalion。您需要收集对话数据集。具体来说,互联网 RP 风格的对话。收集部分可能相当具有挑战性;你得自己弄清楚这

需要为数据集概述一个结构。

数据多样性:如果不希望模型执行一项非常具体的任务。在我们假设的用例中,我们正在训练一个聊天模型,但这并不意味着数据只与一种特定类型的聊天/RP 有关。您将希望使训练样本多样化,包括各种场景,以便您的模型可以学习如何为各种类型的输入生成输出。

数据集大小:与 LoRA 或软提示不同,需要相对大量的数据。当然,这与预训练数据集不在一个级别上。根据经验,确保至少有 10 MiB 的数据用于微调。过度训练模型非常困难,因此堆叠更多数据始终是一个好主意。

数据集质量:数据的质量非常重要。您希望数据集反映模型的结果。如果你给它喂垃圾,它会吐出垃圾。

数据集处理

您现在可能有一堆文本数据。在继续之前,需要将它们解析为合适的格式以进行预处理。假设数据集处于以下条件之一:

如果从网站抓取数据,则可能有 HTML 文件。在这种情况下,的首要任务是从 HTML 元素中提取数据。如果会尝试使用纯 RegEx 来做到这一点。这是非常低效的,所以值得庆幸的是,有一些库可以处理这个问题。您可以使用 Beautiful Soup Python 库来帮助您完成此操作。您可以阅读其文档,

如果从在线开放数据源获取数据集,则可以拥有 CSV 文件。解析它们的最简单方法是使用 pandas python 库

SQL更难一些。可以采取明智的方法,使用 MariaDB 或 PostgreSQL 等数据库框架将数据集解析为明文,但也有用于此目的的 Python 库;一个例子是 SQLParParse

最好的语言模型是随机的,这使得即使输入提示保持不变,也很难预测它们的行为。这有时会导致低质量和不理想的输出。您需要确保清除数据集中不需要的元素。如果您的数据源是合成的,即由 GPT-4/3 生成,则这一点尤为重要。您可能希望截断或删除诸如“作为 AI 语言模型...”、“有害或冒犯性内容...”、“...由 OpenAI 训练...“等。ehartford 的这个脚本是这个特定任务的一个很好的过滤器。您也可以参考 gptslop 存储库

训练超参数

训练超参数在塑造模型的行为和性能方面起着至关重要的作用。这些 hparams 是指导训练过程的设置,用于确定模型如何从提供的数据中学习。选择合适的 hparams 可以显著影响模型的收敛性、泛化和整体有效性。

随机梯度下降 (SGD) 是一种具有多个超参数的学习算法。经常让新手感到困惑的两个因素是 batch size 和 epoch 数。它们都是整数值,并且似乎执行相同的操作。让我们回顾一下本节的主要内容:

Stochastic Gradient Descent(随机梯度下降):当批处理大小设置为 1 时,称为随机梯度下降。在这里,每个样本都被单独处理,并且模型参数在每个样本之后都会更新。这种方法在学习过程中引入了更多的随机性。

Batch Gradient Descent:当批量大小等于训练样本的总数时,称为批量梯度下降。在这种情况下,整个数据集用于计算预测并在更新模型之前计算误差。

Mini-Batch Gradient Descent:当批量大小大于 1 且小于训练数据集的总大小时,称为 Mini-Batch Gradient Descent。该算法适用于小批量样本,进行预测并相应地更新模型参数。小批量梯度下降在批量梯度下降的效率和随机梯度下降的随机性之间取得了平衡。

Batch Size:Batch size 是梯度下降中的一个超参数,用于确定在更新模型的内部参数之前处理的训练样本数。换句话说,它指定了每次迭代中使用多少个样本来计算误差和调整模型。批量大小是在更新模型之前处理的样本数。epochs 数是通过训练数据集的完整遍历次数。

批次的大小必须大于或等于 1 (bsz=>1) 且小于或等于训练数据集中的样本数 (bsz=< 否。Samples) 的 Samples 进行。

批量大小是一个 hparam,用于确定在更新模型的内部参数之前处理的样本数。想象一个 “for 循环” 迭代特定数量的样本并进行预测。处理批处理后,将预测与预期输出进行比较,并计算误差。然后,该误差用于通过调整其参数来改进模型,沿误差梯度的方向移动。

Number of Epochs(时期数):时期数是梯度下降中的另一个超参数,它控制通过训练数据集的完整遍历次数。每个 epoch 涉及处理整个数据集一次,并且模型的参数在每个 epoch 之后更新。

纪元数可以设置为介于一 (1) 和无穷大之间的整数值。您可以根据需要运行算法,甚至可以使用固定纪元数以外的其他标准停止算法,例如模型误差随时间的变化(或没有变化)。

您可以将 epoch 数可视化为迭代训练数据集的 “for 循环”。在此循环中,还有另一个嵌套的“for 循环”遍历每批样本,其中一个批次根据批次大小包含指定数量的样本。

学习率配置

从合理的范围开始:首先考虑类似任务中常用的学习率值范围。找出用于正在微调的预训练模型的学习率,并以此为基础。例如,常见的起点是 1e-5 (0.00001),这通常被发现对 transformer 模型有效。

观察训练进度:使用所选学习率运行训练过程,并在训练期间监控模型的性能。密切关注损失或准确率等指标,以评估模型的学习情况

太慢了?如果学习率太小,您可能会注意到模型的训练进度很慢,并且需要很长时间才能收敛或做出明显的改进。在这种情况下,请考虑提高学习率以加快学习过程。

太快了?如果学习率太大,模型可能会学习得太快,从而导致结果不稳定。LR 过高的迹象包括训练期间损失或准确性的剧烈波动。如果您观察到此行为,请考虑降低 lr

迭代调整学习率:根据步骤 3 和 4 中的观察结果,迭代调整学习率并重新运行训练过程。逐渐缩小产生最佳性能的学习率范围。

base_lr * sqrt(supervised_tokens_in_batch / pretrained_bsz)

base_lr是指预训练模型的学习率。在 Mistral 的情况下,这是 5e-5。supervised_tokens_in_batch是指监督标记的总数(一旦您开始训练,蝾螈会报告此数字),将其除以总步骤数(也由蝾螈报告)除以纪元总数;即 total_supervised_tokens / (num_steps / num_epochs)。pretrained_bsz 是指基本模型的原始批量大小。如果是 Mistral 和 Llama,则为 4,000,000(400 万)。例如,假设我们正在使用包含 200 万个监督标记的数据集来训练 Mistral,并且我们正在批处理大小为 1 的单个 GPU 上进行训练。我们还假设这需要 350 个步骤。最终公式将如下所示:

5e-5 * sqrt(2000000/(350/1) / 4000000) = 0.00000189 (1.89e-6)
5e-5 * sqrt(2000000/(350/1) / 4000000) = 0.00000189 (1.89e-6)

梯度累积

解释

批处理大小越高,内存消耗就越高。梯度累积旨在解决这个问题。

梯度累积是一种机制,用于将样本批次(用于训练模型)拆分为几个小批量的样本,这些样本将按顺序运行。

梯度累积是一种技术,我们在不更新模型变量的情况下执行多个计算步骤。相反,我们跟踪在这些步骤中获得的梯度,并在以后使用它们来计算变量更新。其实很简单!为了理解梯度累积,让我们考虑将一批样品分成更小的组,称为小批量。在每个步骤中,我们都会处理其中一个小批量,而不会更新模型的变量。这意味着模型对所有小批量使用相同的变量集。通过在这些步骤中不更新变量,我们可以确保为每个小批量计算的梯度和更新与我们使用原始完整批次相同。换句话说,我们保证从所有小批量获得的梯度之和等于从完整批次获得的梯度。总而言之,梯度累加允许我们将批次划分为小批次,在不更新变量的情况下对每个小批次执行计算,然后从所有小批次中累积梯度。这种累积可确保我们获得与使用完整批次相同的总体梯度。

作用原理

假设我们在 5 个步骤中累积梯度。在前 4 个步骤中,我们不更新任何变量,但存储渐变。然后,在第五步中,我们将前面步骤的累积梯度与当前步骤的梯度相结合,以计算和分配变量 updates。

在第一步中,我们处理一小批样品。我们遍历前向和后向传递,这使我们能够计算每个可训练模型变量的梯度。但是,我们并没有实际更新变量,而是专注于存储梯度。为此,我们为每个可训练模型变量创建额外的变量来保存累积的梯度。在计算了第一步的梯度之后,我们将它们存储在我们为累积梯度创建的相应变量中。这样,第一步的梯度将可用于后续步骤。

在接下来的三个步骤中,我们重复此过程,在不更新变量的情况下累积梯度。最后,在第五步中,我们得到了前四个步骤的累积梯度和当前步骤的梯度。将这些梯度组合在一起,我们可以计算变量更新并相应地分配它们。这是一个例子:

现在第二步开始,第二次小批量的所有样本再次传播到模型的所有层,计算第二步的梯度。就像前面的步骤一样,我们还不想更新变量,因此无需计算变量更新。不过,与第一步不同的是,我们不是仅仅将第二步的梯度存储在变量中,而是将它们添加到变量中存储的值中,这些值当前保存着第一步的梯度。

第 3 步和第 4 步与第 2 步几乎相同,因为我们还没有更新变量,而是通过将梯度添加到变量中来累积梯度。

然后在第 5 步中,我们确实需要更新变量,因为我们打算在 5 个步骤中累积梯度。在计算了第五步的梯度后,我们将它们添加到累积的梯度中,得到这 5 个步骤的所有梯度之和。然后,我们将获取此总和并将其作为参数插入到优化器中,从而使用这 5 个步骤的所有梯度计算更新,并在全局批处理中的所有样本上计算。

如果我们以 SGD 为例,让我们在第五步结束时更新后的变量,使用这 5 个步骤的梯度计算(以下示例中为 N=5):

配置梯度累计数量

需要使用梯度累积步骤来实现接近或大于所需批次大小的有效批次大小。例如,如果所需的批次大小为 32 个样品,但 VRAM 有限,只能处理 8 个批次大小,则可以将梯度累积步骤设置为 4。这意味着在执行更新之前,可以在 4 个步骤中累积梯度,从而有效地模拟 32 (8 * 4) 的批量大小。一般来说,我建议平衡梯度累积步骤与可用资源,以最大限度地提高计算效率。累积步骤太少可能会导致梯度信息不足,而太多会增加内存需求并减慢训练过程。

学习曲线

学习曲线是从训练数据集中逐步学习的算法最常用的工具之一。将使用验证拆分对模型进行评估,并为损失函数创建一个图,以测量模型的当前输出与预期输出相比的差异。让我们尝试回顾一下学习曲线的细节,以及如何使用它们来诊断模型的学习和泛化行为。

 欠拟合学习曲线

它可能显示相对较高的损失的平坦线或噪声值,表明模型根本无法学习训练数据集。让我们看一下下面的示例,当模型没有适合数据集复杂性的容量时,这种情况很常见:无论训练如何,训练损失都保持平稳。训练损失继续减少,直到训练结束。

过拟合学习曲线

这是指一个模型对训练数据集的学习得太好,导致数据记忆化而不是泛化。这将包括训练数据集中存在的统计噪声或随机波动。过拟合的问题在于,模型对训练数据越专业化,它对新数据的泛化能力就越差,从而导致泛化误差增加。泛化误差的增加可以通过模型在验证数据集上的性能来衡量。如果模型的容量超过所需问题所需的容量,并且反过来又具有太大的灵活性,则通常会发生这种情况。如果模型训练时间过长,也可能发生这种情况。

拟合良好的学习曲线

通常确定一个好的拟合是训练和验证损失,它减少到一个稳定点,并且两个最终损失值之间的差距最小.模型在训练数据集上的损失始终低于验证数据集。这意味着我们应该预期训练和验证损失学习曲线之间会有一些差距。此差距称为 “泛化差距”。

参考资料

https://rentry.org/llm-training

https://arxiv.org/abs/1706.03762

Transformer 数学 101 |EleutherAI 博客 --- Transformer Math 101 | EleutherAI Blog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值