地平线与英伟达工具链 PTQ 工具功能参数对比与实操

1.理论简介

在阅读本文之前,希望大家对 PTQ(Post-Training Quantization) 训练后量化有一定的了解~

地平线 OpenExplorer 和 NVIDIA TensorRT 是两家公司为适配自己的硬件而开发的算法工具链,它们各自具有独特的特点和优势。分开看的时候,网上有很多资料,但却没找到将他们放在一起对比的文章,本文从 PTQ 通路进行对比介绍。

  • OpenExplorer 中文名为天工开物,是地平线发布的算法开发平台,主要包括模型编译优化工具集、算法仓库和应用开发 SDK 三大功能模块。

  • TensorRT 是 NVIDIA 高性能深度学习的推理 SDK,包含深度学习推理优化器和运行时环境,可加速深度学习推理应用。利用 Pytorch、TensorFlow 等 DL 框架训练好的模型,通过模型转换编译生成可以在各家硬件上运行的格式(TensorRT xxx.engine/xxx.trt 或地平线 xxx.hbm/xxx.bin),提升这个模型在各家硬件(英伟达 GPU、地平线 BPU)上运行的速度。

为了让深度学习模型高效的在自家硬件上运行起来,他们都会做很多优化,包括但不限于 量化、数据压缩、算子替换、算子拆分、算子融合等等优化措施,下面以两家 show 出来的算子融合为例来看一下:

  • 英伟达宣传材料中的图片

  • 地平线宣传材料中的图片

量化方案:两家都是对称均匀量化,weight 是 per channel,feature 为 per tensor,地平线在有限条件下支持 feature per channel 量化

硬件相关

  • TensorRT 的核心在于对模型算子的优化(算子合并、量化、利用 GPU 特性选择特定核函数等策略),通过 tensorRT 能够在 NVIDIA 系列 GPU 上获得最好的性能,因此 tensorRT 的模型,需要在目标 GPU 上实际运行的方式选择最优算法和配置,也就是说 tensorRT 生成的模型只能在特定条件下运行(编译的 trt 版本、cuda 版本、编译时的 GPU 型号),不同硬件之间的优化是不能共享的。但是从 tensorrt8.6 开始,–hardwareCompatibilityLevel 参数可以允许用户在不同的架构上构建和运行模型,可能会带来一些性能损失(视情况而定,5%左右,若某个大的优化只支持特定架构,性能损失会比较大);

  • OpenExplorer 进行 PTQ 量化时需要指定参数 march,指定产出混合异构模型需要支持的平台架构,针对不同硬件,地平线提供的 OpenExplorer 是不同的,当然,本质上用到的几个 whl 包是相同的。

2.参数对比解读

  • NVIDIA
    • trtexec 工具提供的参数总体上可以分为:Model Options、Build Options、Inference Options、Reporting Options、System Options,最常用到的是前三个;
  • Horizon 征程 5
    • hb_mapper 工具提供的参数总体上可以分为:模型参数组、输入信息参数组、校准参数组、编译参数组、自定义算子参数组;
    • hrt_model_exec 工具提供模型信息查看、模型推理、模型性能评测三组参数;
  • Horizon 征程 6
    • hb_compile 等效于 hb_mapper 工具,新增了一些功能参数,用法上稍有不同
    • 同样使用 hrt_model_exec 工具 可以粗暴理解为:NVIDIA trtexec = Horizon J5 hb_mapper/J6 hb_compile + hrt_model_exec 本文将以 NVIDIA trtexec 工具(TensorRT-8.6.1)为核心,看看地平线 J5 OpenExplorer1.1.68 和 J6 OpenExplorer3.0.17 是如何提供与 trtexec 工具类似功能的。

2.1 模型转换编译(构建)

trtexec 工具常用参数与 J5 hb_mapper/J6 hb_compile 工具对比

2.1.1 Model Options

--onnx=<file> ONNX model # 指定 onnx model 的路径
  • J5 hb_mapper: –model
  • J6 hb_compile:–model NVIDIA 支持 ONNX、Caffe、UFF,Horizon 支持 ONNX、Caffe,但均主流支持 ONNX,本文仅介绍 ONNX 相关内容

2.1.2 Build Options

--minShapes=spec Build with dynamic shapes using a profile with the min shapes provided # 指定动态输入形状的范围最小值
--optShapes=spec Build with dynamic shapes using a profile with the opt shapes provided # 指定动态输入形状的范围常见值
--maxShapes=spec Build with dynamic shapes using a profile with the max shapes provided # 指定动态输入形状的范围最大值
动态shape时,三个参数均必须配置,可以配置为一样的
Example
--minShapes=input0:1x3x224x224
--optShapes=input0:1x3x224x224
--maxShapes=input0:16x3x224x224
多输入时 spec:
input0:1x3x256x256,input1:1x3x128x128
  • 征程 5 不支持动态 shape
  • 征程 6 支持动态 shape,hb_compile 对应参数:待完善
输入/输出  数据精度和排布` `--inputIOFormats=spec Type and format of each of the input tensors (default = all inputs in fp32:chw)` `--outputIOFormats=spec Type and format of each of the output tensors (default = all outputs in fp32:chw)` `精度可选:fp32、fp16、int32、int8` `排布可选 chw、hwc、chw16 等` `Example` `--inputIOFormats=fp32:chw,fp32:chw     # 两个输入` `-outputIOFormats=fp16:chw,fp16:chw     # 两个输出
  • J5 hb_mapper 相关参数有:
    • node_info # 配置节点输入/输出精度
    • input_type_rt # 板端模型输入数据格式 nv12/rgb/featuremap 等
    • input_layout_rt # 板端输入数据排布 NCHW/NHWC
    • 输出排布和 onnx 保持一致
    • input_type_train # 原始浮点模型的输入数据类型 rgb/bgr 等
    • input_layout_train # 原始浮点模型的输入数据排布 NCHW/NHWC
  • J6 hb_compile 相比于 J5 hb_mapper 的差异:
    • 取消 input_layout_rt,板端输入 layout 与原始浮点输入数据排布相同
    • 增加 quant_config 参数,支持对模型算子计算精度进行精细化配置

NVIDIA 的 tensor core 和地平线的 BPU core 都是 HWC 排布的,HWC 数据排布是为了编译优化模型性能,虽然 CHW 和 HWC 排布均支持,但内部会进行转换。 针对网络输入/输出数据排布,允许用户进行一些控制

--workspace=N    # Set workspace size in MiB,可融进memPoolSize参数中
--memPoolSize=poolspec # Specify the size constraints of the designated memory pool(s) in MiB.
Example: --memPoolSize=workspace:1024.5,dlaSRAM:256
  • J5 hb_mapper 以及 J6 hb_compile 不需要配置类似参数
--profilingVerbosity=mode           # Specify profiling verbosity. mode ::= layer_names_only|detailed|none (default = layer_names_only), 打印信息的详细程度
Example: --profilingVerbosity=detailed    # 构建期间保留更多逐层信息
  • 类似于 hb_mapper/hb_compile 中 advice 和 debug 参数
--refit    # 标记生成的这个engine是refittable,即可以指定稍后更新其权重
  • hb_mapper 与 hb_compile 中无相关参数

允许用户在运行时重新适配(refit)TensorRT 引擎的权重。对于需要在推理过程中动态更新模型权重的场景比较有用,例如在模型部署后需要根据新数据进行微调,强化学习中或在保留相同结构的同时重新训练模型时,权重更新是使用 Refitter(C++、Python)接口执行的。

--sparsity=spec    # Control sparsity (default = disabled),spec ::= "disable", "enable", "force"` `disable = 不稀疏` `enable  = 权重满足稀疏规则才稀疏` `force   = 强制稀疏
  • hb_mapper 与 hb_compile 中无对应参数,默认支持稀疏化
--noTF32    # 禁用TF32精度,(default is to enable tf32, in addition to fp32)
--fp16      # 使能fp16精度
--fp8       # 使能fp8精度
--int8      # 使能int8量化精度
  • J5 hb_mapper 不支持 TF32/fp16/fp8、可以通过 run_on_cpu 或 node_info 将节点配置运行在 CPU 上

  • J5 hb_mapper 支持 int8 和 int16 以及尾部 conv 节点 int32 量化,可以通过 node_info 或 run_on_bpu 参数进行配置

  • J6 hb_compile 通过 node_info 和 quant_config,支持对模型算子计算精度进行精细化配置

TF32 是英伟达提出的代替 FP32 的单精度浮点格式,TF32 采用与半精度( FP16 )数学相同的 10 位尾数位精度,这样的精度水平远高于 AI 工作负载的精度要求。同时, TF32 采用与 FP32 相同的 8 位指数位,能够支持与其相同的数字范围。 注意:NV 不论配置哪一项,fp32 都是会使用的。举例:配置–fp16,网络会使用 fp16+fp32;配置–int8 和–fp16,网络会使用 int8+fp16+fp32

--best   # fp32+fp16+int8 同时使用,找一个速度最快的
  • 类似于 hb_mapper/hb_compile --fast-perf 功能,主要用于性能评测
--precisionConstraints=spec    # 精度限制spec ::= "none" | "obey" | "prefer",(default = none),联合下面两个参数使用
        # none = 无限制
        # prefer = 优先满足--layerPrecisions/--layerOutputTypes设置的精度限制,不满足可回退到默认精度
        # obey =   优先满足--layerPrecisions/--layerOutputTypes设置的精度限制,不满足报错退出
--layerPrecisions=spec  # 控制per-layer的运算精度,仅在precisionConstraints为obey或prefer时有效. (default = none)
        # spec::= layerName:precision
        # precision::= "fp32"|"fp16"|"int32"|"int8"
--layerOutputTypes=spec # 控制per-layer的输出精度(类型),仅在precisionConstraints为obey或prefer时有效. (default = none)
        # spec::= layerName:type
        # type ::= "fp32"|"fp16"|"int32"|"int8"
  • 类似于 J5 hb_mapper run_on_bpu、run_on_cpu、node_info 三个参数的功能,支持对模型算子计算精度进行精细化配置

  • 类似于 J6 hb_compile node_info 和 quant_config,支持对模型算子计算精度进行精细化配置

--layerDeviceTypes=spec    # 指定layer运行的器件
        # spec ::= layerName:deviceType
        # deviceType ::= "GPU"|"DLA"
  • 类似于 hb_mapper/hb_compile node_info、run_on_cpu、run_on_bpu 参数的功能
--calib   # 指定int8校准缓存文件
  • 类似于 hb_mapper/hb_compile 中如下参数:
    • cal_data_dir # 模型校准使用的样本存放目录
    • cal_data_type # 指定校准数据的数据存储类型

下面针对校准数据进行一些更详细的介绍:

trtexec 工具支持送入校准数据,在校准过程中,可以使用 --calib 选项来指定一个包含校准数据的文件。这个文件通常是一个缓存文件,包含了模型在特定输入数据集上运行时的激活值的统计信息。

trtexec --onnx=model.onnx --int8 --calib=calibrationCacheFile.cache

trtexec 工具本身不提供校准数据的生成功能,需要事先通过其他方式(例如使用 TensorRT 的 API trt.IInt8EntropyCalibrator2)生成,包含了用于量化的统计信息。

NVIDIA 校准方法

  • IInt8EntropyCalibrator2 熵标定选择张量的尺度因子来优化量子化张量的信息论内容,通常可以抑制分布中的异常值。目前推荐的熵校准器。默认情况下,校准发生在层融合之前,适用于 cnn 类型的网络。

  • IInt8EntropyCalibrator 最原始的熵校准器,目前已不推荐使用。默认情况下,校准发生在层融合之后。

  • IInt8MinMaxCalibrator 该校准器使用整个激活分布范围来确定比例因子。推荐用于 NLP 任务的模型中。默认情况下,校准发生在层融合之前。

  • IInt8LegacyCalibrator 该校准器需要用户进行参数化,默认情况下校准发生在层融合之后,不推荐使用。

hb_mapper/hb_compile 工具支持送入校准数据(cal_data_dir),且在模型转换编译过程中选择校准方法(cal_data_type)。与 NVDIA 不同的是,地平线校准数据仅是满足模型输入的图像/数据文件,并不包含校准信息。

hb_mapper/hb_compile 工具本身不提供校准数据的生成功能,需要事先通过其他方式(例如使用 numpy)生成,不包含用于量化的统计信息。

地平线校准方法(校准发生在融合之后)

  • default default 是一个自动搜索的策略,会尝试从系列校准量化参数中获得一个相对效果较好的组合。

  • mix mix 是一个集成多种校准方法的搜索策略,能够自动确定量化敏感节点,并在节点粒度上从不同的校准方法中挑选出最佳方法, 最终构建一个融合了多种校准方法优势的组合校准方式。

  • kl KL 校准方法是借鉴了 TensorRT 提出的解决方案 , 使用 KL 熵值来遍历每个量化层的数据分布,通过寻找最低的 KL 熵值,来确定阈值。 这种方法会导致较多的数据饱和和更小的数据量化粒度,在一些数据分布比较集中的模型中拥有着比 max 校准方法更好的效果。

  • max max 校准方法是在校准过程中,自动选择量化层中的最大值作为阈值。 这种方法会导致数据量化粒度较大,但也会带来比 KL 方法更少的饱和点数量,适用于那些数据分布比较离散的神经网络模型。

--saveEngine    # 保存序列化后的引擎文件,常见后缀名有 model.engine,model.trt
  • 类似于 hb_mapper/hb_compile 中如下参数:
    • working_dir # 模型转换输出结果的存放目录
    • output_model_file_prefix # 指定转换产出物名称前缀
--timingCacheFile=<file>    # 保存/加载序列化的 global timing cache,减少构建时间
  • hb_mapper/hb_compile 默认启用 cache 功能

通过时序缓存保存构建阶段的 Layer 分析信息(特定于目标设备、CUDA 版本、TensorRT 版本),如果有其他层具备相同的输入/输出张量配合和层参数,则 TensorRT 构建器会跳过分析并重用缓存结果。

--builderOptimizationLevel      # 构建时编译优化等级,范围是0~5,默认是3
    # Higher level allows TensorRT to spend more building time for more optimization options.
    # 日志中默认时该参数显示-1,其他整数也都可以配置,从官方手册看,3比较稳定,性能也ok,4和5可能会构建失败。
  • J5 hb_mapper 相关参数:optimize_level,范围是 O0~O3,默认是 O0,耗时评测时需要配置为 O3

  • J6 hb_compile 相关参数:optimize_level,范围是 O0~O2,默认是 O0,耗时评测时需要配置为 O2

下图是 trtexec 关于优化等级对于构建 build 耗时与延迟 latency 耗时,hb_mapper/hb_compile 也是类似的情况。

--versionCompatible    # 标记engine是软件版本兼容的(相同OS、只支持显式batch)
  • hb_mapper/hb_compile 无相关参数,默认兼容,跨多个版本时可能会存在不兼容的情况
--hardwareCompatibilityLevel=mode  # 生成的engine模型可以兼容其他的GPU架构 (default = none)
                                   # 硬件兼容等级: mode ::= "none" | "ampere+"
                                   #     none = no compatibility
                                   #     ampere+ = compatible with Ampere and newer GPUs
  • hb_mapper/hb_compile 无相关参数,通过 march 指定架构即可,例如 征程 5 的 bayes、征程 6 的 nash-e/m 等
--maxAuxStreams=N     # Set maximum number of auxiliary streams per inference stream that TRT is allowed to use to run kernels in parallel if the network contains ops that can run in parallel, with the cost of more memory usage.           # 指定 TensorRT 在构建引擎时可以使用的最大辅助流(auxiliary streams)数量
       # 辅助流是 CUDA 流的一种,用于在网络中的不同层之间实现并行计算,特别是某些层可以独立执行时,可能提高整体的推理性能。
       # 默认根据网络的结构和可用的 GPU 资源自动决定使用多少辅助流。
       # 使用辅助流可能会增加 GPU 内存的使用,因为每个流都需要自己的内存副本。
  • hb_mapper/hb_compile 相关参数:
    • compile_mode # 编译策略选择
    • balance_factor # balance 编译策略时的比例系数

stream 是 CUDA 中为了实现多个 kernel 同时在 GPU 上运行,实现对 GPU 资源划分,利用流水线的方法提高 GPU 吞吐率的机制。 并行化操作,地平线会在模型编译时由编译器完成。

2.2 模型推理(评测) Inference Options

trtexec 工具常用参数与 hrt_model_exec 工具(J5、J6 都有这个工具)对比

--loadEngine=<file>        # 加载序列化后的引擎文件
  • hrt_model_exec 相关参数:
    • model_file # 模型文件路径
    • model_name # 指定模型中某个模型的名称,针对打包模型,用的较少
--shapes=spec     # 针对动态 shape,推理时使用的 shape` `多输入时示例` `--shapes=input0:1x3x256x256, input1:1x3x128x128
  • 征程 5 尚不支持动态 shape、征程 6 待呈现
--loadInputs=spec    # 加载输入数据,默认使用随机值,主要用于debug engine推理结果是否和 pytorch 一致
        # spec ::= name:file
        # 输入的 binary 通过 numpy 导出即可
  • hrt_model_exec 相关参数:input_file
--iterations=N      # 运行最少 N 次推理 ,default = 10
  • hrt_model_exec 相关参数:frame_count
--warmUp=N    # 性能测试时执行 N 毫秒的 warmup,default = 200
  • hrt_model_exec 不支持 warmup,采用多帧推理获取平均值的方式,可以很大程度上规避第一帧多出来的耗时
--duration=N    # 最少运行 N 秒 (default = 3),配置为-1会一直执行
  • hrt_model_exec 相关参数:perf_time
--sleepTime=N    # 推理前延迟 N 毫秒(between launch and compute),默认N=0
--idleTime=N     # 两次连续推理之间空闲 N 毫秒,默认N=0
  • hrt_model_exec 无相关参数
--infStreams=N         # Instantiate实例化N个engine,测试多流执行时是否提速 (default = 1),以前是--streams
  • J5 hrt_model_exec 无相关参数,没有多流的概念

在 CUDA 中,流(stream)是一种执行模型,它允许开发者将多个计算任务(如内核执行、内存拷贝等)组织成队列,由 GPU 异步执行。使用多个流可以提高 GPU 利用率,因为当一个流的任务等待内存拷贝或其他非计算密集型操作时,GPU 可以切换到另一个流执行计算密集型任务。infStreams 与 CUDA 流的概念直接相关,它影响的是模型推理任务在 GPU 上的并行执行。 maxAuxStreams 是 TensorRT 内部用于优化网络层执行的机制,它允许 TensorRT 在内部使用多个流来并行化可以并行化的层。 两者之间的关系在于它们都旨在通过并行化策略来提高 GPU 上的推理性能,但它们作用的层面和具体实现方式不同。

--exposeDMA    # Serialize DMA transfers to and from device (default = disabled)
    # 默认动态内存分配:TensorRT 会在执行推理时动态地分配和释放内存,用于存储中间层的激活值等。
    # DMA:直接内存访问,允许硬件设备直接在内存中读写数据,绕过CPU,从而减少CPU负载和提高数据传输效率。
    # 在某些情况下,使用 DMA 可能会增加程序的复杂性,因为它需要正确管理内存的分配和释放
  • hrt_model_exec 无相关参数
  • 地平线 CPU 和 BPU 是共享内存的
--noDataTransfers    # Disable DMA transfers to and from device (default = enabled)
    # 勿将数据传入和传出设备,用于什么场景呢?
    # 猜测:禁用数据在主机和设备之间的传输,方便分析模型计算部分耗时?没有H2D/D2H(Host/Device)数据传输可能会存在GPU利用率
  • hrt_model_exec 无相关参数
--useManagedMemory    # Use managed memory instead of separate host and device allocations (default = disabled).
    # 猜测:使用托管内存(Managed Memory),允许 CPU 和 GPU 同时访问而不需要显式的数据传输,提高数据共享的效率
  • hrt_model_exec 无相关参数
--useSpinWait    #  主动同步 GPU 事件。 此选项可能会减少同步时间,但会增加 CPU 使用率和功率(default = disabled)
  • hrt_model_exec 无相关参数

启用这个参数时,TensorRT 在等待 GPU 计算完成时使用自旋等待(spin wait)策略,而不是阻塞等待(block wait)。

  • 阻塞等待:在默认情况下,当 TensorRT 引擎执行推理任务时,如果 GPU 计算尚未完成,它会挂起(阻塞)当前线程,直到 GPU 计算完成并返回结果。这种等待方式可能会导致线程在等待期间不执行任何操作,从而影响整体的 CPU 利用率和系统性能。
  • 自旋等待:启用 --useSpinWait 参数后,TensorRT 会采用自旋等待策略。在这种模式下,线程会循环检查 GPU 计算是否完成,而不是挂起。自旋等待可以减少线程挂起和恢复的开销,从而在某些情况下,例如 GPU 计算时间与 CPU 处理时间相比 较短的情况下。通过减少线程挂起的频率,可以提高 CPU 的利用率,从而可能提升整体的系统性能。
  • GPU 计算时间不稳定或较短时,自旋等待可以减少线程上下文切换的开销,并保持 CPU 核心的活跃状态。然而,自旋等待也可能导致 CPU 资源的过度使用,特别是在 GPU 计算时间较长的情况下,因此需要根据具体的应用场景和硬件配置来权衡是否使用这个参数。
--threads       # 启用多线程以驱动具有独立线程的引擎 or 加速refitting  (default = disabled)
  • hrt_model_exec 相关参数:thread_num

"stream(流)"和"thread(线程)"是两个不同的概念,用于处理并发和数据流的情况。

  1. 线程(Thread): 线程是计算机程序中执行的最小单位,也是进程的一部分。一个进程可以包含多个线程,它们共享进程的资源,如内存空间、文件句柄等。线程可以并行执行,使得程序能够同时处理多个任务。线程之间可以共享数据,但也需要考虑同步和互斥问题,以避免竞争条件和数据损坏。
  2. 流(Stream): 流是一种数据传输的抽象概念,通常用于输入和输出操作。在计算机编程中,流用于处理数据的连续流动,如文件读写、网络通信等。流可以是字节流(以字节为单位处理数据)或字符流(以字符为单位处理数据)。流的一个常见特性是按顺序处理数据,不需要一次性将所有数据加载到内存中。 总之,线程是一种用于实现并发执行的机制,而流是一种用于处理数据传输的抽象概念。
--useCudaGraph    # Use CUDA graph to capture engine execution and then launch inference (default = disabled)
  • hrt_model_exec 无相关参数

useCudaGraph 参数允许用户指示 TensorRT 在执行推理时使用 CUDA 图(CUDA Graph)。CUDA 图是一种 CUDA 编程技术,它允许开发者创建一个或多个 CUDA 内核及其内存依赖关系的静态表示,这可以提高执行效率和性能。 CUDA 图的优势

  • 性能提升:通过使用 CUDA 图,可以减少运行时的开销,因为它们允许预编译一组 CUDA 操作,从而减少每次执行操作时的启动延迟。
  • 重用性:一旦创建了 CUDA 图,它可以被重用于多个推理请求,这使得它特别适合于高吞吐量和低延迟的应用场景。
  • 并行化:CUDA 图可以并行执行多个节点,这有助于提高 GPU 的利用率和整体的推理性能。 使用场景
  • 高并发推理:在需要处理大量并发推理请求的场景中,使用 --useCudaGraph 可以提高处理速度和效率
  --timeDeserialize    # 测量序列化引擎文件(.engine)的反序列化时间
  • hrt_model_exec 会在终端中打印 加载 板端模型 的时间
  • 反序列化时间:–timeDeserialize 参数会让 trtexec 测量将序列化的 TensorRT 引擎文件加载到 GPU 内存中所需的时间。
  • 性能分析:通过测量反序列化时间,开发者可以了解模型加载阶段的性能瓶颈,并探索减少模型加载时间的方法。
--timeRefit    # Time the amount of time it takes to refit the engine before inference.
  • hrt_model_exec 无相关参数

猜测:重新适配(refitting)是指在模型转换为 TensorRT 引擎后,根据新的权重或校准数据更新引擎的过程,比如将模型的权重从一种精度转换为另一种精度,或者根据新的校准数据调整量化参数。

--separateProfileRun    # 控制性能分析和推理测试的执行方式,配置它时,二者会分开进行(两次)
  • 类似于 hb_mapper/hb_compile 中 debug 参数,debug 默认配置为 True,编译后会在 html 静态性能评估文件中增加逐层的信息打印,可以帮助分析性能瓶颈。该参数开启后不会影响模型的推理性能,但会极少量地增加模型文件大小。

trtexec 使用该参数,一次用于收集性能分析数据的运行,另一次用于计算性能基准测试的运行,提高分析/测试的准确性。

--skipInference    # 只构建engine,不推理engine进行性能测试(default = disabled),以前是--buildOnly
  • 地平线 模型构建与模型推理/性能评测是分开的,无相关参数
--persistentCacheRatio # Set the persistentCacheLimit in ratio, 0.5 represent half of max persistent L2 size,默认是0
  • 地平线无相关参数
  • 缓存管理:–persistentCacheRatio 参数用于控制 TensorRT 引擎在执行推理时分配给持久化缓存的内存比例
  • 性能优化:合理设置缓存比例可以提高模型的推理性能,尤其是在处理大型模型或复杂网络结构时
  • 内存使用:增加持久化缓存的比例可能会减少内存占用,但也可能导致缓存溢出
  • TensorRT 会自动管理缓存,因此手动设置–persistentCacheRatio 不是必须的。只有需要精细控制内存使用或优化性能时才会用到

2.3 报告选项 Reporting Options

--verbose    # 使用详细的日志输出信息(default = false)
  • 地平线无相关参数

日志中增加很多信息,类似于:[08/09/2024-17:18:51] [V] [TRT] Registered plugin creator - ::BatchedNMSDynamic_TRT version 1

--avgRuns=N    # 指定在性能测试中连续执行推理的次数,以计算平均性能指标(default = 10)
  • 类似于 hrt_model_exec 中 frame_count 参数

为了减少偶然因素对性能测试结果的影响,通过多次运行推理并取平均值来提供一个更加稳定和可靠的性能度量。

--percentile=P1,P2,P3,...    # 指定在性能测试中报告的执行时间百分比,0<=P_i<=100 (default = 90,95,99%)
  • hrt_model_exec 中无相关参数

设置 --percentile=99,trtexec 将会报告第 99 百分位的执行时间,这意味着在 100 次推理中,有 99 次的执行时间会小于或等于报告的值,而只有 1 次的执行时间会大于这个值。故:0 representing max perf, and 100 representing min perf

--dumpRefit    # Print the refittable layers and weights from a refittable engine
  • hrt_model_exec 中无相关参数
--dumpLayerInfo             # 打印 engine 的每层信息 v (default = disabled)` `--exportLayerInfo=<file>    # 将 engine 的 layer 打印信息存储下来,xxx.json (default = disabled)` `Example: --exportLayerInfo=layer.json --profilingVerbosity=detailed
  • hb_mapper/hb_compile 默认会在日志中打印层的信息
--dumpProfile               # 打印每一层的 profile 信息 (default = disabled)
--exportProfile=<file>      # 将 profile 打印信息存储下来,xxx.json (default = disabled)
  • hb_mapper/hb_compile 默认开启 debug 参数后,会在转换编译过程中生成 html 文件,其中有类似的层耗时信息

  • hrt_model_exec 工具中 profile_path 参数

--dumpOutput                将推理结果直接打印出来 (default = disabled)
--dumpRawBindingsToFile     将 input/output tensor(s) of the last inference iteration to file(default = disabled)
--exportOutput=<file>       将 ouput 打印信息存储下来,xxx.json (default = disabled)
--exportProfile=<file>      将 profile 打印信息存储下来,xxx.json (default = disabled)
  • 类似于 J5 hrt_model_exec 中 dump_intermediate、enable_dump、dump_format 等
--exportTimes=<file>        # 将各个层的执行时间存储下来 (default = disabled)
  • hrt_model_exec 无相关参数

2.4 系统选项 System Options

--device=N        # Select cuda device N (default = 0),选择执行的GPU
--useDLACore=N    # Select DLA core N for layers that support DLA (default = none),使用较少
  • 类似于 hrt_model_exec 中 core_id 参数
# 加载插件,实现自定义算子的编译工作,区分动/静态插件,替代以前的--plugins
--staticPlugins             Plugin library (.so) to load statically (can be specified multiple times)
--dynamicPlugins            Plugin library (.so) to load dynamically and may be serialized with the engine if they are included in --setPluginsToSerialize (can be specified multiple times)
# 允许将插件序列化进engine中,结合--dynamicPlugins参数使用
--setPluginsToSerialize     Plugin library (.so) to be serialized with the engine (can be specified multiple times)
  • 类似于 J5 hb_mapper 中 custom_op_method、op_register_files、custom_op_dir 参数

  • J6 hb_compile 待确定

看到这儿,trtexec 大部分参数就介绍完成了,还有少量不常用到参数,例如–minTiming、–avgTiming=M、–tempdir、–tempfileControls、–useRuntime=runtime、–leanDLLPath=、–excludeLeanRuntime、–ignoreParsedPluginLibs 未进行介绍,欢迎大家自行探索。

各家的工具都会针对自己的硬件或特性设计针对性的参数,只要满足开发者需要的功能即可,例如地平线工具链的一些参数,有一些就没介绍到。

这么多参数其实并不是都会用到,大家根据自己的需求选择性使用即可。

3.实操演示

3.1 onnx 模型生成

import torch.nn as nn
import torch
import numpy as np
import onnxruntime

class MyNet(nn.Module):
    def __init__(self, num_classes=10):
        super(MyNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),   # input[3, 28, 28]  output[32, 28, 28]          
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),  # output[64, 14, 14]
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2)                             # output[64, 7, 7]
        )
        self.fc = nn.Linear(64 * 7 * 7, num_classes)

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.fc(x)
        return x

# -----------------------------------#
#   导出静态ONNX
# -----------------------------------#
def model_convert_static_onnx(model, dummy_input, output_path):
    input_names = ["input1"]        # 导出的ONNX模型输入节点名称
    output_names = ["output1"]      # 导出的ONNX模型输出节点名称
    torch.onnx.export(
        model,
        dummy_input,
        output_path,
        verbose=False,          # 如果指定为True,在导出的ONNX中会有详细的导出过程信息description
        opset_version=11,       # J5目前仅支持为 10 or 11
        input_names=input_names,
        output_names=output_names,
    )

# -------------------------------------------------------------------------#
#   导出动态ONNX
#   dynamic_axes 参数中可以只指定输入动态,因为输出维度会根据模型的结构自动推出来。
#   一般场景,只做 batch 维度动态
# -------------------------------------------------------------------------#
def model_convert_dynamic_onnx(model, dummy_input, output_path):
    input_names = ["input1"]        # 导出的ONNX模型输入节点名称
    output_names = ["output1"]      # 导出的ONNX模型输出节点名称
    torch.onnx.export(
        model,
        dummy_input,
        output_path,
        verbose=False,          # 如果指定为True,在导出的ONNX中会有详细的导出过程信息description
        opset_version=11,       # J5目前仅支持为 10 or 11
        input_names=input_names,
        output_names=output_names,
        dynamic_axes={"input1": {0: "batch"}, "output1":{0: "batch"}}
    )

if __name__ == '__main__':
    torch.manual_seed(1)

    model = MyNet()
    # 将模型转成 eval 模式
    model.eval()
    # 网络输入
    input_shape = (28, 28)
    dummy_input = torch.randn(1, 3, input_shape[0], input_shape[1])
    # torch推理
    with torch.no_grad():
        torch_output = model(dummy_input)
        print("torch_output:", torch_output)

    # 导出静态ONNX模型
    output_static_path = './static.onnx'
    model_convert_static_onnx(model, dummy_input, output_static_path)
    print("model export static onnx finsh.")
    static_sess = onnxruntime.InferenceSession(output_static_path)
    static_output = static_sess.run(None, {"input1": dummy_input.numpy()})
    print("static_output: ", static_output)

上述代码运行后,会生成一个 static.onnx,接下来就可以使用这个 onnx 啦。

3.2 性能评测实测

实操的方向不同,使用的命令和脚本也会有差异,本文重点在对比两家工具链的 PTQ 功能参数对比介绍上,因此只选择一个性能评测方向进行实操演示。

  • 英伟达 trtexec

构建用于性能评测的 engine,另外性能数据可以一起产出,脚本如下:

trtexec \
    --onnx=static.onnx \
    --saveEngine=static.engine \
    --useCudaGraph \
    --noDataTransfers \
    --useSpinWait \
    --infStreams=8 \
    --maxAuxStreams=8 \
    --builderOptimizationLevel=5 \
    --threads \
    --best \
    --verbose \
    --profilingVerbosity=detailed \
    --dumpProfile \
    --dumpLayerInfo \
    --separateProfileRun \
    --avgRuns=100 \
    --iterations=1000 >1.log 2>&1

会产出 engine 文件:resnet18.engine,以及一些日志,例如:

[08/09/2024-14:11:17] [I] === Performance summary ===
[08/09/2024-14:11:17] [I] Throughput: 21241.3 qps
[08/09/2024-14:11:17] [I] Latency: min = 0.0292969 ms, max = 4.11438 ms, mean = 0.036173 ms, median = 0.03479 ms, percentile(90%) = 0.0389404 ms, percentile(95%) = 0.0422974 ms, percentile(99%) = 0.0679932 ms
[08/09/2024-14:11:17] [I] Enqueue Time: min = 0.0141602 ms, max = 4.099 ms, mean = 0.0175454 ms, median = 0.017334 ms, percentile(90%) = 0.0184326 ms, percentile(95%) = 0.0209961 ms, percentile(99%) = 0.0261841 ms
[08/09/2024-14:11:17] [I] H2D Latency: min = 0.00366211 ms, max = 4.05176 ms, mean = 0.0064459 ms, median = 0.00561523 ms, percentile(90%) = 0.00682068 ms, percentile(95%) = 0.00720215 ms, percentile(99%) = 0.0361328 ms
[08/09/2024-14:11:17] [I] GPU Compute Time: min = 0.0214844 ms, max = 4.10327 ms, mean = 0.024971 ms, median = 0.0244141 ms, percentile(90%) = 0.0274658 ms, percentile(95%) = 0.0285645 ms, percentile(99%) = 0.0317383 ms
[08/09/2024-14:11:17] [I] D2H Latency: min = 0.00268555 ms, max = 0.0428467 ms, mean = 0.0047562 ms, median = 0.00415039 ms, percentile(90%) = 0.0065918 ms, percentile(95%) = 0.00695801 ms, percentile(99%) = 0.0113525 ms
[08/09/2024-14:11:17] [I] Total Host Walltime: 3.00005 s
[08/09/2024-14:11:17] [I] Total GPU Compute Time: 1.59128 s
  • 地平线 hb_compile 与 hrt_model_exec

转换编译用于性能评测的 hbm

hb_compile --fast-perf --model static.onnx --march nash-m

会生成 static.hbm,以及一些日志

在板端评测性能数据

hrt_model_exec perf --model_file static.hbm --thread_num 8 --frame_count 400 --internal_use

由于时间原因,就先到这儿了,下次再聊~

posted @ 2024-10-17 21:45  地平线智能驾驶开发者  阅读(48)  评论(0编辑  收藏  举报