Khorina · 2020年06月19日

使用Arm Forge 优化NVIDIA CUDA ML推理应用程序

NVIDIA 最近宣布即将提供对Arm服务器的独立NVIDIA GPU的支持,因此Arm Forge团队很高兴能带来他们领先的开发人员工具去支持该平台。  
在发布完整版本之前,我们将给使用Arm架构CPU和NVIDIA GPU的服务器开发人员展示一个示例介绍如何使用Arm Forge。Arm Forge是我们为Linux开发人员提供的跨平台应用程序分析和调试工具。它也被NVIDIA CUDA开发人员广泛使用,尤其是那些工作建立在大型多服务器HPC系统上的开发人员。
举例来说,我们使用的是NVIDIA Tegra设备,即NVIDIA Jetson Nano,它具有与即将发布支持Arm服务器的版本完全相似的软件环境,这使我们能够演示如何调节和优化ML推理应用程序。

准备NVIDIA Jetson Nano

要想重现本博客里的步骤,需要安装具有MicroSD卡映像的NVIDIA Jetson Nano开发人员工具包。关于这些的资源和说明位于Jetson Nano入门页面。  
首次启动后,请进入Jetson推理示例页面(https://github.com/dusty-nv/jetson-inference)并按照说明页面从源下载并构建的步骤进行。当要“下载模型”选项出现时,请选择“所有模型”,当

“安装PyTorch”选项出现时-选择两者,我们可能会在以后需要它们。  

影像分类

我们将探索的示例使用了经过预训练的神经网络(使用ImageNet数据集进行训练)对给定的示例图像进行分类-dog\_0.jpg。

$ cd ~/jetson-inference/build/aarch64/bin
$ ./imagenet-console –network=resnet-50 dog_0.jpg
..
..
imagenet-console:  'dog_0.jpg' -> 34.40443% class #199 (Scotch terrier, Scottish terrier, Scottie)

在首次运行时,它将下载约100MB经过预先训练的网络。但后随后需要几秒钟时间加载这个本地缓存的副本。这里我们看到它已将狗识别出来并将它的品种标识为最有可能的苏格兰梗。  

 照片集分类

对一张照片进行分类很有趣,但是我的目标是对3,500张度假照片进行分类和标记。我可以使用批处理脚本,以处理一个图像大约5秒钟的速度,这样全过程大概需要5个小时。

从程序的文本输出中我们可以看到,在一开始加载经过训练的网络的过程花费很多时间。 

我们对这个应用程序做了微调,这样经过训练的网络只加载一次,就可以用于整个图像列表。

~/jetson-inference/build/aarch64/bin/imagenet-console --network=resnet-50 *.jpg

此时浏览12张照片大约需要45秒钟。比批处理脚本需要的60秒要快,但这样好吗?让我们来看看。

分析图像分类程序

我们使用Arm Forge的分析工具(MAP)来探索应用程序如何花费时间。MAP是一个应用程序性能分析器,支持C,C ++,Fortran Python和NVIDIA CUDA。(评估许可证获取和软件包下载,点击这里
安装完成后,让我们启动MAP并进行分析

$ ~/arm/forge/bin/map ./jetson-inference/build/aarch64/bin/imagenet-console --network resnet-50 ~/images/*.jpg

MAP显示了应用程序的配置文件:时间轴分别标识了CPU时间(绿色)和等待GPU的时间(紫色)。
image.png屏幕底部是堆栈视图,执行代码从上而下的视图。我们的初始运行时间为42秒。大约5-6秒(12%)的时间在加载网络(imageNet :: create),然后用36秒对12个图像进行分类(classifyImage )。每张图片需要3秒,完成所有假期快照的分类需要3小时。

时间轴大部分是绿色的,实际上绿色部分占用大约90%的时间。 

这很重要,这表明CPU不需要花费很多时间来等待GPU。这意味着不需要优化GPU的时间:CPU不会因此而停止工作。换句话说,CPU是此应用程序的性能瓶颈,而不是GPU。

图像进行分类的时间占用超过83%。让我们对图像分类进行更深入地研究和探索。

image.png
大部分时间用于加载图像(占总时间的61.3%),而不是实际分类的时间。这显然是可以进行优化的重点对象。

首次轻松优化

Jetson推理套件中使用STBI作为图像阅读器,用于读取图像的单文件库。对于其他更专业的JPEG工具,也许我们可以做得更好? 

经过一些简短的研究得出的结论是,最新的GCC具有“ -O3” 优化级别,这将使STBI几乎与目前开放的最快实施方案一样快。让我们这样试试看是否有帮助。

$ cmake -DCMAKE_CXX_FLAGS="-g -O4" ../
$ make

确实有效,运行时间减为20秒,加载时间减为6秒,每个图像处理时间几乎减半一秒。 

现在我们只需要1个小时来处理照片了。

那么,这就足够了吗,一个简单的编译标志就可以完成所有工作吗?让我们再次使用MAP进行概要分析。
image.png
时间轴显示,尽管GPU使用率成比例增加,但由于CPU现在加载图像的速度更快,我们在GPU上的等待花费仍不到20%。在CPU内核上加载图像仍然花费大量时间。因此,优化仍然需要在CPU部分中有所作为。

使用线程进行并行化

Jetson
Nano

如果我们使用所有CPU内核将图像提供给GPU,我们应该能更快地完成任务。

使用C ++ 11的线程类,并创建一个线程池来处理图像(每个内核一个线程)。我们使每个线程负责一个图像,分配其内存,加载该图像,并使用该图像调用GPU上经过训练的网络。由于没有关于底层imageNet类线程安全性的承诺,我们将使用Mutex进行安全播放,并防止一些关键的GPU周围出现错误。通过检查我们知道JPEG阅读器是安全的。

这次总处理时间降到了12秒。 

通过选择GUI中的“View
threads”选项,用MAP对四个内核如何使用进行了另一番观察。
image.png
启用此选项后,“Application activity”栏中的不同高度分别对应四个物理核。图中显示了初始的网络加载阶段(一个内核的活动线程为绿色,还有一个守护进程线程是灰色的)。接下来是非常繁忙的部分,四个CPU内核处于活动状态,而GPU的等待时间却很少(紫色)。该图的绿色部分较浅,其中工作线程在(最多)四个核上运行,而绿色部分表示主线程处于活动状态(主要在等待线程连接)。
image.png
最终的代码查看表明加载图像部分仍然很重要,同时线程同步(互斥锁)也会耗费一些时间,因此我们可能还可以做到更好的优化。 

但是能做到仅6秒钟即可加载网络,仅6秒钟即可处理12张照片,即每张0.5秒钟的速度,我们做的也已经足够了。

3500张照片将在30分钟内完成。

有关使用Arm Forge进行应用程序性能分析的更多信息,请访问 Arm Developer

作者:David Lecomber
翻译:Khorina
原文链接:https://community.arm.com/developer/tools-software/hpc/b/hpc-blog/posts/optimizing-nvidia-cuda-ml-inference-application-performance-with-arm-forge

推荐阅读
关注数
23582
内容数
1033
Arm相关的技术博客,提供最新Arm技术干货,欢迎关注
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息