咱们在帮助某AI客户排查一个UFS文件存储的性能case时发现,其应用的Pytorch训练IO性能和硬件的IO能力有很大的差距(前面内容有具体性能比照数据)。

让咱们感到困惑的是: UFS文件存储,咱们应用fio自测能够达到单实例最低10Gbps带宽、IOPS也可达到2w以上。该AI客户在高IOPS要求的AI单机小模型训练场景下,或者之前应用MXNet、TensorFlow框架时,IO都能跑到UFS实践性能,甚至在大型分布式训练场景中,UFS也能够齐全胜任。

于是咱们开启了和客户的一次深度联结排查。

初步尝试优化

一、调整参数:

基于上述情况,首先思考是不是应用Pytorch的姿态不对?参考网上提到教训,客户调整batch_size、Dataloader等参数。

Batch_size

默认batch_size为256,依据内存和显存配置尝试更改batch_size大小,让一次读取数据更多,发现理论对效率没有晋升。通过剖析是因为batch_size设置与数据读取逻辑没有间接关系,IO始终会保留单队列与后端交互,不会升高网络交互上的整体延时(因为用的是UFS文件存储,前面会讲到为什么用)。

Pytorch Dataloader

Pytorch框架dataloader的worker负责数据的读取和加载、调配。通过batch_sampler将batch数据调配给对应的worker,由worker从磁盘读取数据并加载数据到内存,dataloader从内存中读取相应batch做迭代训练。这里尝试调整了worker_num参数为CPU核数或倍数,发现晋升无限,反而内存和CPU的开销晋升了不少,整体减轻了训练设施的累赘,通过 worker加载数据时的网络开销并不会升高,与本地SSD盘差距仍然存在。

这个也不难理解,前面用strace排查的时候,看到CPU更多的时候在期待。

所以:从目前信息来看,调整Pytorch框架参数对性能简直没有影响。

二、尝试不同存储产品

在客户调整参数的同时,咱们也应用了三种存储做验证,来看这里是否存在性能差别、差别到底有多大。在三种存储产品上放上同样的数据集:

  1. 单张均匀大小20KB的小图片,总量2w张。
  2. 以目录树形式存到三种存储下的雷同门路,应用Pytorch罕用的规范读图接口CV2和PIL

测试后果,如下图:

注:SSHFS基于X86物理机(32核/64G/480G SSD*6 raid10)搭建,网络25Gbps

论断:通过对存储性能实测, UFS文件存储较本地盘、单机SSHFS性能差距较大。

为什么会选用这两种存储(SSHFS和本地SSD)做UFS性能比照?

以后支流存储产品的选型上分为两类:自建SSHFS/NFS或采纳第三方NAS服务(相似UFS产品),个别场景中也会将须要的数据下载到本地SSD盘做训练。传统SSD本地盘领有极低的IO延时,一个IO申请解决根本会在us级别实现,针对越小的文件,IO性能越显著。受限于单台物理机配置,无奈扩容,数据根本 “即用即弃”。而数据是否平安也只能依赖磁盘的稳定性,一旦产生故障,数据恢复难度大。然而鉴于本地盘的劣势,个别也会用作一些较小模型的训练,单次训练任务在较短时间即可实现,即便硬件故障或者数据失落导致训练中断,对业务影响通常较小。

用户通常会应用SSD物理机自建SSHFS/NFS共享文件存储,数据IO会通过以太网络,较本地盘网络上的开销从us级到ms级,但根本能够满足大部分业务需要。但用户须要在日常应用中同时保护硬件和软件的稳定性,并且单台物理机有存储下限,如果部署多节点或分布式文件系统也会导致更大运维精力投入。

咱们把后面论断放到一起看:

  1. 隐形论断:Tensorflow、Mxnet框架无问题。
  2. 调整Pytorch框架参数对性能简直没有影响。

3、Pytorch+UFS的场景下, UFS文件存储较本地SSD盘、单机SSHFS性能差距大。

联合以上几点信息并与用户确认后的明确论断:

UFS联合非Pytorch框架应用没有性能瓶颈, Pytorch框架下用本地SSD盘没有性能瓶颈,用SSHFS性能可承受。那起因就很显著了,就是Pytorch+UFS文件存储这个组合存在IO性能问题。

深刻排查优化

看到这里,大家可能会有个疑难:是不是不必UFS,用本地盘就解决了?

答案是不行,起因是训练所需的数据总量很大,很容易超过了单机的物理介质容量,另外也出于数据安全思考,寄存单机有失落危险,而UFS是三正本的分布式存储系统,并且UFS能够提供更弹性的IO性能。

依据以上的信息疾速排查3个论断,基本上能够判断出: Pytorch在读UFS数据过程中,文件读取逻辑或者UFS存储IO耗时导致。于是咱们通过strace察看Pytorch读取数据整体流程:

通过strace发现,CV2形式读取UFS里的文件(NFSV4协定)有很屡次SEEK动作,即使是单个小文件的读取也会“分片”读取,从而导致了屡次不必要的IO读取动作,而最耗时的则是网络,从而导致整体耗时成倍增长。这也是合乎咱们的猜想。

简略介绍一下NFS协定特点:

NAS所有的IO都须要通过以太网,个别局域网内延时在1ms以内。以NFS数据交互为例,通过图中能够看出,针对一次残缺的小文件IO操作将波及元数据查问、数据传输等至多5次网络交互,每次交互都会波及到client与server集群的一个TTL,其实这样的交互逻辑会存在一个问题,当单文件越小、数量越大时则延时问题将越显著,IO过程中有过多的工夫耗费在网络交互,这也是NAS类存储在小文件场景下面临的经典问题。

对于UFS的架构而言,为了达到更高扩展性、更便当的维护性、更高的容灾能力,采纳接入层、索引层和数据层的分层架构模式,一次IO申请会先通过接入层做负载平衡,client端再拜访后端UFS索引层获取到具体文件信息,最初拜访数据层获取理论文件,对于KB级别的小文件,理论在网络上的耗时比单机版NFS/SSHFS会更高。

从Pytorch框架下两种读图接口来看:CV2读取文件会“分片”进行,而PIL尽管不会“分片”读取,然而基于UFS分布式架构,一次IO会通过接入、索引、数据层,网络耗时也占比很高。咱们存储共事也理论测试过这2种办法的性能差别:通过strace发现,相比OpenCV的形式,PIL的数据读取逻辑效率绝对高一些。

优化方向一: 如何升高与UFS交互频次,从而升高整体存储网络延时

CV2:对单个文件而言,“分片读取”变“一次读取”

通过对Pytorch框架接口和模块的调研,如果应用 OpenCV形式读取文件能够用2个办法, cv2.imread和cv2.imdecode。

默认个别会用cv2.imread形式,读取一个文件时会产生9次lseek和11次read,而对于图片小文件来说屡次lseek和read是没有必要的。cv2.imdecode能够解决这个问题,它通过一次性将数据加载进内存,后续的图片操作须要的IO转化为内存拜访即可。

两者的在零碎调用上的对比方下图:

咱们通过应用cv2.imdecode形式替换客户默认应用的cv2.imread形式,单个文件的总操作耗时从12ms降落到6ms。然而内存无奈cache住过大的数据集,不具备任意规模数据集下的训练,然而整体读取性能还是晋升显著。应用cv2版本的benchmark对一个小数据集进行加载测试后的各场景耗时如下(提早的非线性降落是因为其中蕴含GPU计算工夫):

PIL:优化dataloader元数据性能,缓存文件句柄

通过PIL形式读取单张图片的形式,Pytorch解决的均匀提早为7ms(不含IO工夫),单张图片读取(含IO和元数据耗时)均匀提早为5-6ms,此性能程度还有优化空间。

因为训练过程会进行很多个epoch的迭代,而每次迭代都会进行数据的读取,这部分操作从屡次训练任务上来看是反复的,如果在训练时由本地内存做一些缓存策略,对性能应该有晋升。但间接缓存数据在集群规模回升之后必定是不事实的,咱们初步只缓存各个训练文件的句柄信息,以升高元数据拜访开销。

咱们批改了Pytorch的dataloader实现,通过本地内存cache住训练须要应用的文件句柄,能够防止每次都尝试做open操作。测试后发现1w张图片通过100次迭代训练后发现,单次迭代的耗时曾经根本和本地SSD持平。然而当数据集过大,内存同样无奈cache住所有元数据,所以应用场景绝对无限,仍然不具备在大规模数据集下的训练伸缩性。

UFS server端元数据预加载

以上client端的优化成果比拟显著,然而客户业务侧须要更改大量训练代码,最次要是client端无奈满足较大数据量的缓存,利用场景无限,咱们持续从server端优化,尽量升高整个链路上的交互频次。

失常IO申请通过负载平衡达到索引层时,会先通过索引接入server,而后到索引数据server。思考到训练场景具备目录拜访的空间局部性,咱们决定加强元数据预取的性能。通过客户申请的文件,引入该文件及相应目录下所有文件的元数据,并预取到索引接入server,后续的申请将命中缓存,从而缩小与索引数据server的交互,在IO申请达到索引层的第一步即可获取到对应元数据,从而升高从索引数据server进行查问的开销。

通过这次优化之后,元数据操作的提早较最后可能降落一倍以上,在客户端不做更改的状况下,读取小文件性能已达到本地SSD盘的50%。看来单单优化server端还是无奈满足预期,通过执行Pytorch的benchmark程序,咱们失去UFS和本地SSD盘在整个数据读取耗时。

此时很容易想到一个问题:非Pytorch框架在应用UFS做训练集存储时,为什么应用中没有遇到IO性能瓶颈?

通过调研其余框架的逻辑发现:无论是MXNet的rec文件,Caffe的LMDB,还是TensorFlow的npy文件,都是在训练前将大量图片小文件转化为特定的数据集格局,所以应用UFS在存储网络交互更少,绝对Pytorch间接读取目录小文件的形式,防止了大部分网络上的耗时。这个区别在优化时给了咱们很大的启发,将目录树级别小文件转化成一个特定的数据集存储,在读取数据做训练时将IO施展出最大性能劣势。

优化方向二:目录级内的小文件转换为数据集,最大水平降到IO网络耗时

基于其余训练框架数据集的共性功能,咱们UFS存储团队连忙动工,几天开发了针对Pytorch框架下的数据集转换工具,将小文件数据集转化为UFS大文件数据集并对各个小文件信息建设索引记录到index文件,通过index文件中索引偏移量可随机读取文件,而整个index文件在训练任务启动时一次性加载到本地内存,这样就将大量小文件场景下的频繁拜访元数据的开销齐全去除了,只剩下数据IO的开销。该工具后续也可间接利用于其余AI类客户的训练业务。

工具的应用很简略,只波及到两步:

  • 应用UFS自研工具将Pytorch数据集以目录模式存储的小文件转化为一个大文件存储到UFS上,生成date.ufs和index.ufs。
  • 应用我方提供Folder类替换pytorch原有代码中的torchvision.datasets.ImageFolder数据加载模块(即替换数据集读取办法),从而应用UFS上的大文件进行文件的随机读取。只需更改3行代码即可。

20行:新增from my_dataloader import *

205行:train_dataset = datasets.ImageFolder改为train_dataset = MyImageFolder

224行:datasets.ImageFolder改为MyImageFolder

通过github上Pytorch测试demo对imagenet数据集进行5、10、20小时模拟训练,别离读取不同存储中的数据,具体看下IO对整体训练速度的影响。(数据单位:实现的epoch的个数)

测试条件:

GPU服务器:P404物理机,48核256G,数据盘800G6 SATA SSD RAID10

SSHFS:X86物理机32核/64G,数据盘480G*6 SATA SSD RAID10

Demo:https://github.com/pytorch/examples/tree/master/imagenet

数据集:总大小148GB、图片文件数量120w以上

通过理论后果能够看出: UFS数据集形式效率曾经达到甚至超过本地SSD磁盘的成果。而UFS数据集转化形式,客户端内存中只有大量目录构造元数据缓存,在100TB数据的体量下,元数据小于10MB,能够满足任意数据规模,对于客户业务上的硬件应用无影响。

UFS产品

针对Pytorch小文件训练场景,UFS通过屡次优化,吞吐性能已失去极大晋升,并且在后续产品布局中,咱们也会联合现有RDMA网络、SPDK等存储相干技术进行继续优化。具体请拜访:https://docs.ucloud.cn/storage_cdn/ufs/overview

本文作者:UCloud 解决方案架构师 马杰

欢送各位与咱们交换无关云计算的所有~~~