到这里,我们已经讲解了
边缘计算笔记(一): Jetson TX2上从TensorFlow 到TensorRT
边缘计算笔记(二): 从tensorflow生成tensorRT引擎的方法
这是第三个部分,也是最后一个部分,我们继续讲解如何转换生成TensorRT优化的引擎,
例如我们想将训练好的Inception V1现成模型,从TensorFlow转换为TensorRT, 我们可以从(TensorBoard)显示的结构图的最发现找到输入节点,(从该节点的右上角信息)中,可以看出来它被叫做input,具有placeholder运算类型,然后在这段信息的output部分,我们可以看到该节点的张量输出维度(信息)。第一个维度我们可以看到这里被画上了"?"号(Question mark), 因为它代表了batch size, 是可变的。 后续的三个维度,分别代表了图像的高度、宽带、以及通道数。(这信息框)里的这些数值,在我们转换到TensorRT的时候,需要用来作为参考.
在这个网络结构图的最上方,是输出节点。它的操作类型为Squeeze, 在本网络中/在本实例中,只是用来去除输入的空间维度。这个操作层的名字叫做InceptionV1/Logits/SpatialSqueeze,从上一个节点的输出可以看到,1001个不同class label作为本节点的输入。
我们还注意到,该网络图中的每层操作,都属于被TensorRT支持的类型,所以这就允许我们转换生成TensorRT优化的引擎。
好了,既然确定了网络图层的相关信息,也生成过了冻结图文件,则在创建TensorRT推理引擎的时候,使用上这些参数就可以了!
对模型优化和创建引擎的时候,TensorRT允许设定一些自定义的引擎构建参数。 这里我们具体看下TX2的,相关优化构建参数。 首先我们可以更改最大batch size参数,该参数通常是我们调用一次推理引擎,所能一次处理的最大图片数量。 当然在运行的时候,使用比创建引擎时候所制定的这个参数小的数量,也是可以的。 第二点则是我们可以设定引擎使用的内部数据类型。如同我们之前了解过的那样,当使用FP16而不是FP32的时候,往往有显著的性能提升。 注意即使引擎内部使用了FP16数据类型,它所执行的推理网络的输入和输出依然以原来的FP32不变(这里说,FP16会提升性能。用了FP16不需要用户接口发生变化,因为网络的输入输出还是用的老类型,这样用户不需要改动代码就能享受到内部的提速)。最后第三点这是,我们设定最大的WorkSpace大小。该大小是指的网络中的不同层运行的时候,所能用到的内存大小
在NV GitHub代码库中,提供了转换脚本。而现在我们已经有了所需要的全部输入数据和信息,可以运行该脚本了。
代码库从github克隆到本地后,我们从该项目的根目录下,调用python来执行脚本。这里我们演示了一个转换InceptionV1网络模型的例子。 按照从上到下的顺序,先是冻结图文件的路径,然后是要生成的引擎序列化后的保存路径,网络的输入层名称,输入高度、大小,输出层名字,最大的batch size,最大的worksapce大小,以及最后是用FP32还是半精度。
对于一些模型来说,这个转换脚本能够毫无问题的运行,并生成序列化后的引擎。但对于一些其他模型,包含有TensorRT所不支持的操作类型的时候,会出现这个幻灯片中类似的这种警告信息。在这种情况下引擎生成/导出的过程可能会依然能够成功,但再次的反序列化/解析二进制引擎文件,和创建引擎,都会失败。 TensorRT当前并不支持TensorFlow所导出的intermediate文件中的插件层。因此我们不能在网络结果图中有不被支持的操作。 对于这种情况,我们有两种可选的处理方案:
第一种方案是,对于这些不被TensorRT支持的部分,手工在TRT引擎的输入前面,或者在TRT引擎的输出后面,用CUDA或者C++写上处理过程。
第二种方案则是修改TensorFlow中的网络结构图,删除或者替换掉这些不被支持的操作层。这种方案并不总是可行的。但只要它可行,这就意味着我们不需要手工拆分网络结构图成多个部分,并在每个部分中间手工的添加实现不被支持的操作。
下面的例子,我们将替换TensorFlow网络结构图中的,一种不被支持的操作,将它替换成一系列其他(支持的)操作。
在TensorFlow-Slim模型库中,有一种叫MobileNet的预先训练好的现成网络模型,这种模型使用了Relu6()操作层,而该操作/函数,并不被TensorRT支持。
但是,常规ReLU()函数和减法操作,可以被TensorRT支持。只要你意识到,ReLU()可以等价于这两种操作/运算的组合,我们就可以将ReLU6()替换成对应的子网络。
我们直接对GraphDef进行修改,以完成这种替换。
你可以看到如图的代码片段,我们循环遍历网络结构图中的所有节点,并判断如果它是ReLu6()运算, 那么我们先保存/标记它原本的输入和输出名,我们将一个等效的子网络结构图,同样使用刚才保存的输入和输出,然后这将原本主网络中的Relu6()节点替换成这个子图。
注意我们之前说过的,这种方法并不能对网络中所有不被支持的操作生效。 想看到更多实现该网络修改的细节,请直接参考GitHub项目页面的convert_relu6.py这个Python模块代码。既然这TensorFlow网络结构图里面已经不再有任何不被支持的操作了,我们可以运行转换脚本来生成优化的推理引擎了。
我们和之前一样,运行那个9个参数的python脚本,来生成优化的推理引擎。 只是这里我们可以确信了,只要用新的修改后冻结图文件作为输入,则转换脚本执行完成后,我们一定能得到优化的推理引擎,和能得到它序列化后保存到的文件。
到这里就结束了如何用TensorRT来优化TensorFlow模型的讨论。(然后我再讲一点)如何执行你刚才生成的优化引擎。 在台式机上,推理引擎可以通过TensorRT的C++或者Python接口(API)来执行。然而在Jetson上,TensorRT没有提供Python接口,所以我们只能用C++来执行推理引擎。 首先用任何(你喜欢的)的方法,将序列化后的引擎数据,载入到内存。 这样就得到了一些二进制字节表示的我们的引擎。 然后,通过简单的一些TensorRT的(C++ )API调用,我们将可以反序列化该引擎,并创建ExecutionContext。引擎对象保存了网络中的常数,例如权重值;而ExecutionContext对象,则保存了每次调用会变化的的数据,例如网络的activations。
一般来说,反序列化引擎,然后用它创建ExecutionContext对象,只需要进行一次。
一旦我们从引擎对象创建好了ExecutionContext对象后,我们就能通过它一句话运行网络,进行推理了!,Yeah!但在运行引擎进行推理之前,我们需要确保,能正确读取要处理的图片,还需要保证图片格式正确。 你可以用任何方式获取/读取图片,但重要的是,你需要确保图片的格式和形状大小,变成该TensorRT推理引擎所期待的样子。 因此我们需要做一些简单的图像预处理工作,来格式化图片。这种工作可以通过CUDA或者C++来完成。 当图片格式转换好后,我们需要将图片像素内容传到显存,然后获取该图片在显存中的指针。 以及,还需要准备一个指向存放引擎推理结果内容的显存指针,
两个指针一同在执行推理引擎的时候使用。 最后我们调用引擎,然后再做任何所需的后处理过程,例如将结果从显存复制回内存,然后在从结果中找到最大概率的类别索引。
我们在GitHub上的代码库里,提供了一个范例程序,演示给你/能让你看如何执行推理引擎的过程。该范例程序,从磁盘读取序列化后的引擎文件,然后读取图像文件,预处理图片,然后调用TensorRT推理引擎。然后继续后续处理引擎的的执行结果,从中选出最可能的类别。
本范例程序,分别以要被分类的图片、序列化后的引擎文件,以及其他参数作为输入。 只需在你从github上克隆到本地项目的根目录中,调用classify_image可执行文件,即可执行该程序。 执行后等一小会,就看看到程序打印出来了Top 5最可能的类别。 请注意!屏幕上这里显示的调用命令行只是用于演示目的。关于具体如何调用该范例程序,请参考GitHub上的代码库中的描述。
好了,本次笔记所涉及知识点的资源都在此了,
但是注意!!这里有一个bug!,第5个地址应该是:https://github.com/NVIDIA-AI-IOT/tf_to_trt_image_classification
更多关于TensorRT:
来份TensorRT的教程,要实战的哟!