文本内容参考论文《A Survey of Large Language Models》
论文标题:A Survey of Large Language Models
论文链接:https://arxiv.org/pdf/2303.18223v10.pdf
大模型技术发展概述 -(二)
- 4. LLM预训练
- 4.1 数据收集
- 4.1.1 数据源
- 4.1.2 数据预处理
- 4.1.3 预训练数据对LLMs的影响
- 4.2 常用的LLM架构
- 4.2.1 主流架构
- 4.2.2 详细配置
- 4.2.3 预训练任务
- 4.3 模型训练
- 4.3.1 优化设置
- 4.3.2 可扩展训练技术
4. LLM预训练
预训练是LLMs能力的基础。通过在大规模语料库上进行预训练,LLMs可以获取基本这个语料库的语言理解和生成技能。在这个过程中,预训练语料库的规模和质量对于LLMs获得强大的能力至关重要。此外,为了有效地进行LLMs的预训练,需要设计良好的模型架构、加速方法和优化技术。
- 接下来,我们首先讨论数据的收集和处理,
- 然后介绍常用的模型架构,
- 最后介绍稳定和高效地优化LLMs的训练技术。
4.1 数据收集
与小规模语言模型相比,LLMs对于模型预训练需要更高质量的数据,并且它们的模型性能在很大程度上依赖于预训练语料库及其预处理方式。在这一部分,将讨论预训练数据的收集和处理,包括数据源、预处理方法以及预训练数据如何影响LLMs性能的重要分析。
4.1.1 数据源
为了开发具备能力的LLM,关键是从各种数据源收集大量的自然语言语料。现有的LLMs主要利用多样化的公共文本数据集混合作为预训练语料库。下图显示了几个代表性LLMs的预训练数据源的分布。
预训练语料库的来源可以广泛分为两类:通用数据和专用数据。
通用数据例如网页、图书和对话文本,由于其大规模、多样性和易获取性,被大多数LLMs使用。这些数据可以增强LLMs的语言建模和泛化能力。鉴于LLMs展示出的惊人泛化能力,还有研究将预训练语料库扩展到更专门的数据集,例如多语言数据、科学数据和代码,以赋予LLMs特定的任务解决能力。接下来,我们将介绍这两类预训练数据源及其对LLMs的影响.
- 通用文本数据。从上图可以看出,绝大多数LLMs采用通用预训练数据,例如网页、图书和对话文本,这些数据提供了丰富的主题文本来源。
- 专用文本数据:专门的数据集对于提升LLMs在下游任务上的特定能力非常有用。接下来,我们介绍三种专门数据。
- 多语言文本:整合多语言语料库可以增强LLMs的多语言理解和生成能力。例如,BLOOM和PaLM使用涵盖多种语言的预训练语料库,这使得它们在多语言任务上表现出令人印象深刻的性能,并在目标语言上与经过微调的最先进模型相媲美甚至更优秀。
- 科学文本:将科学语料库纳入LLMs的预训练可以增强其对科学知识的理解。通过在大量科学文本上进行预训练,LLMs在科学和推理任务中展现出令人印象深刻的性能。现有工作主要收集arXiv论文、科学教科书和其他相关资源来构建科学语料库,并使用特定的标记化和预处理技术将不同格式的数据转换为可处理的形式。
- 代码:在PLMs领域,程序合成的研究备受关注。最近的研究发现,通过在大规模代码语料库上进行预训练,LLMs可以生成高质量和准确的程序。这些程序可以通过单元测试用例或解决编程问题进行验证。代码语料库的来源可以是编程问答社区和公共软件仓库,其中包括了丰富的代码数据。代码的格式使得LLMs能够处理长程依赖关系和准确的执行逻辑。此外,通过代码进行训练还可能提高LLMs的复杂推理能力,并产生更准确的结果。
4.1.2 数据预处理
在收集大量文本数据之后,对数据进行预处理是必要的,以构建预训练语料库。预处理过程主要是去除噪声、冗余、不相关和潜在有害的数据,这些因素可能会在很大程度上影响LLMs的规模和性能。在这部分,我们回顾了提高收集数据质量的详细数据预处理策略。预训练数据的典型预处理流程图如图所示。
🎙🕺🤟🏀
1️⃣质量过滤(Quality Filtering)
为了从收集到的语料库中去除低质量数据,现有的工作通常采用两种方法:(1)基于分类器,和(2)基于启发式规则。前一种方法基于高质量文本训练选择分类器,并利用它来识别和过滤低质量数据。通常,这些方法会使用精心策划的数据(如维基百科页面)作为正例,使用候选数据作为负例来训练二元分类器,并预测衡量每个数据示例质量的分数。然而,一些研究也发现,基于分类器的方法可能会意外地去除方言、口语和社会语言学语言中的高质量文本,这可能导致预训练语料库中的偏差,并降低语料库的多样性。作为第二种方法,一些研究(如BLOOM和Gopher)采用基于启发式规则的方法通过一组精心设计的规则来消除低质量文本,这些规则可以总结如下:
- 基于语言的过滤。如果LLMs主要用于特定语言的任务,可以过滤掉其他语言的文本。
- 基于指标的过滤。可以利用生成文本的评估指标,如困惑度,来检测并删除不自然的句子。
- 基于统计的过滤。可以利用语料库的统计特征,如标点符号分布、符号与单词比例和句子长度,来衡量文本质量并过滤低质量数据。
- 基于关键词的过滤。根据特定的关键词集,可以识别和删除文本中的噪声或无用元素,如HTML标记、超链接、样板和攻击性词汇。
2️⃣去重(De-duplication)
现有研究发现,语料库中的重复数据会降低语言模型的多样性,可能导致训练过程不稳定,从而影响模型的性能。因此,需要对预训练语料库进行去重。去重可以在不同的粒度进行,包括句子级、文档级和数据集级的去重。
首先,应该删除包含重复词语和短语的低质量句子,因为它们可能引入语言建模中的重复模式。在文档级别上,现有的研究主要依靠文档之间表面特征的重叠比例(如词语和n-gram的重叠)来检测并删除包含相似内容的重复文档。此外,为了避免数据集污染问题,还必须防止训练集和评估集之间的重叠,通过从训练集中删除可能的重复文本来实现。
3️⃣隐私处理
大多数预训练文本数据来自网络来源,包括涉及敏感或个人信息的用户生成内容,这可能增加隐私泄露的风险。因此,有必要从预训练语料库中删除个人可识别信息(PII)。一种直接有效的方法是采用基于规则的方法,如关键词检测,来检测和删除姓名、地址和电话号码等PII。此外,研究人员还发现,LLMs在隐私攻击下的脆弱性可以归因于预训练语料库中存在重复的PII数据。因此,去重也可以在一定程度上减少隐私风险。
4️⃣标记化
标记化也是数据预处理的关键步骤之一。它的目标是将原始文本分割成单个标记序列,随后用作LLMs的输入。虽然利用现有的标记器(如OPT和GPT-3使用GPT-2的标记器)是方便的,但使用专门为预训练语料库设计的标记器可能非常有益,尤其是对于包含不同领域、语言和格式的语料库。因此,几个最近的LLMs使用SentencePiece专门为预训练语料库训练定制的标记器。使用字节级别的Byte Pair Encoding (BPE) 算法可以确保标记化后的信息无损失。然而,BPE中的归一化技术,如NFKC,可能会降低标记化性能。
4.1.3 预训练数据对LLMs的影响
与小规模PLMs不同,由于计算资源需求巨大,多次迭代预训练LLMs通常是不可行的。因此,在训练LLM之前,构建一个准备充分的预训练语料库尤为重要。在这部分中,我们讨论预训练语料库的质量和分布如何潜在地影响LLMs的性能。
1️⃣数据来源的混合
如前所述,来自不同领域或场景的预训练数据具有不同的语言特征或语义知识。通过在多源文本数据上进行预训练,LLMs可以获取广泛的知识范围,并可能表现出强大的泛化能力。在混合不同来源的数据时,需要仔细设置预训练数据的分布,因为这也可能影响LLMs在下游任务上的性能。Gopher对数据分布进行了消融实验,以检查混合来源对下游任务的影响。在LAMBADA数据集上的实验结果显示,增加图书数据的比例可以提高模型捕捉文本中长期依赖关系的能力,而增加C4数据集的比例则会提高在C4验证数据集上的性能。然而,也会有一个副作用,过多训练特定领域的数据会影响LLMs在其他领域的泛化能力。因此,建议研究人员应该仔细确定预训练语料库中来自不同领域的数据比例,以开发更好地满足特定需求的LLMs。
2️⃣预训练数据量
为了预训练一个有效的LLM,收集足够的高质量数据以满足LLM的数据量需求非常重要。现有研究发现,随着LLM中参数规模的增加,还需要更多的数据来训练模型:与模型大小相对应,数据大小也存在类似的缩放规律,与模型性能相关。最近的研究表明,由于不充分的预训练数据,许多现有的LLMs在训练过程中出现了次优训练。通过进行大量实验证明,以相等规模增加模型大小和数据大小可以得到更高效的计算模型(即Chinchilla模型)
最近的LLaMA研究表明,通过增加数据量和训练时间,较小的模型也可以达到良好的性能。总体而言,建议研究人员应该更加关注足够训练模型所需的高质量数据量,特别是在扩展模型参数时。
3️⃣预训练数据质量
现有研究表明,预训练低质量语料库(如嘈杂、有毒和重复数据)可能会损害模型的性能。为了开发性能良好的LLM,必须同时考虑所收集训练数据的数量和质量。
如T5、GLaM和Gopher,已经调查了数据质量对下游任务性能的影响。通过比较在经过过滤和未经过过滤的语料库上训练的模型的性能,它们得出了同样的结论,即在清理过的数据上进行预训练可以提高LLMs的性能。具体而言,数据重复可能导致“双谷”现象(指性能最初恶化,随后改善的现象),甚至压倒训练过程。此外,研究表明,重复数据会降低LLMs从上下文中复制的能力,进一步影响使用上下文学习的LLMs的泛化能力。
4.2 常用的LLM架构
在本节中,我们回顾LLMs的架构设计,包括主流架构、预训练目标和详细配置。下表呈现了几个具有公开细节的代表性LLMs的模型
4.2.1 主流架构
由于Transformer架构具有出色的并行化能力和容量,它已成为开发各种LLMs的事实标准,使得语言模型的参数可以扩展到数千亿或数万亿。一般而言,现有LLMs的主流架构可以大致分为三种类型,即编码器-解码器(Encoder—Decoder,ED)、因果解码器(Causal Decoder,CD)和前缀解码器(Prefix Decoder,PD),如下图所示。
在这里,蓝色、绿色、黄色和灰色的圆角矩形分别表示前缀标记之间的注意力、前缀标记和目标标记之间的注意力、目标标记之间的注意力以及屏蔽注意力。
这里的前缀解码器(Prefix Decode)在一些论文也叫Non-causal decoder(ND)
下面我结合大模型任务举例来解释这三个结构,让读者更好的理解这三种结构:
Causal decoder-only (CD):Causal decoder-only结构指的是仅使用Transformer解码器的模型。这类模型主要用于语言生成任务,如文本生成或机器翻译。在训练过程中,模型通过预测上文来生成当前的标记。一个典型的例子是GPT(Generative Pre-trained Transformer)系列模型,如GPT-2和GPT-3。
🤪举例来说,对于文本生成任务,CD模型的输入是上文序列,而输出则是根据上文来生成下一个标记。模型通过自回归的方式,逐个生成下一个标记,每个标记的生成都依赖于之前已生成的标记。这种结构使得模型能够根据上文预测下一个可能的标记,从而生成连贯的文本。
Non-causal decoder-only (ND):Non-causal decoder-only结构是为了在给定条件下生成文本而设计的。在这种结构中,训练时可以让模型看到一部分输入序列的信息。与CD不同,ND模型可以基于输入的一部分内容来生成文本,而不仅仅依赖于上文。
😎举例来说,假设我们要构建一个根据给定的问题生成答案的模型。ND模型的输入是问题序列,而输出是生成的答案序列。在训练时,模型可以同时看到问题序列和部分答案序列,从而根据这些信息生成下一个标记,逐步构建出完整的答案序列。
Encoder-decoder (ED):Encoder-decoder结构是最常见的序列到序列模型结构,它由一个编码器和一个解码器组成。编码器将输入序列编码为一个固定长度的向量表示,解码器根据编码器的输出和先前生成的标记来生成下一个标记。
🤩举例来说,对于机器翻译任务,ED模型的输入是源语言的句子,输出是目标语言的句子。编码器将源语言句子编码为一个向量表示,解码器根据这个向量表示和之前生成的目标语言标记来生成下一个目标语言标记,直到生成完整的目标语言句子。
到这里,经常用chatgpt的应该很容易就看出来,这三个结构做的事情,就是对应我们平时问gpt的任务
编码器-解码器架构
基础的Transformer模型建立在编码器-解码器架构上,其中编码器和解码器分别由两个Transformer块堆叠而成。编码器采用堆叠的多头自注意力层对输入序列进行编码,生成其潜在表示,而解码器对这些表示进行交叉注意力操作,并自回归地生成目标序列。编码器-解码器PLMs(例如T5和BART)在各种自然语言处理任务中表现出良好的效果。目前,只有很少一部分LLMs基于编码器-解码器架构构建,例如Flan-T5。
下面提供一个小的代码demo来更好的理解
import torch
import torch.nn as nn
from torch.nn import TransformerEncoder, TransformerDecoder
# 定义Encoder-Decoder模型
class EncoderDecoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_heads):
super(EncoderDecoder, self).__init__()
self.encoder = TransformerEncoder(
nn.TransformerEncoderLayer(hidden_size, num_heads),
num_layers)
self.decoder = TransformerDecoder(
nn.TransformerDecoderLayer(hidden_size, num_heads),
num_layers)
def forward(self, input_sequence, target_sequence):
encoder_output = self.encoder(input_sequence)
decoder_output = self.decoder(target_sequence, encoder_output)
return decoder_output
在ED模型中,我们同时使用了Transformer编码器和解码器。编码器将输入序列编码为一个固定长度的向量表示,解码器基于编码器的输出和之前生成的标记来生成下一个标记。这个例子展示了如何创建一个简单的Encoder-Decoder模型。
因果解码器架构
因果解码器架构引入了单向注意力掩码,确保每个输入标记只能与过去的标记和自身进行关注。输入和输出标记通过解码器以相同的方式处理。作为该架构的代表性语言模型,GPT系列模型基于因果解码器架构进行开发。特别是,GPT-3成功展示了这种架构的有效性,也展示了LLMs的惊人上下文学习能力。有趣的是,GPT-1和GPT-2并没有展现出像GPT-3那样的优越能力,似乎规模扩展在增加该模型架构的容量方面起到了重要作用。到目前为止,因果解码器已被各种现有LLMs广泛采用,例如OPT、BLOOM和Gopher 。需要注意的是,后文提到的“仅解码器架构”主要指现有文献中的因果解码器架构,除非另有说明。
下面提供一个小的代码demo来更好的理解
import torch
import torch.nn as nn
from torch.nn import TransformerDecoder
# 定义Causal Decoder模型
class CausalDecoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_heads):
super(CausalDecoder, self).__init__()
self.decoder = TransformerDecoder(
nn.TransformerDecoderLayer(hidden_size, num_heads),
num_layers)
def forward(self, input_sequence):
output_sequence = self.decoder(input_sequence)
return output_sequence
在CD模型中,我们只使用了Transformer解码器。模型接收输入序列,然后通过Transformer解码器进行处理。解码器通过自回归的方式逐个生成下一个标记,以生成连贯的文本。这个例子展示了如何创建一个简单的Causal Decoder模型。
非因果(前缀)解码器架构
前缀解码器架构(又称非因果解码器)修改了因果解码器的屏蔽机制,使其能够对前缀标记进行双向注意力操作,而只能在生成的标记上进行单向注意力操作。这样,与编码器-解码器架构类似,前缀解码器可以双向编码前缀序列,并自回归地逐个预测输出标记,其中在编码和解码期间共享相同的参数。与从头开始预训练相比,一个实际的建议是连续训练因果解码器,然后将其转换为前缀解码器以加速收敛,例如U-PaLM是由PaLM演化而来的。基于前缀解码器的现有代表性LLMs包括GLM-130B和U-PaLM。
下面提供一个小的代码demo来更好的理解
import torch
import torch.nn as nn
from torch.nn import TransformerDecoder
# 定义Non-causal Decoder模型
class NonCausalDecoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_heads):
super(NonCausalDecoder, self).__init__()
self.decoder = TransformerDecoder(
nn.TransformerDecoderLayer(hidden_size, num_heads),
num_layers)
def forward(self, input_sequence):
output_sequence = self.decoder(input_sequence)
return output_sequence
在ND模型中,我们同样使用了Transformer解码器,但在训练时可以让模型看到输入序列的一部分内容。这个例子展示了如何创建一个简单的Non-causal Decoder模型
4.2.2 详细配置
自Transformer发布以来,人们提出了各种改进来增强其训练稳定性、性能和计算效率。在这部分中,将讨论Transformer的四个主要部分(归一化、位置嵌入、激活函数和注意力与偏置)的相应配置。为了使本文更具自包含性,我们在下表中给出了这些配置的详细公式。
归一化
对于预训练LLMs而言,训练不稳定是一个具有挑战性的问题。为了缓解这个问题,在Transformer架构中广泛使用了层归一化(Layer Norm, LN)。LN的位置对于LLMs的性能至关重要。虽然最初的Transformer使用的是后LN,但大多数LLMs使用前LN进行更稳定的训练,尽管性能会有所下降。基于前LN,Sandwich-LN在残差连接之前添加了额外的LN,以避免数值爆炸。然而,发现Sandwich-LN有时无法稳定LLMs的训练,可能导致训练崩溃。
最近,一些先进的归一化技术作为LN的替代方案被提出。在Gopher 和Chinchilla中采用了RMS Norm,因为它在训练速度和性能上具有优势。与LN相比,DeepNorm在确保训练稳定性方面表现更好,GLM-130B采用后归一化使用了DeepNorm。此外,在嵌入层之后添加额外的LN也可以稳定LLMs的训练,但往往会导致显著的性能下降,因此在一些最近的LLMs中已经去除了这一操作。
激活函数
为了获得良好的性能,激活函数在前馈网络中也需要适当设置。在现有的LLMs中,广泛使用GeLU激活函数。此外,在最新的LLMs(如PaLM和LaMDA)中,也采用了GLU激活函数的变体,尤其是SwiGLU和GeGLU变体,在实践中通常可以获得更好的性能。然而,与GeLU相比,它们在前馈网络中需要额外的参数(约50%)。
位置嵌入
由于Transformer中的自注意力模块是置换等变的,因此使用位置嵌入来注入绝对或相对位置信息以建模序列。在经典的Transformer中,有两种绝对位置嵌入的变体,即正弦函数和学习的位置嵌入,其中后者在LLMs中常被采用。相对位置编码与绝对位置嵌入不同,它根据键和查询之间的偏移量生成嵌入,因此可以在训练期间看不到的更长序列上表现良好,即外推。ALiBi 通过基于键和查询之间的距离的惩罚对注意力分数进行调整。实证结果表明,与其他位置嵌入相比,它具有更好的零样本泛化能力和更强的外推能力。此外,通过根据绝对位置设置特定的旋转矩阵,RoPE 中键和查询之间的得分可以使用相对位置信息计算,这对于建模长序列很有用。因此,RoPE已被广泛采用在一些最新的LLMs中。
注意力和偏置
除了原始Transformer中的全自注意力机制,GPT-3中采用了计算复杂度较低的稀疏注意力机制(即分解注意力)。为了有效和高效地建模更长的序列,人们进行了更多尝试,包括引入特殊的注意力模式或考虑GPU内存访问(即FlashAttention)。此外,大多数LLMs在保留每个稠密核和层归一化中的偏置时,遵循了原始Transformer的做法。然而,在PaLM和Galactica中,偏置被移除。这表明对于LLMs来说,没有偏置可以增强训练的稳定性。
综合以上讨论,我们总结了现有文献中关于详细配置的建议。为了更强的泛化能力和训练稳定性,建议选择预RMS Norm用于层归一化,并选择SwiGLU或GeGLU作为激活函数。然而,在嵌入层之后不应立即使用LN,这可能导致性能下降。此外,在位置嵌入方面,RoPE或ALiBi是更好的选择,因为它们在长序列上表现更好。
4.2.3 预训练任务
预训练在将大规模语料库的通用知识编码到庞大的模型参数中起着关键作用。对于训练LLM,常用的预训练任务有两种,即语言建模和去噪自编码。
语言建模
语言建模任务(LM)是预训练仅有解码器的LLM(如GPT3和PaLM )最常用的目标。给定一个token序列
x
=
x
1
,
.
.
.
,
x
n
x = {x_1, . . . , x_n}
x=x1,...,xn,LM任务旨在基于token序列中的前导的前
j
j
j个序列
x
=
x
1
,
.
.
.
,
x
j
,
j
<
i
x = {x_1, . . . , x_j},j<i
x=x1,...,xj,j<i,自回归地预测目标
x
i
x_i
xi。一般的训练目标是最大化以下似然函数:
去噪自编码
除了传统的语言建模任务,去噪自编码任务(DAE)也被广泛用于预训练语言模型。DAE任务的输入
x
∖
x
~
x \setminus \tilde x
x∖x~是包含随机替换片段的损坏文本。然后,语言模型被训练以恢复被替换的token:
x
~
\tilde x
x~。形式上,DAE的训练目标如下所示:
然而,DAE任务在实现上似乎比LM任务更复杂。因此,它尚未被广泛用于预训练大型语言模型。采用DAE作为预训练目标的现有LLM包括T5和GLM-130B。这些模型主要通过自回归方式恢复被替换的片段。
4.3 模型训练
在本部分,将回顾训练LLM时的重要设置和技巧。
4.3.1 优化设置
对于LLM的参数优化,我们介绍了批次训练、学习率、优化器和训练稳定性的常用设置。
批次训练
对于语言模型的预训练,现有工作通常将批次大小设置为较大的数值(例如,8,196个样本或16M个标记),以提高训练的稳定性和吞吐量。对于像GPT-3和PaLM这样的LLM,它们引入了一种新的策略,在训练过程中动态增加批次大小,最终达到百万级别。具体而言,GPT-3的批次大小逐渐从32K增加到3.2M个标记。实证结果表明,批次大小的动态调整策略可以有效稳定LLM的训练过程。
学习率
现有的LLM通常在预训练期间采用类似的学习率调度和热身策略。具体而言,在初始的0.1%至0.5%的训练步骤中,采用线性热身调度逐渐增加学习率,直至最大值,最大值的范围大约在 5 × 1 0 − 5 5 × 10^{-5} 5×10−5到 1 × 1 0 − 4 1 × 10^{-4} 1×10−4之间(例如,GPT-3为 6 × 1 0 − 5 6 × 10^{-5} 6×10−5)。然后,在后续的步骤中采用余弦衰减策略,将学习率逐渐降低到其最大值的约10%,直到训练损失收敛。
优化器
Adam优化器和AdamW优化器广泛用于LLM的训练(例如,GPT-3),它们基于对一阶梯度优化的适应性低阶矩的估计。通常,它的超参数设置如下: β 1 = 0.9 β_1 = 0.9 β1=0.9, β 2 = 0.95 β_2 = 0.95 β2=0.95和 ε = 1 0 − 8 ε = 10^{-8} ε=10−8。同时,Adafactor优化器也被用于LLM的训练(例如,PaLM和T5),它是Adam优化器的一种变体,专门设计用于在训练过程中保存GPU内存。Adafactor优化器的超参数设置为: β 1 = 0.9 β_1 = 0.9 β1=0.9和 β 2 = 1.0 − k − 0.8 β_2 = 1.0 − k^{-0.8} β2=1.0−k−0.8,其中k表示训练步骤的数量。
稳定训练
在LLM的预训练过程中,经常遇到训练不稳定的问题,这可能导致模型崩溃。为了解决这个问题,通常会使用权重衰减和梯度裁剪,其中现有的研究通常将梯度裁剪的阈值设置为1.0,权重衰减率设置为0.1。然而,随着LLM的规模增大,训练损失的突然上升也更容易发生,导致训练不稳定。为了缓解这个问题,PaLM和OPT使用一种简单的策略,在出现突增之前从较早的检查点重新开始训练,并跳过可能导致问题的数据。此外,GLM发现嵌入层的异常梯度通常导致突增,并提出缩小嵌入层梯度以减轻问题。
下图为几个现有LLM的详细优化设置
4.3.2 可扩展训练技术
随着模型和数据规模的增加,在有限的计算资源下有效训练LLM变得具有挑战性。特别是,需要解决两个主要的技术问题,即增加训练吞吐量和将更大的模型加载到GPU内存中。在本部分,回顾了现有工作中解决上述两个挑战的几种常用方法,即3D并行性、ZeRO和混合精度训练,并给出了如何利用它们进行训练的一般建议。
3D并行性
3D并行性实际上是三种常用并行训练技术的组合,即数据并行性、管道并行性和张量并行性。下面介绍这三种并行训练技术。
- 数据并行性:数据并行性是提高训练吞吐量的基本方法之一。它通过在多个GPU上复制模型参数和优化器状态,并将训练数据分配到各个GPU上进行处理,从而实现并行计算。每个GPU只需处理自己分配的数据,并执行前向和反向传播以计算梯度。最后,这些梯度被聚合以更新模型。数据并行性具有高度可扩展性,可以通过增加GPU数量来提高训练吞吐量。此外,主流深度学习库如TensorFlow和PyTorch已经实现了数据并行性,使其在实践中易于使用。
- 管道并行性:管道并行性将LLM的不同层分布到多个GPU上,减少了在GPU之间传输计算的成本。然而,简单的实现可能导致GPU利用率降低。为了解决这个问题,GPipe和PipeDream等方法引入了填充多批数据和异步梯度更新等技术,以提高管道效率。
- 张量并行性:张量并行性将LLM的参数矩阵进行分解,并将其分配到多个GPU上执行计算。通过将矩阵乘法运算在不同的GPU上并行执行,并进行跨GPU通信,可以实现高效的计算。张量并行性已经在几个开源库中得到支持,可以扩展到更高维度的张量。此外,Colossal-AI还实现了更高维度张量的张量并行性,并提出了序列并行性来处理序列数据。
ZeRO
ZeRO技术是由DeepSpeed库提出的,主要解决数据并行性中的内存冗余问题。正如前面提到的,数据并行性要求每个GPU存储LLM的相同副本,包括模型参数、模型梯度和优化器参数。然而,并非所有这些数据都需要在每个GPU上都保留,这会导致内存冗余问题。为了解决这个问题,ZeRO技术旨在在每个GPU上仅保留部分数据,而其余的数据可以在需要时从其他GPU中检索。具体而言,ZeRO提供了三种解决方案,取决于如何存储三个数据部分,即优化器状态分区、梯度分区和参数分区。实证结果表明,前两个解决方案不会增加通信开销,而第三个解决方案会增加大约50%的通信开销,但可以节省与GPU数量成比例的内存。PyTorch已经实现了与ZeRO类似的技术,称为FSDP。
*混合精度训练
在以前的PLM(例如BERT)中,主要使用32位浮点数(FP32)进行预训练。近年来,为了预训练极大规模的语言模型,一些研究开始使用16位浮点数(FP16),这样可以减少内存使用量和通信开销。此外,由于流行的NVIDIA GPU(例如A100)的FP16计算单元是FP32的两倍,FP16的计算效率还可以进一步提高。然而,现有研究发现FP16可能会导致计算精度损失,从而影响最终的模型性能。为了缓解这个问题,一种叫做Brain Floating Point(BF16)的替代方法被用于训练,它分配给指数位更多的位数,较少的有效位数,而不同于FP16。对于预训练,BF16在表示精度上通常优于FP16。
总体训练建议
以下是关于LLM训练的总体建议的要点:
- 并行训练技术:数据并行性、张量并行性和管道并行性是提高训练吞吐量的关键技术。
- 组合并行性:联合使用多种并行训练方法,如数据并行性、张量并行性和管道并行性,可以进一步提高训练效率和大模型的加载。
- 开源库支持:使用开源库如DeepSpeed、Colossal-AI和Alpa,可以很好地支持并行训练方法。
- 内存优化:使用技术如ZeRO、FSDP和激活重新计算来减少内存冗余,这些技术已经集成到一些开源库中。
- 混合精度训练:利用混合精度训练技术如BF16,可以提高训练效率和减少GPU内存使用,但需要支持相应硬件。
- 性能预测:引入可预测缩放机制,可以使用较小的模型对大模型进行性能预测,有助于开发LLM。
- 推理速度优化:采用量化技术,如INT8量化,可以减少LLM在推理阶段的时间和空间成本,并实现更快的推理速度。
- 公开可用的量化模型:已经发布了几个公开可用的语言模型的量化模型副本,可以使用它们来减小模型大小并加速推理。
请注意,这些是总体建议的要点,具体实施方法可能因具体情况而有所不同。