NEWS

NVIDIA嵌入式解决方案架构师Jeff:从X86移植到NVIDIA Jetson的实践分享

2021-11-17 source:米文动力

大家好,我是NVIDIA嵌入式解决方案架构师Jeff,主要负责Jetson嵌入式解决方案方面的技术支持。今天跟大家分享的内容是关于X86向Jetson移植过程中需要注意的事项及相关内容的分享。

今天的内容大致分成5部分:

第一部分介绍Jetson的硬件结构和特点。

第二部分介绍Jetson的软件栈以及基于这些软件栈,如何更好的移植到X86。

第三部分介绍深度学习模型、算法模型的使用方法和注意事项。

第四部分介绍CUDA移植过程中需要注意的事项。

第五部分介绍性能分析需要使用的工具及使用方法。


01、Jetson的硬件结构和特点

首先,来看一下 Jetson在行业的使用情况。

 

从行业列表里(如下图),可以看到 Jetson目前在各行各业的使用还是比较广泛的,从左侧可以看到像一些自主机器,包括零售、智慧城市、农业、服务、物流等行业里面都有Jetson的身影,特别是在低功耗、便携移动的场景里,Jetson的使用非常广泛。此外, Jetson的开发者社区发展也非常迅速,使用的开发人员正在不断增加。

X86ToJetson.jpg

在这些应用场景里,可能会面临一个问题,就是用户并不是一开始就使用Jetson去做解决方案,也许是历史原因,也许是X86的解决方案已经落地使用了,但这些项目需要使用Jetson去做升级。


那如何更好、更快速的移植到Jetson,去完成硬件的升级换代呢?今天的内容就是围绕这个话题展开。


假设下图是我们的移植场景。一个自主机器,最初在X86的开发环境下,硬件架构是英特尔的CPU,有类似1070的独立显卡插在上面,完成GPU加速。


硬件结构方面,整体功耗在200W以上,因为独立显卡的功耗会相对大一些, CPU单独主板也会有一定的功耗,算力有28Tops,体积也会大一些,具体到这个 case大约有4000cm³,这是一个基于X86的解决方案。

X86ToJetson-2.jpg


如果要把这个方案移植到Jetson上面,情况会怎样?


目前,Jetson系列中算力比较强的是Jetson AGX Xavier,这款模组的功耗是30W,算力是32Tops,略高于上图X86的解决方案,最关键是它的体积比较小(仅600 cm³),功耗也有数量级的降低。

X86ToJetson-3.jpg

但是,Jetson的体积虽然减小了,算力没有降低,把之前的整个pipeline移植到Jetson AGX Xavier平台上,对于很小嵌入式来说,移植过来会比较复杂。


对自主的机器来说,可能面临着前端有Sensor融合、深度估计、定位规划、障碍物检测、通讯、人机交互的可视化展示、以及对整个机器的控制。


所以对于整个pipeline来说,如果想在 Jetson平台上有更好的运行效果,就需要更深入的了解相关硬件,各个环节尽量的用硬件去做加速,并用恰当的软件做软件的优化,这就是X86向Jetson移植的Case。


想在 Jetson AGX Xavier上面能够有更好的加速,就需要对 Jetson的硬件结构有比较清晰的了解。


以Jetson AGX Xavier为例,我们来看一下它的内部结构,Jetson AGX Xavier的主要处理器除了常规的CPU、 GPU外,还有硬件加速单元——加速DLA,它起到推理加速的作用,还有视觉加速引擎,这是个单独的硬件单元,可以对视觉算法进行加速,同时还可以通过VPI接口进行调用。

X86ToJetson-4.jpg

另外对于很多项目来说,视频编解码是非常重要的一环,而Jeston AGX Xavier有专用的视频编解码硬件来进行编解码的操作,大大减轻了CPU负担。pipeline移植过来后,只有充分使用到这些硬件加速单元,才有可能让整个pipeline有比较好的性能。


02、Jetson软件栈迁移分析

那如果要移植,Jetson的软件栈有哪些软件可以使用呢?


这是Jetson整体软件栈的结构图,可以看到底层是硬件,往上一层是CUDA统一的架构,做底层的硬件加速,再往上一层是TensorRT,做推理引擎加速,还有多媒体的接口,可以直接调用编解码、转码等专用加速硬件,还有VisionWorks包括OpenCV,基于这些视觉库来加速计算。

X86ToJetson-5.jpg


再往上一层提供了Pre-Training Model,可以帮助大家更快的去做移植开发。因为很多模型,如果单纯从X86移植过来,其实还有很多优化工作,我们提供预训模型以后,在这个优化框架下(目前叫TAO),可以更快的在Jetson进行部署。


再往上一层是两个 Framework级别的框架,一个是 DeepStream,另外一个是Isaac,更多可能会用DeepStream来快速搭建应用。


再来看一下整个刷机,如果是要完成SDK种类比较多的刷机,Jetson提供了Jetpack开发包,可以做快速的刷机,里面所有的SDK会直接部署到硬件上面,并且跟硬件适配,不会有环境上的问题。

X86ToJetson-6.jpg


对于最终的性能,我们也提供了Profiling工具,就是Nsight Systems这些,运行在host主机上面,一般是Linux、Ubuntu这样的系统,可以对Jetson的Program进行性能分析


想做移植首先要对环境有所了解,这是Linux系统,我们管它叫L4T(Linux for Tegra),是Jetson版本的Linux系统,目前最新版本的Kernel是4.9,Ubuntu是18.04。明年年初会出推出5.0版本,到时候Ubuntu会升级到20.04,Kernel也会升级到5.0以上。


CPU方面,目前Jetson系列的CPU都是Arm架构,Xavier系列都是INT8。所以这其实是X86向Arm环境下去做移植的过程。

X86ToJetson-7.jpg


在了解了硬件和软件环境后,就可以来考虑对于这样的环境,做移植有哪些注意事项?


这里有个通用的移植总结(跟硬件没有太大关系),主要从两个方面来说:

X86ToJetson-8.jpg

一方面,假设开发项目是开源的,源码是完整的,只需在Arm环境下重新做一次编译就可以,这是最简单最直接的方法。


当然可能除了本身的代码之外,可能也会提供Arm Package,如果有这样的Package,那么可以选择源码安装,也可以直接选用Package包直接安装,这样速度是最快的。


如果没有提供Arm Package,那就只能是源码编译,源码编译除了是自己的代码外,在开发过程中可能会依赖第三方的库,依赖第三方的库的话,就需要注意看看有没有对应的源码编译,或者说有没有提供Arm Package安装,如果有的话就可以做源码编译,或者是下载Arm Package包去安装。


但如果源码没有,Package也没有,这样的依赖库就需要换掉,换成其他Arm版本对应的库,这是基于开源项目开发的一些注意事项。

 

另一方面,从编程角度来说,假设项目是以Java或者Python这种解释型语言作为开发的,这种移植起来问题不大,因为Jetson下面其实就是Ubuntu系统,它也支持Python或者Java这些解释性语言,直接移植过来就可以运行。


如果开发环境是基于C或者C++的,就需要把源码在Jetson硬件环境下做编译,可以直接在Jetson上编译,如果工程比较大比较耗时,可以选择交叉编译。这里我们提供了链接去适配Jetson版本,如果做交叉编译的话,可以用对应版本的交叉工具去做编译。


对于Jetson直接移植,有几个Program可以借鉴,第一个是基于DeepStream,这个应用非常典型(如下图),从RTSP到解码到预处理推理,再到后处理、显示等。

X86ToJetson-9.jpg

如果是在X86上移植,依赖的硬件加速主要是GPU,其他部分可能是在CPU上跑,当移植到Jetson之后,就要充分使用Jetson已有的硬件做加速。同时Jetson有对应的NVDEC做解码。


预处理方面,提供了VIC这样专用处理器,做缩放转码之类的硬件处理,可以加快这方面的速度,也减小CPU的负担。


推理方面,除了GPU之外,还有DLA可以辅助做推理,可以提高推理吞吐。


显示方面,有专门的显示接口作为硬件的加速。

 

可以看到在移植过程当中,从X86转到DeepStream去做升级,硬件加速还是比较方便,模型推理方面,需要基于 TensorRT去手动去做一些优化,使用GPU或者DLA等等,但整体来说使用DeepStream这样的硬件加速起来会比较方便。


当然,在实际开发当中DeepStream可能会稍微麻烦点,因为它的封装比较多,中间去控制某些环节可能不太容易,所以还提供另外一个接口供大家使用,就是Multimedia API,这个基于v4I2的开源框架,封装后提供了一些接口,可以直接调用硬件加速去做Program的开发,可以看到这里面的Sample很多,从解码到CUDA的使用,包括到底下完整的从解码到推理到TensorRT再到CUDA的计算都有完整的Sample code,基于这样的一个Multimedia开发起来会更加灵活,但是相对来说复杂度也更高一些。


这里是基于Multimedia开发的 Sample,可以看到这是一个多路IVA应用,从获取视频到解码到解码再到转码或缩放,通过VIC然后再做CUDA处理,然后再去做OpenGL显示。同时也可以一路做CUDA的处理,一路做TensorRT的处理,然后再把一些处理结果做叠加显示出来。

X86ToJetson-11.jpg

基于 Multimedia Framework,可以把CUDA、TensorRT和前面的解码、转码这些硬件加速串起来,这也是没有问题的。


另外,给大家介绍一个硬件加速接口——VPI,一个视觉编程接口。这个接口主要是为了方便大家更好的去调用硬件,包括GPU、CPU、PVA、VIC都可以去调用,使用起来类似OpenCV,同时它又集成了很多现成算法,无需自己再做开发,相关API比OpenCV效率还高。

X86ToJetson-12.jpg


目前VPI是个1.1版本,从这张图可以清晰的看到,基于VPI统一接口,可以调用不同的硬件完成不同的功能,使用起来还是比较方便的。

X86ToJetson-13.jpg

这是Benchmark测试的结果,分别是OpenCV CPU跟OpenCV GPU版本,可以看到整体来说有十几倍的加速,当然有个别还有待升级,但多数来说还是有比较大幅的一个提升,所以这也是大家从X86向Jetson移植过程当中可以选择的一条路径。

X86ToJetson-14.jpg


移植过程中还有一点想跟大家强调,可能有些用户说现在有些代码实现已经用OpenCV去做了,也不是特别复杂,但是现在想快速移植做验证,能不能不用那么复杂的DeepStream或者是Multimedia重新开发,做个简单的加速优化,可不可以?


这个也是没有问题的。


基于OpenCV 实现读取视频流的时候,其实CPU的解码过程效率是比较低的,所以OpenCV提供了一个调用gstream的接口,可以硬解码,其实内部就是调用 NVIDIA的gsteamer plugin做硬件加速,所以如果是基于OpenCV来做的,可以把解码这块作为硬件加速,对应整个pipeline性能会有一个提升。

X86ToJetson-15.jpg


03、深度学习模型在移植过程当中有什么注意事项?

在Jetson或者 NVIDIA其他板卡上去做移植,一般会用TensorRT模型加速,因为只有这样才能够充分使用GPU上的算力。但这样有个问题,比如之前X86上面的模型,是在一种数据中心上显卡训练模型,如果想在Jetson上使用,那么转TRT engine这个过程要在Jetson上重新做一遍,无论是在Xavier或是TX2,都得重新做一遍才可以,这是需要注意的。

X86ToJetson-16.jpg

另外一个模型加速的方法,就是可以利用迁移学习工具——TAO,这个框架主要完成的事情是,我们提供了很多预训练模型,这些模型可以利用TAO框架完成二次训练、裁剪,然后用TAO优化完之后,转成TAO的模型。


转成TAO模型之后就可以快速部署到 DeepStream上面了,这是无缝集成的。


训练出模型之后,可以直接在DeepStream上去做调用,DeepStream也会主动地转成一个Engine,第一次调用会生成Engine,第二次再用就直接可以调用Engine去跑了,所以整个这条框架就是已有模型的使用,到最后转到Jetson上做部署,整体来说速度会非常快。


因此,如果在实际项目当中,有些应用场景跟这些已有的预训练模型比较吻合,可以尝试使用TAO模型,这样也会让你迁移起来更加方便,因为本身这些模型也是做过大量的数据,网络结构也做过优化,非常适合快速做部署。所以在X86向Jetson移植过程当中,如果有模型迁移的困难,可以考虑基于这条路径来走一走试一试。

X86ToJetson-17.jpg


所有的这些工作都做完之后,还面临一个问题就是要做测试,看整个pipeline性能怎么样,有的时候会出现跑完后性能非常差。这时候首先要怀疑迁移过来的这个模型本身推理性能怎么样,这个推理就利用另外一个工具——Trtexec,这是TersonRT上面的工具,可以直接去生成一些数据测试模型的推理速度,看它跑起来效果如何。


最关键的还是模型推理时间,这个时间要提前看一下,如果转过来以后模型这块变得非常慢,整个pipeline就先不要考虑优化了,要先把这个模型的推理时间优化上去,所以这点是需要大家在移植过程当中首先要考虑的问题。


04、代码迁移中涉及CUDA 移植,有哪些注意事项?

那Jetson跟显卡的结构有什么不一样呢?


首先数据中心的GPU卡;显卡的CPU Memory跟GPU Memory是物理分离的,GPU显卡是通过PCIE插上去,这两块Memory是独立的,相互通过PCIE总线去数据传递,但Jetson不一样,Jetson是一个物理内存,一些在传统显卡上的方法,可能在Jetson这边会显得比较冗余,比如说来回的数据拷贝。

X86ToJetson-19.jpg

Jetson有三种Memory可以供大家使用,一种是的Page Memory分页内存,一种是Pin Memory固定内存,还有一个种是Unify Memory统一内存


其中,Page Memory分页内存就是大家最常说的一种 Memory,但这个Memory不能直接在GPU Device端用,还需要Copy Device上的memory,Copy 到GPU上再做处理,这叫iGPU(Integrated GPU),然后在模组上集成到GPU再Copy回来,这是完整的Copy操作。


但物理内存来回Copy其实有些多余,这种情况下可以申请pin Memory固定内存,这个不会被分页交换出去,iGPU可以直接访问,而且没有Cache直接访问,效率会高很多。


另外一种Unify Memory,跟可分页内存类似,但是它把Cache这段隐藏在了Driver层面,没有Memory来回Copy的操作,隐藏在 Memory层面,这种效率也比较高,所以有这三种不同的Memory可以供大家选择。

X86ToJetson-20.jpg

这是关于CUDA Memory在Jetson的使用案例,主要就是Cache,像Unify Memory两头都在Cache,对固定内存来说是非常重要的点,GPU没有Cache,在CPU中有Cache,这就意味着在使用过程中,不同的场景可以选不同的方式,效率也会更高些。


所以,我们来总结一下,Page Memory不适合频繁的数据操作,如果是大块的数据来回搬运就比较合适;而Pin Memory,因为没有Cache,所以适合数据不重复的操作,就是本身不需要缓存的场景下,数据量需要频繁操作,这种场景Pin Memory就比较合适。


而统一内存Unify Memory,因为统一内存两侧都带Cache,而且简化了拷贝,所以对于重复操作也是比较合适的选择,但是对一些频繁操作,而且有一定重复性还比较合适,因为它有Cache。但如果没有特别高的重复性,那就Pin Memory比较合适,否则这样Cache了也是浪费。


下面是针对于Pin Memory和Page Memory的测试,是基于的CUDA 0_Sample零拷贝的例子,因为是零拷贝,没有传统的配置Memory,加完之后做个对比,对比后可以看到Page Memory有来回有两次拷贝动作,就DtoH HtoD这样的拷贝。对于Copy来说 Pin Memory比Page Memory稍微减小些。

X86ToJetson-22.jpg

最直观的理解就是,如果使用Pin memory至少减少两次来回拷贝动作,大家再做这种类似这种Jetson CUDA移植的时候,如果性能不太好,可以重点关注一下这个。


05、性能分析需要使用的工具及使用方法

当所有的Program都移植完成,最后一步就是Profiling的操作,NVIDIA提供了Nsight这样的工具帮助大家去快速定位,目前有三个不同的模块让大家使用,Nsight Systems是做整个Pipeline级别的一个Profiling。


Nsight Computer是对kernel内部更细力度的分析,Nsight Graphics是对做渲染类应用的分析,一般来说在Jetson上最常用的是Nsight Systems。

X86ToJetson-23.jpg


需要注意的是,Nsight Systems安装不能直接从官网下载,因为它是X86版本,刷机的时候需要选择Host Machine,选中之后Nsight Systems会把这些组件装上,装好后就可以在主机启动,启动完以后,可以远程刷IP去连接。


开始跑运行以后,在这里可以看到业务代码、Memory Copy、CUDA Core、时间占比以及时序之间的关系,都看得非常清楚,这样帮助大家更好的定位瓶颈。

X86ToJetson-25.jpg

对于Profiling再稍微多说一句,在Nsight Systems之前也有Profiling翻译工具叫NVPROF,基于这个也可以做Profiling工作,这个工具目前Jetson版本里面还有,所以大家如果不装Nsight Systems用这个也可以做直接做分析,这里可以看到各个API调用的时间占比。

X86ToJetson-26.jpg

另外,Jetson还有一个CUDA加速库叫NPP,NVIDIA已经做了优化加速的图像库和项目处理库,实现了一些功能,如果大家有类似的需求,也不一定要自己去开发,可以看到一下NPP里面有没有直接的使用,如果有的话这样也会节省你的时间。这个NPP在Jetson和显卡里面都有,所以在移植过程当中,NPP也是可以考虑优化的选择。

X86ToJetson-27.jpg


最后给大家列了一些在移植过程当中可以参考的资料,主要是关于CUDA方面。

X86ToJetson-28.jpg


今天跟大家分享的内容就是这些,谢谢大家,再见。