TensorRT ONNX 基礎(續)
PyTorch正確導出ONNX
幾條推薦的原則,可以減少潛在的錯誤:
- 對于任何使用到 shape、size 返回值的參數時,例如
tensor.view(tensor.size(0), -1)
這類操作,避免直接使用tensor.size()
的返回值,而是用 int 強轉以下,斷開跟蹤,即:tensor.view(int(tensor.view(0)), -1)
。 - 對于
nn.Unsample
、nn.functional.interpolate
函數,使用 scale_factor 指定倍率,而不是使用 size 參數指定大小。 - 關于 batch 動態 shape 還是寬高動態 shape
- 對于
reshape
、view
操作時,-1 的指定請放到 batch 維度,其他維度手動計算出來填上即可。batch 維度盡量不要指定為大于 -1 的明確數字。 torch.nn.export
指定 dynamic_axes 參數,并且只指定 batch 維度,盡量不要把其他指定為動態
- 對于
- 使用 opset_version = 11 ,盡量不要使用 11 以下的版本
- 避免使用 inplace 操作,例如
y[..., 0: 2] = y[..., 0: 2] * 2 -0.5
- 盡量少地出現 5 各維度,例如在 ShuffleNet Module 中,會出現 5 個維度,在導出時可以考慮合并 wh 避免 5 維
- 盡量把后處理部分放在 ONNX 模型中實現,這也可以讓推理引擎順帶給加速了
- 就是用我們之前提到的在 ONNX 模型中添加節點(如前后處理等)的方法:先將前后處理寫成 PyTorch 模型,單獨導出為一個 ONNX 模型,再加到原模型中
這些做法的必要性體現在簡化過程的復雜度,去掉 gather、shape 之類的節點,有些時候,看似不這么該也是可以跑通的,但是需求復雜后總是會出現這樣那樣的問題,而這樣遵守這些原則,可以盡可能地較少潛在的錯誤。做了這些,基本就不用 onnx-simplifier 了。
原則一(不規范) | 原則一(規范) |
---|---|
![]() | ![]() |
使用ONNX解析器來讀取ONNX文件
onnx 解析器的使用有兩個選項:
libnvonnxparser.so
(共享庫)- https://github.com/onnx/onnx-tensorrt (源代碼)
對應的頭文件是:NvOnnxParser.h
其實源代碼編譯后就是共享庫,我們推薦使用源代碼,這樣可以更好地進行自定義封裝,簡化插件開發或者模型編譯的過程,更加容易實現定制化需求,遇到問題可以調試。
源代碼中主要關注的是 builtin_op_importers.cpp
文件,這個文件中定義了各個算子是怎樣從 ONNX 算子解析成 TensorRT 算子的,如果我們后續有新的算子,也可以自己修改該源文件來支持新算子。
從下載onnx-tensorrt到配置好再到成功運行
TODO:自己試一下
插件實現
插件實現重點:
- 如何在 PyTorch 里面導出一個插件
- 插件解析時如何對應,在 onnx parser 中如何處理
- 插件的 creator 實現,除了插件,還要實現插件的 creator ,TensorRT 就是通過 creator 來創建插件的
- 插件的具體實現,繼承自
IPluginV2DynamicExt
- 插件的序列化與反序列化
TODO:看代碼,自己寫
插件實現的封裝
課程老師將上述復雜的 plugin 實現做了通用的、默認的封裝,大部分情況下使用默認的封裝即可,少數有需要自己改動的地方按照上面介紹的內容再去改,極大地減少代碼量。
TODO:看代碼,自己寫
int8量化
int8量化簡介
int8 量化是利用 int8 乘法替換 float32 乘法實現性能加速的一種方法
- 對于常規模型:y=kx+by=kx+by=kx+b ,其中 x,k,bx, k, bx,k,b 都是 float32,對于 kxkxkx 的計算使用的當然也是 float32 乘法
- 對于 int8 模型:y=tofp32(toint8(k)×toint8(x))+by=tofp32(toint8(k)\times toint8(x))+by=tofp32(toint8(k)×toint8(x))+b ,其中 kxkxkx 的計算是兩個 int8 相乘,得到 int16
經過 int8 量化的模型無疑精度會降低,各種 int8 模型量化算法解決的問題就是如何合理地將 fp32 轉換為 int8,從而盡可能減小精度損失
除了 fp32 和 int8,很多時候會用 fp16 模型,精度損失會比 int8 少很多,并且速度也很快。但是需要注意 fp16 僅在對 fp16 的顯卡上有很好的性能,在對 fp16 無優化的顯卡甚至可能速度還不如 fp32,總之 int8、fp16、fp32 要看業務對精度和速度的要求,還有部署的顯卡設備來綜合決定。
TensorRT int8量化步驟
- 配置
setFlag nvinfer1::BuilderFlag::kINT8
- 實現
Int8EntropyCalibrator
類,其繼承自IInt8EntropyCalibrator2
- 實例化一個
Int8EntropyCalibrator
并設置到config.setInt8Calibrator
Int8EntropyCalibrator
的作用是:讀取并預處理圖像數據作為輸入
標定過程的理解
- 對于輸入圖像 A,使用 fp32 推理之后得到 P1,再用 int8 推理之后得到 P2,調整 int8 權重使得 P1 和 P2 足夠接近
- 因此標定時需要使用到一些圖像,通常 100 張左右即可
Int8EntropyCalibrator類主要關注
getBatchSize
,告訴引擎,這次標定的 batch 是多少getBatch
,告訴迎請,這次標定的數據是什么,將指針賦值給 bindings 即可,返回 false 即表示沒有數據了readCalibrationCache
,若從緩存文件加載標定信息,則可避免讀取文件和預處理,若該函數返回空指針則表示沒有緩存,程序會通過getBatch
重新計算writeCalibrationCache
,當標定結束后,會調用該函數,我們可以儲存標定后的緩存結果,多次標定可以使用該緩存實現加速