因为各种项目的原因,其实已经用过一段时间tensorrt了,但是一直没仔细梳理过理论知识了,在b站刷到官方教程,写个博客梳理一下tensorrt基础知识和开发流程。
(写到一半莫名其妙后半部分被csdn吞了,草稿找不到了,以后还是本地写了)
-
tensorrt简介
tensorrt是NV官方的深度学习部署工具
➢ 用于高效实现已训练好的深度学习模型的推理过程的 SDK ➢ 内含推理优化器和运行时环境 ➢ 使 DL 模型能以更高吞吐量和更低的延迟运行 ➢ 有 C++ 和 python 的 API,完全等价可以混用
tensorrt主要进行以下工作
➢ 构建期(推理优化器) ➢ 模型解析 / 建立 加载 Onnx 等其他格式的模型 / 使用原生 API 搭建模型 ➢ 计算图优化 横向层融合(Conv),纵向层融合(Conv+add+ReLU), …… ➢ 节点消除 去除无用层,节点变换(Pad, Slice, Concat, Shuffle), …… ➢ 多精度支持 FP32 / FP16 / INT8 / TF32(可能插入 reformat 节点) ➢ 优选 kernel / format 硬件有关优化 ➢ 导入 plugin 实现自定义操作 ➢ 显存优化 显存池复用 ➢ 运行期(运行时环境) ➢ 运行时环境 对象生命期管理,内存显存管理,异常处理 ➢ 序列化反序列化 推理引擎保存为文件或从文件中加载
2.tensorrt的部署简要流程
-
搭建tensorrt的基本流程
➢ 基本流程 ➢ 构建期 ➢ 建立 Builder(引擎构建器) ➢ 创建 Network(计算图内容) ➢ 生成 SerializedNetwork(网络的 TRT 内部表示) ➢ 运行期 ➢ 建立 Engine 和 Context ➢ Buffer 相关准备(Host 端 + Device 端 + 拷贝操作) ➢ 执行推理(Execute)
示例代码在官方github的01-SimpleDemo/TensorRT8中的 TRT8-cudart.py 或 TRT8.cpp(python 和 C++ 等价版本)
官方github上的代码更新过,和教程不完全相同,但思路完全一样。
构建期:
logger = trt.Logger(trt.Logger.ERROR) # 指定 Logger,可用等级:VERBOSE,INFO,WARNING,ERRROR,INTERNAL_ERROR if os.path.isfile(trtFile): # 如果有 .plan 文件则直接读取 with open(trtFile, 'rb') as f: engineString = f.read() if engineString == None: print("Failed getting serialized engine!") return print("Succeeded getting serialized engine!") else: # 没有 .plan 文件,从头开始创建 builder = trt.Builder(logger) # 网络元信息,Builder/Network/BuilderConfig/Profile 相关 network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) profile = builder.create_optimization_profile() config = builder.create_builder_config() config.max_workspace_size = 1 << 30 inputTensor = network.add_input('inputT0', trt.DataType.FLOAT, [-1, -1, -1]) # 指定输入张量 profile.set_shape(inputTensor.name, [1, 1, 1], [3, 4, 5], [6, 8, 10]) # 指定输入张量 Dynamic Shape 范围 config.add_optimization_profile(profile) identityLayer = network.add_identity(inputTensor) # 恒等变换 network.mark_output(identityLayer.get_output(0)) # 标记输出张量 engineString = builder.build_serialized_network(network, config) # 生成序列化网络 if engineString == None: print("Failed getting serialized engine!") return print("Succeeded getting serialized engine!") with open(trtFile, 'wb') as f: # 将序列化网络保存为 .plan 文件 f.write(engineString) print("Succeeded saving .plan file!")
运行期:
```engine = trt.Runtime(logger).deserialize_cuda_engine(engineString) # 使用 Runtime 来创建 engine if engine == None: print("Failed building engine!") return print("Succeeded building engine!") context = engine.create_execution_context() # 创建 context(相当于 GPU 进程) context.set_binding_shape(0, [3, 4, 5]) # Dynamic Shape 模式需要绑定真实数据形状 nInput = np.sum([engine.binding_is_input(i) for i in range(engine.num_bindings)]) # 获取 engine 绑定信息 nOutput = engine.num_bindings - nInput for i in range(nInput): print("Bind[%2d]:i[%2d]->" % (i, i), engine.get_binding_dtype(i), engine.get_binding_shape(i), context.get_binding_shape(i), engine.get_binding_name(i)) for i in range(nInput,nInput+nOutput): print("Bind[%2d]:o[%2d]->" % (i, i - nInput), engine.get_binding_dtype(i), engine.get_binding_shape(i), context.get_binding_shape(i), engine.get_binding_name(i)) data = np.arange(3 * 4 * 5, dtype=np.float32).reshape(3, 4, 5) # 准备数据和 Host/Device 端内存 bufferH = [] bufferH.append(np.ascontiguousarray(data.reshape(-1))) for i in range(nInput, nInput + nOutput): bufferH.append(np.empty(context.get_binding_shape(i), dtype=trt.nptype(engine.get_binding_dtype(i)))) bufferD = [] for i in range(nInput + nOutput): bufferD.append(cudart.cudaMalloc(bufferH[i].nbytes)[1]) for i in range(nInput): # 首先将 Host 数据拷贝到 Device 端 cudart.cudaMemcpy(bufferD[i], bufferH[i].ctypes.data, bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice) context.execute_v2(bufferD) # 运行推理计算 for i in range(nInput, nInput + nOutput): # 将结果从 Device 端拷回 Host 端 cudart.cudaMemcpy(bufferH[i].ctypes.data, bufferD[i], bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost) for i in range(nInput + nOutput): print(engine.get_binding_name(i)) print(bufferH[i].reshape(context.get_binding_shape(i))) for b in bufferD: # 释放 Device 端内存 cudart.cudaFree(b)
-
tensorrt搭建与开发方式 官方列出了三种方案
➢ 使用框架自带 TRT 接口(TF-TRT, Torch-TensorRT) ➢ 简单灵活,部署仍在原框架中,无需书写 Plugin
➢ 使用 Parser(TF/Torch/… → ONNX[1] → TensorRT) ➢ 流程成熟, ONNX 通用性好,方便网络调整,兼顾效率性能
➢ 使用 Parser(TF/Torch/… → ONNX[1] → TensorRT) ➢ 流程成熟, ONNX 通用性好,方便网络调整,兼顾效率性能
三种方案各有优劣
-
我自己做过的以及网上开源的项目中,更过是采用第二种方式,第一种方案也有,第三种则见得少一些(毕竟我也是刚开始接触tensorrt)
github中也给出了使用API搭建MNIST手写识别模型的示例
不过pytorch和paddle的demo都是TODO待更新,目前只有TensorFlow的
代码基本流程如下: ➢ TensorFlow 中创建并训练一个网络 ➢ 提取网络权重,保存为 para.npz ➢ TensorRT 中重建该网络并加载 para.npz 中的权重 ➢ 生成推理引擎 ➢ 用引擎做实际推理
-
tensorrt运行期
engine是trt中的计算引擎,有点类似于cpu计算中的线程,算是基本计算单元。
➢生成 TRT 内部表示 ➢ serializedNetwork = builder. build_serialized_network(network, config) ➢生成 Engine ➢ engine = trt.Runtime(logger).deserialize_cuda_engine( serializedNetwork ) ➢创建 Context ➢ context = engine.create_execution_context() ➢绑定输入输出(Dynamic Shape 模式必须) ➢ context.set_binding_shape(0,[1,1,28,28]) ➢准备 Buffer ➢ inputHost = np.ascontiguousarray(inputData.reshape(-1)) ➢ outputHost = np.empty(context.get_binding_shape(1), trt.nptype(engine.get_binding_dtype(1))) ➢ inputDevice = cudart.cudaMalloc(inputHost.nbytes)[1] ➢ outputDevice = cudart.cudaMalloc(outputHost.nbytes)[1] ➢执行计算 ➢ cudart.cudaMemcpy(inputDevice, inputHost.ctypes.data, inputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice) ➢ context.execute_v2([int(inputDevice), int(outputDevice)]) ➢ cudart.cudaMemcpy(outputHost.ctypes.data, outputDevice, outputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
这里补了一点 CUDA异构计算的知识
➢CUDA 异构计算 ➢ 同时准备 CPU 端内存和 GPU端显存 ➢ 开始计算前把数据从内存拷贝到显存中 ➢ 计算过程的输入输出数据均在 GPU端读写 ➢ 计算完成后要把结果拷贝会内存才能使用
在运行过程中就需要涉及到内存和显存的数据拷贝
➢Buffer ➢ 内存和显存的申请 ➢ inputHost = np.ascontiguousarray(inputData.reshape(-1)) ➢ outputHost = np.empty(context.get_binding_shape(1), trt.nptype(engine.get_binding_dtype(1))) ➢ inputDevice = cudart.cudaMalloc(inputHost.nbytes)[1] ➢ outputDevice = cudart.cudaMalloc(outputHost.nbytes)[1]
➢ 内存和显存之间的拷贝 ➢ cudart.cudaMemcpy(inputDevice, inputHost.ctypes.data, inputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice) ➢ cudart.cudaMemcpy(outputHost.ctypes.data, outputDevice, outputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
➢ 推理完成后释放显存 ➢ cudart.cudaFree(inputDevice) ➢ cudart.cudaFree(outputDevice)
-
1
-
1
-
开发者辅助工具
➢trtexec TensorRT 命令行工具,主要的 End2End 性能测试工具 ➢Netron 网络可视化 ➢onnx-graphsurgeon onnx 计算图编辑 ➢polygraphy 结果验证与定位,图优化 ➢Nsight Systems 性能分析
等用熟了之后看看能不能单独写个博客
主要参考自nvidia官方给出的教程,教程是基于8.2.3版本的TensorRT。
trt-samples-for-hackathon-cn/cookbook at master · NVIDIA/trt-samples-for-hackathon-cn · GitHub
在b站官方还有个简单的讲解视频,不过个人觉得ppt比讲解本身重要一点。
以及