当前位置:   article > 正文

TensorRT8 meets Python(三) Onnx+TensorRT推理(案例基于yolov5 6.0)_导出模型时加了half onnx推理时也要加 --half

导出模型时加了half onnx推理时也要加 --half

 1.前言

        在前面两篇我们介绍了TensorRT的环境部署以及TensorRT的功能性介绍。

        在使用tensorRT的时候,最常见的方式就是各种训练框架都基于Onnx来做中间转换,通过Onnx来生成TensorRT engine,进而享受到TensorRT的推理速度。所以本文就以常见的检测模型yolov5 6.0 来进行TensorRT模型的部署。整体测试都是基于TensorRT python backend,在开始之间请大家思考如下问题?

        1.推理时所选的BatchSize 和 Fps的关系是怎样的?

        2.TensorRT推理一定比pytorch推理快吗,我们要无脑替换成TensorRT是正确的使用姿势吗?受到哪些因素的影响?对于使用python作为主要开发语言的同学来说,什么时候考虑使用tensorRT作为推理引擎,什么时候使用pytorch呢?

        带着这些问题,我们共同开始今天的学习。本文的实验环境是GPU V100S 32G,Intel(R) Xeon(R) Gold 6248R CPU @ 3.00GHz 。本文选用的方式是Explict batchSize,batchsize是从onnx模型继承过来的,关于Explicit 和 Implicit的方式,我们后续再进行介绍。

2.onnx转化nvidia engine的两种方法

2.1 onnx模型导出

        关于onnx模型导出的问题,我觉得还是另开篇章去讲述吧。export的代码yolov5官方已经提供了。大家可以参考一下这份代码:

yolov5官方onnx的转换代码

使用方法:

python export.py --weights yolov5s.pt --include torchscript onnx openvino engine coreml tflite

重要参数:

  • --weights : yolov5 pt模型的路径
  • --batch-size: 推理的batchsize,这里使用静态的batchsize,必须在onnx内指定。
  • --half:如果使用fp16精度进行推理,则需要指定这个参数。

我们需要测试不同batchsize下面的tensorRT模型,所以导出的时候指定不同的batchsize。(注意这是在Explicit batch模式下进行的)。另外我这边选择测试的是fp16,所以需要都加上--half参数。

2.2 基于Trtexec命令行工具

       最简单的方法就是使用trtexec作为onnx转换的工具。trtexec是Nvidia TensorRT自带的命令行工具。一方面可以帮助我们进行onnx或者其他格式的模型快速转换到TensorRT engine,包括指定batchsize、precision等,另一方面可以帮助我们测试模型的基准性能。

        因为我要测试不同batchsize下,tensorRT与pytorch之间的推理性能差距,所以在导出的时候需要使用对应的onnx模型。

trtexec --onnx=yolov5s.onnx  --saveEngine=yolov5s_engine_fp16_b64.trt  --inputIOFormats=fp16:chw --outputIOFormats=fp16:chw --explicitBatch --fp16

        这里因为是测试半精度,所以需要--inputIOFormats=fp16:chw --outputIOFormats=fp16:chw,这两个参数。我这里遇到一个坑就是,只指定--fp16,另外两个参数如果不指定的话,得到的模型推理速度会很慢。查阅文档是说默认是按照fp32的精度进行,即使单独指定了--fp16参数。

        我分别测试了4、8、16、32、64五组batchsize,得到了对应的5个engine模型。

2.3 基于tensorRT的python接口进行转换

另一种方式是基于tensorRT的python接口进行engine转换。

  1. EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
  2. TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
  3. def ONNX_to_TRT(onnx_model_path=None, trt_engine_path=None, fp16_mode=False):
  4. """
  5. 仅适用TensorRT V8版本
  6. 生成cudaEngine,并保存引擎文件(仅支持固定输入尺度)
  7. Serialized engines不是跨平台和tensorRT版本的
  8. fp16_mode: True则fp16预测
  9. onnx_model_path: 将加载的onnx权重路径
  10. trt_engine_path: trt引擎文件保存路径
  11. """
  12. builder = trt.Builder(TRT_LOGGER)
  13. network = builder.create_network(EXPLICIT_BATCH)
  14. parser = trt.OnnxParser(network, TRT_LOGGER)
  15. config = builder.create_builder_config()
  16. config.max_workspace_size = GiB(1)
  17. if fp16_mode:
  18. config.set_flag(trt.BuilderFlag.FP16)
  19. with open(onnx_model_path, 'rb') as model:
  20. assert parser.parse(model.read())
  21. serialized_engine = builder.build_serialized_network(network, config)
  22. with open(trt_engine_path, 'wb') as f:
  23. f.write(serialized_engine) # 序列化
  24. print('TensorRT file in ' + trt_engine_path)
  25. print('============ONNX->TensorRT SUCCESS============')

3. python调用tensorRT接口进行推理

  1. class TrtModel():
  2. '''
  3. TensorRT infer
  4. '''
  5. def __init__(self, trt_path, device=1):
  6. self.cfx = cuda.Device(device).make_context()
  7. self.stream = cuda.Stream()
  8. TRT_LOGGER = trt.Logger(trt.Logger.INFO)
  9. # 启动一个tensorRT pyton runtime
  10. runtime = trt.Runtime(TRT_LOGGER)
  11. # 反序列化模型,使用runtime加载模型
  12. with open(trt_path, "rb") as f:
  13. self.engine = runtime.deserialize_cuda_engine(f.read())
  14. # 创建执行上下文
  15. self.context = self.engine.create_execution_context()
  16. # 模型输入的尺寸
  17. intype = self.engine.get_binding_dtype("input")
  18. insize = trt.volume(self.engine.get_binding_shape("input"))
  19. # fp16 占2个字节 fp32 占4个字节
  20. insize = insize * 2 if intype == DataType.HALF else insize * 4
  21. # 模型输出的尺寸
  22. otype = self.engine.get_binding_dtype("output")
  23. osize = trt.volume(self.engine.get_binding_shape("output"))
  24. osize = osize * 2 if otype == DataType.HALF else osize * 4
  25. otype = np.float16 if otype == DataType.HALF else np.float32
  26. # 分配输入输出的显存
  27. self.cuda_mem_input = cuda.mem_alloc(insize)
  28. self.cuda_mem_output = cuda.mem_alloc(osize)
  29. self.bindings = [int(self.cuda_mem_input), int(self.cuda_mem_output)]
  30. self.output = np.empty(self.engine.get_binding_shape("output"), dtype=otype)
  31. def __call__(self, img_np_nchw):
  32. '''
  33. TensorRT推理
  34. :param img_np_nchw: 输入图像
  35. '''
  36. self.cfx.push()
  37. #将数据从内存拷贝到显存中
  38. cuda.memcpy_htod_async(self.cuda_mem_input, img_np_nchw.ravel(), self.stream)
  39. #tensorRT异步并发推理
  40. self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
  41. #将数据从显存传输到内存
  42. cuda.memcpy_dtoh_async(self.output, self.cuda_mem_output, self.stream)
  43. # 等待所有cuda核完成计算
  44. self.stream.synchronize()
  45. self.cfx.pop()
  46. return self.output
  47. def destroy(self):
  48. # Remove any context from the top of the context stack, deactivating it.
  49. self.cfx.pop()

        这是一个简化版本的tensorRT推理类。其原理其实就是提前开辟显存空间,然后把数据从内存拷贝到显存。在显卡上面利用cuda核进行并行计算,然后在从显存把结果数据拷贝到内存。

        另外关于pagelock memory(锁页内存)的使用,有些代码里会提前开辟锁页内存,我在实际测试的时候发现,并没有特别的速度提升。

4. 关于实验结论

        我在实际测试的时候发现,对于不同的batchsize,在tensorRT的python backend上面,不是完全都呈现出比pytorch推理更快的现象,而是呈现出有规律的趋势。在batchsize比较小的时候,tensorRT推理具备碾压的性能优势,但是随着batchsize的增大,这个gap在慢慢变小,在大于32、64等的情况下,反而没有pytorch直接推理快。

        在c++上面的测试,整体上tensorRT的推理都比pytorch快,但是同样这个gap在减少。

        有知道原因的朋友,求点拨。下面是我记录的实验记录。

         可以明显的看到这个趋势。所以在用python封装backend的时候,感觉tensorRT更适合小batchsize的使用场景,比如单张图片的某种推理服务。如果是视频推理的情景,可能不间断要一批批帧画面进行推理,对于这种情况,用pytorch的半精度直接推理可能效果反而更好,并不是无脑使用的。

 以上是以yolov5 6.0做的实验,来记录python backend的使用方法和记录测试结果。谢谢阅读。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/57690
推荐阅读
相关标签
  

闽ICP备14008679号