在初创公司甚至VC都在疯狂囤积GPU的狂热年代,干一件事比囤GPU更有价值——搞明白如何高效地使用它们。

在NLP模型训练中,存在着很多不透明的“隐性知识”,如果你没在谷歌、微软、Meta等头部大厂干过,那你基本不大可能搞清楚其中的门道。


【资料图】

直到Eleuther AI的出现。

四月,Eleuther AI团队发布博文《Transformers Math 101》,介绍如何运用简单算式估计大模型的算力成本,大大消除了该领域的信息不对称,在圈内圈外广泛传播,成为该领域最具权威性的博文之一。公式如下:

C = τT ≈ 6PD

其中:C表示Transformer需要的计算量,单位是FLOP;P表示Transformer模型包含的参数量;D表示训练数据规模,以Token数量为单位;τ表示吞吐量,单位为FLOP 表示训练时间;

该公式的原理如下:

C=Cforward+Cbackward:表示训练过程中的前后向传播; Cforward ≈ 2PD:前向传播计算成本约等于两倍的参数量乘以数据规模;Cbackrward ≈ 4PD:反向传播计算成本约等于四倍的参数量乘以数据规模;

C是一个量化计算成本的单位,通常用FLOP表示,亦可用一些新的单位来表示,如FLOP/s-s:表示每秒浮点运算数/秒;PetaFLOP/s-days:表示实际情况下每秒浮点运算数/天。

8月17日,Eleuther AI首席工程师、《Transformers Math 101》的主要作者Quentin Anthony博士,同风险投资公司Decibel Partners 的合伙人兼首席技术官Alessio、Latent Space主理人Swyx进行了一场深度交流,

双方围绕Transformer模型算力评估、混合精度训练、3D并行、INT8量化、分布式挑战等NLP前沿话题展开了深度讨论,文章干货很足,相信对想了解Transformer成本、大模型优化技术等方面的朋友一定大有裨益。

以下为全文内容,大家enjoy~ ✌️

(由ChatGPT翻译,经略微修改)

目录:

● 发文动机

● GPU并非越多越好

● 估计GPT-3训练的计算量

● AMD GPU:可用,但效率不高

● 模型精度(FP32、FP16、BF16等)对内存的影响

● 深度学习模型量化的好处

● 如何计算优化器的内存使用

● 训练内存的各个组成部分

● 并行训练

● 高级3D并行技术

● 异构集群分布的挑战

01 发文动机

Quentin:

聊到撰写《Transformer Math 101》一文的动机,就不得不提到我经常会在深度学习训练领域看到一些推特帖子,比如Hugging Face的Stas Bekman,他会发布一些推文,说什么他们刚刚发现了一个神奇数字,一切都变得快了20%等等。

他们非常兴奋,但实际上并不真正了解发生了什么。对我们来说,我们经常发现很多人可能了解AI训练或推理的理论或基本原理,但没有人真正了解像在自己的机器上如何正确运行分布在两个GPU上的推理之类的细节。

而我们,在Eleuther内部积累了很多笔记,并在工程师之间共享,我们认为,这可能会帮助很多其他人。

这可能不适合写论文,但对于博客文章或技术报告来说,这可能确实能够从已有的硬件中挤出更多性能。

因此,我们在Eleuther有很多经验,我们试图以一种大公司们不会采取的方式将其与人们分享。

Swyx:

是的,这是很少有人会真正干的事情。首先,写下来需要额外的工作。其次,这是一种商业机密,因此很少有人这样做。

因此,通常了解这些内容的唯一方法就是实际在大型模型实验室中工作。而你们,做了很多特别的事情。我唯一能想到的其他另一个情况是Facebook的OPT。还有什么类似的项目,可以共享交流经验,但不是正式的研究知识呢?

Quentin:

我会说是Bloom。比如Hugging Face的Bloom项目在大科学领域等方面,这种开放程度非常高。

我觉得,OPT和Bloom的级别相同,甚至更详细。

除此之外,我想微软有一个有关他们的Turing NLG的文档。他们的文章读起来非常轻松,也确实谈到了工作中的一些挑战。除了OPT, Bloom和我们,我想不出其他的了。这是一个新事物。

Swyx:

重要的是,你们正追求足够好的经验法则。我认为,很多人试图追求精确,但过于精确实际上并不有帮助。

Quentin:

是的,你会在博客文章中看到一些类似表述。你知道,我们不会进一步详细讨论每个小的内存部分,锱铢必较的话可能需要额外的一个月。但是,得到足够好的结果对人们仍然有帮助。

02 最少GPU数量,是最佳方案

Alessio:

聊回您在《Transformer Math 101》一文中给出的估计方程,这里的核心方程式不是计算成本,而是转化Transformer模型所需的计算,大约等于τ乘以T,其中τ是吞吐量,单位为FLOPT表示训练时间,然后T是花费的时间。

我认为人们可以很容易地想象这一点。基本上是你有多少个GPU,你让它们运行多长时间。

之前在Chinchilla论文和OpenAI的规模定律中,有类似的内容,你在博文中也提到了这一点。

现在,人们在构建模型时应该如何考虑这个问题?当他们开始考虑训练自己的基于Transformer 的模型时,他们应该从哪里找到这个方程?

Quentin:

人们通常从数据集开始,你有一些数据集,然后你想在基于此训练一个模型。

让我们开始逐步思考,一开始,从6PD的角度来看,每个参数大约有6个令牌与之对应。因此,这决定了我的模型大小,以此为Chinchilla Optimal。

从那时起,我们发现为了获得良好的质量模型,需要更多的令牌,可能会超过20个。

但从系统的角度来看,你们应该考虑的下一个问题是,这个模型需要多长时间来训练?我应该有什么样的预算?

假设我需要一些云实例,持续一段时间,每个实例都有一个附加的价格。这就是吞吐量的用武之地。

现在,你有了这个模型,这个参数数量,你应该将它映射到一个Transformer架构,并在该模型类型的软件栈上基准测试你的吞吐量,接着你可以在单个GPU上实现每秒的FLOPS。

然后根据并行性方案(我相信我们会详细讨论的,例如数据并行性或张量并行性等),这个FLOPS数将如何扩展到不同数量的GPU?

从这里,你将得到一个时间。进而,得到了时间,就能算出成本。

这些都是你可以使用这个公式得到的商业答案。这就是为什么我们将其分成了T和吞吐量术语,以便你可以解出其中之一,通常是获得吞吐量、时间,从时间算出成本。

简而言之,这就是答案。

Alessio:

我注意到另一件事,你提到一些这些定律,只在1个小时内使用1000 GPU的成本与1个GPU运行1000小时的成本相同才成立。

考虑目前我们缺乏大量GPU的实际情况,在这方面,对于人们如何优先考虑这一点,你有什么想法吗?

Quentin:

我会说,首先应该找出刚好能容纳你的模型的最小GPU数量。

如果您在训练一个相当大的模型,内存瓶颈是你最大的问题。如果只在训练一个小模型,那没人会在意。

大多数人关心的模型,都需要分配到多个GPU上。因此,找到适合你的模型的一个实例的最小gpu数量,然后计算需要多长时间。如果是时间合理,那么你完成了。如果时间太长,那么你就需要开始考虑使用多个模型实例。

我总是觉得,人们应该选择最小数量的GPU。你拥有的GPU数量越多,出现问题的可能性就越大。

所以我会说,只要找出什么时间对你来说是合理的,然后将gpu的数量与之匹配,而不需更多。

人们往往会变得贪婪,他们会说,如果我有两倍的GPU,我可以在半个时间内完成这项工作。但事实上,最终可能花了三倍时间,因为每天都会出现各种问题。那时,可能需要痛苦地在午夜时分起床,加班加点抢修你的模型。

Swyx:

你们会不会有一个类似的开源框架,来帮助gpu线性扩展?还是说,这么想过于简单化了?

Quentin:

Mosaic和我们都有一种可以理论上良好扩展的软件堆栈配置,一会我会详细讨论。

Mosaic完全基于优化器分片。它基于ZeRO。因此,你完全将模型优化器、参数和梯度分布在所有不同的GPU上。你的聚合内存就是参数数目除以GPU数目。优化器和其他内容也是一样。

而我们在Eleuther使用了Megatron DeepSpeed的库,会更加复杂。因此,效率可能会更高,但也更容易出现故障。

在这两种情况下,回到实际情况,通过增加更多的GPU应该能够获得线性加速。但问题是会出现硬件故障。

如果有太多的GPU,你可能会有问题,例如损失会溢出,或者一个GPU挂掉,你就可能碰到软件问题、同步问题等。

这就是我为什么说,实际上你应该选取你拥有的最小GPU数量,因为这些情况更容易调试。

Alessio:

另一件事情是,电脑就像在前向和后向传递,前向是2PD,后向是4PD。为什么两者之间的比率是如此?你能解释一下吗?为什么是两倍的数量?

Quentin:

简单来说,对于前向传播,您只是在移动,您正在通过该层向前传播输入。

而在后向传播中,你要做的事情比这更复杂一些。你正在做反向传播。我认为我无法足够直观地解释它,需要数字上进一步深入探讨。

Swyx:

实际上,反向传播的速度很低。老实说,这是我喜欢深度学习数学的最基本的原因之一,与大学微积分中遇到的其他数值方法相比,深度学习的数学令人惊讶地高效。

Alessio:

我认为另一件事是,事情听起来很简单,你知道,当人们在Twitter上说,哦,20是最优的比率,然后就是,好吧,为什么是那个数字呢?

答案通常要困难得多,就像我们现在看到的。所以我认为这是一个很好的提醒,数字听起来简单,就像所有最好和最受欢迎的数学方程一样,非常优雅。不过,显然,背后的证明并不那么容易。

02 估计GPT-3训练的计算量

Swyx:

我想稍微测试一下这个方程。我们可以从GPT-3的角度或GPT-NeoX的角度来做。你有实际浮点运算和理论浮点运算之间的区别。

很多时候,当人们报告训练一个模型所需的浮点运算量时,就像我们刚刚在Lama 2中看到的那样,估算的数值就是浮点运算量。

比如,GPT-3需要3.14 x 10的23次浮点运算。这是理论浮点运算。我想弄清楚一个数值是否经得起推敲。

我想知道如何做到这一点,因为我应该能够将这个方程代入其中,对吧?我知道GPT-3是基于3000亿tokens进行训练的。

我知道模型的参数大小是175。是不是只要计算一下6 x 175 x 300就行了?

Quentin:

理论浮点运算通常是根据给定的硬件配置得出的,这是你预计硬件可以实现的。

但问题在于,实际上,我们通常会有一些闲置时间,例如等待从GPU到CPU的数据传输,或者等待跨不同GPU之间的同步。所以训练时候你会花掉很多空闲时间。

Swyx:

闻起来怎么样。(注:效果如何?)

Quentin:

我不确定我是否有一个自己的"闻起来怎么样"的标准,

坦白说,也许我会查看像在A100上期望的一些浮点运算量。

事实上,在某种程度上,对于给定的GPU类型,每个人都知道可以期望多少浮点运算量。

例如,对于A100来说,这个数值大约在100到180之间。对于较旧的GPU类型V100,这个数值可能在30到40之间。

人们通常会根据运行深度学习的内核来确定应该期望的浮点运算量。然后,将这个与人们报告的理论浮点运算量进行比较,看是否与你的预期相符。

03 A100 基线标准:115+ TeraFLOPS/sec

Alessio:

在文章中,你提到对于A100来说,如果你的浮点运算量低于115 TeraFLOPS/秒,那么你的模型或硬件有问题。

你是怎么得出这个115的?是不是根据实际观察和经验,你在过去的几个月里见到的平均水平?你是怎么得出这些数字的?

Quentin:

对于这个数字,基本上,我们比较了很多不同的框架。就像我之前提到的,Mosaic有他们自己的框架,我们也有自己的框架。它们都有自己的浮点运算计数器。

我们看到,在许多不同的硬件配置上,如果你正确调整设置,你应该在几乎所有情况下都能得到超过115的浮点运算量。

所以,如果某个数值低于115,那么你的软件肯定有问题。但这实际上就是比较不同的软件堆栈和硬件系统。

04 AMD GPU:可用,但效率不高

Alessio:

不同的GPU有什么不同吗?

上周,我们邀请了George Hotz,他谈到了AMD显卡,以及理论上他们的浮点运算要比一些Nvidia显卡好得多,但实际上,CUDA运行时会弥补这一差距。

人们应该如何考虑这一点?你知道,像A100的浮点运算量达到115 TeraFLOPS。我是否应该坚持使用它?

Quentin:

这涉及到开发者的时间,哪个更昂贵,归根结底,AMD和Rockham的软件堆栈还有很长的路要走。

我会说,大多数东西都可以在那里运行,但效率不高,而且你可能会遇到以前没有遇到过的奇怪错误。

选择使用Nvidia和PyTorch的软件堆栈的一个重要优点是,有成千上万个GitHub问题与你面临的相同问题,而且,以开源方式快速解决这些问题,这可能是目前选择Nvidia软件堆栈的最大优势。

AMD的硬件与Nvidia相当,但软件不太一样,而且他们在开源领域的势头还没有完全发展起来。

例如,像Flash Attention这样的功能在更多Nvidia GPU类型上的应用要比在AMD上的应用要多。等待这些最新的功能在AMD上实现,对很多人来说是一个障碍,但它正在发展壮大。

我现在正在AMD上运行很多实验,因为它已经进入了政府实验室的超级计算机。所以很多实验现在都在那里进行,而且我预计它会在几年内赶上来。

05 模型精度(FP32、FP16、BF16等)对内存的影响

(注:一般情况下,计算机在进行浮点运算时所采用的是FP32(单精度),其中8位用于存储整数部分,23位存储小数部分,因此其可以存储高精度浮点数。

因此,在显存优化场景下,牺牲浮点运算的精度可以降低存储量。)

Alessio:

让我们稍微谈谈内存需求。所以你之前稍微提到过这个问题,在这之前,我们在播客上也谈到了FlashAttention,内存速度是我们的主要关注点之一,但这次我们受制于实际的内存大小,就是VRAM本身,特别是在模型权重、参数、优化器状态等方面。

我们可以轮流讨论一下,有很多内容需要涵盖,但或许我们可以从模型权重开始。

过去,我们在过去的许多讨论中经常涉及的一个主题就是精度和量化,这显然是内存的主要因素之一。

你在文章中提到,大多数Transformer都是混合精度,如FP16加FP32或BF16 FP32,它们可以进行转换,而且可以进行高达INT8的量化,而不会对性能造成太大的影响。也许,我们可以解释一下一些数学和不同精度之间的字节参数比率?

Quentin:

当我开始入行深度学习时,一切都是FP32。每个参数需要32位,即4个字节。事情相当简单。你不需要进行任何损失缩放。

但问题是,一旦NVIDIA切换到了V100并引入了Tensor核心,FP32就不能提供太多的浮点运算。Tensor核心在FP16精度下执行所有计算。

因此,如果你在FP32下进行操作,你实际上浪费了所有这些。所以一旦硬件迁移到V100,软件就会切换到混合精度,如APEX和AMP等。混合精度的一个让人意想不到的部分是,实际上在训练时你需要更多的内存,因为你需要一个FP16版本的权重副本和一个FP32版本的权重副本。

FP16是在Tensor核心上进行实际计算的地方。因此,你可能得到的吞吐量可能是FP32的两倍。然后在每个步骤中,使用FP16更新FP32的副本,两者都需要存储在内存中。

问题在于,FP16非常精确,但动态范围不太大,

因此,如果从浮点的角度来看,你有一个非常大的尾数,但没有太多的指数。所以BF16将更多的位数从尾数移到指数中。也就是说,你有更大的范围和较低的精度。

这消除了许多不稳定性问题和损失缩放等问题,对于任何熟悉调试的人来说,都知道它有多么不稳定,尤其是在大规模训练中。而BF16则减少了很多这种问题,但只在A100上受支持。所以你看到了硬件和软件之间的来回变化。

每次NVIDIA引入一些新的Tensor核心或BF16支持之类的功能,软件就会适应并开始支持它,然后训练适应。然后你现在提到了Ind8等。

现在,我们看到一些模型已经在FP16、FP32或其他模式下训练,然后现在你想将该模型尽可能地量化为像Ind8这样的更小的表示,同时又保持最小的损失和精度。

实际上,我们发现,由于深度学习是一个如此具有随机性的问题,许多这些最后的精度位实际上并不重要,我认为这种情况将会持续下去。

06 深度学习模型量化的好处

Alessio:

具体一点,当你有一个FP32时,在推理时每个参数需要四个字节的内存来加载它。

如果你有一个量化成八位的模型,你只需要一个字节来表示每个参数。例如,在一个80GB VRAM的A100上,你可以放下700亿个参数的八位模型,而你无法放下一个FP32模型,因为你需要大约280GB的内存。这会有多大影响?

你刚刚提到刚开始时都是FP32,如果有一个带有1TB VRAM的GPU,人们会只将内存加载为FP32权重吗,还是他们仍然会希望将它们量化以使其更加高效?是的。

Quentin:

我认为,即使你有无限的VRAM,你仍然会想要一个量化模型,只是一个更大的经过量化的模型。

因为正如我刚刚提到的,在最后,深度学习是非常随机的,很多时候,你可能拥有世界上所有的精度,但是当你仍然非常依赖输入时,这些精度实际上是没有意义的。你在很大程度上依赖于输入的微小变化,也许更多的训练数据样本会更加重要。

在深度学习中,总体上,这些微小的精度并不是很重要的,重要的是整体的画面。那个神经元实际上在说什么?而不是它可能在想什么微小的细节。我还想提到,即使你拥有A100,实际模型的大小要比你提到的要小得多。

这是因为KV缓存。所以KV缓存在推理过程中,只在推理过程中起作用,想象一下,如果你在写一段话,你想要在写下一个词之前记住之前写过的每一个单词。

所以什么是自回归语言建模?它就是填充下一个词、下一个标记。如果我说"the dog went to the",然后我需要写下一个词,我可能会写"park"之类的。在我写下一个词之前,我的记忆会被清除,然后我必须重新阅读整个句子。

这就是没有KV缓存的情况。KV缓存则是说,在推理过程中,记住我之前生成的所有内容,以及我之前生成内容的所有上下文。但是KV缓存的内存开销通常与模型的内存开销相当,或在某些情况下甚至更大,特别是在上下文很长的情况下。

我认为具体的计算公式大致是,哦,是类似于两倍的层数乘以头数乘以每个头的维度,然后有两个这样的部分,一个是K,一个是V。简要来说,大概就是这样。

Alessio:

我知道,这是Transformer中的数学概念,不过,关于RNNs,也有一些有趣的地方,比如远离随着序列长度增加而增加的KV缓存,而是使用固定的序列传递。

我知道,人们正在研究这些方面的内容。

Quentin:

我参与过的一篇论文叫做RWKV,我建议人们阅读。它回答了这个确切的问题。

那么,如何在不增加Transformer所需的二次注意力开销的情况下获得Transformer质量?这是有趣的,我建议人们阅读这篇论文原文。

Swyx:

我们实际上已经和RWKV的核心成员进行了一次未发布的访谈,他们称之为软注意力或轻量级注意力。我忘记他们具体叫什么了,但是可以近似地表示,使其线性化而不是二次方的方法。这很棒。

Quentin:

RWKV的人员经常出现在Eleuther。

他们与我们密切合作。我的贡献是,我们所有这些关于RNNs的实验是由不同的人完成的,这些实验与Transformer的关系以及我们如何将其转化为一篇论文,以便人们不必阅读一年前的Discord日志就能理解发生了什么。

07 如何计算优化器的内存使用

Swyx:

很棒,现在,我想我们可能要花更多时间在优化器状态和Atom优化器上。当你处理这些优化器时,你的想法是什么?在处理这些优化器时,人们应该记住什么?

Quentin:

我认为,Atom优化器在其领域内是出色的。这是一个比较宽泛的问题,所以让我想一想。你有权重的副本,然后你有你的动量和方差。

用直观的预言来解释一下动量:比如说,你在一个峡谷,你想要到达底部。

如果你只是进行基本的随机梯度下降,那么每一步都会是相同大小的。而如果你使用带有动量项的Atom,那么你的步幅应该会逐渐变大,因为你可以看到,一般的趋势是我们正在迅速地向下前进。但是,由于Atom中有所有这些额外的项,你需要更多的内存来存储它。

例如,与SGD相比,通常需要三倍的内存。如果你在你的优化器状态中花费了所有这些内存,那么,你如何将它分布到GPU上?

你会发现,在给定的GPU上,实际上比起裸计算、原始的计算能力,你更多地受限于并行性。这归结为在将模型分割成需要在许多GPU上分割之前,你能够将多少模型放在单个GPU上。

然后,你会发现花费的时间更多地用于它们相互通信,而不是实际取得进展。所以,我们在博文中花费了很多时间来讨论如何分布你的模型?所有这些不同的分布策略是什么样的?哪些策略更有效?考虑到很多内存都花费在了优化器上,你如何有效地分布这个优化器?

很多人在谈论并行性时,他们谈论的是模型并行性,即参数本身。实际上,在训练时,相当大一部分的内存实际上是花费在优化器状态上的。所以你想要深入探讨其中的哪个部分吗?你想要深入探讨零、分片优化器之类的吗?

Alessio:

Quentin,你在博文中提到"Atom是神奇的",实际上,即使对于像你这样与底层很接近的人,有多少内容是实际上的"神奇",有一些东西对你来说只是教义?有一些事情是你实际上在考虑在日常工作中进行改进的吗?

Quentin:

所以我是一个系统工程师,很多这些事情对我来说是神奇的,Atom对我来说是神奇的。

我认为,这就是一个深度学习模型的训练方式。这就是下一步的计算方式。然后我说,好的,如何使其变得更快?

我会说我会看看如何通过使用第二阶优化器来改进它。因为有很多关于这方面的研究,因为它们很难分布。

但是对我来说,核心的贡献总是来自于其他人已经进行了一些深度学习的优化,我需要使其运行得更快。所以我实际上无法谈论为什么Atom产生了,除非像我刚刚提到的,使用动量的一些简单直观的东西。

对我来说,Atom所占用的内存比SGD要多三倍,这一点很重要。所有这些内存都需要找个地方存放,它需要有效地分布。

Alessio:

所以,当你把所有这些加在一起时,使用普通的Adam优化器,每个参数需要12字节。

然后,你仍然需要在内存中保存模型参数,就像你提到的那样,需要同时保存FB32、FB16混合量化的两份副本,因此有不同的精度级别。所以每个参数需要6字节,对吧?

Quentin:

再回顾一下,大多数人认为模型会变得很大,所以需要纯粹的模型并行处理,比如张量并行处理。

但实际上,如果我们使用FB16,模型每个参数只需要2字节。而优化器本身需要每个参数4字节的模型状态、4字节的动量、4字节的方差。所以更重要的是如何高效地分割优化器以及如何高效地存储它。

像"bits and bytes"中的8位Adam优化器,每个参数的优化器状态只需要1字节,而不是4字节之类的。这会比将纯FB16模型权重量化到int8之类的方式,更有效地降低模型训练所需的内存开销。

因此,对于训练来说,优化器内存占用非常重要,在大多数情况下最为重要。

Alessio:

在我们深入讨论Zero之前,让我们先总结一下稍后会对参数、优化器状态和梯度进行分片处理。可以简要谈一下这个问题,然后我们可以讨论如何在GPU上高效地加载它们。

Quentin:

所谓的参数,是参数的FP32副本。

我们在优化器讨论中将它们包括在内。有些人不这样做,但只是为了清楚起见,优化器状态每个参数需要12字节,其中有四个字节是用于FP32的权重副本。

四个字节是用于动量。我已经解释过为什么存储动量很重要,但这也是每个参数。你需要存储该参数未来的前进方向以及它在过去的前进方向。

你还需要知道,我们知道它往哪走,但是在这个峡谷上会有凹陷。

所以我们需要存储它的方差。这些凹陷的频率是多少?我们应该更关注动量吗?还是这个参数在到处跳来跳去?这些都是优化器需要存储的重要答案,它是每个参数的。

所以这三个术语都是针对每个参数的。我们还包括了一些竞争性的位和字节,例如SGD,以显示根据优化器的不同,你可以存储所有或不存储这些,并以不同的表示方式存储。

08 训练内存的各个组成部分(模型内存、优化器内存、梯度内存、激活内存)

Alessio:

我正在查看总训练内存。您实际上有模型内存、优化器内存、梯度内存和激活内存。我认为,这是我们讨论的最后一个问题。

所以也许简要地向人们介绍一下,,比方说活动内存再计算、检查点等。

Quentin:

在激活检查点之前,可能会变得复杂,你有模型参数,就像我之前提到的一样,它们曾经是FP32。现在可能是BF16,或者如果是较旧的GPU,则可能是FP16。然后有优化器。那是许多内存所在的地方。

通常情况下,这是权重的高精度副本,通常是FP32。所以每个参数需要4字节,然后你还有一些可选项,就像我们刚刚讨论过的,比如动量或方差,或者根据你的优化器而定的其他内容。

接着是梯度,梯度是在模型前向传递后得到的梯度更新。这将是你低精度权重的副本,如果你使用FP16或BF16,每个参数需要两个字节。

所有这些都是固定的。这个开销在训练期间不会消失。在反向传播梯度后,梯度可能会被清除,但优化器状态和模型状态不会消失。

内存开销将一直存在。动态的是激活再计算和激活内存。因此,有些人会遇到这样的问题,模型在训练时加载得很好。但当你实际运行第一次迭代,或者运行某个未来的迭代之类的时候,你会突然发现内存不足。这是因为你实时计算的这些激活。

Alessio:

再计算是在什么时候发生的?它是如何在重新计算和存储之间做出决策的?

Quentin:

有很多不同的方法,但我会说有几种主要的方法。

首先,是一个非常简单的方案。你重新计算所有的东西。你计算出的每一个激活都会被使用或抛弃,直到结束。所以在这种情况下,你非常关心内存,但对计算关心不大。也许这会是一种情况,你必须在许多不同的GPU上分布,而且你的通信速度非常低。

然后可能是选择性再计算。在选择性再计算中,Megatron有一篇很好的论文,我认为我们博客文章中的图表也是从那里来的。在这种情况下,你为每个激活做出加权决策。

对于非常大的激活张量,你决定,从内存角度来看,是将其保存更昂贵,还是从计算角度来看,重新计算更昂贵?这是Megatron实施的智能方案。

他们使用了许多不同的启发式方法。在这里可能没有必要提到超长的方程式,但如果你对选择性再计算感兴趣,你应该去阅读那篇论文。然后大多数人使用的是一个相当愚蠢的方案,包括NeoX,就是不使用所有这些启发式方法,而是只是说,如果我的张量大于X,我就抛弃它。然后将X设置为某个固定的数字,就是这样。

对于许多情况来说,这足够了。

Swyx:

为什么足够呢?

Quentin:

你不希望存储超过X大小的张量。有些超过了这个限制,有些没有。你不想挤压。

你更关心的是让某些张量尽可能地接近实际启发式方法,而不是实际计算启发式代码,因为你不想花时间编写那些启发式代码。

Swyx:

好的。我想这确实为我们带来了内存数学的一次大观。在我们进入分布式处理、Zero等细节之前,还有什么高层次的要点吗?

Quentin:

我想大概就是这样了。

我要说的是,对于训练和推断来说,情况会大不相同。人们常常说,BF16是最好的。然而,更正确的看法是,在训练期间,精度会更加重要。

因此,在训练中,BF16会比在推断中使用更久一些,因为在推断中,模型已经基本成型,它绝对不需要一些精度的最后几位,因此,对于推断来说,更容易将精度降至int8,而不是训练。

所以,你在训练中学到的所有东西都必须在推断中重新学习,反之亦然。

Swyx:

还有第三类情况。你谈到了训练与推断之间的区别。这第三类情况与微调和可能的参数高效微调方法有关。

实现微调的朴素方法只是进行更多的训练。但我不知道你是否对微调有什么直觉,是否值得在这里插入。有什么直觉吗?如果你要编写微调的数学公式,会包含什么内容?

Quentin:

我认为,关于微调还有很多问题没有得到解答。

例如,我们知道训练的缩放规律。有些人已经做了关于微调的缩放规律。但已经在一个领域训练过的模型如何转移到另一个领域进行微调,就微调大小而言,我们不清楚。

对于微调数据集,每个参数应该有多少个标记?也许我是无知的,但我觉得关于模型如何在原始训练数据集中没有的新能力进行微调或理解的问题,肯定会包含在未来关于微调的博客文章中。

此外,数据集转移、学习率转移也是我们博客感兴趣的内容之一。

09 并行训练

Alessio:

您在这里首先提到的是ZeRO,它专注于分片优化器。也许可以给人们解释一下如何理解它。

Quentin:

ZeRO围绕两个通信操作展开。第一个是scatter。人们应该看一下我们有的ZeRO图表。

在我谈论这个问题时,论文中有一个包含参数、梯度和优化器状态的图表。每个GPU将获得自己等量的切片。

如果我们进行...有ZeRO的不同阶段,但让我们首先假设它是优化器状态、梯度和参数的等量切片。

在这种情况下,那将是零三,第三阶段。我们通过scatter实现这一点。scatter的计算是,每个GPU分配1/结束的GPU数量,加上切片的偏移量,切片被分配给该GPU。现在,所有的GPU都有一个相等的切片,按照其排名顺序。

在每个训练步骤中,该GPU将等待所有其他切片进行通信,以便我们现在在该GPU上拥有整个数据。一旦我们有了整个数据,我们对其进行正向传递。

然后,我们使用gather将该正向传递分发给所有其他GPU。所以,它是scatter,具体来说是减少scatter,然后是对所有其他GPU的gather。并且您每一步都这样做。因此,它的要点是您正在将这些状态分片到不同的GPU上。

通过不同的阶段,您将在图表中看到,优化器状态占据了最大的比例,这是因为我之前提到的。我们包括FP32副本并进行原子操作。所以我们需要每个参数的4个字节,用于动量和方差。然后,零阶段一,这是最常见的一个,仅为优化器。

零阶段二是优化器加梯度。零阶段三是优化器梯度和模型参数。但归根结底,所有这些都是围绕着分片和来回聚合展开的。所以您从ZeRO中得到了很多通信开销。但其中的正面部分是您可以将其中大部分的移动与计算重叠。

Alessio:

如何确定使用多少个GPU进行这种分布是最佳的?分片过多是否会造成太多的开销?

Quentin:

这更多地取决于您的互连是什么。放慢一步,所有这些GPU之间都需要同步,而且这些同步往往是累积的。

因此,如果您在速度较慢的互连上使用过多的GPU,那么您最终会花更多的时间在同步上。在哪个GPU上花费更多时间同步的这个魔法数字会因您的互连和GPU内存的情况而异。每个GPU获得多小的切片呢?

例如,对于Summit,我不能给出确切数字,但大约在200亿参数左右。现在您有200亿个参数,那么对于那个数量的GPU,大约需要100到200个规模。

超过这个数量,您最终只会花费更多的时间在通信上。实际的FLOPS跌破您预先设定的某个数值,将取决于您最终确定的最佳选择。

10 高级3D并行技术(数据、张量、流水线)

Alessio:

那么,对我来说,这部分内容比较难理解,所以我很高兴您能解释一下3D并行。

Quentin:

好的,首先,让我们来看一个非常基本的维度,即数据并行。

数据并行是您有一个模型的副本。为简单起见,假设一个副本恰好适合一个GPU。数据并行是,现在您有两个GPU,所以GPU一上有一个副本,GPU二上有一个副本。两者都执行正向和反向传递,然后进行同步和平均梯度。然后这就是一个步骤。对于3D并行来说,数据并行实际上是零。所以,您正在将优化器状态分片到所有不同的GPU上。

接下来,是张量并行。张量并行是将模型分割。例如,如果您有两个GPU,将模型在中间分开,每个GPU上的张量将在其上进行前向或后向操作。然后,仅在必要时,它会将该张量操作与另一个GPU同步。这比一些像流水线并行这样的维度要复杂一些。

而在流水线并行中,假设您的模型有四个层次,有四个GPU。您将一层放在每个GPU上,然后GPU一执行前向传递,然后将其激活的输出发送到GPU二。它执行前向传递,将激活发送到三,您只是在沿着一条线移动。那是一个朴素的方案,所有其他GPU在单个GPU执行其前向或后向传递时都处于空闲状态。所以它被称为流水线并行,因为您将小批次分成了微小批次。

因此,GPU一将在微小批次一上执行前向传递,然后发送到GPU二。然后,在GPU二在第一个微小批次上运行时,GPU一正在下一个微小批次上工作。因此,您在每个微小批次的移动和计算之间形成了流水线。

但问题在于,您需要一个非常大的批次大小,以将其分成小批次和微小批次。因此,将这三者结合起来,您将得到一个3D网格,其中每个参数、优化器状态等都映射到每个GPU上。这就是3D并行。

Alessio:

进行此操作时是否需要所有GPU都相同?还是也可以使用不匹配的GPU?

Quentin:

有两件事情很重要。如果两种不同类型的GPU之间的VRAM有差异,那么您将受到VRAM较少的GPU的限制,因为它会耗尽内存。然后您不能使用较大的GPU上剩余的部分。

据我所知,没有像GPU单个GPU感知的内存开销方案来解决这个问题。第二个问题是,假设所有GPU的VRAM都相同,但其中一半速度很慢。

问题在于,我之前提到的同步操作会让您受限。因此,在这种情况下,您的移动速度将与您最慢或最小的GPU相同。

在这两种情况下,您最终都会回归到最慢或最小的GPU。因此,最好使用相同的GPU。同样适用于CPU和互连。回到Eleuther训练的200亿参数模型,那是在COVID期间,由于网络交换机等供应短缺,他们构建的一个Frankenstein式的集群。

因此,每个节点都有一个不同的网络交换机。所以,您最终会以最慢交换机的速度运行,并且调整一切,使其不比最慢交换机差,这是一个现实世界中可能会遇到的问题。

11 异构集群分布的挑战

Alessio:

这项工作是否被广泛接受?在为这一集进行研究之前,我还不知道这一点。这是否仍然是人们正在尝试和研究的领域?还是每个人都对此有所了解,并在实际中运行?

就是分片优化器和3D并行,将两者结合在一起,采用这种网格策略。

Quentin:

我会说,很多主要的基于GPT的模型都使用了这种方案。现在很多人更倾向于纯粹的ZeRO方案。所以就是纯粹的分片。

您只需对所有内容进行分片。因为这样非常容易,每个人都可以得到相等的切片。不再有流水线阶段的问题。不再有哪个张量应该放在哪个GPU上的问题。

相反,我们平均分片,平等对待一切。这个问题比使用3D并行方案更容易调试、检查点,以及进行训练。

我认为,3D并行给您最多的控制权,也是犯错的最多方式。具体是选择哪种取决于您是拥有更多工程师还是更多GPU。

Swyx:

这也不算太难,对吧?

您基本上已经概述了需要记住的五六个不同的数字。如果需要达到这种控制水平,您已经为每个人提供了主要的控制杠杆。这很好。绝对的。

Quentin:

问题在于,比如说,好吧,GPT-4发布了。现在我们有了VLLMs。

Swyx:

Virtual LLMs,就像Metro of Expert的东西吗?不,是视觉的。

Quentin:

所以现在我们有了多模态模型之类的东西。我们如何进行分布?我们将其分配到流水线阶段吗?我们只是将其分片吗?将张量分割并进行张量并行吗?

当您具有这种3D并行方案时,要更改模型并添加新功能变得有点困难。我说困难,我的意思是将其调整和修改为适应新功能会变得困难。

Alessio:

您还在这个领域进行着其他研究吗?您想要提一下吗?我知道通常我们会问的问题是,AI领域最有趣的未解决问题是什么?

我很好奇,您是否仍然认为这些问题集中在训练、推理、数学优化上,还是还有其他领域需要大家关注?

Quentin:

我认为在我研究的领域里,有两个问题是我认为人们应该非常关心的。

首先是多模态并行和RLHF。我们越来越多地看到强化学习融入到训练循环中。

因此,如何将某些模型或某些GPU用于推理,而某些GPU用于训练?就像我之前提到的,你需要重新学习所有内容,而且它们具有非常独特的挑战。

例如,在训练期间如何分割一个KV缓存?这些都是尚未得到充分研究的挑战。

在多模态方面,你可能有一个视觉变换器和一个文本变换器。如何分割它们?你是否平均分配?将它们放在不同的GPU上,还是只是将一些视觉、一些文本参数放在一个GPU上?

然后,第二个问题是通信往往是一个瓶颈。所以我们谈论了3D并行,但很多情况下,比如张量并行,你不能跨节点使用。你会在通信中受到限制。

我要说的是,在通信发生之前,你应该如何对通信进行压缩?所以在传递时压缩,你有一些需要传递的缓冲区。你可以用GPU内核进行压缩,然后将其发送到网络,再进行解压缩,类似于这样。

让人们在通信基础设施上花费更少的金钱,而更多地用于GPU,这也是人们需要探索的领域。

关键词: