六. 部署分类器-trt-engine-explorer

前言

自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考

本次课程我们来学习课程第六章—部署分类器,一起来学习 trt-engine-explorer 工具的使用

课程大纲可以看下面的思维导图

在这里插入图片描述

0. 简述

本小节目标:学习如何使用 trt-engine-explorer 工具

这个小节的案例代码与 C++ 没有多大的关系,它更多的是对我们生成的 trt-engine 模型的分析,用 python 脚本把 engine 的模型架构进行可视化

下面我们开始本次课程的学习🤗

1. 案例运行

在正式开始课程之前,博主先带大家跑通 6.4-trt-engine-inspector 这个小节的案例🤗

源代码获取地址:https://github.com/kalfazed/tensorrt_starter

首先大家需要把 tensorrt_starter 这个项目给 clone 下来,指令如下:

git clone https://github.com/kalfazed/tensorrt_starter.git

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/14 日,若有改动请参考最新

整个项目后续需要使用的软件主要有 CUDA、cuDNN、TensorRT、OpenCV,大家可以参考 Ubuntu20.04软件安装大全 进行相应软件的安装,博主这里不再赘述

下面我们要创建一个虚拟环境来安装 trt-engine-explorer 工具,博主这里准备了一个可以运行本小节案例代码的虚拟环境,大家可以按照这个环境来,也可以参考 trt-engine-explorer 官方文档 进行安装,指令如下:

conda create -n env_trex python=3.9
conda activate env_trex
git clone https://github.com/NVIDIA/TensorRT.git
cd TensorRT/tools/experimental/trt-engine-explorer
python3 -m pip install -e.
pip install werkzeug==3.0.0 Flask==3.0.0
sudo apt-get --yes install graphviz

安装完成后大家可以通过 pip 指令查看安装好的 trex 工具,如下图所示:

在这里插入图片描述

假设你的项目、环境准备完成,下面我们一起来运行下 6.4-trt-engine-inspector 小节案例代码

首先我们需要准备一个 engine 模型文件供 trt-engine-explorer 工具分析,这里我们直接拿上个小节生成的 resnet50-int8.engine 过来

下面我们需要利用脚本文件生成分析文件,指令如下:

# 将已有的分析文件全部删除
cd tensorrt_starter/chapter6-deploy-classification-and-inference-design/6.4-trt-engine-inspector/
rm -rf result/resnet50/*
# 将上小节创建好的 engine 拷贝过来
cp ../6.3-in8-calibration/models/engine/resnet50-int8.engine ./result/resnet50/
cd src/python
conda env_trex
python process_engine.py ../../result/resnet50/resnet50-int8.engine ../../result/resnet50 --profile-engine

输出如下图所示:

在这里插入图片描述

同时在 results/resnet50 目录下生成了一系列的文件,如下图所示:

在这里插入图片描述

我们主要关注 resnet50-int8.engine.graph.json 文件,它将 engine 各个 layer 的详细信息都保存下来了,包括 layer 的 name、dimensions、format 以及 data type 等等,其他的文件例如 profile.json、timing.json 则是一些 layer 执行时间的信息,profile.log 则是 trtexec 执行输出的日志信息

我们生成 resnet50-int8.engine.graph.json 文件后可以绘制出其网络结构,执行指令如下:

python draw_engine.py ../../result/resnet50/resnet50-int8.engine.graph.json

你可能会遇到如下的问题:

在这里插入图片描述

这个问题主要是因为 TensorRT 前段时间 10.0 版本发布时相关代码进行了修改,所以这里韩军老师提供的 draw_engine.py 脚本文件的 API 发生了变化

我们直接从目前 TensorRT 官网下的 draw_engine.py 替换当前的内容,如下所示:

#!/usr/bin/env python3
#
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


"""
This script generates an SVG diagram of the input engine graph SVG file.

Note: this script requires graphviz which can be installed manually:
    $ sudo apt-get --yes install graphviz
    $ python3 -m pip install graphviz
"""


import warnings
import argparse
import shutil
import trex.graphing
import trex.engine_plan


def draw_engine(engine_json_fname: str, profiling_json_fname: str=None, **kwargs):
    graphviz_is_installed = shutil.which("dot") is not None
    if not graphviz_is_installed:
        print("graphviz is required but it is not installed.\n")
        print("To install on Ubuntu:")
        print("sudo apt --yes install graphviz")
        exit()

    try:
        if kwargs["display_constants"] and not kwargs["display_regions"]:
            warnings.warn("Ignoring argument --display_constants because it requires --display_regions.")
    except KeyError:
        pass

    plan = trex.engine_plan.EnginePlan(engine_json_fname, profiling_file=profiling_json_fname)
    layer_node_formatter = trex.graphing.layer_type_formatter
    graph = trex.graphing.to_dot(plan, layer_node_formatter, **kwargs)
    trex.graphing.render_dot(graph, engine_json_fname, "svg")


def make_subcmd_parser(subparsers):
    draw = lambda args: draw_engine(
        engine_json_fname=args.input,
        profiling_json_fname=args.profiling_json,
        display_regions=args.display_regions,
        display_layer_names=not args.no_layer_names,
        display_constants=args.display_constant,
    )
    draw_parser = subparsers.add_parser("draw", help="Draw a TensorRT engine.")
    draw_parser.set_defaults(func=draw)
    _make_parser(draw_parser)


def _make_parser(parser):
    parser.add_argument("input", help="name of engine JSON file to draw.")
    parser.add_argument("--profiling_json", "-pj",
        default=None, help="name of engine JSON file to draw")
    parser.add_argument("--display_regions", "-dr",
        action='store_true', help="render memory regions as graph nodes.")
    parser.add_argument("--display_constant", "-dc",
        action='store_true', help="render constant input tensors.")
    parser.add_argument("--no_layer_names", "-no_ln",
        action='store_true', help="render constants.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    args = parser.parse_args(_make_parser(parser))

    draw_engine(
        engine_json_fname=args.input,
        profiling_json_fname=args.profiling_json,
        display_regions=True,
        expand_layer_details=False,
        display_latency=True,
    )

再次执行该脚本,输出如下:

在这里插入图片描述

执行成功后会生成一个 .svg 的图片格式文件,我们可以在浏览器中打开 .svg 的结构图,如下图所示:

在这里插入图片描述

如果大家能够看到上述输出结果,那就说明本小节案例已经跑通,下面我们就来看看具体的 engine 结构变化

2. 补充说明

在分析具体结构变化之前我们先来看下韩君老师在这小节中写的 README 文档

trt-engine-explorer 是 NVIDIA 官方提供的分析 TensorRT 优化后的推理引擎架构的工具包,可以在 TensorRT 官方的 git repository 中找打,具体细节大家可以参考:trt-engine-explorer

在这里插入图片描述

大家参考官方提供的 README.md 把环境搭建起来之后,我们可以尝试分析下我们的 engine 模型。为了方便,在 6.4 小节案例代码中的 src/python 目录下放置了一些官方提供的案例文件可以使用,比如:

通过 TensorRT 优化的推理引擎获取各个 layer 的信息,以 json 形式保存

python process_engine.py ../../result/resnet50/resnet50.engine ../../result/resnet50/resnet50.engine --profile-engine

通过保存得到的 json 信息,绘制出 TensorRT 优化后的网络模型架构,以 SVG 格式保存成图片

python draw_engine.py ../../result/resnet50/resnet50.engine.graph.json

通过 jupyter 在浏览器里打开 SVG 模式下的结构图

jupyter-notebook --ip=0.0.0.0 --no-browser

大家这里可以仔细比较一下各个模型的 .onnx 架构和 .engine 架构的不同,以及同一个模型的不同精度的 .engine 的不同。观察 TensorRT 优化后哪些层被融合了,哪些层是新添加的,大家感兴趣的还可以了解下 reformatter 节点是什么,其实 reformatter 非常重要,因为它涉及到了 TensorRT 的各个 layer 所支持的 data layout,如果想要把部署优化做到极致,理解 data layout 是必须要做的。

3. engine分析

我们对比着看下 resnet50.onnx 模型和 resnet50-int8.engine 引擎的结构变化

在这里插入图片描述

上图中左边是 onnx 右边是 engine,首先在 engine 中 input0 后面的 Reformat 是新添加的节点,接着有一个虚线框,这个代表着之前 onnx 中存在的节点但是现在经过 tensorRT 优化后不存在了。我们可以清晰的看到 onnx 模型中的 Conv、Relu 和 MaxPool 在 engine 中变成了一个 ConvActPool 节点,这个就是 TensorRT 做的层融合后的效果

Note:Engine 中的 Reformat 节点是什么呢?我们是不是应该尽量避免它的产生?

在 TensorRT 的引擎中,Reformat 节点用于在不同层之间处理数据格式的转换。这些节点通常出现在需要更改数据布局(例如从 NCHW 到 NHWC)或调整数据类型(如 FP32、FP16 和 INT8 之间的转换)时,这些转换是必要的,因为引擎中的不同层或操作可能偏好活需要不同的数据格式或精度,以实现性能优化。

通常来说,Reformat 节点越少越好,因为每次格式转换操作都会引入额外的开销,影响引擎的性能。过多的 Reformat 操作会减慢推理速度,因为这需要额外的内存操作

在进行 INT8 量化时,我们最好确保校准过程在各层之间是一致的,一致的量化可以减少在具有不同量化参数的层之间进行 Reformat 的需求

在这里插入图片描述

我们往后看可以发现 ONNX 中的 Relu 节点都不存在了,都变成了虚线框,resnet50 模型比较简单,所以整个 engine 架构还是比较好看的,没有添加什么冗余的东西

在这里插入图片描述

我们在生成 engine 的时候指定的精度是 INT8,那是不是意味着我们所有的节点都是 INT8 的呢,那当然不是,像 Convolution 这种密集型的计算大部分都是 INT8 精度的,但是像上图中的 SoftMax 节点的输入输出都是 FP32 精度

在这里插入图片描述

还有一个点大家需要注意,大家可以看到我们前面的数据都是 NCHW 这种格式,这个大家比较熟悉,但是经过 ConvActPool 之后就变成了 NC/32HW32 这种格式,那这个其实是 TensorRT 内部自定义的一种数据格式,更多细节大家可以看官方文档:data-format-desc

resnet 相对来说比较简单,这里大家可以再看看其他的模型例如 MobileNet 模型 engine 等等,像我们下一个章节要讲的 YOLO 模型,它的 INT8 engine 可能就没有这么好看,它的模型结构相对比较复杂,量化后会存在很多的 reformat 节点

在 tools 文件夹中有一个 build.sh 脚本文件,通过 trtexec 生成 engine,我们可以利用它来生成 mobilenet 的 int8 engine 模型,在此之前我们需要在该脚本文件中添加 --int8 参数,如下图所示:

在这里插入图片描述

接着执行如下指令:

bash tools/build.sh result/mobilnet/mobilenetV2.onnx int8

输出如下:

在这里插入图片描述

同时在当前目录会生成 .engine 和一系列日志文件,如下图所示:

在这里插入图片描述

那大家可能有所疑问为什么可以直接生成 INT8 的 engine 呢?难道不需要 calibration 吗?那其实 trtexec 提供了 --int8 参数运行我们生成 INT8 量化的 engine,如果你不提供 calibration_table,不提供各个 layer 的 dynamic range,那它量化出来的效果非常差,但是我们这里只是查看 mobilenet 的 engine 的结构,所以没有问题

下面我们就如法炮制,跟 resnet50 一样利用 trt-engine-explorer 去生成 engine 的 .svg 图片,这里博主就不再展示了,mobilenetV2-int8.engine 的结构如下图所示:

在这里插入图片描述

结语

本次课程我们学习了 trt-engine-explore 工具的使用,将 TensorRT 优化好的 engine 的结构绘制成 .svg 图片可视化出来。这个工具在做 QAT 量化时可能使用得更多,因为在 QAT 的过程中,模型的各个层之间经常会出现数据格式和精度不匹配的情况,导致 TensorRT 在构建引擎时插入 Reformat 节点。另外可以查看 QAT 模型中哪些层并没有按照我们期望的做 INT8,利用该工具可以有助我们优化模型结构,减少额外的转换操作,检查关键层的量化精度等等

OK,以上就是 6.4 小节案例的全部内容了,下节开始我们进入第七章节的学习,敬请期待😄

下载链接

参考

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值