TENSORRT:深度学习推理加速
1.TensorRT概述
深度学习应用开发的两个阶段
- 训练:利用训练数据生成和优化网络模型
- 推理:把网络模型集成到应用程序,输入现实数据,得到推理结果
TensorRT深度优化了推理的运行效率
- 自动选取最优kernel
矩阵乘法、卷积有多种CUDA实现方式,根据数据大小和形状自动选取最优实现 - 计算图优化
通过kernel融合、减少数据拷贝等手段,生成网络的优化计算图 - 支持fp16/int8
对数值进行精度转换与缩放,充分利用硬件的低精度高通量计算能力
TENSORRT的加速效果:
- ResNet50 (输入1080p) 运行于T4
fp32: 1.4x
fp16: 6.4x (fp16 vs fp32: 4.5x) - EDVR (输入176×144) 运行于T4
fp32: 1.1x
fp16: 2.7x (fp16 vs fp32: 2.4x)
不同模型的加速效果不同,卷积模型加速较显著,含大量数据拷贝(slice/transpose)的模型加速效果一般,且fp16无明显帮助
2.快速上手TENSORRT
1)通过框架内部集成的TRT。TF-TRT/MXNet-TensorRT/TRTorch
易于使用,但远未达到最佳效率。
2)从现有框架导出模型(ONNX),再导入TRT。难度适中,效率尚可,但需要解决的问题:如何导出,如何导入。
3)使用TRT C++/Python API自行构造网络。兼容性最强,效率最高,难度最高。
普遍采用方法2,简单易上手。
2.1TRT编程流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import tensorrt as trt logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) builder.max_workspace_size = 1 << 30 network = builder.create_network() # 用TRT的层重建计算图可由ONNX parser代劳↓ data = network.add_input("data", trt.DataType.FLOAT, (c, h, w)) # ... # Add network layers # ... network.mark_output(outputLayer.get_output(0)) # 用TRT的层重建计算图可由ONNX parser代劳↑ # build过程比较长,半个小时至1个小时,build后的engine可保存至磁盘上,后续无需上述步骤,可直接加载使用 engine = builder.build_cuda_engine(network) #engine可被保存到磁盘,或从磁盘加载 context = engine.create_execution_context() context.execute_async(bindings=[d_input, d_output]) |
2.2从框架导出ONNX
ONNX是中立的计算图表示,可从PyTorch,TensorFlow(借助tf2onnx)和MXNet导出。
一般情况下,导出的ONNX仍具备运行能力。有时不能直接运行,而是需要补充ONNX Runtime。但“ONNX Runtime能支持运行”并不是“可被TRT顺利导入”的先决条件。
以PyTorch为例
1 2 3 4 5 6 |
# 参数:模型 输入数据 保存文件名 标记输入 输出名 # verbose=True以文本形式将ONNX网络结构打印出来,方便后期调试 # opset_version在支持版本中尽量调高,导出可能性更大 torch.onnx.export(resnet50, input_data, 'resnet50.onnx', input_names=input_names, output_names=output_names, verbose=True, opset_version=11) |
2.3用PARSER将ONNX导入TRT
TRT官方开发包自带trtexec
1 2 |
trtexec --verbose --onnx=resnet50.onnx --saveEngine=resnet50.trt |
trtexec的功能也可以自己编程实现,但是一般没必要:基本功能容易实现,但可配置的参数较多。
若trtexec运行成功,说明ONNX成功导入TRT:以TRT自有的层重建了等价的计算图,且计算图被构建为TRT engine,并保存成文件。
2.4运行ENGINE
加载engine文件,提供输入数据,即可运行。
注意事项:
对比TRT与原框架的计算结果a b,计算相对误差均值(avg(abs((a-b)/a)))
理想情况:fp32在1e-6数量级,fp16在1e-3数量级
正确测量加速比:
1)用cuda event:注意stream要设置正确
2)较粗略的简易方法:
gpu sync; 取t0; 启动gpu程序; gpu sync; 取t1,得t1- t0
2.5使用FP16/INT8加速计算
TRT后端的计算模式默认使用fp32。可在Volta以及更新的GPU上设置使用fp16/int8,输入数据保持不变。
使用fp16较为简单,设置标志即可:
1 2 |
config.set_flag(tensorrt.BuilderFlag.FP16) |
对于EDVR,用ONNX导出的模型:
1)fp32: 0.9x
2)fp16: 1.8x
API搭建:1.1x/2.7x。API搭建效果更好。
建议使用fp16,加速效果较好。
使用int8需要校正数据集:float向int8转换需要量化,调整weight精度,校正数据集会让量化尽可能准确。
2.6总结与建议
对于初级用户,推荐试用框架集成的TRT,尝试加速效果,效果一般。
对于中级用户,用ONNX Parser导入模型,可能需要自定义Plugin。
对于高级用户,推荐使用网络定义API,实现完全迁移,需要了解参数格式。
推荐使用混合精度,fp16只需略微修改代码,明显提高速度,对精度影响较小。int8有更高的计算性能,一般会有精度下降。
2.7补充TensorRT Api示例(TRT 8.2 Wenet encoder)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import os import numpy as np from cuda import cudart # 使用 cuda runtime API import tensorrt as trt onnxFile = "./encoder_graphsurgeon.onnx" trtFile = "./encoder.plan" # TensorRT 中加载 .onnx 创建 engine ---------------------------------------------- logger = trt.Logger(trt.Logger.VERBOSE) if os.path.isfile(trtFile): with open(trtFile, 'rb') as f: engine = trt.Runtime(logger).deserialize_cuda_engine(f.read()) if engine == None: print("Failed loading engine!") exit() print("Succeeded loading engine!") else: builder = trt.Builder(logger) network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) profile = builder.create_optimization_profile() config = builder.create_builder_config() # 上文tensorflow开启fp16模式,所以此处也开启fp16(范例) # config.flags = 1 << int(trt.BuilderFlag.FP16) # 开启 FP16模式,将本行注释掉即可返回 FP32 模式 config.max_workspace_size = 3145728000 << 30 parser = trt.OnnxParser(network, logger) if not os.path.exists(onnxFile): print("Failed finding ONNX file!") exit() print("Succeeded finding ONNX file!") with open(onnxFile, 'rb') as model: if not parser.parse(model.read()): print("Failed parsing ONNX file!") for error in range(parser.num_errors): print(parser.get_error(error)) exit() print("Succeeded parsing ONNX file!") inputTensor = network.get_input(0) inputTensor.shape = [-1, -1, 80] profile.set_shape(inputTensor.name, (1, 16, 80), (4, 64, 80), (16, 256, 80)) inputTensor = network.get_input(1) inputTensor.shape = [-1] profile.set_shape(inputTensor.name, (1,), (4,), (16,)) config.add_optimization_profile(profile) engineString = builder.build_serialized_network(network, config) if engineString == None: print("Failed building engine!") exit() print("Succeeded building engine!") with open(trtFile, 'wb') as f: f.write(engineString) |
3.比赛
英伟达TensorRT加速AI推理Hackathon 2021 —— Transformer模型优化赛
参赛队伍源码链接
本次参加英伟达官方比赛,学习了TensorRT的应用,收获良多。