本文主要记录一下,windows+Anaconda配置TensorRT的教程,通过此教程配置完TensorRT后,可以在Anaconda的虚拟环境内使用TensorRT
注意:本文背景环境为Windows 10 和 Anaconda已经安装好,若没有安装好,可以参考之前的一篇文章 点击这里进行跳转,里面记录了Anaconda的相关教程
本文主要记录一下,windows+Anaconda配置TensorRT的教程,通过此教程配置完TensorRT后,可以在Anaconda的虚拟环境内使用TensorRT
注意:本文背景环境为Windows 10 和 Anaconda已经安装好,若没有安装好,可以参考之前的一篇文章 点击这里进行跳转,里面记录了Anaconda的相关教程
进入conda命令控制台,进入方法如下图所示
输入以下指令
conda create -n tensorrt python=3.8
输入y即可,输入以下指令进入创建好的虚拟环境
activate tensorrt
前往官网下载cudatoolkit,点击这里进行跳转,选择合适的版本后下载,建议下载local包,一步到位,本教程的环境是windows 10,选择结果如下图所示
nvcc -V
前往官网下载cudnn安装包点击这里进行跳转,注意要选择与cudatoolkit版本相兼容的cudnn,这里我们选择cudnn8.2.1对应的windows版本,如下图所示:
下载解压后可以看到有lib include bin三个子目录,如下图所示,将这三个子文件夹直接复制
复制完毕后,打开cudatoolkit的安装目录,例如:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA,然后直接对应版本的cudatoolkit文件目录下粘贴即可,如下图所示
还需要将cudatoolkit目录下的bin、include和lib路径添加到环境变量中。右键桌面的此电脑,点击左侧最下方的高级系统设置,如下图所示
选择环境变量,如下图所示
点击系统环境变量的path,点击新建,将刚才复制的路径粘贴进来,如下图所示(注意:默认bin目录已经是系统环境变量的,如下图第一条所示,如果已经有的话就不要再添加bin的路径了)
然后下载一个zlib包,解压缩后找到zlibwapi.dll文件,剪切到C:\Windows\System32位置下面(这是cudnn依赖的动态链接库):http://www.winimage.com/zLibDll/zlib123dllx64.zip
4.1 下载TensorRT
首先,前往官网下载TensorRT点击这里进行跳转,这一步需要注册为英伟达开发者用户,这里就不再过多介绍了,登录后会出现不同版本的tensorrt资源,如下图所示。
本文使用的是tensorrt版本为8,点击展开,根据系统版本以及cudatoolkit版本选择对应的资源,根据本文背景环境,选择的版本如下图所示:
4.2 配置TensorRT系统环境变量
下载完毕后,进行解压,并且进入lib子文件夹,如下图所示,将路径复制下来,例如,C:\Users\admin\Downloads\Compressed\TensorRT-8.2.1.8\lib
依次确定保存出来即可。
4.3 安装TensorRT依赖
进入刚才解压后的TensorRT文件夹内的python子目录,根据python版本选择好对用的whl文件,如下图所示。
重新打开conda控制台(一定要重新打开,环境变量发生改动后,cmd并不会刷新!!),重新进入tensorrt的虚拟环境。
activate tensorrt
拼接路径 C:\Users\admin\Downloads\Compressed\TensorRT-8.2.1.8\python\tensorrt-8.2.1.8-cp38-none-win_amd64.whl,输入以下指令并执行,执行结果如下图所示。
pip install C:\Users\admin\Downloads\Compressed\TensorRT-8.2.1.8\python\tensorrt-8.2.1.8-cp38-none-win_amd64.whl
安装完成后会出现successfully的字样,到这里tensorrt已经安装结束
pycuda依赖是封装好的cuda api接口,可以用来申请显存等操作。
前往下载合适的版本,点击这里跳转,如下图所示。
进入下载的位置,拼接好路径,例如:c:\users\admin\downloads\pycuda-2021.1+cuda115-cp38-cp38-win_amd64.whl
进入tensorrt虚拟环境后,输入以下指令安装pycuda
pip install c:<span class=“hljs-built_in”>users\admin\downloads\pycuda-2021.1+cuda115-cp38-cp38-win_amd64.whl
安装完成后会提示successfully installed的信息
重新打开控制台,进入刚才的虚拟环境(一定要重新打开,因为修改了环境变量,重新打开才能刷新)
tensorrt官方提供了可供测试的样例,进入刚才下载好的tensorrt文件夹下面的samples\python\目录下,这里我们选择一个手写数字识别的示例,如下图所示(注意,这一步是依赖pytorch框架的,也可以选择不需要pytorch的demo进行测试)。
拷贝路径,在tensorrt的虚拟环境下,cd 此路径,然后输入如下指令
python sample.py
此时会进行训练,并且在训练结束后给出相应的预测结果。如下图所示,sample代码如果能够正常给出预测结果,则表明当前机器上的tensorrt配置成功。
本文主要是宏观地阐述一下如何使用TensorRT来部署深度学习模型以实现对模型的加速,从而提高深度学习模型运行的效率,根据我自己的实测,确实可以达到官方所说的六倍以上的速度(如下图所示)。
但是本文适合快速入门了解TensorRT使用的宏观流程,具体细节还是建议参考TensorRT的官方文档。
目前,TenorRT已经支持了主流的深度学习框架,并且截至本文发布前,TensorRT已经更新到了8.2的版本,说明TensorRT还是比较成功的
(说实话,英伟达在AI领域的布局已经基本完成了,从硬件到软件的生态几乎已经彻底完善了,按照当前的趋势,盲猜英伟达将会在不远的未来抛弃CPU和运行内存,因为数据从内存拷贝到显存貌似这部分时间开销挺大的)
其实“Tensort支持了主流深度学习框架”这句话的意思是指:TensorRT可以直接从这些深度学习框架中获取深度学习模型的定义和权重。
这句话很好理解,因为不同的深度学习框架自然有自己的模型定义方式,因此TensoRT想要获取深度学习模型的神经网络结构和相关参数权重,那必然是需要先能够“读懂”框架的“语言”。
TensorRT可以对网络进行压缩、优化以及运行时部署,并且没有框架的开销。TensorRT通过combines layers,kernel优化选择,以及根据指定的精度执行归一化和转换成最优的matrix math方法,改善网络的延迟、吞吐量以及效率。
加速原理比较复杂,它将会根据显卡来优化算子,以起到加速作用(如下图所示)。简单的来说,就是类似于你出一个公式1+1+1,而你的显卡支持乘法,直接给你把这个公式优化成了1*3,一步算完,所以自然更快。
当然这个加速程度很大程度上取决于你的显卡算力还有模型结构复杂程度,也有可能因为没有优化的地方,从而没有多大的提升,至少我用的是GTX1660S显卡可以把HRNet的运行速度提升6倍以上
安装教程我已经在上一篇文章中给出,有需要的话,可以自行前往查看,点击此处进行跳转
TensorRT使用流程如下图所示,分为两个阶段:预处理阶段和推理阶段。其部署大致流程如下:1.导出网络定义以及相关权重;2.解析网络定义以及相关权重;3.根据显卡算子构造出最优执行计划;4.将执行计划序列化存储;5.反序列化执行计划;6.进行推理
可以从步骤3可以得知,tensorrt实际上是和你的硬件绑定的,所以在部署过程中,如果你的硬件(显卡)和软件(驱动、cudatoolkit、cudnn)发生了改变,那么这一步开始就要重新走一遍了。
这一步主要是为了将深度学习模型的结构和参数导出来。考虑到实际部署环境的精简性,我这里还是建议大家在使用中先将深度学习模型导出ONNX文件,然后拿着ONNX文件去部署就可以了。
原因很简单,因为ONNX不像Pytorch和TensorFlow那样,还需要安装这些框架运行的依赖包(比如 conda install pytorch,不然你没办法用pytorch的代码),TensorRT可以直接从ONNX文件中读取出网络定义和权重信息。
除此以外,ONNX更像是“通用语言”,ONNX的出现本身就是为了描述网络结构和相关权重,除此以外,还有专门的工具可以对ONNX文件进行解析,查看相关结构,例如网站:https://lutzroeder.github.io/netron/
可以直接将onnx文件拖进去,查看网络结构
# 构造模型实例
model = HRNet()
# 反序列化权重参数
model.load_state_dict(torch.load(self.weight_path),strict=False)
model.eval()
# 定义输入名称,list结构,可能有多个输入
input_names = ['input']
# 定义输出名称,list结构,可能有多个输出
output_names = ['output']
# 构造输入用以验证onnx模型的正确性
input = torch.rand(1, 3, 384, 288)
# 导出
torch.onnx.export(model, input, output_path,
export_params=True,
opset_version=11,
do_constant_folding=True,
input_names=input_names,
output_names=output_names)
参数介绍:
在这一步骤的主要目的是,根据onnx所描述的模型结构和权重数值和当前的软硬件环境生成对应的执行计划,并且序列化为xxx.engine文件持久化保存,这一步时间比较长,所以需要序列化执行文件,这样在推理阶段直接加载此文件构造出Engine
以实现快速推理,这一步可以使用tensortrt安装包内自带的trtexec.exe实现,也可以用python代码自行实现。
使用trtexec.exe实现预推理需要系统安装好cudatoolkit和cudnn,否则无法正常运行
打开tensorrt安装包的子目录bin,如下图所示
搜索运行cmd,并且cd到此目录下,并且将需要部署的onnx文件复制到此目录下
输入以下指令:
trtexec --onnx=xxx.onnx --saveEngine=xxx.engine --fp16
参数解释
--fp16开启 float16精度的推理(推荐此模式,一方面能够加速,另一方面精度下降比较小)
--int8 开启 int8精度的推理(不太推荐,虽然更快,但是精度下降太厉害了)
--onnx onnx路径
--saveEngine执行计划(推理引擎)序列化地址
和3.2一样,也需要cudatoolkit和cudnn:
# 导入必用依赖
import tensorrt as trt
# 创建logger:日志记录器
logger = trt.Logger(trt.Logger.WARNING)
# 创建构建器builder
builder = trt.Builder(logger)
# 预创建网络
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
# 加载onnx解析器
parser = trt.OnnxParser(network, logger)
success = parser.parse_from_file(onnx_path)
for idx in range(parser.num_errors):
print(parser.get_error(idx))
if not success: pass # Error handling code here
# builder配置
config = builder.create_builder_config()
# 分配显存作为工作区间,一般建议为显存一半的大小
config.max_workspace_size = 1 << 30 # 1 Mi
serialized_engine = builder.build_serialized_network(network, config)
# 序列化生成engine文件
with open(engine_path, "wb") as f:
f.write(serialized_engine)
print("generate file success!")
在本阶段,需要使用代码实现加载执行计划文件(xxx.engine),为了更方便的理解工作逻辑,数据的逻辑关系已在下图中给出。
其中input/output为你的输入数据,h_input/h_output为锁页内存(非锁页内存也是可以的,但是建议用锁页内存防止被系统页面置换到外存中),d_input/d_output为显存
同时,在TensorRT官方文档中,CPU+内存被称为host,而GPU+显存被称为device,可以明显地看出host和device实际上是异步工作的,因此需要同步操作。
#导入必用依赖
import tensorrt as trt
import pycuda.autoinit #负责数据初始化,内存管理,销毁等
import pycuda.driver as cuda #GPU CPU之间的数据传输
#创建logger:日志记录器
logger = trt.Logger(trt.Logger.WARNING)
#创建runtime并反序列化生成engine
with open(“sample.engine”, “rb”) as f, trt.Runtime(logger) as runtime:
engine = runtime.deserialize_cuda_engine(f.read())
#分配CPU锁页内存(防止被系统置换到虚拟内存中)和GPU显存
h_input = cuda.pagelocked_empty(tuple(context.get_binding_shape(0)), dtype=np.float32)
h_output = cuda.pagelocked_empty(tuple(context.get_binding_shape(1)), dtype=np.float32)
d_input = cuda.mem_alloc(h_input.nbytes) d_output = cuda.mem_alloc(h_output.nbytes)
#创建cuda流
stream = cuda.Stream()
#创建context并进行推理
with engine.create_execution_context() as context:
# Transfer input data to the GPU.
cuda.memcpy_htod_async(d_input, h_input, stream)
# Run inference.
context.execute_async_v2(bindings=[int(d_input), int(d_output)], stream_handle=stream.handle)
# Transfer predictions back from the GPU.
cuda.memcpy_dtoh_async(h_output, d_output, stream)
# Synchronize the stream(将显存内数据刷回锁页内存)
stream.synchronize()
# Return the host output.
return h_output
这里建议对代码进行封装,例如分配锁页内存和显存其实只需要一次就行了,可以来回复用,而数据的传入可以用以下代码实现:
# 构造你自己的输入数据
input = np.randn(1, 3, 384, 288)
# 拷贝到锁页内存
np.copyto(h_input, input)
同理,推理数据拷出代码如下:
# 构造你自己的输出数据
output = np.randn(1, 17, 96, 72)
# 拷贝到锁页内存
np.copyto(output, h_output)
在我的上一篇文章中,已经讲述了TensorRT如何使用,并且是支持对单个数据的输入以及处理并且输出结果,然而在实际应用中,我们往往是需要将多个输入数据构造为一个batch,一次性喂入深度学习模型并进行预测,并获得对应的结果。
同样的,TensorRT也是支持batch批处理输入数据的,并且也能够使检测速度有一定的提升。
本文将会简单地介绍如何实现batch批处理预测,至于具体细节,请参考我的上一篇文章
我们在导出模型的时候,需要将模型输入的batch参数声明为动态参数,例如,hrnet的输入数据维度为(1, 3 , 384, 288),第一个维度为batch的大小,第二个维度为RGB三个色彩通道,第三个维度为图片的高,第四个维度为图片的宽,因此这里我们在导出onnx模型时,需要将第一个维度的参数声明为动态参数,具体代码如下:
# 定义输入名称,list结构,可能有多个输入
input_names = ['input']
# 定义输出名称,list结构,可能有多个输出
output_names = ['output']
# 声明动态维度,这里我们把input的第0维度赋名为batch_size
dynamic_axes = {
'input': {0: 'batch_size'}
}
# 构造输入,用以onnx验证
input = torch.randn(2, 3, 384, 288, requires_grad=True)
torch.onnx.export(model, input, output_path,
export_params=True,
opset_version=10,
do_constant_folding=True,
input_names=input_names,
output_names=output_names,
dynamic_axes=dynamic_axes)
这一步我们同样使用tensorrt自带的trtexec.exe实现利用onnx模型构造trt的模型
首先将上一步导出的onnx文件拉到trtexec.exe所在的目录下,并且在cmd控制台中运行以下命令,其中需要–shapes参数以确定动态参数具体的值,乘号为字母x:
trtexec --fp16 --shapes=input:32x3x384x288 --onnx=xxxx.onnx --saveEngine=xxxx.trt
在执行阶段,唯一需要注意的是TensorRT
的输入必须是严格固定的batch_size
大小,即每次输入到trt模型时,输入必须是batch_size
大小严格等于构造阶段的输入,因此对于小于batch_size
大小的数据需要0
填充处理