TensorRT ONNX 基础(续)
PyTorch正确导出ONNX
几条推荐的原则,可以减少潜在的错误:
- 对于任何使用到 shape、size 返回值的参数时,例如
tensor.view(tensor.size(0), -1)
这类操作,避免直接使用tensor.size()
的返回值,而是用 int 强转以下,断开跟踪,即:tensor.view(int(tensor.view(0)), -1)
。 - 对于
nn.Unsample
、nn.functional.interpolate
函数,使用 scale_factor 指定倍率,而不是使用 size 参数指定大小。 - 关于 batch 动态 shape 还是宽高动态 shape
- 对于
reshape
、view
操作时,-1 的指定请放到 batch 维度,其他维度手动计算出来填上即可。batch 维度尽量不要指定为大于 -1 的明确数字。 torch.nn.export
指定 dynamic_axes 参数,并且只指定 batch 维度,尽量不要把其他指定为动态
- 对于
- 使用 opset_version = 11 ,尽量不要使用 11 以下的版本
- 避免使用 inplace 操作,例如
y[..., 0: 2] = y[..., 0: 2] * 2 -0.5
- 尽量少地出现 5 各维度,例如在 ShuffleNet Module 中,会出现 5 个维度,在导出时可以考虑合并 wh 避免 5 维
- 尽量把后处理部分放在 ONNX 模型中实现,这也可以让推理引擎顺带给加速了
- 就是用我们之前提到的在 ONNX 模型中添加节点(如前后处理等)的方法:先将前后处理写成 PyTorch 模型,单独导出为一个 ONNX 模型,再加到原模型中
这些做法的必要性体现在简化过程的复杂度,去掉 gather、shape 之类的节点,有些时候,看似不这么该也是可以跑通的,但是需求复杂后总是会出现这样那样的问题,而这样遵守这些原则,可以尽可能地较少潜在的错误。做了这些,基本就不用 onnx-simplifier 了。
原则一(不规范) | 原则一(规范) |
---|---|
使用ONNX解析器来读取ONNX文件
onnx 解析器的使用有两个选项:
libnvonnxparser.so
(共享库)- https://github.com/onnx/onnx-tensorrt (源代码)
对应的头文件是:NvOnnxParser.h
其实源代码编译后就是共享库,我们推荐使用源代码,这样可以更好地进行自定义封装,简化插件开发或者模型编译的过程,更加容易实现定制化需求,遇到问题可以调试。
源代码中主要关注的是 builtin_op_importers.cpp
文件,这个文件中定义了各个算子是怎样从 ONNX 算子解析成 TensorRT 算子的,如果我们后续有新的算子,也可以自己修改该源文件来支持新算子。
从下载onnx-tensorrt到配置好再到成功运行
TODO:自己试一下
插件实现
插件实现重点:
- 如何在 PyTorch 里面导出一个插件
- 插件解析时如何对应,在 onnx parser 中如何处理
- 插件的 creator 实现,除了插件,还要实现插件的 creator ,TensorRT 就是通过 creator 来创建插件的
- 插件的具体实现,继承自
IPluginV2DynamicExt
- 插件的序列化与反序列化
TODO:看代码,自己写
插件实现的封装
课程老师将上述复杂的 plugin 实现做了通用的、默认的封装,大部分情况下使用默认的封装即可,少数有需要自己改动的地方按照上面介绍的内容再去改,极大地减少代码量。
TODO:看代码,自己写
int8量化
int8量化简介
int8 量化是利用 int8 乘法替换 float32 乘法实现性能加速的一种方法
- 对于常规模型:y=kx+by=kx+by=kx+b ,其中 x,k,bx, k, bx,k,b 都是 float32,对于 kxkxkx 的计算使用的当然也是 float32 乘法
- 对于 int8 模型:y=tofp32(toint8(k)×toint8(x))+by=tofp32(toint8(k)\times toint8(x))+by=tofp32(toint8(k)×toint8(x))+b ,其中 kxkxkx 的计算是两个 int8 相乘,得到 int16
经过 int8 量化的模型无疑精度会降低,各种 int8 模型量化算法解决的问题就是如何合理地将 fp32 转换为 int8,从而尽可能减小精度损失
除了 fp32 和 int8,很多时候会用 fp16 模型,精度损失会比 int8 少很多,并且速度也很快。但是需要注意 fp16 仅在对 fp16 的显卡上有很好的性能,在对 fp16 无优化的显卡甚至可能速度还不如 fp32,总之 int8、fp16、fp32 要看业务对精度和速度的要求,还有部署的显卡设备来综合决定。
TensorRT int8量化步骤
- 配置
setFlag nvinfer1::BuilderFlag::kINT8
- 实现
Int8EntropyCalibrator
类,其继承自IInt8EntropyCalibrator2
- 实例化一个
Int8EntropyCalibrator
并设置到config.setInt8Calibrator
Int8EntropyCalibrator
的作用是:读取并预处理图像数据作为输入
标定过程的理解
- 对于输入图像 A,使用 fp32 推理之后得到 P1,再用 int8 推理之后得到 P2,调整 int8 权重使得 P1 和 P2 足够接近
- 因此标定时需要使用到一些图像,通常 100 张左右即可
Int8EntropyCalibrator类主要关注
getBatchSize
,告诉引擎,这次标定的 batch 是多少getBatch
,告诉迎请,这次标定的数据是什么,将指针赋值给 bindings 即可,返回 false 即表示没有数据了readCalibrationCache
,若从缓存文件加载标定信息,则可避免读取文件和预处理,若该函数返回空指针则表示没有缓存,程序会通过getBatch
重新计算writeCalibrationCache
,当标定结束后,会调用该函数,我们可以储存标定后的缓存结果,多次标定可以使用该缓存实现加速