深度学习PyTorch,TensorFlow中GPU利用率较低,CPU利用率很低,且模型训练速度很慢的问题总结与分析

在深度学习模型训练过程中,在服务器端或者本地pc端,输入nvidia-smi来观察显卡的GPU内存占用率(Memory-Usage),显卡的GPU利用率(GPU-util),然后采用top来查看CPU的线程数(PID数)和利用率(%CPU)。往往会发现很多问题,比如,GPU内存占用率低,显卡利用率低,CPU百分比低等等。接下来仔细分析这些问题和处理办法。

(ps:对于如何在Intel CPU,ARM架构CPU,以及Jetson TensorRT上部署深度学习模型,以及部署遇到的速度问题,该如何解决。请查看我的另外一篇文章。如何定制化编译Pytorch,TensorFlow,使得CNN模型在CPU,GPU,ARM架构和X86架构,都能快速运行,需要对每一个平台,有针对性的调整。如何做到最大化加速深度学习在不同平台部署性能。请看我的这篇文章。)

深度学习模型Intel与ARM部署性能分析,Intel和ARM CPU上CNN计算速度差距分析。_arm x86 深度学习_是否龙磊磊真的一无所有的博客-CSDN博客

1. GPU内存占用率问题

        这往往是由于模型的大小以及batch size的大小,来影响这个指标。当你发下你的GPU占用率很小的时候,比如40%,70%,等等。此时,如果你的网络结构已经固定,此时只需要改变batch size的大小,就可以尽量利用完整个GPU的内存。GPU的内存占用率主要是模型的大小,包括网络的宽度,深度,参数量,中间每一层的缓存,都会在内存中开辟空间来进行保存,所以模型本身会占用很大一部分内存。其次是batch size的大小,也会占用影响内存占用率。batch size设置为128,与设置为256相比,内存占用率是接近于2倍关系。当你batch  size设置为128,占用率为40%的话,设置为256时,此时模型的占用率约等于80%,偏差不大。所以在模型结构固定的情况下,尽量将batch size设置大,充分利用GPU的内存。(GPU会很快的算完你给进去的数据,主要瓶颈在CPU的数据吞吐量上面。)

2. GPU利用率问题

        这个是Volatile GPU-Util表示,当没有设置好CPU的线程数时,这个参数是在反复的跳动的,0%,20%,70%,95%,0%。这样停息1-2 秒然后又重复起来。其实是GPU在等待数据从CPU传输过来,当从总线传输到GPU之后,GPU逐渐起计算来,利用率会突然升高,但是GPU的算力很强大,0.5秒就基本能处理完数据,所以利用率接下来又会降下去,等待下一个batch的传入。因此,这个GPU利用率瓶颈在内存带宽和内存介质上以及CPU的性能上面。最好当然就是换更好的四代或者更强大的内存条,配合更好的CPU。

        另外的一个方法是,在PyTorch这个框架里面,数据加载Dataloader上做更改和优化,包括num_workers(线程数),pin_memory,会提升速度。解决好数据传输的带宽瓶颈和GPU的运算效率低的问题。在TensorFlow下面,也有这个加载数据的设置。

torch.utils.data.DataLoader(image_datasets[x],
                            batch_size=batch_size, 
                            shuffle=True,
                            num_workers=8,
                            pin_memory=True)

为了提高利用率,首先要将num_workers(线程数)设置得体,4,8,16是几个常选的几个参数。本人测试过,将num_workers设置的非常大,例如,24,32,等,其效率反而降低,因为模型需要将数据平均分配到几个子线程去进行预处理,分发等数据操作,设高了反而影响效率。当然,线程数设置为1,是单个CPU来进行数据的预处理和传输给GPU,效率也会低。其次,当你的服务器或者电脑的内存较大,性能较好的时候,建议打开pin_memory打开,就省掉了将数据从CPU传入到缓存RAM里面,再给传输到GPU上;为True时是直接映射到GPU的相关内存块上,省掉了一点数据传输时间。

3. CPU的利用率问题

        很多人在模型训练过程中,不只是关注GPU的各种性能参数,往往还需要查看CPU处理的怎么样,利用的好不好。这一点至关重要。但是对于CPU,不能一味追求超高的占用率。如图所示,对于14339这个程序来说,其CPU占用率为2349%(我的服务器是32核的,所以最高为3200%)。这表明用了24核CPU来加载数据和做预处理和后处理等。其实主要的CPU花在加载传输数据上。此时,来测量数据加载的时间发现,即使CPU利用率如此之高,其实际数据加载时间是设置恰当的DataLoader的20倍以上,也就是说这种方法来加载数据慢20倍。当DataLoader的num_workers=0时,或者不设置这个参数,会出现这个情况。

CPU利用率查看结果

 下图中可以看出,加载数据的实际是12.8s,模型GPU运算时间是0.16s,loss反传和更新时间是0.48s。此时,即使CPU为2349%,但模型的训练速度还是非常慢,而且,GPU大部分是时间是空闲等待状态。

 num_workers=0,模型每个阶段运行时间统计

当我将num_workers=1时,出现的时间统计如下,load data time为6.3,数据加载效率提升1倍。且此时的CPU利用率为170%,用的CPU并不多,性能提升1倍。

 num_workers=1时,模型每个阶段运行时间统计

 此时,查看GPU的性能状态(我的模型是放在1,2,3号卡上训练),发现,虽然GPU(1,2,3)的内存利用率很高,基本上为98%,但是利用率为0%左右。表面此时网络在等待从CPU传输数据到GPU,此时CPU疯狂加载数据,而GPU处于空闲状态。

由此可见,CPU的利用率不一定最大才最好。

        对于这个问题,解决办法是,增加DataLoader这个num_wokers的个数,主要是增加子线程的个数,来分担主线程的数据处理压力,多线程协同处理数据和传输数据,不用放在一个线程里负责所有的预处理和传输任务。

        我将num_workers=8,16都能取得不错的效果。此时用top查看CPU和线程数,如果我设置为num_workers=8,线程数有了8个连续开辟的线程PID,且大家的占用率都在100%左右,这表明模型的CPU端,是较好的分配了任务,提升数据吞吐效率。效果如下图所示,CPU利用率很平均和高效,每个线程是发挥了最大的性能。

 num_workers=8时,CPU利用率和8个连续PID任务

此时,在用nvidia-smi查看GPU的利用率,几块GPU都在满负荷,满GPU内存,满GPU利用率的处理模型,速度得到巨大提升。

上图中可以看见,GPU的内存利用率最大化,此时是将batch size设置的较大,占满了GPU的内存,然后将num_workers=8,分配多个子线程,且设置pin_memory=True,直接映射数据到GPU的专用内存,减少数据传输时间。GPU和CPU的数据瓶颈得到解决。整体性能得到权衡。

        此时的运行时间在表中做了统计:

处理时间统计
处理阶段 时间
数据加载 0.25s
模型在GPU计算 0.21s
loss反传,参数更新 0.43s

 

 

4. 总结

        对上面的分析总结一下,第一是增加batch size,增加GPU的内存占用率,尽量用完内存,而不要剩一半,空的内存给另外的程序用,两个任务的效率都会非常低。第二,在数据加载时候,将num_workers线程数设置稍微大一点,推荐是8,16等,且开启pin_memory=True。不要将整个任务放在主进程里面做,这样消耗CPU,且速度和性能极为低下。

                                                                                                                                                           

                                                                                                                                                           

                                                                                                                                                           

        Supplementary:看到大家在评论回复的问题比较多,所以再加一些叙述!

        开这么多线程。第一个,查看你的数据的batch_size,batchsize小了,主CPU直接就加载,处理,而且没有分配到多GPU里面(如果你使用的是多GPU);如果是单GPU,那么就是CPU使劲读数据,加载数据,然后GPU一下就处理完了,你的模型应该是很小,或者模型的FLOPs很小。检查一下模型问题。还有就是,现在这个情况下,开8个线程和1个线程,没什么影响,你开一个num_workers都一样的。如果速度快,没必要分配到多个num_workers去。当数据量大的时候,num_workers设置大,会非常降低数据加载阶段的耗时。这个主要还是应该配合过程。

        在调试过程,命令:top     实时查看你的CPU的进程利用率,这个参数对应你的num_workers的设置;

        命令:   watch -n 0.5 nvidia-smi    每0.5秒刷新并显示显卡设置。

实时查看你的GPU的使用情况,这是GPU的设置相关。这两个配合好。包括batch_size的设置。

5. 再次补充内容

        有很多网友都在讨论一些问题,有时候,我们除了排查代码,每个模块的处理信息之外,其实还可以查一下,你的内存卡,是插到哪一块插槽的。这个插槽的位置,也非常影响代码在GPU上运行的效率。

        大家除了看我上面的一些小的建议之外,评论里面也有很多有用的信息。遇到各自问题的网友们,把他们的不同情况,都描述和讨论了一下,经过交流,大家给出了各自在训练中,CPU,GPU效率问题的一些新的发现和解决问题的方法。

        针对下面的问题,给出一点补充说明:

问题1: CPU忙碌,GPU清闲。

 数据的预处理,和加载到GPU的内存里面,花费时间。平衡一下batch size, num_workers。

 问题2:CPU利用率低,GPU跑起来,利用率浮动,先增加,然后降低,然后等待,CPU也是浮动。

2.1 下面是具体的步骤和对策:
在pytorch训练模型时出现以下情况, 情况描述: 首先环境:2080Ti + I7-10700K, torch1.6, cuda10.2, 驱动440 参数设置:shuffle=True, num_workers=8, pin_memory=True; 现象1:该代码在另外一台电脑上,可以将GPU利用率稳定在96%左右 现象2:在个人电脑上,CPU利用率比较低,导致数据加载慢,GPU利用率浮动,训练慢约4倍;有意思的是,偶然开始训练时,CPU利用率高,可以让GPU跑起来,但仅仅几分钟,CPU利用率降下来就上不去了,又回到蜗牛速度。

 可以采用的方法:
两边的配置都一样吗。另一台电脑和你的电脑。你看整体,好像设置配置有点不同。包括硬件,CPU的核,内存大小。你对比一下两台设备。这是第一个。第二个,还是代码里面的配置,代码的高效性。你一来,CPU利用率低,你看一下每一步,卡到哪里,哪里是瓶颈,什么步骤最耗时。都记录一下每一个大的步骤的耗时,然后在分析。测试了每一个大的过程的时间,可以看见,耗时在哪里。主要包括,加载数据,前向传播,反向更新,然后下一步。

2.2 经过测试之后,第二次问题分析:
经过测试,发现本机卡的地方在加载图像的地方,有时加载10kb左右的图像需要1s以上,导致整个batch数据加载慢!代码应该没有问题,因为在其他电脑能全速跑起来;硬件上,本机的GPU,CPU都强悍,环境上也看不出差距,唯一差在内存16G,其他测试电脑为32G,请问这种现象和内存直接关系大吗?

情况分析
最多可能就在这边。你可以直接测试batch size为1情况下的整个计算。或者将batch size 开到不同的设置下。看加载数据,计算之间的差值。最有可能就是在这个load data,读取数据这块。 电脑的运行内存16g 32g。其实都已经够了,然后加载到GPU上,GPU内存能放下,影响不大。所以估计是你的内存相对小了,导致的问题。试一下。

2.3 问题定位,解决方法:
这台电脑的内存条插的位置不对,4个插槽的主板,1根内存的时候应该插在第2个插槽(以cpu端参考起),而组装电脑的商家不专业,放在了第4个插槽上,影响性能,更换位置后,速度飞起来了!关于插槽详情,有遇到的朋友去网上收,一大堆!
        在自己电脑,或者自己配的主机上,跑GPU的时候,记得注意查看你自己的内存卡是插到哪一个槽上的。

5.1 再次新补充一些内容

        有网友补充了一些在执行上面的问题中遇到的实际问题,附上他的解决方法。

        使用win 10修改num_workers后可能会报错Broken pipe。

解决方法:1. 把代码放到if __name__ == "__main__":下运行;或者2.num_workers默认为0即可;或者3. 在Linux进行代码运行

 有一些内容需要说明:在Windows下面,设置num_threads,除了在做数据加载的时候,设置num_workers,还可以用torch.set_num_threads(4)多线程,单线程,都会用多个CPU核,跑多个CPU core的的。只是CPU利用率不高。你设置8线程,12线程,CPU会在每个核上,都进行分配,只是单核的占用率,不一样。即使设置2线程,在6核12线程的CPU,也会在每个核心上,分配计算资源的。只是单核分配的很少。

5.2 关于加速CPU端训练的方法(无GPU)

        在单独的CPU上,做训练,或者做推理,intel CPU提供了OpenMP 和MKL-DNN的加速库。一般torch或者TensorFlow都做了这一块的优化。可以查看你的pytorch版本,是否支持。

print(torch.get_num_threads())
print(torch.__config__.parallel_info())

print(*torch.__config__.show().split("\n"), sep="\n")


os.environ["OMP_NUM_THREADS"]="8"  #设置OpenMP计算库的线程数
os.environ["MKL_NUM_THREADS"]="8"  # 设置MKL-DNN CPU加速库的线程数。
torch.set_num_threads(8)
print(torch.get_num_threads())
print(torch.__config__.parallel_info())

print(*torch.__config__.show().split("\n"), sep="\n")


os.environ["OMP_NUM_THREADS"]="8"  #设置OpenMP计算库的线程数
os.environ["MKL_NUM_THREADS"]="8"  # 设置MKL-DNN CPU加速库的线程数。
torch.set_num_threads(8)

分析:

        上面这几个,都可以试一下。看你的pytorch版本,是否在编译之后,支持MKL-DNN加速。为了能控制你使用的线程数,set_num_threads(8) 这个线程数的多少,可以自己按照需求来设定。当你全力跑网络模型,当然设置大点。如果需要留一部分CPU性能来做其他的业务,4线程,6线程?都可以。自己试一试。配合着任务管理器或者htop top 在linux下实时查看CPU使用状态和设置多线程数量的关系。来定性的分配。

        print(torch.__config__.parallel_info()) , 这个函数,查看你的pytorch支持的intel加速库的信息。

        print(*torch.__config__.show().split("\n"), sep="\n") , 这个函数,查看你编译过程中的信息。

深度学习PyTorch,TensorFlow中GPU利用率较低,CPU利用率很低,且模型训练速度很慢的问题总结与分析_第1张图片

 

实测结果:

        有没有OpenMP支持,速度影响不是太大。在1-2s内的影响。所采用的pytorch版本是否支持mkl-dnn不影响。在mac arm m1芯片下,开启mkl-dnn,速度比没有开启快4s。44s 与 48s的差别。我们的平台,都是支持mkl-dnn。没有mkl-dnn,速度比有mkl-dnn编译的模型,慢1.5倍左右。

        结论:

        mkl-dnn有无,对性能影响不是很大,1-2x的影响。如果你需要这点性能,那么就要重点检测,你的pytorch版本,是否在编译过程中,设置了use_mkl=on,use_mkldnn=on。大多数情况下,咱们安装的pytorch官方版本,都在build过程中,设置了开启mkl加速选项。这是intel Math Kernel  Library for Deep Neural Networks (Intel® MKL-DNN) 专门针对intel CPU做的CPU端深度学习加速库。

        arm平台下,有无OpenMP和mkl-dnn不确定,要查看这个pytorch是否对arm 这个架构有支持。nvidia的arm平台,jetson这一类的,nvidia自己做了重新编译的,都适配了arm的CPU。

再提醒一下:以上设置,如果不进行设计,默认为0,会按照最大的性能,来运行。

补充时间:2021年6月25日。

ps:有任何性能加速上遇到的问题,欢迎提出,我经常会查看大家的问题,能回答的都会回答。

对于如何在Intel CPU,ARM架构CPU,以及Jetson TensorRT上部署,以及部署遇到的速度问题,该如何解决。请查看我的另外一篇文章。

深度学习模型Intel与ARM部署性能分析,Intel和ARM CPU上CNN计算速度差距分析。_arm x86 深度学习_是否龙磊磊真的一无所有的博客-CSDN博客

内容再次补充:2021年11月21日。

        针对网友的一些新的问题,我总结了一下,集中回答一下。

        问题1:文中提到了对CPU加载数据时间、GPU模型运算时间,loss反传和更新时间进行计算,但如果我关心的只是模型跑一个epoch需要的时间,那是不是我都不需要进行前面这些时间的计算,只需要关注一个epoch所耗的时间,然后通过修改batch_size和num_workers来降低这个时间就可以了?是不是当我想比较不同模型或者数据集的表现时,才需要同时关注GPU模型运算时间、loss反传和更新时间?

        回答1: 如果只关注跑一个epoch的时间,要看你是 training 还是evaluation的模式。如果你只关注前向推理,不在乎loss反传,梯度更新。那就只需要关注一个epoch的纯推理时间。如果是训练,epoch一般都是在训练的时候来用的,那么,单个epoch就需要考虑加载数据,模型运算,loss反传和更新了。 batch size和num workers就是来协调加载数据,模型推理数据的。(ImageNet 100多G,一次性加载不到GPU的内存上的,所以在单个epoch,有数据加载这个耗时的。只是在GPU训练推理的时候,也在从CPU端向GPU的内存通信。)所以基本上,你调整batch size 和num workers,也是在综合考虑这些因素的。比较不同模型、数据集的表现,一般只看Top1-Top5,推理速度,Parameters,Model Size,FLOPs等等(训练速度)这些。直接比较就行。不用具体到这些具体参数时间的。 

        问题2: 文中提到了平衡batch_size和num_workers,但我觉得把batch_size调到最大(把显存占满),然后只调整num_workers是不是更加简单高效?因为batch_size的大小本身就会影响我们模型的performance,或者达到相同performance需要的epoch,所以不知道博主说“平衡batch_size和num_workers”的时候是不是因为考虑到显存可能不够用。

        回答2: batch_size调整到最大,然后在分配num_workers,这里面有些技巧的。他的关系不是:batchsize x num_workers = Total。不过可以试一试,你的这个想法。batch_size调整大,num_workers是影响速度的,不影响精度。你4 worker,8 workers, batch size固定了,最后都是在你batch size上 更新模型参数。 配合着来,CPU 和GPU的运行都要兼顾着。

你可能感兴趣的:(人工智能,深度学习,tensorflow,pytorch)