赞
踩
TensorRT 是一种基于英伟达硬件的高性能的深度学习前向推理框架,本文介绍使用 TensorRT 在通用 GPU 上的部署流程。
本地需先安装 CUDA,以 CUDA11.0、TensorRT-8.2.5.1 为例。首先,去 官网 下载(需先登录)对应的压缩包。Python 安装文件 whl 位于解压后根目录下的 python 文件夹内,pip 安装对应版本即可。
本文主要代码来自 TensorRT 仓库:https://github.com/NVIDIA/TensorRT/tree/main/samples/python/yolov3_onnx。
和通用的推理流程类似,本文代码的流程按照:输入图像的预处理,把处理后的速度拷贝到设备(GPU)上,在设备上运行模型推理,将设备上的推理结果拷贝到主机(本地 CPU),针对推理结果的后处理。
本文以 ONNX 为基础,将其转换为基于 TensorRT 推理的 trt 文件格式。关键函数为 runtime.deserialize_cuda_engine
,函数原型为:
deserialize_cuda_engine(self: tensorrt.tensorrt.Runtime, serialized_engine: buffer)→ tensorrt.tensorrt.ICudaEngine
可以看到它是类 tensorrt.tensorrt.Runtime 的成员函数,参数只有 serialized_engine 一个。该参数可来自于读取已存在的 trt 文件,或通过序列化 ONNX 模型得到。
with open(engine_file_path, "rb") as f:
with trt.Runtime(TRT_LOGGER) as runtime:
return runtime.deserialize_cuda_engine(f.read())
序列化 ONNX 模型的关键函数是 build_serialized_network
,函数原型为:
build_serialized_network(self: tensorrt.tensorrt.Builder, network: tensorrt.tensorrt.INetworkDefinition, config: tensorrt.tensorrt.IBuilderConfig)→ tensorrt.tensorrt.IHostMemory
可以看到它是类 tensorrt.tensorrt.Builder 类的成员函数,参数有 network 和 config 两个。函数返回类型和上述 f.read() 的内容类似,作为函数 deserialize_cuda_engine 的参数。
第一个参数 network 的类型为 tensorrt.tensorrt.INetworkDefinition,通过函数 create_network
得到,函数原型为:
create_network(self: tensorrt.tensorrt.Builder, flags: int = 0)→ tensorrt.tensorrt.INetworkDefinition
参数 flags 的内容与 TensorRT 中的显示 Batch 和隐式 Batch 模式有关,相关内容可参考 文档。在 Python 中,TensorRT 推荐写法:
network = builder.create_network(
1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
第二个参数 config 的类型为 tensorrt.tensorrt.IBuilderConfig,通过函数 create_builder_config
得到,函数原型为:
create_builder_config(self: tensorrt.tensorrt.Builder)→ tensorrt.tensorrt.IBuilderConfig
该函数主要用于设置一些配置项,配置内容可参考 文档。
把上述两种方式结合起来,得到 get_engine 函数内容,返回反序列化后的模型。
def get_engine(onnx_file_path, engine_file_path=""): # 如果不指定 engine_file_path 则通过 build_engine 生成 engine 文件 def build_engine(): # 基于 INetworkDefinition 构建 ICudaEngine builder = trt.Builder(TRT_LOGGER) # 基于 INetworkDefinition 和 IBuilderConfig 构建 engine network = builder.create_network( 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) # 构建 builder 的配置对象 config = builder.create_builder_config() # 构建 ONNX 解析器 parser = trt.OnnxParser(network, TRT_LOGGER) # 构建 TensorRT 运行时 runtime = trt.Runtime(TRT_LOGGER) # 参数设置 config.max_workspace_size = 1 << 28 # 256MiB builder.max_batch_size = 1 # 解析 onnx 模型 if not os.path.exists(onnx_file_path): print( f"[INFO] ONNX file {onnx_file_path} not found.") print(f"[INFO] Loading ONNX file from {onnx_file_path}.") with open(onnx_file_path, "rb") as model: print("[INFO] Beginning ONNX file parsing.") if not parser.parse(model.read()): print("[ERROR] Failed to parse the ONNX file.") for error in range(parser.num_errors): print(parser.get_error(error)) return None # 根据 yolov3.onnx,reshape 输入数据的形状 network.get_input(0).shape = [1, 3, 608, 608] print("[INFO] Completed parsing of ONNX file.") print(f"[INFO] Building an engine from {onnx_file_path}.") # 序列化模型 plan = builder.build_serialized_network(network, config) # 反序列化 engine = runtime.deserialize_cuda_engine(plan) print("[INFO] Completed creating engine.") # 写入文件 with open(engine_file_path, "wb") as f: f.write(plan) return engine if os.path.exists(engine_file_path): print(f"[INFO] Reading engine from {engine_file_path}.") with open(engine_file_path, "rb") as f: with trt.Runtime(TRT_LOGGER) as runtime: return runtime.deserialize_cuda_engine(f.read()) else: return build_engine()
本文使用 YOLOv3 模型来自 DarkNet,预处理主要包括 resize 和归一化两种。
class PreprocessYOLO: def __init__(self, input_resolution): self.input_resolution = input_resolution def preprocess(self, image_path): image_raw, image_resized = self.load_and_resize(image_path) image_preprocesed = self.shuffle_and_normalize(image_resized) return image_raw, image_preprocesed def load_and_resize(self, image_path): image_raw = Image.open(image_path) new_resolution = (self.input_resolution[1], self.input_resolution[0]) image_resized = image_raw.resize(new_resolution, resample=Image.CUBIC) image_resized = np.array(image_resized, dtype=np.float32, order="C") return image_raw, image_resized def shuffle_and_normalize(self, image): # 归一化 image /= 255.0 # (w,h,c) -> (c,h,w) image = np.transpose(image, [2, 0, 1]) # (c,h,w) -> (n,c,h,w) image = np.expand_dims(image, axis=0) image = np.array(image, dtype=np.float32, order="C") return image
在执行模型推理前,首先要在本地 CPU 和设备 GPU 上分配内存。
def allocate_buffers(engine): inputs = [] outputs = [] bindings = [] stream = cuda.Stream() for binding in engine: size = trt.volume( engine.get_binding_shape(binding)) * engine.max_batch_size dtype = trt.nptype( engine.get_binding_dtype(binding)) # 分配主机内存和设备内存 host_mem = cuda.pagelocked_empty(size, dtype) device_mem = cuda.mem_alloc(host_mem.nbytes) # 绑定设备内存 bindings.append(int(device_mem)) # 输入输出绑定 if engine.binding_is_input(binding): inputs.append(HostDeviceMem(host_mem, device_mem)) else: outputs.append(HostDeviceMem(host_mem, device_mem)) return inputs, outputs, bindings, stream
然后,在执行推理时首先将图像数据从 CPU 拷贝到 GPU,然后执行推理,最后将推理结果从 GPU 拷贝到 CPU。
def do_inference(context, bindings, inputs, outputs, stream):
# 将输入数据从主机拷贝到设备
[cuda.memcpy_htod_async(inp.device, inp.host, stream) for inp in inputs]
# 推理
context.execute_async_v2(bindings=bindings, stream_handle=stream.handle)
# 将输出数据从设备拷贝到主机
[cuda.memcpy_dtoh_async(out.host, out.device, stream) for out in outputs]
# 同步流
stream.synchronize()
# 仅返回主机上的输出
return [out.host for out in outputs]
得到推理结果后,针对数据结果做后处理,由类 PostprocessYOLO 的各成员函数完成。
本项目的仓库地址:https://github.com/zhangtaoshan/cv_inference_python,欢迎交流。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。