基于TorchScript、TensorRT和DeepStream的1840FPS目标检测

2020-10-18 20:23:28

在此之前,我们采用了一个简单的视频管道,并在不牺牲Python运行时的灵活性的情况下使其尽可能快。你能走到9FPS到650FPS的程度是令人惊讶的,但我们没有达到完全的硬件利用率,流水线也没有线性扩展到单个GPU之外。有证据(使用gil_load衡量)表明,我们受到了一个基本的Python限制,即多线程争夺全局解释器锁(GIL)。

在本文中,我们将进一步提高相同SSD300模型的性能,将Python抛诸脑后,向真正的生产部署技术迈进:

火炬脚本。我们不会直接在Pytorch运行时中运行,而是使用TorchScript跟踪将我们的模型导出到一种可以使用libtorch C++运行时可移植执行的形式。

TensorRT.。NVIDIA的这个工具包包括一个“深度学习推理优化器”--一个用于优化基于CUDA的计算图形的编译器。我们将利用这一点来挤出推理效率的每一滴。

DeepStream公司。虽然GStreamer为我们提供了一个构建媒体管道的广泛元素库,但DeepStream用一组专门用于机器学习的GPU加速元素扩展了这个库。

本文不会是带有代码示例的分步教程,但将展示将这些技术组合在一起时的可能性。关联的存储库位于以下位置:github.com/pbridger/deepstream-video-pipeline.。

TorchScript和TensorRT都可以生成我们模型的部署就绪形式,那么为什么我们需要这两种形式呢?这些伟大的工具最终可能会成为竞争对手,但在2020年,它们是互补的-它们都有各自的弱点,可以由对方来弥补。

火炬脚本。只需几行torch.jit代码,我们就可以从基本上任何Pytorch模型生成部署就绪的资产,该模型可以在libtorch运行的任何地方运行。它本身并不快(它提交的内核序列大致相同),但是libtorch运行时在高并发的情况下会执行得更好。但是,如果不小心,TorchScript输出可能会带来性能和可移植性方面的意外(我将在后面的文章中介绍其中的一些内容)。

TensorRT.。一个无与伦比的模型编译器,适用于NVIDIA硬件,但对于基于Pytorch或ONNX的模型,它支持不完整,可移植性差。有一个插件系统可以添加任意层和后处理,但没有专门部署团队的团队无法完成这项低级工作。TensorRT也不支持交叉编译,因此必须在目标硬件上直接优化模型-这对于嵌入式平台或高度多样化的计算生态系统不是很好。

让我们从本系列上一篇文章的基线开始-目标检测分6步从9FPS到650FPS。

在我之前的帖子中,GPU阶段的后处理在逻辑上最接近我们的第一个DeepStream流水线。在基于Python的优化之旅中,这是一个相当缓慢的早期阶段,但是DeepStream在批处理和内存传输方面的限制使其成为最佳比较。

在我们建立并运行了基本的DeepStream管道之后,我们将从经验上理解并消除我们看到的限制。

我们在DeepStream管道中同时使用TorchScript和TensorRT的方法是构建一个包含两个连续组件的混合模型-TensorRT前端将结果传递给完成计算的TorchScript后端。

我们的混合管道最终将使用DeepStream的nvinfer元素直接在媒体管道中为TensorRT编译形式的SSD300模型提供服务。由于TensorRT无法编译整个模型(由于不支持的ONNX操作),我们将把其余的操作作为TorchScript模块运行(通过parse-bbox-func-name挂钩)。

但是,第一条管道将是最简单的,同时仍然遵循混合模式。TensorRT模型不进行任何处理,只是将帧传递给TorchScript模型,后者执行所有的预处理、推断和后处理。0%TensorRT,100%TorchScript。

该流水线以110FPS的速度运行,没有跟踪开销。但是,这个TorchScript模型已经转换为FP16精度,因此直接与基于Python的管道进行比较有点误导。

让我们使用NVIDIA的NSight Systems深入跟踪以了解执行模式。我已经放大到两个16帧批次的处理过程:

查看GstNvInfer行上的红色NVTX范围,我们可以看到正在处理16帧批次的重叠范围。然而,从16个使用率峰值来看,GPU上的处理模式相当清晰-它是逐帧处理的。我们还看到设备和主机之间持续不断的内存传输。

深入查看只有两帧处理,模式就更清晰了:

Nvinfer将成批的帧发送到已配置的模型引擎(我们的空TensorRT组件)-很好。

然后,nvinfer将模型输出逐帧发送到后处理挂钩(我们的TorchScript组件)。

由于我们已经将整个模型放到了TorchScript后处理钩子中,现在我们正在逐帧处理,没有批处理,这导致GPU利用率非常低。(这就是为什么我们要与没有批处理的Python管道进行比较的原因)。

我们使用DeepStream与设计相反,但是要构建一个真正混合的TensorRT和TorchScript管道,我们需要批处理后处理。

Nvinfer的设计假设模型输出将逐帧进行后处理。这使得编写后处理代码稍微容易一些,但是默认情况下效率很低。预处理、推理和后处理逻辑应始终假定存在批处理维度。

上面的NSight Systems视图还显示了设备到主机,然后是主机到设备的毫无意义的传输序列。紫色设备到主机内存的传输是由于nvinfer将张量发送到系统内存,为后处理代码使用它做好了准备。绿色的主机到设备的传输是我将这个内存放回它所属的GPU上。

这是早期机器学习方法的遗产。现代深度学习管道将数据端到端地保存在GPU上,包括数据增强和后处理。有关这方面的示例,请参阅NVIDIA的DALI库。

谢天谢地,NVIDIA为nvinfer管道元素提供了源代码。我做了两项更改,以更好地支持我们在后处理挂钩中进行重要工作的方法,并修复上述限制:

现在,nvinfer模型引擎输出以单批形式发送到后处理挂钩。

这些nvinfer更改没有发布,也不会出现在配套的存储库(github.com/pbridger/deepstream-video-pipeline))中,因为它们明显是nvinfer的派生版本,我不确定是否会获得许可。NVIDIA的朋友们,请随时联系:paul@paulbridge ger.com。

由于DeepStream已被黑客攻击,而且没有任何模型更改,在没有跟踪开销的情况下进行测量时,此管道现在达到了350FPS。这比常规DeepStream的110 FPS要高。我想我们应该得到一张图表:

Python管道中的并发1x2080Ti阶段现在在FPS和应用的优化方面是最接近的比较。这两条管道都具有批处理推理、在GPU端到端解码和处理视频帧以及批处理级别的并发性(请注意下面重叠的NVTX范围)。Python管道中的另一个并发级别是多个重叠的CUDA流。

我们现在有很好的GPU利用率和非常少的不必要的内存传输,因此前进的道路是优化TorchScript模型。到目前为止,TensorRT组件完全是直通的,从预处理、推理到后处理的一切都在TorchScript中。

现在是开始使用TensorRT优化器的时候了,准备好迎接一些刺激吧。

根据NVIDIA的说法,TensorRT“极大地加速了深度学习推理性能”,那么为什么不用TensorRT编译100%的模型呢?

Pytorch导出到TensorRT由两个步骤组成,这两个步骤都提供了不完全支持的机会:

如果您尝试为整个模型(包括后处理的SSD300)创建优化的TensorRT引擎,您将遇到的第一个问题是在后处理期间将REPEAT_INTERLEVE操作导出到ONNX。Pytorch 1.6不支持此导出,我不知道为什么。

就像在整合编译器之前编写C++一样,通常可以重写模型代码来解决不支持的操作。请参见ds_ssd300_5.py以获取替换了REPEAT_INTERLEVE的示例,现在将导出到ONNX。但是,现在TensorRT编译因另一个不受支持的操作而失败-没有为op:ScatterND注册导入器。

如果您有一个专门的部署团队--只需编写自定义插件和CUDA内核--处理所有这些问题是可以的,但是大多数团队没有这些资源或时间来进行投资。

这就是混合方法工作得如此好的原因--我们可以从TensorRT优化中获得大部分模型的好处,并用TorchScript覆盖其余部分。

从350FPS提升到920FPS是一个巨大的飞跃,我们仍然只使用一个2080Ti的GPU。让我们检查一下夜视系统,了解这是如何实现的:

批次N的TensorRT推断现在与批次N-1的TorchScript后处理交错/并发,有助于填补利用率缺口。

TensorRT预处理和推理比TorchScript版本快得多。大约43ms的TorchScript预处理和推理变成了大约16ms的等效TensorRT处理。

这个Night Systems跟踪输出现在看起来有点像我们的目标:

简而言之,是的,我们确实需要入侵DeepStream以获得最佳吞吐量。除非你喜欢360FPS的声音,当你可以达到920FPS的时候。这是一种倒退,所以我不会将其添加到我们的图表中。

以下是我们使用TorchScript最终处理运行TensorRT优化模型时的跟踪:

将我们的基于Python的流水线可用的硬件增加一倍,将吞吐量从350FPS提高到650FPS,大约增加了86%。这是一个单独的Python进程驱动两个非常强大的GPU,所以这是一个很好的结果。考虑到测量到的GIL争用在45%左右,进一步扩展将变得效率较低,可能需要多进程方法。

我们的DeepStream管道是从Python启动的,但是除了每秒一次的空消息循环之外没有回调,所以没有机会争用GIL。在没有跟踪开销的情况下测量,这些DeepStream管道显示出完美的100%可伸缩性(至少从1个设备到2个设备),最高可达1840 FPS。这就像圣诞节的早晨。

顺便说一句,在启用NSight Systems跟踪的情况下,前面的大多数阶段的吞吐量下降了大约15%,而这条管道的吞吐量下降了40%。如果下载并分析链接的跟踪文件,您将看到这种差异。

我们有一条能够实现1840FPS有效目标检测吞吐量的流水线,这是非常惊人的。这应该会令人信服地证明这些技术协同工作的有效性。

尽管TensorRT优化带来了巨大的收益,DeepStream的高效可伸缩性也带来了巨大的收益,但TorchScript是这个故事中的默默无闻的英雄。轻松导出任何Pytorch模型而无需担心缺少层或操作的能力是巨大的。如果没有TorchScript和libtorch,我仍然会编写TensorRT插件。

在以后的文章中,我将更深入地研究TorchScript导出过程,并解释一些可移植性和性能陷阱。

上面使用的GStreamer/DeepStream管道没有反映100%的实际使用情况。如果您查看流水线图(例如,ds_3_2gpu_batch16_device.Pipeline.dot.png),您将看到单个文件被多次读取并通过管道传输到nvstream mux组件。这就是将多个并发媒体流处理到单个推理引擎中的方式,但我这样做的真正原因是为了解决与批处理有关的nvstream mux的限制。有关详细信息,请阅读链接的问题,但是可以公平地说,nvstream mux不是用于在处理少量输入流时高效地组装大小的批。

同样如上所述,我的“被黑客攻击的DeepStream”代码还没有公开提供。我会努力把它整理好,如果我确定许可情况,我会让它变得可用。

最后,相关存储库中的代码并不是经过打磨的教程代码,它是粗制滥造的研究代码,所以买者自负。