大家好,我是Tim。
自从GPT模型诞生以来,其参数规模就在不停的扩大。但模型并非简单的直接变大,需要在数据、调度、并行计算、算法和机器资源上做相应的改变。
今天就来总结下,什么是大模型,模型变大的难在哪里以及对于CV/NLP或者搜推广场景上有什么应对策略。
大模型,顾名思义主打的就是“大”。主要包括几个方面:
大模型主要分为两类:一个是稀疏大模型,另一个是稠密大模型。
对于搜推广类的稀疏大模型来说,一般包含稀疏特征的嵌入(embedding)和稠密模型两个部分。
其中,稀疏特征的嵌入计算是稀疏大模型的关键,而稠密模型部分一般往往较小,可以放到一个GPU内,因此可以进行data并行以及all reduce通讯。
在训练中,需要在特征嵌入表(embedding table)上需要进行复杂的查找、排列等操作,然后生成张量再做稠密模型的计算。
而特征嵌入表往往会占用非常大的存储空间,需要很多台GPU服务器才能完整存放,这就是典型的tensor并行。
在这样的场景下,就会导致典型的alltoall的通讯模式,而alltoall通讯会带来严重的incast通讯(多打一),进而带来网络拥塞,给网络架构、拥塞控制协议、负载均衡算法等都提出了很高的要求。
对于CV、NLP 任务来说,由于模型参数非常多,远远超过了单个GPU显存所能容纳的空间(NVIDIA最新的A100也就是80GB显存)。
所以往往既需要对模型某一层的tensor并行,也需要不同层之间的pipeline并行,才能放得下整个大模型。
在计算过程中,既有单机内通讯,也有不同机器间的通讯,具体的通讯模式取决于模型的切分和放置方法。
为了加速训练过程,往往完整的大模型之间也会采用data并行,每一个完整的大模型会被投喂不同的训练数据,这就会导致大家熟悉的allreduce通信模式。
总结起来,稠密大模型和稀疏大模型在模型特征上有着明显的差异,对计算/存储/通信资源的需求也存在明显的不同。
要达到GPU算力资源的最大化利用和最好的加速效果,需要结合模型特征和实现方式对GPU服务器架构、网络架构、训练数据存储和拉取、分布式训练框架进行全局的考量和设计。
大模型带来的挑战主要有两点:海量样本、参数(万亿级别)和较长的收敛时间。
别看只有这区区两点,它会衍生出很多要解决的问题。
大模型训练所需的总算力,其实很简单,6 * 模型的参数量 * 训练数据的 token 数就是所有训练数据过一遍所需的算力。这里的 6 就是每个 token 在模型正向传播和反向传播的时候所需的乘法、加法计算次数。
一堆矩阵相乘,简单来想就是左边若干个神经元,右边若干个神经元,组成一个完全二分图。选出其中任意一个左边的神经元 L 和右边的神经元 R。
有了模型训练所需的总算力,除以每个 GPU 的理论算力,再除以 GPU 的有效算力利用比例,就得到了所需的 GPU-hours。
如果训练一个通用大语言模型的基座需要半年或几个月的时间,同时还占用非常多的机器资源,这就使得大模型的训练非常“贵”,使得大模型的训练成为了个别大企业的专用。
例如,如果你有100个实验想试试,而模型训练需要半年,那你只能在其中选择优先级高进行实验。
这也就是为什么现在大家的大语言模型基座都是追踪最新开源的模型。因为自己搞通用大模型基座,一方面搞半天成本上耗不起,另一方面可能还没啥效果。
深度学习训练需要的内存包括模型参数、反向传播的梯度、优化器所用的内存、正向传播的中间状态(activation),显存占用 = 模型参数大小 + Batch Size * 优化器参数与中间变量的大小。
当然也有节省内存资源的办法,例如算力换内存,时间换内存等。
算力换内存的把戏,就是不要存储那么多梯度和每一层的正向传播的中间状态,而是在计算到某一层的时候再临时从头开始重算正向传播的中间状态。
如果每一层都这么干,那么就只要 2 个字节来存这一层的梯度,但是计算中间状态的算力开销会很大。
因此实际中一般是把整个 Transformer 分成若干组,一组有若干层,只保存每组第一层的中间状态,后面的层就从该组第一层开始重新计算,这样就平衡了算力和内存的开销。
时间换内存的把戏,按顺序执行Mini-Batch数据的前向计算梯度,同时对梯度进行累积,累积的结果在最后一个Mini-Batch计算后求平均更新模型变量。
此外,还可以多级缓存,GPU 内存放不下可以换出到 CPU 内存。
例如,对于 LLaMA-2 70B 模型,模型参数需要 140 GB,反向传播的梯度需要 140 GB,优化器的状态(如果用 Adam)需要 840 GB。
对于海量数据样本来说,并不是都喂进去就效果好,哪些数据有价值,哪些没价值。由于数据量的增加,分辨数据的价值也带来很大的困难。
此外,大量的数据存储在哪里,一般可以存储在HDFS或S3。但怎么保证存取能不把机器塞满且能快速调取,对于相同数据的模型多次训练,是否可以通过cache来加速模型训练的时间。
图片
Transformer在训练并行性、推理效率和竞争性能之间很难取得平衡被称为“不可能三角”。
Transformers 由于其固有的每步 O(N) 复杂度和内存限制的键值缓存,在推理过程中表现出次优效率。这种低效率使它们的实际部署变得复杂,特别是对于长序列。
因为这个领域最近几年才开始热门,而之前的框架pytorch、tensorflow等是早就出现的,当时并没有针对大模型的分布式训练的需求场景做深入的抽象设计和优化。
所以这个领域需要通过大数据框架到深度学习框架的端到端打通,形成这样的一套新的编程范式和对应的计算框架来解决掉。
下面我们以CV、NLP场景和搜推广场景进行分别详细说明。
CV和NLP场景:
对CV和NLP场景来说,其特点主要有:
当面对GPT-3这种Dense部分大的模型,Allreduce 单卡内存无法容纳,我们需要采用模型并行(model parallelism)的方式将计算图划分到不同的设备上构建有向无环图(DAG)进行分布式训练,其中Gpipe, Megatron, Oneflow和Whale都提出模型并行的相关解决方案。
相比于数据并行每个worker只有一部分数据,模型并行下每个node使用所有数据。
下面我们简单说明几种模型并行的方法:
对于CTR大模型场景来说,其具有模型小,词表大的特点。
因此,解决CTR大模型的这种稠密参数较大的模型,关键是将Sparse参数由单机存储改造为分布式存储,并主要通过数据并行提高吞吐。
下面我们说明下对训练框架优化点。核心的两点,一个在于分布式通信拓扑的设计,还有一个在于Embedding Lookup的性能优化。
在实现上,可以通过替换TF原生算子进行Sparse参数的读取过程(核心算子是GatherV2算子)。
该算子的作用是从Embedding表中读取ID列表索引对应的Embedding数据并返回,本质上是一个Hash查询的过程;
通过替换该算子,并同时修改上下游节点的Input/Output,将从本地Embedding表中查询,改造为从分布式KV中查询。
可以说,训练大模型,不仅需要充足的计算资源和数据,还需要深厚经验和技能,还需要一定的耐心和定力,就像“炼丹”一样。
每次炼丹师的出手都有着巨大时间和经济成本,如何在最小成本下找到最优解,就是我们一直在探索的。