欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > TensorRT基础入门(二)

TensorRT基础入门(二)

2025/4/18 20:00:42 来源:https://blog.csdn.net/weixin_36354875/article/details/146809279  浏览:    关键词:TensorRT基础入门(二)

TensorRT基础入门(二)

本文属于学习笔记,在重点章节或代码位置加入个人理解,欢迎批评指正!

参考
CUDA与TensorRT部署部署实战第三章


六. onnx注册算子的方法

学习目标

  • 学习pytorch导出onnx不成功的时候如何解决(无plugin版本)

6.0 当导出onnx出错时,如何思考?

难度有易到难:
1)修改onnx opset版本
2)若不考虑自己写tensorRT plugin插件,怎还需考虑onnx-trt是否有算子支持
3)替换pytorch中的算子组合
4)pytorch中未登记onnx算子
5)直接修改onnx, 创建plugin, 使用onnx-surgeon

6.1 方式一

符号函数 + register_custom_op_symbolic

Eg. Asinh 函数

# 定义符号函数,参数与onnx中算子对应上
def asinh_symbolic(g, input, *, out=None):return g.op("Asinh", input)   #onnx中的算子
# 注册,aten是一个实现张量运算的C++库
register_custom_op_symbolic('aten::asinh', asinh_symbolic, 12)

容易混淆的地方

  1. register_op中的第一个参数是PyTorch中的算子名字: aten::asinh
  2. g.op中的第一个参数是onnx中的算子名字: Asinh

6.2 方式二

使用类似于torch/onnx/symbolic_opset.py中的写法

#    让此算子可以与底层C++实现的aten::asinh绑定
#    一般如果这么写的话,其实可以把这个算子直接加入到torch/onnx/symbolic_opset*.py中
_onnx_symbolic = functools.partial(registration.onnx_symbolic, opset=9)
@_onnx_symbolic('aten::asinh')
def asinh_symbolic(g, input, *, out=None):return g.op("Asinh", input)

6.3 方式三

注册算子类

class CustomOp(torch.autograd.Function):@staticmethod # 自定义符号函数def symbolic(g: torch.Graph, x: torch.Value) -> torch.Value:return g.op("custom_domain::customOp2", x)@staticmethoddef forward(ctx, x: torch.Tensor) -> torch.Tensor:ctx.save_for_backward(x)x = x.clamp(min=0)return x / (1 + torch.exp(-x))customOp = CustomOp.apply

七. onnx-graph-surgeon

学习目标

  • 使用onnx-graphsurgeon ,比较与onnx.helper的区别
  • 学习快速修改onnx以及替换算子/创建算子的技巧,5个参考案例

7.1 onnx-graphsurgeon

是TensorRT下的一个工具包,与onnx的protobufu组织结构有区别,onnx-graphsurgeon更简单。
在这里插入图片描述
Eg.
1) 使用原生的gs创建一个conv

    # 输入tensor ,开始推理前是不确定的input = gs.Variable(name  = "input0",dtype = np.float32,shape = (1, 3, 224, 224))weight = gs.Constant(name  = "conv1.weight",values = np.random.randn(5, 3, 3, 3))bias   = gs.Constant(name  = "conv1.bias",values = np.random.randn(5))output = gs.Variable(name  = "output0",dtype = np.float32,shape = (1, 5, 224, 224))node = gs.Node(op      = "Conv",inputs  = [input, weight, bias],outputs = [output],attrs   = {"pads":[1, 1, 1, 1]})graph = gs.Graph(nodes   = [node],inputs  = [input],outputs = [output])

2) gs可以自定义一些函数算子去创建onnx,使整个onnx的创建更加方便

####在graph注册调用的函数####
@gs.Graph.register()
def add(self, a, b):return self.layer(op="Add", inputs=[a,b], outputs=["add_out_gs"]
@gs.Graph.register()
def mul(self, a, b):return self.layer(op="Mul", inputs=[a, b], outputs=["mul_out_gs"])
@gs.Graph.register()
def gemm(self, a, b, trans_a=False, trans_b=False):attrs = {"transA": int(trans_a), "transB": int(trans_b)}return self.layer(op="Gemm", inputs=[a, b], outputs=["gemm_out_gs"], attrs=attrs)
@gs.Graph.register()
def relu(self, a):return self.layer(op="Relu", inputs=[a], outputs=["act_out_gs"])#### 设计网络架构, 更简单
gemm0    = graph.gemm(input0, consA, trans_b=True)
relu0    = graph.relu(*graph.add(*gemm0, consB))
mul0     = graph.mul(*relu0, consC)
output0  = graph.add(*mul0, consD)

7.2 挖出子图,使用polygraphy分析

用途:某些网络结构复杂,想分析部分网络结构(子图)或者精度。
Eg. 从swin-tf中摘取出LayerNorm层

    #LayerNorm部分print(tensors["/patch_embed/Transpose_output_0"])print(tensors["/patch_embed/norm/LayerNormalization_output_0"])# 去onnx图中的相应模块地方 找到上一个output的name 然后将这个tensor复制进来graph.inputs = [tensors["/patch_embed/Transpose_output_0"].to_variable(dtype = np.float32, shape = (1, 3136, 128))]# 同理输出graph.outputs = [tensors["/patch_embed/norm/LayerNormalization_output_0"].to_variable(dtype = np.float32, shape = (1, 3136, 128))]# ** 重要apigraph.cleanup()  onnx.save(gs.export_onnx(graph), "models/LN-swin-subgraph.onnx")

7.3 替换算子

将minmax替换为clip。

#####################通过注册的clip算子替换网络节点####################
#          input (5, 5)
#            |
#         identity 
#            |
#           clip
#            |
#         identity  
#            |
#          output (5, 5)
def change_onnx_graph():graph = gs.import_onnx(onnx.load_model('../models/smaple-minmax.onnx'))tensors = graph.tensors()inputs = [tensors["identity_output_0"],tensors["onnx_graphsurgeon_constant_5"],tensors["onnx_graphsurgeon_constant_2"]]outputs = [tensors["max_output_6"]]## 替换子网,把子网和其周围节点断开联系for items in inputs:item.outputs.clear()for item in outputs:item.inputs.clear()# 通过注册的clip,重新把断开的联系链接起来graph.clip(inputs, outputs)# 删除所有额外的节点graph.cleanup()onnx.save(gs.export_onnx(graph), "../models/sample-minmax-to-clip.onnx")

注意:替换layerNorm算子例子参考课件工程中代码,因为低版本中此算子展开很丑陋,opset17才开始支持


八. 快速分析开源代码并导出onnx

学习目标

以swin-transformer为例,导出并分析

完整实验流程

1)opset_version 设置为9, 将export.py 导出脚本拷贝到swin-transformer官方工程目录下,执行导出脚本.

cd Swin-Transformer/
python export.py --eval --cfg configs/swin/swin_base_patch4_window7_224.yaml --resume ../weights/swin_tiny_patch4_window7_224.pth --data-path data/ --local_rank 0

参数说明

  • eval: 评估模式
  • cfg: 配置文件地址
  • –resume :加载预训练权重文件
  • data-path: 数据集根目录路径

结果:
roll算子不支持。
torch.roll(input, shifts, dims=None) 用于沿指定维度滚动张量元素,超出边界的元素会循环填充到另一侧‌。

# 原始数据是4*4 的矩阵
# 分别按照行下滚动两行,右滚动两列
torch.roll(a, shifts=(2, 2), dims=(0, 1))
# 输出:
tensor([[10, 11,  8,  9],[14, 15, 12, 13],[ 2,  3,  0,  1],[ 6,  7,  4,  5]])

Tips
实际实验过程中,opset_version 设置为9, PatchMerging::downsample 算子不支持, 改为12直接成功了.
原因是本地torch版本较高1.13.1,已经在onnx/opset_symbolic9.py中实现了roll算子。

2)opset_version 设置成12, 将自定义的roll算子放入到onnx/symbolic12.py中进行onnx算子注册,再导出。

## 在 ONNX 符号函数中,第一个参数是 g(GraphContext),第二个参数通常命名为 input 或 self(代表输入张量)。
@parse_args('v', 'is', 'is')
def roll(g, self, shifts, dims):  assert len(shifts) == len(dims)result = self## roll算子 =  “slice切片” + “concat” 组合形式for i in range(len(shifts)):shapes = []# - 2 表示倒数第二个元素shape = sym_help._slice_helper(g,result,axes=[dims[i]],starts=[-shifts[i]],ends=[maxsize])shapes.append(shape)shape = sym_help._slice_helper(g,result,axes=[dims[i]],starts=[0],ends=[-shifts[i]])shapes.append(shape)result = g.op("Concat", *shapes, axis_i=dims[i])return result

Tips.
layerNorm在opset17之后开始支持, onnx模型中会更简洁,TensorRT8.,6之后也支持了该算子。


九. trtexec使用

学习目标

学习使用trtexec,以及通过trtexec的日志理解TensorRT内部所做的优化

9.1 onnx导出TensorRT模型三种方式

  • 使用trtexec工具(二进制文件,可修改很多参数):快速生成推理引擎
  • 使用TensorRT python api
  • 使用TensorRT C++ api

9.2 使用trtexec工具

1.build脚本

trtexec执行build脚本以及option参数含义, 详细可以参考官网中Reference/Command-Line Programs

trtexec --onnx=${1}  \--memPoolSize=workspace:2048 \--saveEngine=${ENGINE_PATH}/${PREFIX}.engine \--profilingVerbosity=detailed \--dumpOutput \--dumpProfile \--dumpLayerInfo \--exportOutput=${LOG_PATH}/build_output.log \--exportProfile=${LOG_PATH}/build_profile.log \--exportLayerInfo=${LOG_PATH}/build_layer_info.log \--warmUp=200 \--iterations=50 \--verbose \--fp16 \>${LOG_PATH}/build.log

参数说明

  • onnx: onnx模型地址,如sample.onnx
  • memPoolSize: 生成引擎时,使用memory大小,一般为1024或者2048, 4096
  • saveEngine:引擎保存地址
  • sparsity=[disable|enable|force]: 是否开启稀疏化结构策略。disable:禁用所有使用结构化稀疏性的策略,默认设置。enable:使用结构化稀疏性启用策略。只有当ONNX文件权重满足结构化稀疏性要求时,才会使用策略。force:启用使用结构化稀疏性的策略,并允许trtexec覆盖ONNX文件中的权重,以强制它们具有结构化稀疏性模式。请注意,精度不会得到保留,这只是为了获得推理性能。
  • fp16: 指明精度,同时还支持–bf16 ,–int8, --fp8, ----noTF32,–best。
  • minShapes=< shapes>, --optShapes=< shapes>, and --maxShapes=< shapes>: 指明输入的shape
  • timingCacheFile:当启用该选项时,TensorRT 在构建新引擎时,会优先从缓存文件中加载历史时序信息,避免对相同层结构重复进行性能分析,显著缩短引擎构建耗时‌
  • verbose:与日志相关
  • skipInference: 跳过推理过程
  • exportOutput,exportProfile,exportLayerInfo: 输出,分析,层信息相关的输出地址
  • precisionConstraints=spec:是否对精度进行约束,因为可能会出现指定了fp16, 但是硬件优化发现fp32强于fp16。
  • layerPrecisions=spec: 指定层的量化精度
  • layerOutputTypes=spec:指定每一个层的输出类型,fp32/fp16等
  • layerDeviceTypes=spec: 指定每一个层的设备类型, DLA/GPU, DLA是边缘设备才有的
  • useDLACore:为特定层指定DLA core
  • allowGPUFallback:不支持在DLA上执行,退回到GPU上执行
  • dynamicPlugins:加载插件动态库,序列化到engine中。
  • builderOptimizationLevel:构建引擎的优化等级,等级越高,优化时间越长,模型越好。
  • warmup:预热, 200指20ms
2. infer脚本
trtexec --loadEngine=${ENGINE_PATH}/${PREFIX}.engine \--dumpOutput \--dumpProfile \--dumpLayerInfo \--exportOutput=${LOG_PATH}/infer_output.log\--exportProfile=${LOG_PATH}/infer_profile.log \--exportLayerInfo=${LOG_PATH}/infer_layer_info.log \--warmUp=200 \--iterations=50 \> ${LOG_PATH}/infer.log
3. profile脚本

Nsight System是Nvidia官方提供的分析软件,具体安装步骤可以参考下面链接,下一章专门讲解安装和工具使用。

nsys profile \--output=${LOG_PATH}/${PREFIX} \--force-overwrite true \trtexec --loadEngine=${ENGINE_PATH}/${PREFIX}.engine \--warmUp=0 \--duration=0 \--iterations=20 \--noDataTransfers \> ${LOG_PATH}/profile.log

profile文件夹下会生成sample-cbr.nsys-rep,将此文件用Nsight System软件进行打开。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词