教程
TensorRT简介
1.用于高效实现已训练好的深度学习模型的推理过程的SDK
2.内含推理优化器和运行时环境
3.使DL模型能以更高吞吐量和更低的延迟运行
构建期:推理优化器
1.模型解析 / 搭建
2.计算图优化:横向融合和纵向融合
3.节点消除:去掉无用层
4.多精度支持
5.优选kernel / format:针对硬件选择最优实现
6.导入plugin:实现自定义操作
7.显存优化:显存池
运行期:运行时环境
1.运行时环境:对象生命期管理,内存显存管理,异常处理
2.序列化、反序列化:保存、加载engine
基本流程
构建期
前期准备:Logger、Builder、Config、Profile
创建network
生成序列化网络
运行期
建立Engine 和 Context
Buffer相关准备
执行推理
善后工作
获取网络
1.使用框架自带TRT接口
2.使用Parser从onnx文件导入
3.使用TensorRT原生API搭建网络
1)使用TensorFlow / pyTorch创建并训练一个网络
2)提取网络权重
3)TensorRT中逐层重建该网络并加载权重
4)生成推理引擎
5)加载推理引擎并推理
API
Logger:日志记录器
loggre = trt.Logger(trt.Logger.VERBOSE)
Builder:引擎构建器
builder = trt.Builder(logger)
builder.create_network() # 创建TensorRT网络对象
builder.create_optimization_profile() # 创建用于Dynamic Shape输入的配置器
BuilderConfig: 网络属性选项
config = builder.create_builder_config()
config.config_set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 指定构建起显存
config.flag = # 设置标志位开关
config.int8_calibrator = # 指定INT8-PTQ的校正器
config.add_optimization_profile() # 添加用于Dynamic Shaped输入的配置器
...
Network:网络具体构造
network = builder.create_network(<int(tensorrt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
network.add_input() # 标记网络输入
network.mark_output() # 标记网络输出
network.name / network.num_layers / network.num_inputs / network.num_outputs
Profile: 指定输入张量大小范围
profile = builder.create_optimization_profile()
profile.set_shape(tensorName,minShape,commonShape,maxShape) # 给定输入张量的最小、最常见、最大尺寸
config.add_optimization_profile(profile) # 将设置的profile传递给config以创建网络
Layer / Tensor: 网络层 和 张量
# Layer
layer.name / layer.type / layer.precision / layer.get_output()
# Tensor
tensor.name / tensor.shape / tensor.dtype
Engine: 计算引擎
serializedNetwork = builder.build_serialized_network(network,config)
engine = trt.Runtime(logger).deserialize_cuda_engine(serializedNetwork)
engine.num_io_tensors # 获取engine绑定的输入输出张量总数
engine.num_layre # 获取engine总蹭树
Engine.get tensor_name(i) # 第i个张量的名字
engine.get_tensor_dtype(iTensorName[i]) # 第i个张量的数据类型,传入张量名字而非索引
engine.get tensor shape(iTensorName[i]) # 第i个张量的张量形状,Dynamic Shape模式下结果可能含-1
engine.engine.get tensor mode(iTensorName[i]) # 第i个张量属于是输入还是输出张量
Context: 推理进程
context = engine.create_execution_context()
context.set input shape(iTensorName[i],shapeofInputTensor) # 设定第i个张量的形状(Dynamic Shape模式中使用)
context.get_tensor_shape(iTensorName[i]) # 获取第i个张量的形状
context.set_tensor_address(iTensorName[i],address) # 设定输入输出张量的指针
context.execute_async_v3(srteam) # Explicit batch模式的异步执行
自定义扩展
Plugin: 插件
功能:
以.so的形式插入到网络中实现某些算子
实现TensorRT不原生支持的层或结构
替换性能不足的层或结构
手动合并TensorRT没有自动融合的层或结构
限制条件:
自己实现CUDA C++kernel,为结果精度和性能负责
Plugin与其他Layer.之间无法fusing
可能在Plugin节点前后插入reformatting节点,增加开销
为了支持FP16模式,Plugin要允许float16的输入输出张量类型,并实现float16的CUDA C++kernel
为了支持lnt8模式下的calibration过程,Plugin需要实现FP32的支持,否则要手工指定所有输入输出张量的dynamic_rang
Int8模式下,Plugin内部张量的dynamic range也要手工指定
实现步骤:
继承IPluginV2DynamicExt类实现一个Plugin类
继承IPluginCreator类实现一个PluginCreator类
实现用于计算的CUDA C++ kernel
将Plugin编译为.so保存
加载编译好的.so
在Plugin Registry里找到需要的Plugin
通过Plugin Creator构造需要的Plugin
将Plugin插入网络中(搭建网络情景)
Parser自动识别(通过Parser加载模型场景)
交互;
构建期:
TensorRT向Plugin传输参数和权重
Plugin向TensorRT报告其输入输出张量信息,包括数量、形状(Shape)、数据类型(DataType)和数据排布(Layout)组合
Plugin向TensorRT报告其需要的workspace大小
TensorRT尝试各种允许的组合,选择性能最佳的输入输出组合(可能在Plugin前后插入reformat节点)
Plugin不参与层fusing
运行期
TensorRT为Plugin提供输入输出张量的地址,workspace的地址,以及所在的stream
插件类型:
IPluginV2 # 支持单一input / output格式
IPluginV2Ext # 支持单一input格式和混合output格式
IPluginV2IOExt # 支持混合input / output格式,Implicit Batch模式
IPluginV2 DynamicExt # 支持混合input / output格式,Dynamic Shape模式
关键特性
Explicit batch
可应用Dynamic Shape模型。
适用于输入张量形状在推理时才决定网络
除了Batch维,其他维度也可以推理时才决定
需要Optimazation Profile帮助网络优化
需用context.set_input_shape绑定实际输入数据形状
FP16
config.flags = 1 << int(trt.BuilderFlag.FP16)
需要更长的构建时间,需要插入Reformat节点
部分层可能出现精度下降导致误差,使用polygraphy等工具,可以强制该层使用FP32进行计算
config.set_flag(trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS)
layer.precision = trt.float32
Int8模式——PTQ
不需要对网络模型做出改变,只需要一个校准集
1. 需要有校准集(输入范例数据)
2. 自己实现calibrator(如右图)
3. config.set_flag(trt.BuilderFlag.INT8)
4. config.int8_calibrator
Int8模式——QAT
训练时修改网络
1. config.set_flag(trt.BuilderFlag.INT8)
2. 在pyTorch网络中插入Quantize/Dequantize层
内存管理
CUDA异构计算
1. 同时准备CPU端内存和GPU端显存
2. 开始计算前把数据从内存拷贝到显存中
3. 计算过程的输入输出数据均在GPU端读写
4. 计算完成后要把结果拷贝会内存才能使用
5. 内存释放
可以将cuda对于显存的优化应用于到tensorrt中。
一些技巧
1. 如果不用Int8模式,onnx parser的代a码几乎是通用的
可以用trtexec,基本等价于脚本中的APl
2. 遇到TensorRT不支持的节点
1. 修改源模型
2. 修改Onnx计算图
3. TensorRT中实现Plugin
4. 修改Parser: 修改TRT源码并重新编译TRT
TensorRT环境
CUDA + Cudnn + TensorRT:
注意版本对应关系
使用NVIDIA-optimized Docker
安装步骤:https:/docs.nvidia.com/datacenter/cloud-native,/container--toolkit/install--guide.html#docker
镜像列表:https:/docs.nvidia.com/deeplearning/frameworks/,support-matrix/index.html
推荐镜像:nvcr.io/nvidia/pytorch:23.03-py3 (pyTorch1.14+TRT8.5.2),nvcr.io/nvidia/pytorch:23.04-py3 (pyTorch2.0 + TRT8.6.1)
python库
nvidia-pyindex,cuda-python (python>3.7),pycuda,onnx,onnx-surgeon,onnxruntime-gpu,opencv-python,polygraphy
开发辅助工具
trtexec:模型转换、推理工具
位于tensor-xx/bin/trtexec,docker中位于: /opt/tensorrt/bin/
# 功能
由onnx文件生成TensorRT引擎并序列化为Plan文件
查看onnx文件或Plan文件的网络逐层信息
模型性能测试:测试TensorRT基于随机输入或给定输入下的性能
# 示例
# 使用onnx构建引擎并推理
trtexec
--onnx=model.onnx \
--minShapes=x:0:1x1x28x28 \
--optShapes=x:0:4x1x28x28 \
--maxShapes=x:0:16x1x28x28 \
--workspace=1024 \
--saveEngine=model-FP32.plan \
--shapes=x:0:4x1x28x28 \
--verbose
# 使用onnx构建引擎并推理,使用fp16模式
trtexec
--onnx=model.onnx \
--minShapes=x:0:1x1x28x28 \
--optShapes=x:0:4x1x28x28 \
--maxShapes=x:0:16x1x28x28 \
--workspace=1024 \
--saveEngine=model-FP32.plan \
--shapes=x:0:4x1x28x28 \
--verbose \
--fp16
# 读取引擎并推理
trtexec \
--loagEngine=./moder-Fp32.plan \
--shapes=x:0:4x1x28x28 \
--verbose
Netron: 网络模型可视化工具
Onnx-graphsurgeon: Onnx文件修改工具
onnx原生的api也能对onnx做一些修改
功能:
修改计算图:图属性 / 节点 / 张量 / 节点和张量的连接 / 权重
修改子图:添加 / 删除 / 替换 / 隔离
优化计算图:常量折叠 / 拓扑排序 / 去除无用层
功能和API上有别于onnx库
下载和参考文档:
pip install nvidia-pyindex onnx-graphsurgeon
https://github.com/NVIDIA/TensorRT/tree/master/tools/onnx-graphsurgeon/examples
https://docs.nvidia.com/deeplearning/tensorrt/onnx-graphsurgeon/docs/index.html
Polygraphy: 深度学习模型调试器
功能:
使用多种后端运行推理计算,包括TensorRT,onnxruntime,TensorFlow
比较不同后端的逐层计算结果
由模型文件生成TensorRT引擎并序列化为.plan
查看模型网络的逐层信息
修改Onnx模型,如提取子图,计算图化简
分析Onnx转TensorRT失败原因,将原计算图中可以/不可以转TensorRT的子图分割保存
隔离TensorRT中的错误tactic
下载和参考文档:
pip install polygraphy
https://docs.nvidia.com/deeplearning/tensorrt/polygraphy/docs/index.html
https:/www.nvidia.com/en-us/on-demand,/session/,gtcspring.21-s31695/(onnx-graphsurgeon和polygraphy的-个视频教程)
分为不同的模式,用以实现不同的功能
nsight systems: 性能调试器
随cuda安装。/usr/local/cuda/bin/nsys 和 /usr/local/cuda/bin/nsys-ui
用于替代就工具nvprof 和 nvvp
1. 使用nsys profile xxx, 获得.qbrep 或 .qdrep-nsys 文件
2. 打开nsys-ui,将上述文件拖入即可观察timeline
只计量运行阶段,而不分析构建期
构建期肘打开profiling以便获得关于Layer的更多信息
builder_config.profilling_verbosity trt.ProfilingVerbosity.DETAILED
可以搭配trtexec使用:nsys profile-o myProfile trtexec-loadEngine:=mode.plan -=-warmUp=0 --duration
也可以配合自己的script使用:nsys profile-o myProfile python myScript.py
高级话题
Optimization Profile
功能:
缩小每个Profile范围,方便TensorRT自动优化
推理时,根据数据形状选择相应Profile
注意输入输出数据的绑定位置
间接作用:
多Context的基础工作
增加显存占用、引擎尺寸和.plan尺寸
Cuda event、cuda stream
与pinned-memory
使用Context进行多线程调用
contextList [engine.create_execution_context()for index in range(nContext)]
Cuda Graph
优点:
降低CPU Launch cost
CUDA工作流优化
缓解大量kernel调用时的Launch Bound
要点:
Graph 定义,Graph实例化,Graph 执行
Dynamic Shape模式中,实际数据形状发生改变时(调用context..set binding_shape)
要先跑一遍context..execute再重新捕获Graph,最后再实例化和执行Graph
Timing Cache
节约engine的构建时间
优点:
优化单次引擎构建时间(模型内多个同参数的算子)
优化多次引擎构建时间(debug、参数更新后重新构建)
尤化同环境下多个引擎构建时间(跨builder可用)
用于反复生成一模一样的引擎
要点:
类似引擎序列化反序列化,将Timing Cache保存出来下次用
类似.plan,不可跨平台和开发环境使用
Algorithm Selector
为某一层的实现选择对应的算法
要点:
自己实现一个MyAlgorithmSelector类
关键是实现两个成员函数
一个用来挑选特定层的算法
一个用来报告所有层的挑选结果
构建网络时交给BuilderConfig
实际工作流程:
先通过polygraphy等工具发现某层的个Tactic
构造Algorithm Selector屏蔽掉盖层的该tactic
构建引擎
Refit
更新权重,而不重新构建engine
优点:
节约反复构建引擎的时间
强化学习必备
要点:
BuilderConfig中设置相应Flag
在构建好engine的基础上更新权重
更新某层权重后,邻近层可能他需要更新(尽管其值可能不变),如Convolution层中的kernel和bias
注意权重的排布方式
Dynamic Shape模式暂不支持
Tactic Source
要点:
BuilderConfig中设置相应Flag
可以开启或关闭cuBLAS、cuBLASLt、.cuDNN
优点:
节约部分内存、显存,减少构建时间
缺点:
不能使用某些优化,可能导致性能下降
可能导致构建失败
后续版本中,TensorRT将彻底断开对外部Library依赖
版本兼容
config.set flag(trt.BuilderFlag.VERSION COMPATIBLE)
runtime.engine_host_code_allowed True
前向后向均可
可能会有少许性能损失