:CUDA/cuDNN/TensorRT版本匹配與C++推理)
簡介本資源是面向Windows平臺AI推理開發(fā)者的Paddle Inference 3.0.0預編譯開發(fā)包專為需快速集成高性能深度學習推理能力的C工程而優(yōu)化適用于模型部署、邊緣計算及工業(yè)級服務開發(fā)等場景。壓縮包共623個文件涵蓋569個頭文件h/hpp用于接口調用與類型定義13個靜態(tài)庫lib和2個導出文件exp支撐鏈接構建5個核心DLL如paddle_inference.dll、mklml.dll、mkldnn.dll提供運行時推理引擎與數(shù)學加速能力另有proto與pb文件支持模型序列化解析整體體積達528.04MB。內容預覽顯示其深度整合Intel MKL含lapacke.h等數(shù)學接口、CUDA 11.8、cuDNN 8.6.0及TensorRT 8.5.1.7具備AVX指令集優(yōu)化與VS2019兼容性。目前已有132人下載學習開箱即用省去復雜環(huán)境編譯與依賴適配過程顯著降低Windows下PaddlePaddle C推理部署門檻。 拿到這個文件名的時候我第一反應是這是一臺Windows機器的Paddle Inference推理環(huán)境全家桶。x86-64-cuda11.8-cudnn8.6.0-trt8.5.1.7-mkl-avx-vs2019-paddle-inference-3.0.0.zip光看名字就能拆出七八個關鍵信息。如果你是做深度學習模型部署的或者剛接手一個C推理項目看到這種預編譯包時最怕的不是別的就是版本對不上、DLL缺一堆、跑起來直接崩。這篇文章就圍繞這個包把我實際部署Paddle Inference時踩過的坑、驗證過的步驟、調過的參數(shù)全部梳理一遍從環(huán)境匹配講到推理代碼再到TensorRT加速和常見報錯排查爭取讓你拿到包之后能一口氣跑通。先說這個包具體能干什么。Paddle Inference是PaddlePaddle的官方高性能推理引擎跟訓練框架解耦專門為上線部署優(yōu)化。這個zip是預編譯好的Windows x64版本編譯時用的是CUDA 11.8、cuDNN 8.6.0、TensorRT 8.5.1.7同時開啟了MKL數(shù)學庫和AVX指令集構建環(huán)境對應VS2019。什么意思呢就是說只要你的機器滿足這些依賴版本解壓這個包配置好環(huán)境變量和CMake工程就能直接做GPU加速的模型推理完全不需要自己從源碼編譯Paddle省下幾個小時甚至一整天的編譯時間。適合誰看剛入行做模型部署的工程師、需要在Windows上集成Paddle推理到C項目的同學以及被各種CUDA/cuDNN版本折磨過的老哥。1. 內容整體設計與拆解思路1.1 文件名里的版本矩陣代表什么先把這個文件名當密碼一樣逐段拆開看。x86-64平臺架構表示64位x86指令集幾乎所有現(xiàn)代Intel和AMD處理器都適用。如果你的機器是ARM架構比如部分Windows平板、Surface Pro X這個包就用不了。cuda11.8NVIDIA CUDA Toolkit的版本號。推理庫編譯時鏈接的是CUDA 11.8的運行時庫意味著你的機器上必須安裝CUDA 11.8或者更新但兼容的版本向下兼容方面一般驅動版本不能低于525.60.13具體看NVIDIA官方文檔。這里有個常見誤區(qū)CUDA有兩個概念一個是顯卡驅動自帶的運行時兼容層一個是獨立的Toolkit后面會細說。cudnn8.6.0NVIDIA cuDNN深度學習原語庫版本。CUDA負責通用并行計算cuDNN專門對卷積、池化、歸一化等神經(jīng)網(wǎng)絡核心算子做了深度優(yōu)化兩者配合才能發(fā)揮GPU的性能。trt8.5.1.7TensorRT版本。這是NVIDIA的高性能推理優(yōu)化器能把訓練好的模型做層融合、精度校準、動態(tài)shape優(yōu)化再生成推理引擎。Paddle Inference啟用TensorRT之后某些模型的推理延遲可以再降30%到50%。mklIntel Math Kernel Library數(shù)學核心庫。CPU上跑算子的時候會用MKL加速矩陣乘法、卷積等計算。Paddle Inference在CPU模式下如果沒有MKL性能會差挺多這個包默認帶上了。avxAdvanced Vector ExtensionsCPU指令集。AVX允許CPU單條指令處理多個浮點數(shù)據(jù)Paddle為這個指令集專門優(yōu)化過算子跑CPU推理時速度提升明顯。注意如果你的老舊CPU不支持AVX這個包會直接報非法指令錯誤。vs2019構建工具鏈版本Visual Studio 2019。這個主要影響你編譯C程序時MSVC運行時版本如果你用VS2015或VS2017去鏈接這個庫大概率會遇到運行時庫沖突或者符號解析失敗。paddle-inference-3.0.0Paddle Inference主版本號。3.0.0是相對較新的版本API設計和舊版有差異最明顯的是頭文件引用方式變了后面代碼部分會展示。zip打包格式Windows下直接用解壓工具解壓即可。一句話總結這個包是Paddle Inference團隊在特定軟硬件環(huán)境下編譯出來的產(chǎn)物想用好它你的開發(fā)機環(huán)境要跟這個版本矩陣對齊。1.2 CUDA、cuDNN、TensorRT三者的“鐵三角”關系很多人分不清CUDA、cuDNN、TensorRT的關系我打個比方。CUDA Toolkit相當于GPU的“操作系統(tǒng)API”你寫CUDA代碼、編譯GPU程序、管理顯存都靠它。顯卡驅動是“硬件驅動層”CUDA Toolkit是“用戶態(tài)開發(fā)庫”兩者有對應關系驅動包含一個兼容層只要驅動版本夠新就能運行較舊版本的CUDA Toolkit編譯出來的程序。cuDNN是構建在CUDA之上的“深度神經(jīng)網(wǎng)絡加速庫”它把卷積、LSTM等常用算子優(yōu)化到極致訓練和推理框架都會調用它。Paddle推理庫編譯時指定cuDNN 8.6.0運行時就會去找對應版本的cudnn64_8.dll。TensorRT則更上層它是個“推理引擎優(yōu)化器”。它不關心你怎么訓練模型只負責把訓練好的模型轉換成推理引擎通過層融合、kernel自動調優(yōu)、FP16/INT8量化等手段壓縮推理耗時。Paddle Inference跑GPU推理時要啟用TensorRT就必須在運行時加載TensorRT的動態(tài)庫nvinfer.dll等版本跟編譯時保持一致或兼容否則會報算子不匹配或版本不兼容的錯。這三者的匹配邏輯是驅動 CUDA Toolkit cuDNN版本可接受范圍 TensorRT版本可接受范圍。實際部署中最讓人頭疼的就是dll版本沖突老版本和新版本往往只差一兩個小版本號但行為卻截然不同。1.3 為什么需要預編譯的Paddle Inference包可能有人會問直接pip install paddlepaddle-gpu不就行了對于Python調用確實簡單但C部署場景完全不同。首先C推理需要的是靜態(tài)庫和頭文件pip包里雖然有但接口不穩(wěn)定、沒有頭文件、不方便鏈接。其次Paddle Inference源碼編譯在Windows下極其痛苦要裝Python、CMake、VS2019、CUDA、cuDNN、TensorRT還要處理各種依賴沖突編譯一次少說兩小時。預編譯包相當于把這些都辦好你的任務只是“解壓 配置 鏈接”。從工程視角看預編譯包還能保證推理行為的一致性你自己編譯的庫可能因為編譯選項不同算出的結果有細微差異但官方預編譯的版本是統(tǒng)一優(yōu)化過的批量部署時更可控。2. 環(huán)境配置從零準備CUDA 11.8、cuDNN和TensorRT2.1 Windows下安裝CUDA 11.8的完整步驟很多人在Windows裝CUDA時翻車主要原因是把“顯卡驅動”和“CUDA Toolkit”混為一談。實際上如果你只是要跑現(xiàn)成的Paddle推理包NVIDIA驅動里已經(jīng)包含了最低限度的CUDA運行時兼容庫但頭文件、nvcc編譯器、開發(fā)庫必須額外裝Toolkit。第一步檢查顯卡驅動。打開NVIDIA控制面板左下角“系統(tǒng)信息”里能看到驅動版本。CUDA 11.8要求驅動版本至少525.60.13如果驅動太老后面加載cuda runtime的時候會報找不到入口點或版本不兼容。驅動更新就直接去NVIDIA官網(wǎng)下載Game Ready或Studio驅動都可以開發(fā)機建議用Studio驅動穩(wěn)定性更好。第二步下載CUDA Toolkit 11.8。到NVIDIA官網(wǎng)的CUDA Toolkit Archive頁面選擇11.8版本W(wǎng)indows x86_64的exe安裝包大概2.8GB。安裝時選擇“自定義”把CUDA下的“Visual Studio Integration”選上其他組件默認。注意安裝路徑不要有中文和空格我習慣用默認的C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8。第三步驗證安裝。打開命令行cmd或PowerShell輸入nvcc -V如果正常輸出版本信息說明Toolkit安裝成功。這里我遇到過一個問題裝完CUDA后命令行輸入nvcc提示不是內部或外部命令原因是沒有把C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin加到系統(tǒng)環(huán)境變量PATH里。重新配置環(huán)境變量后重開命令行即可。還有一個非常常見的坑CUDA安裝的時候提示需要VS的某類組件如果你機器上裝的是VS2022而不是VS2019也沒關系CUDA 11.8對VS2022也有一定支持只是官方在VS2019下驗證得最充分。Paddle這個包標注VS2019不代表VS2022不能用只是建議盡量接近構建環(huán)境減少鏈接時的小問題。2.2 cuDNN 8.6.0下載與部署cuDNN的安裝比CUDA簡單本質上就是往CUDA目錄里放幾個文件。到NVIDIA cuDNN Archive頁面選擇Download cuDNN v8.6.0 for CUDA 11.xWindows版本下載得到一個zip壓縮包。解壓后里面有3個文件夾bin、include、lib。操作邏輯很直接把bin里的cudnn64_8.dll復制到C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bininclude里的cudnn.h復制到對應的include目錄lib里的cudnn.lib復制到lib\x64目錄。復制的時候記得管理員權限。驗證方式有兩個。第一查看C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin下是否存在cudnn64_8.dll第二在命令行執(zhí)行nvidia-smi如果驅動正常再執(zhí)行一個小的CUDA樣本程序比如bandwidthTest.exe能通過就說明CUDA環(huán)境沒問題cuDNN因為是動態(tài)庫只有真正跑深度學習推理時才會被調用。關于cuDNN我多說一句版本號中數(shù)字的兼容性比你想象中嚴格。比如cuDNN 8.6.0生成的動態(tài)庫名為cudnn64_8.dll其中“64_8”表示64位平臺、支持CUDA 11.x的API。如果你換成cudnn 8.9.x文件名可能會變成cudnn64_9.dll此時Paddle Inference編譯時鏈接的還是cudnn64_8這個導入庫運行時就會報找不到dll。所以不要隨便升級cuDNN。2.3 TensorRT 8.5.1.7的安裝與環(huán)境配置TensorRT的安裝也是解壓式。到NVIDIA TensorRT Archive頁面下載TensorRT 8.5.1.7 for Windows x86_64 and CUDA 11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8的zip包解壓到一個你方便找到的路徑比如D:\TensorRT-8.5.1.7。TensorRT包里面包括bin目錄trtexec等工具、include目錄頭文件、lib目錄dll和導入庫、還有python包目錄。我們的核心任務是把lib目錄加入PATH或者把dll復制到安全位置。Paddle Inference運行時加載TensorRT是動態(tài)加載的它會在默認路徑和PATH中查找nvinfer.dll、nvonnxparser.dll等文件。所以最簡單的方法是把D:\TensorRT-8.5.1.7\lib加到系統(tǒng)環(huán)境變量PATH里并確保在PATH中的位置比較靠前。如果你在開發(fā)機上不想污染全局環(huán)境也可以在做CMake工程時通過cmake變量把TensorRT路徑傳進去運行exe前在腳本里加一下PATH。TensorRT還有個小坑它在運行時需要額外的CUDA驅動支持如果你用了精簡版驅動或者老驅動初始化TensorRT時會卡在加載cudart64_11.dll。所以再次確認驅動版本別低于525.60.13。2.4 驗證整套環(huán)境是否匹配在開始寫代碼之前先用最簡單的方式驗證一下環(huán)境。打開命令行依次執(zhí)行nvcc -V確認CUDA 11.8。where cudnn64_8.dll如果能找到路徑說明cuDNN在PATH里。where nvinfer.dll確認TensorRT核心庫可見。另外可以用TensorRT自帶的trtexec工具做一次快速自檢比如加載一個ONNX模型轉成engine能跑通說明TensorRT初始化正常D:\TensorRT-8.5.1.7\bin\trtexec.exe --onnxyour_model.onnx --saveEnginetest.engine如果這個命令報錯先別查Paddle大概率是TensorRT環(huán)境本身的問題早點發(fā)現(xiàn)早點排查。整個環(huán)境配置下來我的經(jīng)驗是能不改系統(tǒng)PATH就盡量不改所有的依賴庫通過拷貝dll到指定目錄的方式管理項目可控性更高。但既然做開發(fā)PATH方式最省事二選一即可。3. Paddle Inference 3.0.0的工程集成與推理實現(xiàn)3.1 解壓并理解預編譯包的目錄結構把下載的zip解壓后你會看到類似這樣的目錄結構paddle_inference/ ├── paddle/ │ ├── include/ │ │ ├── paddle_analysis_config.h │ │ ├── paddle_inference_api.h │ │ ├── paddle_pass_builder.h │ │ └── ... │ └── lib/ │ ├── paddle_inference.lib │ ├── paddle_inference.dll │ └── ... ├── third_party/ │ ├── install/ │ │ ├── cuda/ │ │ ├── cudnn/ │ │ ├── tensorrt/ │ │ ├── mkl/ │ │ └── ... │ └── ... ├── version.txt └── ...重點在于paddle/include和paddle/lib。頭文件放的是C API聲明庫文件放的是導入庫和動態(tài)庫。third_party目錄里通常會有Paddle依賴的第三方庫但實際運行時大多數(shù)還需要系統(tǒng)環(huán)境的CUDA、cuDNN等別指望這個目錄里的文件能替代你手動安裝的CUDA Toolit。version.txt里會寫明精確的構建信息建議打開看一眼確認跟你理解的版本一致。在Windows下用VS2019建工程時配置包含目錄為paddle/include庫目錄為paddle/lib并且把paddle_inference.dll、paddle2ONNX.dll等動態(tài)庫拷貝到exe輸出目錄或放到PATH可達的位置。否則編譯能過運行就報找不到paddle_inference.dll。3.2 最小CMake工程配置我習慣用CMake來管理Windows下的C工程比直接手寫.vcxproj方便。下面是我驗證過可用的CMakeLists.txt模板針對VS2019 Paddle Inference 3.0.0cmake_minimum_required(VERSION 3.16) project(paddle_infer_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CONFIGURATION_TYPES Release CACHE STRING FORCE) # Paddle Inference路徑 set(PADDLE_INFER_DIR D:/paddle_inference CACHE PATH Path to paddle_inference) include_directories(${PADDLE_INFER_DIR}/paddle/include) link_directories(${PADDLE_INFER_DIR}/paddle/lib) # CUDA include_directories(C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.8/include) link_directories(C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.8/lib/x64) # TensorRT set(TENSORRT_DIR D:/TensorRT-8.5.1.7) include_directories(${TENSORRT_DIR}/include) link_directories(${TENSORRT_DIR}/lib) # MKL如果推理時想用CPU算子MKL路徑也要加 set(MKL_DIR C:/Program Files (x86)/Intel/oneAPI/mkl/latest) include_directories(${MKL_DIR}/include) link_directories(${MKL_DIR}/lib) add_executable(paddle_demo main.cpp) target_link_libraries(paddle_demo paddle_inference cudart nvinfer nvonnxparser # 釋放版本下Paddle還需要關注paddle2onnx依賴但鏈接paddle_inference時通常會自動帶 )這里要特別說明兩點。第一CMake的link_directories在Windows下有時不生效因為MSVC在鏈接時更依賴具體的庫文件路徑。遇到“找不到paddle_inference.lib”的情況最省事的辦法是在target_link_libraries里寫全路徑target_link_libraries(paddle_demo ${PADDLE_INFER_DIR}/paddle/lib/paddle_inference.lib )第二Release模式必須和Release模式匹配。Paddle的預編譯庫一般是Release版如果你用Debug配置去鏈接大概率會在運行時出現(xiàn)無法解析的外部符號或堆損壞因為MSVC的運行時庫/MD和/MTd不同。3.3 編寫一份能正常加載PIR模型的C推理代碼Paddle Inference 3.0.0的API和2.x有些變化核心頭文件是paddle_inference_api.h。下面是一個最基礎的單模型GPU推理示例流程是創(chuàng)建Config - 配置模型路徑 - 啟用GPU - 創(chuàng)建Predictor - 構造輸入 - Run - 讀取輸出。#include iostream #include vector #include paddle_inference_api.h int main() { // 1. 創(chuàng)建AnalysisConfig paddle_infer::Config config; // 假設模型是Paddle保存的靜態(tài)圖模型包含model.pdmodel和model.pdiparams config.SetModel(path/to/model.pdmodel, path/to/model.pdiparams); // 2. 開啟GPU推理 config.EnableUseGpu(1024, 0); // 參數(shù)1顯存預分配大小MB參數(shù)2GPU設備ID // 如果不想用GPU可以改用config.DisableGpu() // 3. 啟用TensorRT可選 config.EnableTensorRtEngine(1 30 /* workspace_size */, 1 /* batch_size */, 10 /* min_subgraph_size */, paddle_infer::PrecisionType::kFloat32, false /* use_static */, false /* use_calib_mode */); // 4. 開啟MKLDNN僅CPU模式下用GPU模式下此選項一般無效 // config.EnableMKLDNN(); // 5. 創(chuàng)建Predictor auto predictor paddle_infer::CreatePredictor(config); // 6. 獲取輸入輸出張量的名稱 auto input_names predictor-GetInputNames(); auto output_names predictor-GetOutputNames(); // 7. 構造輸入 auto input_tensor predictor-GetInputHandle(input_names[0]); // 假設輸入shape是 [1, 3, 224, 224]float32 std::vectorfloat input_data(1 * 3 * 224 * 224, 0.5f); input_tensor-Reshape({1, 3, 224, 224}); input_tensor-CopyFromCpu(input_data.data()); // 8. 推理 predictor-Run(); // 9. 獲取輸出 auto output_tensor predictor-GetOutputHandle(output_names[0]); std::vectorfloat output_data; output_tensor-CopyToCpu(output_data.data()); // 注意上面這種CopyToCpu方式存在隱患因為output_data沒有預分配空間。 // 更穩(wěn)妥的方式是先獲取shape再分配內存 // auto out_shape output_tensor-shape(); // int out_num std::accumulate(out_shape.begin(), out_shape.end(), 1, std::multipliesint()); // output_data.resize(out_num); // output_tensor-CopyToCpu(output_data.data()); return 0; }這段代碼有幾個細節(jié)容易踩坑。首先GetOutputHandle之后一定要先調用shape()獲取輸出維度再給vector分配空間最后CopyToCpu。如果直接CopyToCpu到一個空vector會觸發(fā)訪問越界或只拷出一部分數(shù)據(jù)。我在實際項目中遇到過輸出數(shù)據(jù)全0的情況排查半天發(fā)現(xiàn)是vector沒resizeCopyToCpu只寫了應該寫的長度但程序讀取越界了。其次Config.EnableUseGpu的第一個參數(shù)是顯存預分配大小這個值不是硬性限制Paddle會在需要時繼續(xù)申請但預分配越大運行中顯存碎片化越少越不容易因為顯存不足報錯。一般圖像分類模型給1024就夠檢測或分割模型給2048以上。另外Paddle的模型文件有兩種一種是舊版的__model__和params文件另一種是新版的.pdmodel和.pdiparams。3.0.0的接口對舊版也有兼容但我建議統(tǒng)一用新版格式保存模型時用paddle.jit.save。3.4 理解Paddle Inference的模型加載機制你可能會問模型文件到底放在哪程序怎么知道網(wǎng)絡結構這其實是“靜態(tài)圖”和“動態(tài)圖”的問題。訓練時用的是動態(tài)圖方便調試保存推理模型時用的是靜態(tài)圖把計算邏輯固化下來。.pdmodel文件就是序列化后的靜態(tài)計算圖包含所有算子和權重.pdiparams是權重文件。Paddle Inference加載模型時會做一系列圖優(yōu)化比如算子融合、常量折疊這些優(yōu)化由一系列pass組成。你可以通過config.SwitchIrOptimization(true)或false來開啟或關閉。我建議默認開啟尤其是在GPU TensorRT模式下圖優(yōu)化對性能影響很大。有時你會在日志里看到類似“I1125 09:00:01.123456 12345 analysis_predictor.cc:99] Optimize to PaddlePredictor...”的信息這說明圖優(yōu)化生效了。如果模型加載后推理結果不對可以試著SwitchIrOptimization(false)跑一遍對比結果看是不是優(yōu)化pass引入的問題。4. 實操過程與核心環(huán)節(jié)實現(xiàn)4.1 用C跑通一個圖像分類模型的全流程實錄為了減少變量我用PaddleClas里的MobileNetV3作為示例模型流程如下。第一準備模型。如果你手頭沒有Paddle模型可以用一行Python代碼導出一個示例模型import paddle import paddle.vision.models as models model models.mobilenet_v3_small(num_classes1000) model.eval() # 構造一個示例輸入用于固定輸入shape input_spec [paddle.static.InputSpec(shape[-1, 3, 224, 224], dtypefloat32, nameinput)] paddle.jit.save(model, mobilenetv3, input_specinput_spec, output_specNone)這樣會生成mobilenetv3.pdmodel和mobilenetv3.pdiparams兩個文件。第二把模型文件放好。我在D盤建了一個項目目錄結構是D:\paddle_demo\ ├── CMakeLists.txt ├── main.cpp ├── models\ │ ├── mobilenetv3.pdmodel │ └── mobilenetv3.pdiparams └── build\第三編譯工程。在VS2019的“x64 Native Tools Command Prompt”里執(zhí)行cd D:\paddle_demo mkdir build cd build cmake .. -G Visual Studio 16 2019 -A x64 -DPADDLE_INFER_DIRD:/paddle_inference cmake --build . --config Release編譯成功后在build\Release目錄下會生成paddle_demo.exe。執(zhí)行前確保paddle_inference.dll、cudnn64_8.dll、nvinfer.dll等動態(tài)庫都能被找到最保險的做法是寫一個bat腳本把這些dll全部復制到exe同目錄下或者啟動前臨時設置PATH。第四運行。程序跑起來后觀察輸出日志如果能看到Paddle版本信息和模型加載日志基本就通了。遇到問題時先看日志尾部錯誤碼Paddle的錯誤碼通常帶有EROR或FATAL字樣直接定位。4.2 CPU推理與MKLDNN的配置細節(jié)GPU在大多數(shù)服務器上都有但開發(fā)調試階段CPU推理反而更常用。Paddle Inference在x86平臺上開啟MKLDNN即oneDNN前身是MKL-DNN后CPU算子的性能提升非常明顯尤其是卷積和矩陣乘。開啟方式非常簡單config.EnableMKLDNN(); config.SetCpuMathLibraryNumThreads(8);SetCpuMathLibraryNumThreads用于設置CPU線程數(shù)默認是物理核心數(shù)。這個值不是越大越好因為線程切換和內存帶寬都會成為瓶頸我實測8線程左右對大部分模型性價比最高。多路CPU的服務器可能會更復雜建議按模型benchmark去搜一下最佳線程數(shù)。MKLDNN模式下Paddle會自動把部分算子替換為oneDNN實現(xiàn)圖優(yōu)化中也會多做一層算子融合。還有一個附加開關是EnableMKLDNNQuantizer用于INT8量化但一般需要校準數(shù)據(jù)集工程上用得少。4.3 TensorRT加速的完整開啟方式與參數(shù)選擇TensorRT是GPU推理的核心加速手段。在上面示例代碼里我用了EnableTensorRtEngine但這里有幾個參數(shù)值得單獨展開。第一個參數(shù)workspace_size單位是字節(jié)它表示TensorRT允許使用的顯存上限。設置太大可能會因為顯存不足導致TensorRT初始化失敗設置太小TensorRT因為無法執(zhí)行某些優(yōu)化策略性能會打折扣。我的習慣是把值設為顯存大小的三分之一到二分之一比如8GB顯存就設1GB到2GB左移30位是1GB。第二個參數(shù)batch_size這是“最優(yōu)batch數(shù)”等于1表示主要優(yōu)化batch1的推理。如果你的業(yè)務并發(fā)batch是固定的比如batch8這里最好設置為8TensorRT會依據(jù)這個size做kernel選擇。第三個參數(shù)min_subgraph_size這是子圖融合的最小節(jié)點數(shù)。Paddle會把支持TensorRT的算子組合成子圖子圖內的算子在TensorRT上執(zhí)行子圖外的算子留在Paddle上。min_subgraph_size太小時一個單獨的算子也會被調去TensorRT性能反而下降因為大量小算子會有額外傳輸開銷太大會導致TensorRT覆蓋的算子太少加速效果不明顯。官方默認值一般在3到5實踐下來10左右比較均衡。PrecisionType可以選擇kFloat32、kHalf、kInt8。如果對精度要求較高比如醫(yī)療圖像用kFloat32對性能敏感但精度容忍度還行用kHalf也就是FP16。kInt8需要額外校準數(shù)據(jù)前向inference時還需要在use_calib_mode參數(shù)設為true否則可能報錯。另外TensorRT支持序列化engine文件即靜態(tài)engine可以避免每次啟動都重新構建engine。Paddle里對應的是config.EnableTensorRtEngine時設置use_statictrue這樣會把優(yōu)化后的engine緩存到本地文件下次啟動直接加載。這個緩存跟模型、輸入shape、TensorRT版本、顯卡型號都強相關換環(huán)境或改shape之后一定要刪除緩存否則會報“inconsistency”錯誤。4.4 動態(tài)Shape場景下的TensorRT配置很多實際部署場景比如檢測模型的輸入shape是動態(tài)的例如一張圖可以是大是小。TensorRT對動態(tài)shape支持得比靜態(tài)shape復雜需要在配置時顯式指定三個維度范圍min_shape、opt_shape、max_shape。Paddle Inference的接口是config.EnableTensorRtEngine(workspace_size, max_batch_size, min_subgraph_size, precision, use_static, use_calib_mode); config.SetTRTDynamicShapeInfo(min_input_shape, max_input_shape, opt_input_shape, disable_trt_plugin_fp16);其中min_input_shape、opt_input_shape、max_input_shape都是mapkey是輸入張量名value是對應shape。舉個栗子一個[None, 3, None, None]的輸入std::mapstd::string, std::vectorint min_shape {{input, {1, 3, 32, 32}}}; std::mapstd::string, std::vectorint opt_shape {{input, {1, 3, 448, 448}}}; std::mapstd::string, std::vectorint max_shape {{input, {1, 3, 960, 960}}};這個配置決定了TensorRT在運行時能接受的輸入范圍。如果輸入超出了max_shape直接會報維度錯誤如果輸入在min和max之間TensorRT會嘗試在opt_shape附近做優(yōu)化所以opt值要盡量接近你的真實輸入尺寸分布中心。動態(tài)shape還帶來一個隱性風險子圖劃分策略在不同shape下可能不同導致同一個算子有時在TensorRT里、有時在Paddle里執(zhí)行表現(xiàn)出的性能差異較大。所以線上服務如果輸入尺寸波動很大建議還是固定到幾個離散的尺寸規(guī)格比如通過resize統(tǒng)一到448x448或960x960穩(wěn)定性和性能都更好。5. 常見問題與排查技巧實錄5.1 常見錯誤速查表我把實際部署中遇到的錯誤整理成一張表方便大家對照排查。錯誤現(xiàn)象可能原因解決方案運行時提示找不到paddle_inference.dll動態(tài)庫路徑未加入PATH把paddle/lib加到PATH或拷貝dll到exe目錄提示找不到cudnn64_8.dllcuDNN未安裝或版本不匹配檢查bin目錄下是否存在cudnn64_8.dll復制到系統(tǒng)PATH初始化GPU時卡死或報cudaErrorInsufficientDriver顯卡驅動版本過低更新驅動至525.60.13以上編譯時無法解析paddle::AnalysisConfig鏈接庫時找不到paddle_inference.lib檢查CMake鏈接的lib路徑是否寫對用絕對路徑最穩(wěn)運行時報非法指令Illegal instructionCPU不支持AVX指令集換成支持AVX的機器或下載noavx版本TensorRT engine初始化失敗TensorRT DLL版本不匹配確認nvinfer.dll版本為8.5.1.x且CUDA版本是11.8推理結果全是0或NaNvector未正確分配空間或輸入數(shù)據(jù)異常檢查輸出tensor的shape分配空間后再CopyToCpu顯存不足CUDA out of memory顯存被其他進程占用或workspace_size過大減小EnableUseGpu的預分配或關閉其他GPU進程日志中出現(xiàn)算子不支持TensorRT當前模型某些算子無法被TensorRT識別調大min_subgraph_size讓不支持的算子留在Paddle里執(zhí)行注意這張表只覆蓋了最常見的問題。實際上Windows環(huán)境的問題千奇百怪比如殺毒軟件刪dll、動態(tài)庫搜索路徑失效、顯卡驅動被系統(tǒng)更新覆蓋等等遇到問題時先冷靜逐層排查。5.2 DLL加載順序的坑與解決方案Windows加載動態(tài)庫的順序是exe所在目錄 - 系統(tǒng)目錄 - system32 - PATH環(huán)境變量。這帶來一個問題如果系統(tǒng)里殘留了舊版本的cuda DLL或cudnn DLLPaddle可能會加載到錯誤的版本導致莫名其妙的行為。我遇到過的一個典型案例機器上裝了Anacondaconda環(huán)境里有自己的一套cudnn加上Paddle的third_party目錄里也有一份dll還有系統(tǒng)裝的CUDA 11.8三份dll一起出現(xiàn)程序運行時根本不知道加載了哪一份。解決方法是在啟動exe的bat腳本里顯式把Paddle依賴的DLL目錄放到PATH最前面同時把conda的Library\bin挪到后面。更激進的做法是把工程需要的dll全部拷貝到exe目錄做到一個目錄全含。另外TeamViewer等遠程軟件或圖形驅動會注入一部分庫到進程中這類庫跟CUDA本無關系但偶爾會改變DLL搜索路徑優(yōu)先級。遇到玄學問題可以先在干凈環(huán)境下測試。5.3 顯存不足與顯存碎片化的排查思路GPU推理的顯存管理是個經(jīng)典問題。Paddle Inference默認用緩存分配器預分配顯存后不會馬上歸還給驅動這跟訓練框架類似。在EnableUseGpu里指定的第一個參數(shù)就是預分配大小。顯存不足的常見情況推理進程啟動很慢顯存占用持續(xù)上漲最終報CUDA out of memory。這時候先確認是不是有多個GPU進程在跑Windows下可以用nvidia-smi查看GPU占用。如果只有一個進程看看是不是模型過大或者TensorRT構建engine時臨時分配了大量顯存。有時候訓練和推理共用GPU也會導致沖突。比如顯存只有6GB訓練任務占了5.5GB推理任務只有500MB可用任何模型都會OOM。建議配置GPU隔離Windows下一般用環(huán)境變量CUDA_VISIBLE_DEVICES來切換但注意這個變量在多進程場景下要提前設置好別在代碼里改。如果你想在推理進程退出時完整釋放顯存Paddle提供了paddle_infer::Predictor::ClearInterpreter之類的方法但更實用的做法是直接調用config.EnableMemoryOptim(true)開啟內存優(yōu)化。這個選項讓Paddle在算子執(zhí)行前后復用顯存峰值占用能明顯下降但代價是增加一點調度開銷實際推理性能幾乎無影響。5.4 多模型加載與單例Predictor的設計建議實際項目里很少只跑一個模型通常會同時加載多個模型或者同一個模型多個實例。Paddle每個Predictor對應一份模型實例它們之間的顯存和線程是獨立的所以要注意整體資源控制。我比較推薦的做法是每個模型建立一個Predictor池池的大小根據(jù)并發(fā)請求確定。Predictor本身不是線程安全的多個線程并發(fā)執(zhí)行Run可能會有問題最安全的方式是每個線程持有自己的Predictor實例。Paddle內部有一些線程池和常量優(yōu)化如果你有很多Predictor實例每個實例都會初始化一遍圖優(yōu)化和算子顯存和CPU開銷會翻幾倍。解決辦法是提前構建好TensorRT靜態(tài)engine并緩存避免重復構建。還有一種方案是把多模型融合成一個模型在某些場景下可行但通用性不高。實際操作中我通常把模型按業(yè)務模塊拆分用獨立的Predictor管理類統(tǒng)一控制生命周期在服務啟動時預加載避免運行時頻繁創(chuàng)建銷毀。6. 性能調優(yōu)從能用變成好用6.1 CPU與GPU推理的取舍很多人拿到Paddle Inference就默認用GPU其實小模型在CPU上可能更快。這涉及一個基礎概念GPU的延遲不一定比CPU低但吞吐量遠高于CPU。對于batch1的低延遲場景CPU可能反而有優(yōu)勢因為不需要拷貝數(shù)據(jù)到GPU。所以如果你的模型很小比如幾百MB的輕量模型且請求量不大CPU推理就夠了MKLDNN開啟后性能也很可觀。跑benchmark時我習慣于反復預熱后再計時因為CUDA運行時、TensorRT engine構建、Paddle圖優(yōu)化這些都會在第一次推理時發(fā)生導致第一次很慢。Paddle也提供了config.SwitchIrOptimization(true)和預熱循環(huán)的配合用10到20次無關推理把運行時狀態(tài)跑熱再統(tǒng)計真實延遲。6.2 TensorRT與Paddle算子融合的細節(jié)觀察當啟用TensorRT引擎時Paddle并不會把所有算子全部交給TensorRT而是先做圖分析把支持的部分切成子圖再交給TensorRT。這意味著最終推理圖可能是“Paddle原生算子 TensorRT子圖”混合的。如果你想知道哪些子圖被切到了TensorRT可以打開調試日志config.EnableDebug(); config.SwitchIrDebug(true);日志里會打印pass執(zhí)行情況包括每個子圖的算子數(shù)量、輸入輸出張量。我常常通過這個日志判斷為什么某些模型TensorRT加速效果不明顯——往往是因為切出來的子圖太小TensorRT根本來不及發(fā)揮優(yōu)化能力。一個模型如果全是卷積和pooling子圖會非常大TensorRT加速效果顯著。如果模型里有大量自定義算子、控制流if/loop或動態(tài)shape操作子圖會被切得很碎加速效果大打折扣。這不算錯誤只是性能優(yōu)化的約束條件。6.3 顯存預分配、緩存與批處理的高級調優(yōu)對于高吞吐場景比如推薦系統(tǒng)或者視頻流處理盡量使用batch推理。Paddle Inference支持動態(tài)batch但你需要手動拼接輸入數(shù)據(jù)。一張一張推理和批量推理吞吐量差距可能達到5到10倍尤其是GPU上。一個常見的誤區(qū)把batch設得越大越好。實際上batch太大TensorRT在kernel選擇時會傾向并行度更高的策略但模型本身有內存帶寬限制超過某個閾值后吞吐不再增長反而會因為顯存不足報錯。我一般用nvidia-smi看顯存殘量找到batch的上界。還可以考慮多流推理。Paddle對單卡多流的支持比較有限但你可以自己開多個線程每個線程一個Predictor分別跑不同的batch間接實現(xiàn)多流并行。這種情況下要注意所有Predictor共享同一塊GPU顯存總量要精打細算。6.4 從Windows到Linux的遷移注意事項雖然本文圍繞Windows展開但很多生產(chǎn)服務器是Linux。Paddle Inference的Linux預編譯包同樣有對應的版本遷移時主要注意幾個點。第一動態(tài)庫后綴不同。Windows是dllLinux是so。代碼里不要硬編碼路徑盡量用配置或相對路徑。第二Linux下cuDNN和TensorRT的安裝路徑不同通常用軟鏈接管理版本。第三CUDA的運行時庫搜索路徑是rpath和LD_LIBRARY_PATH需要導出環(huán)境變量。第四Linux下沒有Visual Studio但GCC/G版本要跟預編譯包要求對應一般Paddle官方推薦GCC 8.2以上的版本。我個人的建議是Windows版用于本地開發(fā)和單機驗證一旦確認模型和推理邏輯無誤就用Linux版部署到服務器。因為Windows和Linux的TensorRT engine緩存不通用正式環(huán)境用Linux的包重新構建一次engine緩存才能保證最優(yōu)性能。7. 寫在最后的經(jīng)驗之談這套Paddle Inference環(huán)境我已經(jīng)在好幾個項目里用過了一個非常深刻的感受是版本對齊就是最大的坑但也是最好解決的問題。只要你嚴格按照CUDA 11.8 cuDNN 8.6.0 TensorRT 8.5.1.7 VS2019這套組合去裝依賴版本兼容性問題會少很多。反而是那些“順便升級一下”的操作比如把cuDNN從8.6升到8.9、把TensorRT從8.5升到8.6容易引發(fā)各種dll不匹配。另外一個心得是不要過度依賴預編譯包里自帶的third_party目錄。它只是把Paddle需要的一些第三方庫打個包方便你不用單獨下載MKL之類的東西但CUDA和cuDNN這種重量級依賴還是得你手動裝好把環(huán)境弄得干凈可控才是正道。如果你把這個zip當成“解壓就能用”的黑盒那多半會遇到一堆問題。但如果把它當成“Paddle Inference的Windows版本參考實現(xiàn)”對照著它的編譯選項去配置自己的環(huán)境很多問題就能系統(tǒng)性地避開。我一般會在項目里寫一個configure.bat把PATH、MKL、TensorRT、CUDA的路徑全部設置好每次開新終端都執(zhí)行一遍比手動改環(huán)境變量穩(wěn)定多了。最后再提一個小技巧在Windows上萬一遇到奇怪崩潰先試試把Compile with AVX對應的運行庫CPU指令集匹配問題排除掉老的CPU比如一些低功耗的奔騰、賽揚真的可能不支持AVX換成noavx版本的Paddle Inference包會立刻解決。這個坑我?guī)屯屡挪檫^兩次每次都是CPU硬件太老不是代碼的問題。本文還有配套的精品資源點擊獲取