地平線與英偉達工具鏈 PTQ 工具功能引數對比與實操

地平线智能驾驶开发者發表於2024-10-17

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 在進行 PTQ 量化(預設進行 fp32 不量化) 時,會在最佳化網路的時候嘗試 int8 精度,假設某一層在 int8 精度下速度優於預設精度(fp32),則優先使用 int8。支援的資料型別與精度:https://docs.nvidia.com/deeplearning/tensorrt/support-matrix/index.html#layers-precision-matrix,對應的 onnx 運算元:https://github.com/onnx/onnx-tensorrt/blob/main/docs/operators.md

  • 地平線 OpenExplorer 預設使用 int8 量化,部分節點 BPU 支援 int16、int32,可以透過 node_info、run_on_bpu、run_on_cpu 引數控制量化精度以及執行的器件。

硬體相關

  • 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

由於時間原因,就先到這兒了,下次再聊~

相關文章