让咱们感到困惑的是: UFS文件存储,咱们使用fio自测能够达到单实例最低10Gbps带宽、IOPS也可达到2w以上。该AI客户在高IOPS要求的AI单机小模型训练场景下,或者以前使用MXNet、TensorFlow框架时,IO都能跑到UFS理论性能,甚至在大型分布式训练场景中,UFS也能够彻底胜任。git
因而咱们开启了和客户的一次深度联合排查。github
基于上述状况,首先考虑是否是使用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框架参数对性能几乎没有影响。负载均衡
在客户调整参数的同时,咱们也使用了三种存储作验证,来看这里是否存在性能差别、差别到底有多大。在三种存储产品上放上一样的数据集:框架
测试结果,以下图:
注: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级,但基本能够知足大部分业务需求。但用户须要在平常使用中同时维护硬件和软件的稳定性,而且单台物理机有存储上限,若是部署多节点或分布式文件系统也会致使更大运维精力投入。
咱们把前面结论放到一块儿看:
三、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的数据读取逻辑效率相对高一些。
经过对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方式读取单张图片的方式,Pytorch处理的平均延迟为7ms(不含IO时间),单张图片读取(含IO和元数据耗时)平均延迟为5-6ms,此性能水平还有优化空间。
因为训练过程会进行不少个epoch的迭代,而每次迭代都会进行数据的读取,这部分操做从屡次训练任务上来看是重复的,若是在训练时由本地内存作一些缓存策略,对性能应该有提高。但直接缓存数据在集群规模上升以后确定是不现实的,咱们初步只缓存各个训练文件的句柄信息,以下降元数据访问开销。
咱们修改了Pytorch的dataloader实现,经过本地内存cache住训练须要使用的文件句柄,能够避免每次都尝试作open操做。测试后发现1w张图片经过100次迭代训练后发现,单次迭代的耗时已经基本和本地SSD持平。可是当数据集过大,内存一样没法cache住全部元数据,因此使用场景相对有限,依然不具有在大规模数据集下的训练伸缩性。
以上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发挥出最大性能优点。
基于其余训练框架数据集的共性功能,咱们UFS存储团队赶忙开工,几天开发了针对Pytorch框架下的数据集转换工具,将小文件数据集转化为UFS大文件数据集并对各个小文件信息创建索引记录到index文件,经过index文件中索引偏移量可随机读取文件,而整个index文件在训练任务启动时一次性加载到本地内存,这样就将大量小文件场景下的频繁访问元数据的开销彻底去除了,只剩下数据IO的开销。该工具后续也可直接应用于其余AI类客户的训练业务。
工具的使用很简单,只涉及到两步:
20行:新增from my_dataloader import *
205行:train_dataset = datasets.ImageFolder改成train_dataset = MyImageFolder
224行:datasets.ImageFolder改成MyImageFolder
经过github上Pytorch测试demo对imagenet数据集进行五、十、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,能够知足任意数据规模,对于客户业务上的硬件使用无影响。
针对Pytorch小文件训练场景,UFS经过屡次优化,吞吐性能已获得极大提高,而且在后续产品规划中,咱们也会结合现有RDMA网络、SPDK等存储相关技术进行持续优化。详细请访问:https://docs.ucloud.cn/storage_cdn/ufs/overview
本文做者:UCloud 解决方案架构师 马杰
欢迎各位与咱们交流有关云计算的一切~~~