大数据文摘出品
来源:medium
编译:赵吉克
在过去的几年里,深度学习硬件方面取得了巨大的进步,Nvidia的最新产品Tesla V100和Geforce RTX系列包含专用的张量核,用于加速神经网络中常用的操作。
特别值得一提的是,V100有足够& u X O X k c R :的能力以每月几张图的速度训练神经W ] Q ! 7 * n网络,这基于ImageNet数据集小模型在单GPU上训练只需几小时,与2012年在ImageNet上训练AlexNet模型所花费的5天时间划分简直是天壤之别!
然而,强大的GPU使数据预 n @ 7 j处理管道不堪重负。为了解决这个问题,Tensorflow发布了一个新的数据M b f O } !加载器:tf.data.Dataset,用C ++编写,并使用基于图的方法将多个预处理操作链接在一V . 7起。
同时,PyTorch使用在PIL库& ~ ( % A 9 p w上用Python编写的数据加载器,既方便优柔,但在速度上有欠缺(尽管PIL-SIMD库确实稍微改善了这种情况)。
进入NVIDIA数据加载器(DALI):预先消除数据预先准备,允许训练和推理全速运行。DALI主要用于在GPU上的预调试,但大多数操作同时CPU上有快速实现。本文{ $ X 0 u A q g主要关注PyTorch,但是DALI也支持Tensorflow,MXNet和TensorRT,尤其是TensorRT有高度支持。它允许训练和推理步骤使用完全相同的预处理代码。需注意,不同的框架(如Tej ( D l 6 l ; Rnsorflow和PyTorch)通常在数据加载器之间有很小的差异,这可能会影响精确。
本文是中上一位博主展示了一些技术来提高DALI的使用率并创建了一个完全基于cpu的管道。这些技术用于保持长期的内存稳定,并且与DALI包提供的CPU和GPU管道相比,可以增加s Y b w 3 !50%的批处理大小。
DALIW @ 2 6 K P X长期内存使用
第一个问题v y a是,RAM的使用通过t ^ 1 I @ r _训练时间的增加而增加,这会导致OOM错误(即使是在拥有78GB RA, : g bM的VM上),并且尚未修正。
唯一解决方案是重新导入DAd s ; I d 2LI并每次重新训练和验证通道:
del self.train_lo~ l # P 1ader, self.val_loader, self.train_V J 2 a Npipe, self.val_pipe
torch.cuda& : !.synchronize()
torch.cuda.empty_c ` r o 8ache()
gc.c% . ( Jollect()
importlib.reload(dali)
from dali import HybridTV n srainPipe, HybridValPipe, DaliIteratorCPU, DaliIteratorGPU
<rebuild DALI pipeline>
请注意,使用这种方法,DALI仍然需要大量RAM才能获得最佳的结果。~ Z G考虑到如今RAM的价格,A ? r P T这并不是什么大问题。从可以修剪,DALI的最大批大小可能比TorchVision低5Z % n ! t t 9 V0%:
接下来的部分涉及降低GPU占用率的方法。
构建一个完全基于CPU的管道
让我们首先看看示例: S w g -CPU管道。当不考虑峰值吞吐量时,基于CPU的管道非常有用。= V (CPw ^ L 2 a DU训练管道只在CPU上执行解码和调整大小的操作,而CropMirrorNormalize操作则在GPU上运行。由于仅仅是传1 M Y b输输出到GPU} 8 + 9 / R 1与DALI就使用替换的GPU内存,为了避免这种情况,我们修改了示例CPU管道,可以完全运行在CPU上:
class/ ! B A T @ HybridTr. . NainPipe(Pipeline):
def __init__(self, batch_size, num_threads, device_id, data_dir, crop,
mean, std, local_rank=0, world_size=1, dali_cpu=Falq g 8se, shuffle=True, fp16=False,
min_crop_size=0.08):
# As we\'re recreating the Pipeline at every epoch, the seed must be -1 (random seed)
super(HybridTrainPipe, self).__init__(batch_sizev o m $ W ~, n& u 9 #um_threads, device_id, seed=-1)
# Enabling read_ahead slowed down processing ~40%
self.input = ops.FileReadel ^ H Sr(file_root=data_dir, shard_id=local_rank, num_shards=world_size,
random_shuffle=shuffle)& + j n H N H Y
# Let user decide which pipeline works best with the chosen model
if dali_cpu:
decode_device = \"cpu\"
self.dali_device = \"cpu\"
self.fl% P ` = V } gip = ops.Flip(device=self.dali_devic* = ] .e)
else:
decode_device = \"mixed\: 3 t M G f c 6 J"
se7 5 ) Y F W ( ] 0lf.dali_device =k I e \"gpu\"
output_dt 1 ^ g 0 d { Qtype = types.FLOAT
if self.dali_device == \"gpu\" and fp16:
output_dtype = types.FLOAT16
self.cmn = ops.CropMirrM v m 7 norNormalize(device=\E n 5"gpu\",
output_dtn o ^ z t Mype=ou. ^ f ]tput_dtype,
output_ H _ X_layout=types.NCHW,
crop=(crop, crop),
image_type=types.RGB,
mean=mean,
std=std,)
# To be able to handle all image6 $ V ; Y 7 8 ks from full-sized Images d + i K | =Net, this padding sets the size of the internal nvJPEG buffers without additional reallocations
device_memory_padding = 211025920 if decode_~ m [ Hdevice == \'mixed\' else 0
host_memory_padding = 1S * W40544512 if decode_device == \'mixed\' else 0
self.dec$ 4 a ` { Q % Wode = ops.ImageDecoderRandomCrop(dev* = 9 R * / lice=decode_device, output_type=types.RGB,
device_memory_padding=device_+ 3 z lmemory_padC { + d ~ ( j Mding,Y ( N @ F Z H 2 {
host_memory_padding=host_memory_padding,
random_aspect_ratio=[0.8, 1.25],
random_C 7 c z X narea=[min_crop_size, 1.0],
nu5 K * Tm_attempts=100)
# Resize as desired. To match torchvisi^ N z g d @ D Oon data loader, use triangular interpolatiq M ! f e e 6 |on.
self.res = ops.Resize(device=self.dali_device, resize_7 D - k S @ V {x=crop, resize_y=crop,
interp_type=types.INC = D ,TERP_TRIANGULAR)
self.coin = ops.CoinFlip(probability=0.5)
print(\'DALI \"{0}\" varian- Y ] g pt\'.format(self.dali_device))
def define_graph(self):
rng = self.coin()
self.jpegs, self.labels = self.input(name=\"Reader\")
# Combined decode & random cr` } B J _ ( Vop
images = self.decode(self.jpegs)
# Resize as desirec B E [ Gd
images = self.res(( o ?images)
if self.da& X V y e n Dli_device == \"gpu\":
out` r h # F y ) j 3put = self.cmn(images, mirr{ = l R ~or=rngE % c P ] i X)
else:
# CPU backend us$ _ m F i 1 + Y yes torch to? ! U | applz o ( 7y mean & std
output = self.flip(images, horizontal=rng)
self.labels = self.labels.gpu()
return [output, self.labels]
基于GPU的管道
测试中,在类似最[ | ` 1大批处理大小下,上述CPU管道的速度大约是T2 E J orchVision数据加载器f Z k )的两倍。CPU管道可以很好地与像ResNw ` 2 s S ; = P qet50这样的大型模型一起工作; 然而,当使用像AlexV j * Y 7 ?Net或ResNet18这样的小模型时,CPU 更好。GPU管道的问题是最大批处理大小减少了近50%,限制了钨。
一种显着减少GPU内存使用的方法是将验证管道1 ) J B {与GPU隔离直到最后再调用。这很t 6 * I % (容易做到) + ] 2 2 G,因为我们已经重新导入DALI,并在每个历元中重新创建数据加载器。
更多小提示
在验证时,将数据集均分的批处理大小效果最好,这避免了在验证数据集结束时还需要进行不完整的批c M ( b : I处理。
与Tensorflow和PyTorch数据加载器类似,TorchVision和e t - , 4 5 xDALI管道不会产生相同的输出-您将看到验证精度略有不同。我发现这是由于不同的JPEG图像解码器。且,DALI支持TensorRT,, ( f 9 b允许使用完全相同的预先来进行训练和推理。
对于前端兆,尝试将数据加载器的数量设置为_virtual_CPU核心的数量,2个虚拟核对应1个物理核。
如i y p U # 6 i果您想要绝对最好的性能和不需要有类似TorchVision的输出,尝试关闭DALI三角形插值。
不要忘记磁盘IO。确保您有足够的内存来缓存数据集和/或一个非常快的SSD。DALI读取高达400Mb / s!
合并
为了方便地集成这些修改,我创建了一个数据加载器类,其中包含此处描述的所有修改,包括DALI和TorchVision的。使用很简单。实例化数据加载程序:
dataset = Dataset(data_dir,
batch_size,
val_batch_size
workers,
use_dali,
da5 z @ m ! l : ` Nli_cpu,
fp16)
然后得到训练和g Y ~ { . ^ / 8 a验证数据加W a .载器:
train_loader = dataset.get_train_loader()val_loader = dataset.get_val_loader()
在每个训练周期结 c g J a 束时重置数据加载器:
dataset.reset9 k F % y y - ()
或者,验证管道可以在模型验证: 2 H i p g b h之前在GPU上重新创建:
dataset.prep_for_val()
基准
以下是使用ResNet18的最# Q 大批量大小:
因此,通过应用这些修改,DALI可以在CPU和GPUh / b O模式下使K } c + 0 O F /用的最大批处理大小增加了约50%!
这里是一些使用Shufflenet V2 0.5和批量大小512的骨折图:
这里是一些使用m 1 3 B M )DALI GPU管道训练各种网络,包括在TorchVision:
所有测试都在谷歌Cloud V100实例上运行,该实例有P U 612个vcpu(6个物理核心),78GB RAM,并使用Apex FP16培训。要重现这些结果,请使用以下参数:
— fp16 — batch-size 512 — workers 10 — arcR v b jh “shufflenet_v2_x0_5 or resnet18” — prof — useH h @ Q U M-dali
所f i ~ y P以,DALI因此单核特斯拉V100可以达到接近4000张/秒的图像处理速度!这达到了Nvidia DG@ 8 o , , iX-1的一半多o / = Q v N @ v一点(P l D A f C Y U它有8个V100 gpu),尽管我们使用了小模型。对我9 8 - O来说,v 0 ~ ~ / B V能够在几个小时内在一个GPU上运行Imagm E /eNet是改善进步。
本文中提供的代码如下:
https:q n + x ^ l ^//github.com/yaysummeriscoming/DALI_pytorch_demo
相关报道:
https://towardsdatascience.com/nvidia-dali-speeO V L l +ding-up-pytorch-876c80182440