2023年11月29日发(作者:)

PyTorch2ONNX2TensorRT踩坑⽇志

PyTorch2ONNX2TensorRT 踩坑⽇志

麦克斯韦恶魔 2019-12-07 15:30:05 10543 收藏 26

分类专栏: 学习笔记 # linux gpu 相关 # TRT ⽂章标签: onnx pytorch tensorrt 转换 onnx2tensorrt

版权

PyTorch2ONNX2TensorRT 踩坑⽇志

PyTorch写的⽹络,通过ONNX,使⽤TensorRT序列化,最终完成模型加速的全流程踩坑⽇志。

2019/12/07 初版

2019/12/17 更新AdaptivePooling, BUG思路

2019/12/27 添加AdaptivePooling⽰例

2020/01/01 添加VGG16⽰例链接

实验环境

ONNX可以不⽤安装,对ONNX2TRT没有影响,推荐使⽤anaconda管理包。

Ubuntu 16.04

RTX2080TI, Driver Version: 410.79

CUDA 10.0

cudnn 7.6.3 (经测低版本如7.5.0⽆影响)

pycuda 2019.1.2

pytorch 1.3.1

torchvision 0.4.2

tensorrt 6.0.1.5

python 3.6.9

经测ONNX⽆法使⽤,建议使⽤python 3.7.x

onnx 1.6.0

protobuf 3.9.2 (需要降级到3.9.x,不然onnx会报.20的错)

1. RuntimeError: ONNX export failed: Couldn’t export operator aten::upsample_bilinear2d

⽆法解决,ONNX2TensorRT报错,待TensorRT后续版本⽀持,见后⽂替代⽅法#4

近似地,应该与警告信息 UserWarning: ONNX export failed on upsample_bilinear2d because align_corners == True not supported 相关联。

原因

转换ONNX使⽤的版本较低,不⽀持。另外,参考源码, 默认使⽤ opset_version=9

解决办法

警告信息已经完整说明,ONNX's Upsample/Resize operator did not match Pytorch's Interpolation until opset 11.,因此将ONNX的导出代码中规定其版本,具体如下:

import torch

(model, ..., opset_version=11)

1

2

较完整报错信息

输出的个⼈信息就被我隐去了,也为了报错、警告的简洁,所以这⾥叫做较完整,此说明后续不再赘述。

UserWarning: You are trying to export the model with onnx:Upsample for ONNX opset version 9. This operator might cause results to not match the expected results by PyTorch.

ONNX's Upsample/Resize operator did not match Pytorch's Interpolation until opset 11. Attributes to determine how to transform the input were added in onnx:Resize in opset 11 to support Pytorch's behavior (like coordinate_transformation_mode

We recommend using opset 11 and above for models using this operator.

UserWarning: ONNX export failed on upsample_bilinear2d because align_corners == True not supported

RuntimeError: ONNX export failed: Couldn't export operator aten::upsample_bilinear2d

1

2

3

4

5

6

7

2. RuntimeError: ONNX export failed: Couldn’t export operator aten::adaptive_avg_pool2d

⽆法解决,ONNX2TensorRT报错,待TensorRT后续版本⽀持,见后⽂替代⽅法#5

类似错误 aten::adaptive_avg_pool*donnx#63, pytorch#14395, h#30204

原因

因为PyTorch的⽹络中⽤了 veAvgPool2d ,个⼈感觉,ONNX没有 avg_pool2d 操作,见ONNX Operator,所以就会报错 aten::adaptive_avg_pool2d ⽆法转换。

解决办法

参考pytorch#14395添加额外Option,如下:

import torch

(model, ..., operator_export_type=_ATEN_FALLBACK)

1

2

该⽅法只是阻⽌ONNX替换PyTorchOP、⽽是使⽤ATenOP替换,PyTorch2ONNX能通,但ONNX2TRT却不能通,原因是ONNX phaser识别不到⾮ONNXOP

较完整报错信息

UserWarning: ONNX export failed on adaptive_avg_pool2d because output size that are not factor of input size not supported

RuntimeError: ONNX export failed: Couldn't export operator aten::adaptive_avg_pool2d

1

2

3

3. Error: In node 2 (importGather): UNSUPPORTED_NODE: Assertion failed: !(data->getType() == nvinfer1::DataType::kINT32 && nbDims == 1) && “Cannot perform gather on a shape tensor!”

原因

"Cannot perform gather on a shape tensor!",⽹络内部使⽤x_size = ()[1:]等类似操作,TensorRTtrace的时候,会被解析成⼀个shape layer的输出,获得⼀个shape tensor,⽤Netron⼯具可视化就可以发现,对应的node 2实际上是个Consta

解决办法

不使⽤该操作,另⼀种解法来⾃onnx-tensorrt#192

x_size = ()[1:]

1

4. Error: In node 1 (importUpsample): UNSUPPORTED_NODE: Assertion failed: (nbDims >= 1) && (nbDims <= 3)

使⽤Netron⼯具可视化模型,找到对应的node 1,就可以发现对应的是olate(x, size=(128, 128), mode='bilinear', align_corners=False)操作。

原因

⽬前ONNX2TRT的转换过程中,貌似不⽀持olatebilinear模式,只⽀持linearnearest

解决办法

将所有的bilinear模式替换为nearest模式。

5. 使⽤AvgPooling替换AdaptivePooling

针对2. RuntimeError: ONNX export failed: Couldn't export operator aten::adaptive_avg_pool2d问题,使⽤AvgPooling替换AdaptivePooling。因为ONNX⽀持AvgPoolingPyTorch2ONNXONNX2TRT流程能够跑通。

原因

⽬前PyTorch2ONNX流程中,ONNX并不⽀持AdaptivePooling操作,该操作仅存于PyTorch中。

解决⽅法

参考[开发技巧]·AdaptivePoolingMax/AvgPooling相互转换⼀⽂、PyTorch官⽅⽂档可知,AdaptivePooling可通过输⼊⼤⼩input_size⾃适应控制输出⼤⼩output_size,⽽⼀般的AvgPooling/MaxPooling则是通过kernel_sizestridepadding来计算

o u t p u t _ s i z e = c e i l ( ( i n p u t _ s i z e + 2 p a d d i n g − k e r n e l _ s i z e ) / s t r i d e ) + 1 mathbf{output_size} = ceil(( mathbf{input_size} + 2 * mathbf{padding} - mathbf{kernel_size}) / mathbf{stride})+1

output_size=ceil((input_size+2padding−kernel_size)/stride)+1

因此通过input_sizeoutput_size反推kernel_sizestridepadding,参考官⽅源码将padding设为0,那么可推出去kernel_sizestride

s t r i d e = f l o o r ( i n p u t _ s i z e / o u t p u t _ s i z e ) mathbf{stride} = floor(mathbf{input_size} / mathbf{output_size})

stride=floor(input_size/output_size)

k e r n e l _ s i z e = i n p u t _ s i z e − ( o u t p u t _ s i z e − 1 ) s t r i d e mathbf{kernel_size} = mathbf{input_size}- (mathbf{output_size}-1) * mathbf{stride}

kernel_size=input_size−(output_size−1)stride

⽰例

例如,PyTorch⽹络的某⼀层含有veAvgPool2d(output_size=(14,14)),它的output_size(14, 14),该层的输⼊特征图⼤⼩为10*128*128,那么输出的特征图⼤⼩为10*14*14,那么带⼊公式,可计算出l2d(kernel_size, stride)st

import torch

from torch import nn

input = (10, 36, 36)

AAVP = veAvgPool2d(output_size=(12,12))

AVP = l2d(kernel_size=(3,3), stride=(3,3))

output_AAVP = AAVP(input)

output_AVP = AVP(input)

1

2

3

4

5

6

7

8

9

6. PyTorch2ONNXONNX2TRT到底哪⾥出了问题?

下⾯是遇到⽆法解决的问题后该找谁问的⼀个思路:

PyTorch2ONNX是调⽤的PyTorch内部的转换脚本,所以PyTorch2ONNX出了问题,需要去PyTorch那⾥的issue寻找解决办法;ONNX2TRT是使⽤ONNX⾃⼰写的转换脚本onnx-tensorrt,同理如果ONNX2TRT出了问题,就需要到它的那⾥找解决办法

那怎么让报错暴露出来呢,下⾯是⼀些办法。

解决⽅法

按下列⽅法多半能找到问题所在。

1. PyTorch2ONNX

更新PyTorch到最新版,⼀般最新版中ONNXOP⽀持应该会更多;

按下列代码将⽇志等级调到最⾼,逐⼀分析。

import torch

(..., verbose=True, ...)

1

2

2. 检测ONNX模型

下载Netron可视化⾃⼰的ONNX模型,分析是否与PyTorch模型⼀致,或者与⾃⼰想造的模型⼀致。多看看resizeshapepermute操作,ONNX对这些需要对tensor切⽚的操作不是很⽀持。

3. ONNX2TRT

更新onnx-tensorrt库,也就是。下⾯贴⼀段TRT的安装步骤:

安装TRT.

编译onnx-tensorrt.

移到TRTlib⽂件夹中.

按下列代码将⽇志等级调到最⾼,逐⼀分析。

import tensorrt as trt

TRT_LOGGER = (E)

1

2

最终解决办法

放弃ONNX2TRT吧,PyTorchONNXTRT的版本难以互相⽀持,在版本的迭代中任意节点不⽀持了,整个链路就会断掉,另外TRT是闭源的项⽬,你完全不知道ONNX2TRT的过程中出了哪些问题,就算有堆栈信息,也不可能根据信息去trace它的

Pytorch 2 TRT python API

使⽤TRT提供的python接⼝,构建⽹络,整个流程⼗分简单,⼤家可以看看TRT提供的⽰例/samples/python/network_api_pytorch_mnist/,与之对照的是/samples/python/network_api_pytorch_mnist/

def populate_network(network, weights):

# Configure the network layers based on the weights provided.

input_tensor = _input(name=_NAME, dtype=, shape=_SHAPE)

"""

TRT python API

"""

_output(tensor=_output(0))

1

2

3

4

5

6

7

8

你只需要把这个populate_network写出来就好了,weights就是⽹络的权重了,由()得到,是不是超级简单啊。想使⽤PyTorcholatebilinear模式?TRT提供!下篇⽇志将会记录如何使⽤TRT python API搭建简单的VGG16⽹络,我

————————————————

版权声明:本⽂为CSDN博主「麦克斯韦恶魔」的原创⽂章,遵循CC 4.0 BY-SA版权协议,转载请附上原⽂出处链接及本声明。

原⽂链接:/github_28260175/article/details/103436020