TensorRT安装部署指南
时间:2023/11/01
说明:本指南针对在装有NVIDIA显卡的Windows10系统的计算机上,安装TensorRT推理加速工具,将pytorch中导出的onnx模型转换为trt模型文件,并在python语言中进行模型推理。
一、TensorRT安装
TensorRT官方安装指南:https://docs.nvidia.com/deeplearning/tensorrt/install-guide/index.html#installing-zip
注意:
- 官方给出了pip、RPM、Tar文件、Zip文件等多种安装方式。需要注意,通过zip文件安装是目前 Windows 的唯一选项。Zip文件从官网下载。下载完成后,只需要按照官方安装指南中给出的步骤进行安装和环境配置。
- 安装TensorRT前需要确保已经安装对应的CUDA和cuDNN依赖。CUDA和cuDNN的安装方法可以参考:CUDA和cudnn安装教程。
二、将ONNX模型转换为TensorRT引擎
将ONNX模型转换成TensorRT的最简单的方法就是使用{TensorRT}/bin
下的命令行工具trtexec
:
trtexec --onnx=model.onnx --saveEngine=model.trt
这里的--onnx
和--saveEngine
分别代表onnx模型的路径和保存trt模型的路径。此外,再介绍两个比较常用的trtexec
命令行工具参数:
--explicitBatch
:告诉trtexec在优化时固定输入的 batch size(将从onnx文件中推断batch size的具体值,即与导出onnx文件时传入的batch size一致)。当确定模型的输入batch size时,推荐采用此参数,因为固定batch size大小可以使得trtexec进行额外的优化,且省去了指定“优化配置文件”这一额外步骤(采用动态batch size时需要提供“优化配置文件”来指定希望接收的可能的batch size大小的范围);--fp16
:采用FP16精度,通过牺牲部分模型准确率来简化模型(减少显存占用和加速模型推理)。TensorRT支持TF32/FP32/FP16/INT8多种精度(具体还要看GPU是否支持)。FP32是多数框架训练模型的默认精度,FP16对模型推理速度和显存占用有较大优化,且准确率损失往往可以忽略不计。INT8进一步牺牲了准确率,同时也进一步降低模型的延迟和显存要求,但需要额外的步骤来仔细校准,来使其精度损耗较小。
三、在Python API使用TensorRT模型进行推理
使用Python API运行TensorRT模型推理需要安装pycuda
包:
pip install pycuda
注:若pycuda安装失败,尝试到https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycuda 下载python版本对应的最新的本地安装文件安装
然后参照官方给的示例代码运行TensorRT模型推理:tutorial-runtime.ipynb
下面给出在python中执行tensorrt推理的主要代码:
import pycuda.driver as cuda
# import pycuda.autoinit
# 如果不是在线程中运行,可以使用这行代码自动创建上下文,而不需要使用cuda.Device(0).make_context()手动创建上下文
import tensorrt as trt
cuda.init()
class HostDeviceMem(object):
def __init__(self, host_mem, device_mem):
"""Within this context, host_mom means the cpu memory and device means the GPU memory
"""
self.host = host_mem
self.device = device_mem
def __str__(self):
return "Host:\n" + str(self.host) + "\nDevice:\n" + str(self.device)
def __repr__(self):
return self.__str__()
class TRTModel(object):
def __init__(self, trt_path):
"""
在类初始化中预载入模型并申请锁页内存,避免每次推理重复载入和申请锁页内存,
前者会增加耗时,后者会导致显存占用持续增加
"""
# 手动创建context上下文
self.cfx = cuda.Device(0).make_context()
# 预加载模型
self.engine = self.load_engine(trt_path)
# 在CPU和GPU中申请锁页内存
self.inputs, self.outputs, self.bindings, self.stream = self.allocate_buffers(self.engine)
self.cfx.pop()
def load_engine(self, engine_file_path):
TRT_LOGGER = trt.Logger()
assert os.path.exists(engine_file_path)
print("Reading engine from file {}".format(engine_file_path))
with open(engine_file_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
return runtime.deserialize_cuda_engine(f.read())
def allocate_buffers(self, engine):
"""
Allocate host(CPU) and device(GPU) buffers.
"""
inputs = []
outputs = []
bindings = []
stream = cuda.Stream()
for binding in engine:
binding_idx = engine.get_binding_index(binding)
size = trt.volume(engine.get_binding_shape(binding_idx))
dims = engine.get_binding_shape(binding)
if dims[0] < 0:
size *= -1
dtype = trt.nptype(engine.get_binding_dtype(binding))
# Allocate host and device buffers
host_mem = cuda.pagelocked_empty(size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
# Append the device buffer to device bindings.
bindings.append(int(device_mem))
# Append to the appropriate list.
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
def engine_infer(self, context):
"""
engine: load_engine函数返回的trt模型引擎
"""
# Transfer input data to the GPU.
[cuda.memcpy_htod_async(inp.device, inp.host, self.stream) for inp in self.inputs]
# Run inference
context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
# Transfer prediction output from the GPU.
[cuda.memcpy_dtoh_async(out.host, out.device, self.stream) for out in self.outputs]
# Synchronize the stream
self.stream.synchronize()
outputs = [out.host for out in self.outputs]
return outputs
def postprocess_the_outputs(self, output_host, shape):
"""
Postprocess the outputs.
Trt模型推理后输出1维数组,需要reshape回正常形式
"""
output_host = output_host.reshape(*shape)
return output_host
def model_predict(self, data):
"""
1. 将输入数据传入GPU中申请的锁页内存
2. 执行推理
3. 取出一维的推理结果,并做后处理
"""
# 将输入数据传入在GPU中申请好的内存中
self.inputs[0].host = np.ascontiguousarray(data)
# 调用trt模型进行推理
self.cfx.push()
with self.engine.create_execution_context() as context:
trt_outputs = self.engine_infer(context)
self.cfx.pop()
# 后处理output
shape = (data.shape[0], num_classes, data.shape[2], data.shape[3])
trt_output = self.postprocess_the_outputs(trt_outputs[0], shape=shape)
return trt_output
注意事项
(1)TensorRT是硬件相关的
不同显卡(不同GPU),其核心数量、频率、架构、设计都是不一样的,TensorRT需要对特定的硬件进行优化,不同硬件之间的优化是不能共享的。
(2)TensorRT支持哪几种权重精度
支持FP32、FP16、INT8、TF32等,这几种类型都比较常用。
- FP32:单精度浮点型,深度学习中最常见的数据格式,训练推理都会用到;
- FP16:半精度浮点型,相比FP32占用内存减少一半,有相应的指令值,速度比FP32要快很多;
- TF32:第三代Tensor Core支持的一种数据类型,是一种截短的 Float32 数据格式,将FP32中23个尾数位截短为10bits,而指数位仍为8bits,总长度为19(=1+8 +10)。保持了与FP16同样的精度(尾数位都是 10 位),同时还保持了FP32的动态范围指数位都是8位);
- INT8:整型,相比FP16占用内存减小一半,有相应的指令集,模型量化后可以利用INT8进行加速。