11.3 ONNXRuntime 進階使用
前言
模型部署推理,除了換了一個框架平臺外,最重要的是推理框架平臺支持各種優化方法,使得模型推理效率更高。
在onnxruntime中也提供了一些優化方法,例如float16量化、int8量化、計算圖優化等操作。
本小節就介紹在onnxruntime中實現float16量化、int8量化、混合精度量化、計算圖優化、執行緒管理和IO binding。
- float16量化、int8量化、混合精度量化:都是從資料存儲及運算角度出發,提高運行效率
- 計算圖優化:是從計算邏輯上優化計算過程
- 執行緒管理:是從設備資源配置及調度的角度,充分利用cpu資源以應對並行、串列的任務
- IO binding:是從資料在設備之間遷移的問題考慮,將資料提前綁定到gpu上,減少cpu與gpu之間的通信
float16量化
float16量化是常用的,精度損失較小的量化策略,通常模型的精度是float32,即一個浮點數用32位來表示,它佔用4位元組。
但大多數情況下,用16位元表示浮點數,已經能滿足要求。因此,在推理時可以將模型從float32轉為float16。
float16的量化需要用到 onnxconverter-common庫,先提前安裝:pip install onnx onnxconverter-common。
然後只需要調用convert_float_to_float16,就可以把onnx模型轉換為float16的模型。
import onnx
from onnxconverter_common import float16
model = onnx.load("path/to/model.onnx")
model_fp16 = float16.convert_float_to_float16(model)
onnx.save(model_fp16, "path/to/model_fp16.onnx")
Copy
通過配套代碼,進行性能測試,可以看到相比於float32的效率,float16在輸送量上18.8%((1023-861)/861)的提升。
除了性能效率提升,float16的onnx模型只有48.7MB, float32占97.4MB,磁片存儲佔用少了50%。
int8量化
既然能用低比特表示模型,能否再進一步簡化,採用8bit來表示float32呢?當然可以,8bit量化是一種常用於移動端、邊緣端的優化策略。
量化又分動態量化(dynamic quantization)和靜態量化(static quantization),這裡採用動態量化演示,動態量化直接將資料量化到固定的8bit,而靜態量化需要採用校準資料集進行一段時間的推理後,得到量化結果。
int8/uint8動態量化只需要採用兩行代碼,即可將量化後的模型保存到本地。
from onnxruntime.quantization import quantize_dynamic, QuantType
_ = quantize_dynamic(path_model_float32, path_model_int8, weight_type=QuantType.QInt8)
Copy
int8的onnx模型只有24.4MB, float32占97.4MB,float16占48.7MB,比float32少了75%,比float16少了50%。
通過配套代碼,進行性能測試,發現int8的推理速度非常慢,連cpu的比不上,其中的原因可能是硬體的不匹配?
關於int8量化,也需要支援int8計算的顯卡,例如T4和A100顯卡。
“Hardware support is required to achieve better performance with quantization on GPUs. You need a device that supports Tensor Core int8 computation, like T4 or A100. Older hardware will not benefit from quantization.”
混合精度
當float16精度不夠的時候,可以考慮混合精度,在有必要的地方變為float16,其它地方保持float32,這樣可以實現精度和速度的權衡。
混合精度的實現與float16匯出類似,需要額外注意的是要給模型一些測試資料,用於評估哪些是float16,哪些是float32。
from onnxconverter_common import auto_mixed_precision
import onnx
model = onnx.load("path/to/model.onnx")
model_fp16 = auto_convert_mixed_precision(model, test_data, rtol=0.01, atol=0.001, keep_io_types=True)
onnx.save(model_fp16, "path/to/model_fp16.onnx")
Copy
計算圖優化
除了對資料的量化,計算圖層面的優化也可以提高推理效率,例如移除不必要的層、網路層的融合:
- Identity Elimination
- Slice Elimination
- Unsqueeze Elimination
- Dropout Elimination
- Conv Add Fusion
- Conv Mul Fusion
- Conv BatchNorm Fusion
- Relu Clip Fusion
- Reshape Fusion
整體上,計算圖優化分為三個level:Basic、Extended和Layout Optimizations。
代碼實現上比較簡單,是在InferenceSession產生實體的時候,添加sess_options,就可實現計算圖優化。
sess_options需要設置優化的類型,以及優化後模型保存路徑。
優化類型有4個level可選:
GraphOptimizationLevel::ORT_DISABLE_ALL -> Disables all optimizations
GraphOptimizationLevel::ORT_ENABLE_BASIC -> Enables basic optimizations
GraphOptimizationLevel::ORT_ENABLE_EXTENDED -> Enables basic and extended optimizations
GraphOptimizationLevel::ORT_ENABLE_ALL -> Enables all available optimizations including layout optimizations
Copy
import onnxruntime as rt
sess_options = rt.SessionOptions()
# Set graph optimization level
sess_options.graph_optimization_level = rt.GraphOptimizationLevel.ORT_ENABLE_EXTENDED
# To enable model serialization after graph optimization set this
sess_options.optimized_model_filepath = "<model_output_path\optimized_model.onnx>"
session = rt.InferenceSession("<model_path>", sess_options)
Copy
通過配套代碼,可以觀察輸送量,發現沒有得到任何變化,這可能與設備有關,在這裡暫時不深究,後續生產真用到了,再回來看。
在這裡需要注意的是,由於onnx的顯存不能自動釋放,一次性跑兩個模型的效率評估的話,第二個模型會受到資源問題,導致效率評估不準確,這裡代碼中需要手動切換兩個模型,分兩次跑。上圖中vgg的float32的bs=16時,性能突然降低,或許就是因為資源問題。
執行緒管理
onnxruntime提供執行緒管理功能,可以通過調整不同的參數來控制模型的運行方式和性能。該功能的主要特點包括:
- 控制執行緒數量:使用sess_options.intra_op_num_threads參數可以控制模型運行時所使用的執行緒數
- 循序執行或並存執行:使用sess_options.execution_mode參數可以控制運算子在圖中是循序執行還是並存執行。當模型具有較多的分支時,將該參數設置為ORT_PARALLEL可以提供更好的性能。
- 控制並存執行的執行緒數量:在sess_options.execution_mode = rt.ExecutionMode.ORT_PARALLEL的情況下,可以使用sess_options.inter_op_num_threads參數來控制在不同節點上並存執行圖時使用的執行緒數。
由於配套代碼中運行的結果並沒有得到提升,這裡猜測與硬體設備有關,因此就不討論執行緒參數設置的問題了。
I/O binding
IO Binding 用於在運行計算圖之前將輸入和輸出張量綁定到設備上,以提高運行效率。
IO Binding 可以避免在運行計算圖時將輸入和輸出資料從 CPU 複製到設備上,從而減少資料複製操作所需的時間。
同樣地,配套代碼中的案例沒有得到效率提升,也無法進一步探討了,這裡可以作為參考代碼。
當輸入和輸出張量比較大時,使用 IO Binding 功能可以顯著提高計算圖的執行效率,因此在後續的任務中嘗試使用 IO binding。
運行耗時分析工具
onnxruntime還提供了一個運行耗時分析工具,在sess_options中設置sess_options.enable_profiling = True,就可以在目前的目錄輸出一份json檔,根據json檔中詳細記錄了執行時間和性能資料。每個事件條目包括以下資訊:
- cat:事件的分類,可以是Session(會話)或Node(節點);
- pid:進程ID;
- tid:執行緒ID;
- dur:事件持續時間,以微秒為單位;
- ts:事件的時間戳記,以微秒為單位;
- ph:事件的類型,可以是X(完整事件)或B(事件開始)/E(事件結束);
- name:事件的名稱;
- args:事件的參數,包括輸入和輸出張量的類型、形狀和大小,以及執行緒池的名稱、執行緒ID和執行時間。
通過配套代碼,獲得json檔,還可以通過網站:https://www.ui.perfetto.dev/, open trace file打開json進行視覺化觀察。
小結
本小節介紹了onnxruntime在性能優化上的一些技巧,包括float16量化、int8量化、混合精度量化、計算圖優化、執行緒管理和IO binding。
但由於本機設備原因,並沒有看到有多大的性能優化,大家也可以在自己設備上嘗試一下運行效率的變化,按道理這些技巧是能提速的。
這些優化技巧是模型部署過程中常見的加速技巧,在其它框架中也會有實現,這些在TensorRT中會再詳細展開。