引言
本文意在对Pytorch的分布式框架和数据处理流程进行调研,方便之后对AI训练框架对接分布式数据库做准备。主要是为了应对AI任务训练数据量的逐渐增大,以及数据分散无法集中处理的情况。目前也在研究中。
Pytorch分布式设想
在研究一个方案之前,首先我设想了Pytorch分布式训练架构和分布式数据库可能的对接方式。如下图所示:
整个架构以数据集作为交互点,这里的数据集是输入训练模型的数据,也就是在对原始数据进行了预处理过后多维数据集合。以这个数据集对整个架构进行划分:上面部分是分布式训练架构,下面部分是分布式数据库。笔者准备至上而下的对整个流程进行相关的调研
数据集
什么是数据集
对于输入计算网络的数据集,其实本身就是一组{x:y}的集合,类似图像分类任务:图像+分类;
目标检测任务:图像+bbox、分类;
超分辨率任务;低分辨率图像、超分辨率图像
文本分类任务:文本、分类
通过这样的数据集对和下面的模型,就能够进行训练。
深度学习计算过程
有很多博客对神经网络的前向计算和后向传播都有很好的阐述,这里就不再赘述放两个图片吧。
前向传播
后向传播
Pytorch分布式训练框架
方案一:nn.DataParallel(仅仅适用于单机多卡的单机分布式,单进程多线程)
特点:模型复制到每个GPU一份,将数据进行切分,每个GPU对它分配的数据进行前向计算。将梯度输出到主设备上进行求和,再广播到每个GPU上进行参数更新。它使用的通信方式并不依赖于任何通信后端,而是自己简单的实现了GPU与GPU之间的点对点通信。
缺点:主设备(GPU)压力会很大,无法实现真正的分布式。每个线程之间需要竞争GIL(全局解释器锁)
总结:在分布式训练这方面nn.DataParallel已经不是最优选项了。
方案二:torch.distributed(分布式软件包)
在讨论torch.distributed的之前首先需要了解GPU的通信机理。本文不讨论它的具体实现原理,这里只列举出技术方案,供读者讨论。
GPU的两种通信方式:
点对点通信(Point-to-Point Communication):点对点通信是指在GPU计算中,两个具体的GPU之间进行直接的数据交换和通信。在点对点通信中,一个GPU可以将数据发送到另一个GPU,并且接收方GPU可以接收和使用发送方GPU发送的数据。点对点通信的常用方法包括:
GPU内存拷贝:通过将数据从一个GPU的内存复制到另一个GPU的内存来实现点对点通信。这通常涉及在主机内存和GPU内存之间进行数据传输,并在目标GPU上加载和使用数据。
GPU直接内存访问(GPUDirect Memory Access):GPUDirect是一种技术,允许两个GPU之间直接进行内存传输,而无需通过主机内存。它通过绕过CPU,直接在GPU之间进行数据传输,减少了数据拷贝的次数和延迟,提高了通信性能。
集中交流(Collective Communication):集合通信是指在多个GPU之间进行协作和交换数据的通信模式。在集合通信中,多个GPU之间共同协作来完成某些任务,例如聚合、广播、规约等。这些操作通常需要在所有GPU上进行同步和数据交换,以便实现一致的计算结果。集合通信的常用方法包括:
全局同步:在集合通信中,各个GPU之间进行同步,确保所有GPU上的计算都达到了同一步骤,并准备好进行数据交换或协作。
全局规约(All-Reduce):全局规约是一种集合通信操作,它在多个GPU之间进行数据聚合和计算,得到一个全局的结果。例如,将多个GPU上的梯度进行求和,以更新模型的参数。
广播(Broadcast):广播是一种集合通信操作,其中一个GPU将数据发送给其他所有的GPU,以便共享相同的数据。这对于将模型参数从一个GPU复制到其他所有GPU非常有用。
这些通信方式肯定不需要逐个去实现,而是使用现成的分布式后端。当然,分布式后端不仅仅提供GPU之间的通信实现,还应该有类似分布式计算框架、分布式存储等相关功能。感兴趣的化可以自己去了解,pytorch支持三种分布式后端(NCCL,GLOO,MPI)。他们的各自的应用场景详情可以看pytorch的官方文档:
链接: link
总结来说:
NCCL实现了GPU上CUDA Tensor的集中交流,效率是最高的,数据仅涉及Tensor的话优先使用
gloo实现了所有的CPU通讯方式,和GPU上的所有集中交流操作
Mpi,支持CPU上的所有通讯方式,不过Pytorch并不直接包含。如果要使用需要单独编译,安装。
pytorch将这三种分布式后端的接口封装到c10d库中。
C10d库不仅提供显卡之间的通信,还提供显卡上进程组的创建和数据分发的功能。
torch.distributed(分布式软件包)
torch.distributed分布式软件包主要包括三个主要组件,C10d库,nn.parallel.DistributedDataParallel(分布式数据并行库),以及torch.distributed.rpc(RPC远程调用)
C10D库
c10d库中封装了之前提到的各个分布式后端的接口,还提供进程组管理的相关接口
nn.parallel.DistributedDataParallel(分布式数据并行)
特点:支持模型并行,将模型分块,解决模型过大的问题。通过多进程的方式,不需要竞争GIL达到真正的并行。它包装了底层的分布式通信细节,在前向传播和后向传播之前的时间点通过进程通信,同步所有进程中的模型。
torch.distributed.rpc
许多训练范式不适合数据并行,例如参数服务器范式、分布式管道并行、具有多个观察者或代理的强化学习应用程序等。torch.distributed.rpc旨在 支持一般的分布式训练场景。这一部分我也只是看了一下Pytroch官方文档,可能是碍于应用场景的原因使用的不多。
分布式训练流程
-
进程组创建和进程分配 :将模型加载到主进程,其他进程再从主进程同步,保证所有进程的初始模型同步。
-
分布式数据分配(DistributedSampler):通过DistributedSampler将预处理好的数据分配到每个GPU上,能自定义数据集划分方式,以及采样顺序(pytorch中的Sampler后面有空我会出文章详细分析)。
-
模型的计算、参数更新
每个GPU上都会进行相应的前向计算、计算与真实值的差距、进行后向计算。计算出的所有梯度会求平均值,通过GPU间的通信,保证整个模型同步更新。
模型的数据准备
大致了解了上层的分布式训练框架是怎么样过后,接下来数据的准备成为了整个流程的核心问题。当然,如何通过分布式数据库的方法实现多个数据源的数据预处理仍然是我目前需要去找寻方向的问题。这里只是简单总结一下pytorch的数据处理流程,方便以后进一步探索。
数据的预处理
数据清洗(Data Cleaning):去除数据中的噪声、错误或重复的样本,填补缺失值,处理异常值等。这些操作有助于提高数据的质量和可靠性。
数据标准化(Data Standardization):对数据进行标准化处理,使其具有零均值和单位方差,以消除不同特征之间的量纲差异。常见的标准化方法包括均值方差归一化、最大最小值归一化等。
特征缩放(Feature Scaling):对数据特征进行缩放,将其映射到特定范围或固定的值域。常见的特征缩放方法包括线性缩放、对数变换、正态化等。
数据增强(Data Augmentation):通过对原始数据进行旋转、翻转、缩放、裁剪等操作,生成额外的训练样本,增加数据的多样性和数量,提高模型的泛化能力
数据采样(Data Sampling):在不平衡数据集中,通过过采样(Oversampling)或欠采样(Undersampling)等技术调整样本类别的比例,以平衡训练数据集。
数据集划分(Dataset Splitting):将原始数据集划分为训练集、验证集和测试集,用于模型训练、调优和评估。
早期的数据处理技术
早期的数据处理技术是利用的Dataset+DataLoader的组合,由于我的研究重点是数据存储交互这一方面,因此之后会对这个组合的代码原理进行详细的分析。
Dataset
Map式数据集
Map式dataset需要提供能够通过索引检索到对应数据对的方式(类似通过文件名检索文件的方式)。也就是提供样本的映射。
Iterable数据集(在不知道数据的大小和组织方式的情况下使用)
Iterable数据集会定义一个样本集的迭代器,能通过这个迭代器访问所有样本。
总的来说,dataset只是定义数据的访问方式。
它对于小的数据集,在dataset的初始化阶段就会将整个数据集加载到内存。这种情况下会构建map数据集,此时的映射变成索引到内存中数据的映射。
对于大的数据集,dataset不会在初始化时候加载数据,而是定义数据迭代方式以支持流式的数据加载。由DataLoader调用。
DataLoader
DataLoader会使用dataset中定义的数据访问方式加载数据。
DataLoader能够自定义数据的采样方式,采用用户自定义的采样函数(避免过拟合)
DataLoader能对数据集进行批量划分,批量划分的目的主要是为了减小模型的泛化误差,也就是增强实用性。
DataLoader能对数据集进行预处理,例如将图像转换成”tensor”包括进行标准化等。
DataLoader可以使用多个线程,并行加载和处理数据。
(通过迭代器的方式,对所有的索引进行对应的处理,多线程的方式是先将索引组合成多个队列,使用多线程分别操作多个队列,将处理好的数据放到一个结果队列中)
dataset和DataLoader的缺点:
1.代码的复用性比较差,对不同类型的数据,代码结构类似但是不能够服用。
2.dataloader的功能杂,扩展性差
3.对流式数据的处理过于单一
4.缺少很多的数据处理格式,现在的很多AI任务已经不是简单的图像+标签对的形式,数据的预处理流程更加复杂。
新的数据处理技术
TorchData
为了解决上述的问题,引入新的模块加载组合DataPipe与DataLoader2。
DataPipe(新的分布式数据处理需求)
首先将数据加载、数据预处理、批次划分、采样等功能模块化。
随后利用模块化的工具手动建立数据处理pipe,定义数据处理流程。整个pipe以链式的结构存在。
datapipe = torch.datapipes.iter.FileLister(root_dir)
datapipe = datapipe.filter(filter_fn=filter_for_data)
datapipe = datapipe.open_files(mode=‘rt’)
datapipe = datapipe.parse_csv(delimiter=“,”, skip_lines=1)
# Shuffle will happen as long as you do NOT set shuffle=False
later in the DataLoader
datapipe = datapipe.shuffle()
datapipe = datapipe.map(row_processor)
可在现有的DataPipe基础上扩展更多的数据处理步骤。多条DataPipe链可以组合成一个DataPipe图,对于多个数据源,可能每个数据源中的数据需要处理的方式不同,这种情况下构建一个DataPipe 图,最后将所有的数据源处理完毕后放入网络,达到分布式数据处理的目的。
DataPipe的优点:
高级数据处理:DataPipe提供了更灵活的数据处理和转换能力。它允许用户自定义数据处理流程,包括数据转换、增强、过滤、排序等操作。相比之下,DataLoader主要用于批处理和简单的数据转换,对于复杂的数据处理需求,DataPipe更具优势。
多输入数据流:DataPipe可以方便地处理多个输入数据流,并将它们组合成一个数据流。这在一些场景下很有用,例如需要同时处理图像和文本数据的情况。DataLoader并没有直接支持多输入数据流的能力。
异步数据加载:DataPipe可以使用异步的方式加载数据,从而提高数据加载的效率。它使用了异步编程的概念,可以在数据加载的同时进行其他计算任务,从而提高整体的数据处理效率。相比之下,DataLoader是在主线程中同步加载数据。
分布式数据处理:DataPipe对于分布式数据处理任务更具优势。它可以与分布式训练框架(如PyTorch DistributedDataParallel)无缝集成,实现分布式数据加载和处理。这在处理大规模数据集或在多个GPU上进行训练时非常有用。
DatPipe添加自己的数据处理函数:
class MyProcessor:
def __call__(self, data):
# 自定义数据处理逻辑
# 进行转换、过滤、标准化等操作
processed_data = ...
return processed_data
class MyDataPipe(DataPipe):
def __init__(self, data):
self.data = data
self.processor = MyProcessor()
def __iter__(self):
for item in self.data:
processed_item = self.processor(item)
yield processed_item
#//创建自定义的 DataPipe 实例
datapipe = MyDataPipe(data)
datapipe = datapipe.processor1()
datapipe = datapipe.processor2()
datapipe = datapipe.processor3()
知道了pytorch的相关数据加载和处理流程,那么如何将pytorch和分布式存储系统对接呢?我找到了一个方案是使用Apache Arrow(还未实现)
Apache Arrow
- Apache Arrow 是一个跨语言、跨平台(支持多种语言)的内存数据交换格式和计算库。它提供了和大多数文件系统的数据交互接口。
- 内存数据布局:Apache Arrow 定义了一种内存数据布局,使用零拷贝技术,避免了数据的序列化和反序列化开销,提高了数据传输和处理的效率。
- 高性能计算:Apache Arrow 通过列式存储(columnar storage)和批量处理技术,提供了高效的向量化计算操作,如矢量化的数值计算、聚合操作、条件过滤等,以提高计算效率。
- 大规模数据处理:Apache Arrow 旨在处理大规模数据集,提供了高效的数据处理能力,包括数据加载、转换、过滤、聚合等操作,适用于大数据分析和应用开发。
- 其中Pyarrow:Apache是 Arrow的pythonAPI
Pyarrow与Hadoop的连接的接口:
pyarrow.fs.FileSystem抽象类
- 定义了文件系统接口的通用功能。用于处理不同文件系统的统一访问。
- 文件和目录操作:FileSystem 定义了一组方法,用于操作文件和目录,如创建目录、删除目录、列出目录内容、重命名文件等。
- 文件读写操作:FileSystem 提供了一些方法来读取和写入文件的内容。这包括打开文件、读取文件内容、写入文件内容、追加内容等。
- 文件系统导航:通过 FileSystem ,你可以导航文件系统的层次结构,获取文件和目录的元数据信息,如文件大小、修改时间等。
- 权限管理:FileSystem 允许你检查和修改文件的权限设置,比如文件的所有者、权限模式等。
- 文件系统配置:FileSystem 支持配置文件系统的参数和选项,比如设置缓存、设置超时时间等。
- 文件系统间的复制和移动:FileSystem 提供了方法来在不同的文件系统之间复制和移动文件。
pyarrow.fs.HadoopFileSystem类
class pyarrow.fs.HadoopFileSystem(unicode host, int port=8020, unicode user=None, *, int replication=3, int buffer_size=0, default_block_size=None, kerb_ticket=None, extra_conf=None)
Pyarrow与特定的文件存储系统进行自定义继承
可通过实现pyarrow.fs.FileSystem接口来创建自定义的文件系统适配器。需要注意的是,pyarrow在实现文件系统的接口时候使用了cython编译器,以加快整个代码的运行速度。
总结:
可行的技术路线:
使用pyarrow和分布式文件系统对接,并且可以使用Apache Arrow的内存数据处理功能,通过pytorch中的DataPipe封装整个数据处理流程,最后利用DataLoader2加载数据到模型中,利用pytorch分布式训练框架nn.parallel.DistributedDataParallel对整个训练过程进行数据并行训练。