OpenPPL CUDA 執行時編譯機制

目錄:

一、基於快算演算法選擇的執行時編譯策略

1。 卷積/矩陣運算的解空間

2。 快速演算法選擇

3。 程式碼封裝器

4。 執行時編譯

5。 Runtime

二、執行時編譯的優勢

1。 計算庫體積

2。 初始化時間

3。 效能分析

4。 靈活性增強

前言

OpenPPL CUDA 執行時編譯機制

為了提高 OpenPPL-CUDA 的推理效能,組內小夥伴針對 CNN 中的計算密集型運算元卷積和矩陣運算完成了非常深入的最佳化,並且提出了包括 Precompute-Impgemm、2-level splitk、split-filter 等多種卷積最佳化演算法(目前只開源了一部分,近期會 Release 更多出來)。

配合圖級別最佳化,OpenPPL 的效能可以達到甚至超過 TensorRT 的效能。執行網路之前,OpenPPL 進行 Prepare 的操作,該操作完成演算法選擇。

為了準確的得到最優的計算效能,OpenPPL 採用遍歷+實卡執行的方式進行搜尋。這個操作會增大初始化的時間,社群的使用者也在反饋這個問題。為了提升 OpenPPL 的易用性,在保證效能的前提下,OpenPPL-CUDA 提出了基於快速演算法選擇的執行時編譯策略。

一、基於快速演算法選擇的執行時編譯策略

OpenPPL CUDA 執行時編譯機制

1. 卷積/矩陣運算的解空間

在 NVIDIA 的 GPU 上,卷積演算法會將任務分塊劃分到不同的 SM 上,不同的分塊方得到不同的計算效能。

以 OpenPPL 中的 Precompute-Impgemm 演算法為例,分塊方式包含了: (Tile_m, Tile_n) 表示每個 Block 的計算分塊,(warp_m, warp_n) 表示一個 warp 的計算分塊,buf 表示是否使用 share Memory 的 double buffer 的機制來掩藏計算和訪存延遲, splitk/splitf 表示是否採用 split 的方式對計算進行分解, K 表示 block 一次迴圈計算的迭代步長, S 表示一個計算組的迭代步長。

完整的解空間定義為 (tile_m, tile_n, warp_m, warp_n,k,s, buf, split) ,分塊的劃分越稠密,解空間中的解會越多,對應的 CUDA Kernel 的數量越多。

2. 快速演算法選擇

針對卷積和矩陣運算的解空間,最直接的方式是遍歷全部的解,選擇執行時間最短的配置。

由於選擇範圍較大,採用直接遍歷的方式需要花費比較長的預處理時間或者離線處理時間。為了解決這個問題,OpenPPL CUDA 透過資料分析的方式得到部分經驗公式 F, F 可以根據 Conv/Gemm 的計算配置生成相匹配的解 R。

為了提高解 R 的準確率,快速演算法模組提出候選集合的概念,經驗公式使用快速減枝的方式減小候選集合。OpenPPL CUDA 遍歷候選集合,選擇並記錄最優解 S。

3. 程式碼封裝器

一組分塊選擇引數對應解空間的一個解,OpenPPL 的程式碼封裝器會依據演算法選擇的結果 S,產生對應運算元的計算原始碼。程式碼封裝器為部分運算元提供動態融合功能,根據卷積融合 pattern 新增對應的融合程式碼。

目前支援的融合 pattern 包括:Activation(relu、prelu、sigmoid、clip)、eltwise、concat 等。

4. 執行時編譯

NVIDIA 提供執行時編譯 NVRTC 的支援,可以將 CUDA 原始碼編譯為 PTX 形式,PTX 程式碼可以使用 Driver API cuModuleLoadData 和 cuModuleLoadDataEx 進行載入,官方文件提供使用方式和程式碼例項,這裡不詳細展開。

利用 NVRTC 機制,OpenPPL 新增 cuda_module、cuda_module_manager 等多個模組對程式碼封裝以及執行時編譯進行管理。計算原始碼的編譯、載入和解除安裝由 manager 進行管理。Manager 可以保證程式執行過程中程式碼被編譯和載入一次。

5. Runtime

Manager 維護一個 的表,Kernel 執行階段從表中得到對應的 module。第一次執行之前呼叫 GetKernelFunc() 得到對應 Function,後續呼叫 Driver 的 cuKernelLaunch 去執行 Kernel。

二、執行時編譯的優勢

OpenPPL CUDA 執行時編譯機制

1. 計算庫體積

第一個優勢是

減少計算庫的體積。

執行時編譯機制在網路執行階段編譯需要使用的 Kernel,不需要提前靜態編譯全部程式碼成 Library,可以顯著減少 Lib 的大小。

以 OpenPPL 為例,只編譯 CUDA Arch75 的情況下,靜態編譯方式的庫體積為 125M;而使用 JIT 方式的庫體積為 10M 以內。後續新增全部 Arch 以及多種精度的支援,JIT 機制下的庫體積不會有很大變化,提升效果會更加明顯。

2. 初始化時間

第二個優勢是

降低初始化時間。

在網路規模比較大或者大 Batch 的情況提升效果更加明顯。以 Resnet50 網路 Batch=32 為例,使用 JIT 的預處理時間大約在 200 秒左右, 而使用搜索全部解空間的方式則需要超過 1000 秒。

3. 效能分析

第三個優勢是

推理效能提升。

雖然快速選演算法無法保證選擇解空間中的最優解,但得益於執行時編譯機制可以依據 Conv/Gemm 的實際融合情況,生成對應的程式碼, 整個網路的執行效能有所提升。如下圖所示,相比與全部靜態的編譯方式,大部分網路效能約有 5-15% 左右的效能提升。

OpenPPL CUDA 執行時編譯機制

4. 靈活性增強

第四個優勢是

靈活性增強。

目前 TensorRT 等推理引擎支援序列化的方式儲存中間結果和演算法選擇結果。序列化的方式存在一定問題,不同的裝置可能需要不同的序列化結果(比如 T4 上序列化的結果,放到 1080 上就在不在適用,這會導致部署過程比較複雜,存在演算法 x 裝置數的序列化檔案需要管理,每次更新模型的代價較高)。

執行時編譯的方式可以在執行裝置上線上選擇演算法,提升部署的靈活性。

三、結語

OpenPPL CUDA 執行時編譯機制

以上這些優勢,都非常有利於 OpenPPL 在實際場景中部署。

目前該機制已經在最新的 OpenPPL 中釋出,同時在 CMake 中添加了是否使用 JIT 的 Flag,可以靈活選擇使用 JIT 方式或者以前靜態編譯的方式。

歡迎大家使用!G

itHub 直接搜 OpenPPL 即可