shape實戰(zhàn))
在深度學習推理這條路上走久了你會發(fā)現(xiàn)一個扎心的現(xiàn)實模型在訓練機上跑得飛快一上生產(chǎn)就變成老太太。尤其用 TensorFlow 訓練完模型部署到 GPU 推理時明明顯存占用很高幀率卻上不去。這時候 TensorRT 就是那個能把 GPU 推理速度再榨出幾倍的工具——它不改變你的 TensorFlow 模型結構卻能通過層融合、精度校準、內核自動調優(yōu)這些手段讓同一塊 GPU 干出接近兩倍到數(shù)倍的活。我自己在 T4 這類卡上做過實測一個 YOLO 系列檢測模型640 分辨率輸入純 TensorFlow 跑大概每路 45ms 左右換成 TensorRT 優(yōu)化后直接壓到 12ms 上下單卡能撐的路數(shù)從 5 路出頭變成 20 路以上。這篇文章我把整個改造過程、版本匹配的坑、轉換的三種路徑、動態(tài) shape 的處理細節(jié)以及最終的性能數(shù)據(jù)全部攤開講適合已經(jīng)被推理延遲困擾、準備在生產(chǎn)環(huán)境里動刀子的開發(fā)者參考。1. 為什么要動 TensorRT 這刀GPU 推理瓶頸到底卡在哪先說個反直覺的結論TensorFlow 在 GPU 上的推理慢很多時候不是 GPU 算力不夠而是計算圖太碎了。訓練時我們追求靈活性和可調試性框架會把一個卷積層拆成好多小算子去執(zhí)行每個算子都要啟動一次 CUDA kernel。而 kernel 啟動是有開銷的——這個開銷在毫秒級模型里往往占了大頭。TensorRT 干的第一件事就是把能合并的算子盡量合并比如把卷積、偏置、激活函數(shù)熔成一個融合算子一個 kernel 啟動全干完GPU 利用率自然就上來了。理解這個原理你才能明白為什么 TensorRT 能帶來質的飛躍而不是簡單的 10% 提升。我用兩個數(shù)字來說話一個 ResNet50 的推理圖里TensorFlow 默認展開后大概有上百個算子節(jié)點而 TensorRT 優(yōu)化完可能只剩下三四十個融合節(jié)點。算子少了kernel 啟動次數(shù)少了GPU 深層次的流水線才能吃飽。這就像你做飯每一步都單獨洗一次鍋、熱一次鍋跟連續(xù)炒完一整道菜時間差得可不止一點半點。它對不同模型的收益差別也很大。CNN 類模型因為卷積、BN、ReLU 這種結構非常規(guī)律融合空間極大收益通常最明顯而 Transformer 類模型里面有大量矩陣乘法和 softmax融合沒那么激進但 TensorRT 依然能通過選擇更優(yōu)的 kernel 實現(xiàn)方式拿到不錯的加速。我實測過一個小型 BERT 模型FP16 精度下延遲大約降低 40% 左右雖然沒有 CNN 那么夸張但已經(jīng)足夠值得折騰了。另外還必須提精度這一層TensorRT 支持 FP32、FP16、INT8 三種精度。FP16 對絕大多數(shù)模型來說精度損失可以忽略但顯存占用減半、計算吞吐翻倍INT8 則能再壓一輪延遲代價是需要準備校準數(shù)據(jù)集做量化校準否則精度可能會漂到讓人抓狂。這里建議新手先別碰 INT8FP16 往往已經(jīng)能解決大部分痛點。從部署架構角度講TensorRT 也不是要你重寫整個服務。TensorFlow 模型可以先轉成 SavedModel再用 TF-TRT 接口做圖內優(yōu)化保留 TensorFlow 的 Serving API 不變甚至用 TensorRT 的 Python API 直接加載轉換后的引擎文件。所以它不是替代 TensorFlow而是和 TensorFlow 一起工作——這一點很多教程沒說透導致有人一上來就想全換成 TensorRT 自家生態(tài)結果被復雜的 C API 勸退。我這篇文章走的路線是盡量少改代碼讓圖優(yōu)化自動發(fā)生先講環(huán)境匹配再講轉換路徑最后講怎么調優(yōu)和避坑。2. TF-TRT 的工作機制層融合、精度校準和內核自動調優(yōu)到底在做什么想用好 TensorRT你得先理解它內部做的三件核心事情層融合、精度校準、內核自動調優(yōu)。這不是黑魔法是有明確技術邏輯的。2.1 層融合減少 kernel 啟動才是提速第一功臣GPU 上的算子執(zhí)行不是免費的每次 kernel 啟動都有固定開銷。TensorFlow 的圖執(zhí)行模式會把一個卷積層拆分為 conv2d、bias_add、relu 等多個節(jié)點每個節(jié)點都觸發(fā)獨立 kernel。TensorRT 的圖優(yōu)化器會掃描整個計算圖把卷積偏置ReLU這類固定組合識別出來合并成單個融合層。它的融合策略還支持跨層融合比如把殘差結構里的相加操作也并進去進一步減少中間結果寫回顯存的次數(shù)。這種融合對顯存帶寬的節(jié)省同樣重要。融合前的結構每一層都產(chǎn)生一個中間張量寫入全局顯存下一層再讀出來融合后中間張量直接留在寄存器或共享內存里訪問速度快幾個數(shù)量級。帶寬受限的模型比如 YOLO 這種高分辨率輸入在融合后延遲驟降很大一部分功勞就在這里。2.2 精度校準為什么 FP16 損失小、INT8 必須動數(shù)據(jù)TensorRT 支持三種精度模式關鍵是它實現(xiàn)了自動混合精度。也就是說你設定 FP16 之后不是所有層都一股腦轉成半精度TensorRT 會逐層分析對精度敏感程度把敏感的層保留 FP32不敏感的層換成 FP16。這種策略保證了絕大多數(shù)模型在 FP16 下精度和原始 FP32 幾乎一致。INT8 就不一樣了。它需要把權重和激活值都量化到 8bit必須通過校準過程來確定每個張量的動態(tài)范圍。校準輸入數(shù)據(jù)的選擇直接決定量化效果——如果你用訓練集的子集做校準那生產(chǎn)環(huán)境如果出現(xiàn)和校準分布差異很大的數(shù)據(jù)精度就會明顯下滑。我自己的經(jīng)驗是校準數(shù)據(jù)至少要覆蓋生產(chǎn)環(huán)境中最常見的 100 到 500 個真實樣本不要用隨機噪聲或者增強過的圖片。如果時間緊張可以用 500 張有代表性的真實樣本跑完校準后做一輪邊界樣本驗證發(fā)現(xiàn)問題再回退到 FP16。2.3 內核自動調優(yōu)同一層有幾十種實現(xiàn)選最適配 GPU 的那個TensorRT 在構建引擎時會針對目標 GPU 架構做內核選擇。它內置了大量 kernel 實現(xiàn)比如卷積就有基于 cuDNN 的、基于 Winograd 的、基于隱式 GEMM 的不同實現(xiàn)適合不同的通道數(shù)、卷積核大小和輸入分辨率。構建時它會做 benchmark挑出當前設備上耗時最短的版本記錄下來。這也是為什么 TensorRT 引擎文件不能跨 GPU 架構直接搬——你在 RTX 3090 上構建的引擎拿到 T4 上要么報錯、要么性能不是最優(yōu)因為內核選擇是針對設備做的。這個機制也解釋了為什么轉換過程本身需要花時間。我第一次構建一個 YOLO 模型引擎的時候等待時間接近兩分鐘因為 TensorRT 要把各種 kernel 組合全部試一遍。這點耐心必須有它是一次性成本后面加載引擎就是秒級的事了。3. 版本匹配地獄CUDA、cuDNN、TensorRT 和 TensorFlow 的三角關系在開始動手之前先放下裝最新版的念頭。TensorRT 加速 TensorFlow 推理這件事最大的坑不在算法而在版本兼容性。我見過太多人在這一步卡一周甚至直接放棄。這里把版本匹配的邏輯一次說透。3.1 最省心的路徑直接用官方容器鏡像如果讓我給一條最不會出錯的路那就是使用 NVIDIA 官方發(fā)布 TensorFlow 容器鏡像。鏡像里面 CUDA、cuDNN、TensorRT 和 TensorFlow 的版本是官方測試過的組合開箱即用。比如 nvcr.io/nvidia/tensorflow:24.01-tf2-py3 這個標簽里面內置了 TensorRT 8.6、TensorFlow 2.15、CUDA 12.3 這些配套組件。你直接docker pull然后掛載代碼目錄跑起來省掉的版本糾結時間足夠你寫完整個推理服務了。當然生產(chǎn)環(huán)境不一定允許用容器那就得手工安裝。手工安裝的核心原則是先定 TensorRT 版本再反推 CUDA 和 cuDNN最后找 TensorFlow 版本。順序反了就很容易陷入TensorFlow 說找不到 CUDA 庫、TensorRT 說 cuDNN 版本不對的連環(huán)套。3.2 手工安裝的版本對照邏輯以一套我驗證過的穩(wěn)定組合為例組件版本說明Ubuntu20.04 / 22.04系統(tǒng)影響編譯兼容性建議 20.04 及以上CUDA11.8 或 12.1看 GPU 驅動支持的最高版本驅動向下兼容cuDNN8.6.0必須和 CUDA 版本配套別混搭TensorRT8.5.x / 8.6.x選擇與 CUDA 版本對應的 deb 包TensorFlow2.10 ~ 2.152.10 是最后一個原生支持 Windows GPU 的版本Linux 可以更高這里有個容易被忽視的點TensorFlow 的 pip 包編譯時鏈的是特定版本的 CUDA所以如果你自己裝了 CUDA 12.1但 TensorFlow 2.15 編譯時用的是 11.8運行時它會去找 11.8 的庫找不到就報錯。解決辦法是安裝 TensorFlow 對應的 CUDA 版本或者在容器里規(guī)避。這也是我推薦容器的重要原因。Ubuntu 下安裝 TensorRT 的 deb 包流程我貼一下方便你需要手工操作時參考# 以 Ubuntu 20.04 CUDA 11.8 為例 # 1. 配置 NVIDIA 官方源 sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub sudo add-apt-repository deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ / sudo apt-get update # 2. 安裝 TensorRT以 8.5.3 版本為例 sudo apt-get install tensorrt8.5.3.1-1cuda11.8 sudo apt-get install python3-libnvinfer8.5.3.1-1cuda11.8 # 3. 驗證安裝 dpkg -l | grep TensorRT python3 -c import tensorrt; print(tensorrt.__version__)這套組合我實際用下來TF-TRT 轉換能正常執(zhí)行不會出現(xiàn) CUDA 庫缺失或者符號找不到的幺蛾子。如果你在 Windows 上做開發(fā)我的建議是別挑戰(zhàn)手工匹配直接用 WSL2 或者遠程 Linux 服務器Windows 原生支持 TensorRT 的路徑太窄而且 TF 2.10 以后官方基本放棄 Windows GPU 原生支持了。3.3 驅動、CUDA 與GPU 錯誤 43之間的關系熱詞里有個英偉達 GPU 錯誤代碼 43這個在 Windows 設備管理器里很常見。很多人以為這是驅動壞了其實在 TensorRT 使用場景里錯誤 43 往往和顯存占用、驅動版本不匹配、或者顯卡被物理拔插過有關。排查思路是先用nvidia-smi看驅動是否正常識別顯卡再檢查 CUDA 版本能否被 TensorFlow 識別。# 檢查驅動和 CUDA nvidia-smi python3 -c import tensorflow as tf; print(tf.config.list_physical_devices(GPU))如果 TensorFlow 都看不到 GPU那問題在驅動層而不是 TensorRT 層錯誤 43 大概率對應驅動重置或者顯卡硬件異常先把驅動徹底卸載重裝一遍再說。如果是 Linux 服務器則要檢查顯卡是否被物理移除過——熱詞里那個電腦經(jīng)常提示 GPU 被物理移除多半是供電不穩(wěn)或者 PCIE 插槽松動這種情況 TensorRT 轉換中途會詭異地崩潰日志里還不一定直接報 GPU 錯誤而是報 CUDA error 或 driver shutting down。4. 把模型搬進 TensorRT三條轉換路徑的實測對比現(xiàn)在到了文章的核心怎么把 TensorFlow 模型變成 TensorRT 加速的推理模型。我實測過三條主流路徑各自適用場景不同這里全部展開說。4.1 路徑一TF-TRT 圖內優(yōu)化最省事的上車方式TF-TRT 的全稱是 TensorFlow-TensorRT 集成它在 TensorFlow 計算圖層面做工作加載 SavedModel把圖中能被 TensorRT 優(yōu)化的子圖替換成 TensorRT 引擎節(jié)點剩余部分繼續(xù)走 TensorFlow。代碼量極小非常適合已有 TensorFlow Serving 部署、想快速提性能的場景。核心代碼長這樣import tensorflow as tf from tensorflow.python.compiler.tensorrt import trt_convert as trt # 加載原有模型 saved_model_dir ./saved_model converter trt.TrtGraphConverterV2( input_saved_model_dirsaved_model_dir, precision_modeFP16, maximum_cached_engines100, minimum_segment_size2, use_dynamic_shapeTrue, dynamic_shape_profile_strategyOptimal ) # 轉換 converter.convert() # 構造輸入簽名再保存 def my_input_fn(): yield (tf.zeros([1, 640, 640, 3], dtypetf.float32),) converter.build(input_fnmy_input_fn) converter.save(./trt_saved_model)這段代碼里use_dynamic_shapeTrue很多教程沒提我一開始也踩了這個坑。如果你的模型輸入是固定 shape比如 640x640可以設成 False但如果你要支持不同分辨率或者 batch size 會變化就必須打開動態(tài) shape 模式否則推理時輸入稍微變一下就會報錯。轉換完成后加載和執(zhí)行方式跟普通 SavedModel 幾乎一致唯一區(qū)別是簽名函數(shù)需要顯式指定輸入大小。我用一個 640 分辨率檢測模型測試過轉換后加載本身要花一些時間首次加載需要反序列化引擎所以生產(chǎn)環(huán)境建議預熱服務啟動時先跑一幀再對外提供服務否則第一個請求會有秒級延遲。4.2 路徑二TFLite INT8 量化時順便用 TensorRT不直接走 ONNX 中轉說實話TFLite 主要是給邊緣設備用的和 TensorRT 的 GPU 高性能路線不太搭。如果你在服務端用 GPU更推薦的路徑是TensorFlow → ONNX → TensorRT。這條路徑的適用場景是你手里的模型是 Keras 或者 PyTorch 訓練的熱詞里有人搜 pt 文件轉 tensorrt就是這個情況想統(tǒng)一到 TensorRT 生態(tài)里。步驟拆開是這樣的# 1. TensorFlow/Keras → ONNX # 需要 tf2onnx 工具 pip install tf2onnx python -m tf2onnx.convert --saved-model ./saved_model --output model.onnx --opset 11 # 2. ONNX → TensorRT 引擎用 trtexec 或者 Python API # trtexec 是 TensorRT 自帶的命令行工具最省事 /usr/src/tensorrt/bin/trtexec --onnxmodel.onnx \ --saveEnginemodel_fp16.engine \ --fp16 \ --minShapesinput:1x3x640x640 \ --optShapesinput:8x3x640x640 \ --maxShapesinput:16x3x640x640這里面的minShapes/optShapes/maxShapes是關鍵如果你后面用動態(tài) batch必須在這里把范圍定好構建引擎會針對這個范圍選擇最優(yōu) kernel。optshapes填生產(chǎn)中最常見的 batch 大小比如你一般同時處理 8 路視頻就填8x3x640x640這樣 TensorRT 會重點優(yōu)化這個量級。ONNX 中轉有個容易踩的坑TensorFlow 里某些算子比如部分圖像預處理 op在 ONNX 里沒有對應實現(xiàn)轉換會直接報 unsupported Op。我的建議是轉換前把預處理resize、normalize從前處理里抽出來放在 TensorFlow 外面用 OpenCV 或 NumPy 做只把干凈的網(wǎng)絡結構交給 ONNX。這樣既減小模型體積也避免轉換失敗。4.3 路徑三Runtime API 手動構建引擎自由度最高的硬核路線前兩條路徑是框架幫你干活最后這條是你自己控制一切。TensorRT Python API 允許你直接用 Network Definition 定義網(wǎng)絡結構然后手動設置每層的精度和融合策略。適合對網(wǎng)絡結構極其熟悉、需要魔改中間層或者做非常規(guī)剪枝的開發(fā)者。我基于實際經(jīng)驗建議除非你真的很懂 TensorRT 的層語義否則別走這條路。因為手動構建意味著你要把 TensorFlow 的算子逐一手工映射成 TensorRT 層一旦模型結構復雜工作量巨大。我見過有人為了把 Transformer 里一個自定義 attention 層塞進去折騰了一整周最后性能還不一定比 TF-TRT 自動融合的方案好。如果確實需要手動構建推薦的方式是先把模型轉 ONNX然后用 TensorRT 的 ONNX parser 加載 ONNX 文件再手動修改import tensorrt as trt logger trt.Logger(trt.Logger.WARNING) builder trt.Builder(logger) network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, logger) with open(model.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.max_workspace_size 1 30 # 1GB config.set_flag(trt.BuilderFlag.FP16) engine builder.build_serialized_network(network, config) with open(model.engine, wb) as f: f.write(engine)這個方案的好處是你可以隨時查network里的每一層定位到底哪一層在 FP16 下精度不好。壞處是相關 API 的文檔偏少很多參數(shù)得靠試錯。我建議先跑通前兩條路徑把性能基線摸清楚再決定要不要動這個手術。5. 真正跑起來動態(tài) shape 推理、batch 限制與性能對比轉換完成只是第一步真正部署時會遇到動態(tài) shape、顯存管理、batch 限制等問題。這一段全是實戰(zhàn)中跑出來的細節(jié)。5.1 動態(tài) shape 的實際用法別讓輸入維度綁死你的服務很多推理服務需要支持不同分辨率的輸入比如視頻流里有人臉框大小變化導致裁剪尺寸不同。TF-TRT 轉換時如果指定了固定 shape比如 640x640那推理接口就只接受這個尺寸一旦傳進來 320x320 就會報錯。解決方案就是前面提到的use_dynamic_shapeTrue加dynamic_shape_profile_strategy。但動態(tài) shape 也有代價引擎構建時因為要考慮多種 shape 的組合kernel 選擇會更保守性能會比固定 shape 差 5% 到 10% 左右。所以我的建議是如果你的生產(chǎn)輸入分辨率確實固定比如攝像頭就是 1920x1080 縮放成 640x640那就用固定 shape拿滿全部性能只有非固定場景才開動態(tài) shape。別因為動態(tài)聽起來高級就盲目開啟。5.2 batch 設置的學問吞吐和延遲的取舍batch 是另一個影響性能的關鍵變量。推理服務的 batch 可以分成兩種單幀處理的延遲優(yōu)先模式和多幀批量處理的吞吐優(yōu)先模式。TensorRT 引擎里 batch 是顯式維度你在構建時給的 maxBatch 決定了顯卡最多能一次處理多少張圖。實測下來batch 從 1 提到 8單幀平均延遲反而會下降——因為 GPU 計算資源被更充分地利用每個 kernel 的開銷被攤薄到了多張圖上。但 batch 提到 16 以上有可能因為顯存限制導致 TensorRT 構建或運行時 OOM。這里給一個 T4 16GB 卡 YOLO 系列模型的經(jīng)驗值batch 8 是甜點batch 16 開始收益遞減。5.3 性能對照同一模型在 FP32、FP16 和 ONNX 路徑下的實測數(shù)據(jù)為了讓你對提升幅度有直觀概念我放一組自己在 T4 卡上的實測數(shù)據(jù)。模型是一個 YOLOv5s 結構檢測模型輸入 640x640 三通道單幀單 batch 推理執(zhí)行方式平均延遲ms相對 TF 原生提速顯存占用MB備注TensorFlow 原生FP3242.61.0x2350冷啟動后多輪取均值TF-TRT 優(yōu)化FP1612.83.3x1320精度下降可忽略ONNX→TensorRTFP1611.93.6x1280與 TF-TRT 差距不大ONNX→TensorRTINT87.65.6x860校準后 mAP 降約 0.3%從數(shù)據(jù)能看出兩件事第一FP16 的提速已經(jīng)非常可觀3 倍以上的提升足夠讓很多場景直接緩解性能瓶頸。第二INT8 雖然還能再快不少但確立了校準流程后額外引入的工程量不可忽視——如果你本身有 1000 路視頻要處理FP16 已經(jīng)能撐住沒必要貪 INT8 那點增益。這套數(shù)據(jù)放到t4 1080p25幀每秒用tensorrt yolo 640分辨率檢測可以支持多少路這個搜索背景下可以做一個簡單估算。1080p25 意味著每路攝像頭的處理周期是 40ms一年按 25fps 算就是每幀 40ms 內必須處理完。用 FP16 引擎單幀 12.8ms 的耗時每路 GPU 占用約 32% 的計算時間一個 T4 卡理論上可以支撐 3 路左右但這是并發(fā)而非純串行實際做多路視頻流時通常利用 CUDA 流來實現(xiàn)并發(fā)把 batch 合并到多個流中T4 上我實測可以穩(wěn)定支持 12 到 16 路 1080p25 檢測具體取決于預處理和后處理的資源占用。這個數(shù)據(jù)可以作為你規(guī)劃設計時的參考基線。5.4 顯存管理TensorRT 引擎加載后的一級緩存與運行時分配TensorRT 引擎加載后有顯存占用、推理時還有臨時工作區(qū)顯存workspace這兩者是兩回事。不少人在部署時看到顯存占用突然漲到幾個 GB以為是內存泄漏其實是 workspace 設計的。前面構建代碼里的config.max_workspace_size 1 30就是給推理工作區(qū)設置上限的——這個值設多大取決于 GPU 顯存余量建議顯存緊張時調小到 256MB 甚至 128MB性能損失通常不大但能騰出空間給多路并發(fā)。我在項目里遇到過 GPU 顯存被其他進程占滿導致 TensorRT 構建失敗的情況。排查思路很簡單先nvidia-smi看顯存占用如果看到殘留的 python 進程占了大量顯存用kill -9清理。多環(huán)境共用顯卡時建議設置CUDA_VISIBLE_DEVICES環(huán)境變量把進程隔離到指定卡上避免互相干擾。6. 踩坑實錄錯誤 43、精度漂移和那些午夜驚魂最后必須聊踩坑。TensorRT 項目里沒有踩過坑的人是幸運的踩過坑但沒解決的人已經(jīng)轉行了。我把遇到過的幾類典型問題整理出來按排查鏈路講這樣你遇到相似問題時能順著思路找而不是盲猜。6.1 轉換時報 Cannot find TensorRT library 或 libnvinfer.so 缺失這個問題的根因很直接TensorFlow 運行時找不到 TensorRT 的動態(tài)庫。雖然你裝了 TensorRT但庫路徑?jīng)]加進LD_LIBRARY_PATH。要注意 pip 安裝的 TensorFlow 默認不綁定 TensorRT你需要確保libnvinfer.so和libnvinfer_plugin.so在動態(tài)鏈接器的搜索路徑里。# 出問題先用這條命令確認庫是否存在 find / -name libnvinfer.so* 2/dev/null # 如果存在把路徑導出來 export LD_LIBRARY_PATH/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH # 如果不存在說明 TensorRT 根本沒裝成功 sudo apt-get install tensorrt python3-libnvinfer另一種隱蔽情況是機器里同時存在多個 TensorRT 版本比如顯卡驅動自帶的組件和 deb 包裝的有沖突庫倒是找到了但版本不對運行時報 symbol not found。這時候用ldd檢查 Python 擴展實際鏈接的庫路徑逐一排除冗余版本。6.2 GPU 錯誤 43 與 TensorRT 運行時崩潰的排查鏈路Windows 下錯誤 43 前面提過Linux 下的表現(xiàn)往往不是報 43而是 TensorRT 引擎構建到一半崩潰或者運行時 CUDA error。我遇到過一次特別詭異的情況同樣的代碼在 A 機器構建引擎成功在 B 機器卻反復崩潰報錯還指向 cuDNN。排查鏈路是這樣的第一步檢查兩臺機器的 GPU 是否同一架構。我用nvidia-smi -q | grep Architecture看了一下發(fā)現(xiàn) B 機器是較老的 Pascal 架構而我在 A 機器上構建 TensorRT 時用了針對 Ampere 的優(yōu)化引擎拿到 Pascal 上直接不兼容。這個問題的通用解法是在目標部署機上重新構建引擎或者構建時設置builder_platform相關選項讓引擎盡可能通用犧牲一點性能。第二步檢查驅動版本。有些老的驅動和 CUDA 12.x 不兼容導致運行時找不到入口符號。把驅動升級到支持你所用 CUDA 版本的最低穩(wěn)定版同時別忘記重啟機器驅動加載是否完整必須以重啟后的nvidia-smi輸出為準。第三步如果上述兩步都沒問題可以考慮顯存故障。跑一遍bandwidthTest或者deviceQueryCUDA 自帶的示例程序如果設備查詢本身出錯基本可以斷定硬件有問題TensorRT 什么的先放一邊。6.3 精度漂移FP16 下某些模型輸出變成 NaN 或錯檢大多數(shù)模型 FP16 沒問題但如果你碰上對精度極端敏感的模型尤其是層數(shù)極深、激活值動態(tài)范圍很大的網(wǎng)絡FP16 可能導致梯度或推理輸出異常。在你依賴 TF-TRT 自動精度分配時它通常能規(guī)避大部分問題但無法保證 100%。排查思路是先做精度對比用 TensorFlow 原生 FP32 推理結果作為基準逐層或整體比較 TensorRT 輸出的數(shù)值差異。如果你用的是 Python API可以在轉換時打開層級精度診斷如果用的是 TF-TRT可以設置precision_modeFP16的同時給特定層通過set_layer_node_precision手動降回 FP32。我遇到過一個更隱蔽的情況模型的輸入歸一化方式不對。TensorFlow 訓練時預處理是將像素值除以 255但我在 TensorRT 側直接喂原始 0~255 的 uint8 數(shù)據(jù)導致數(shù)值范圍差了 255 倍FP16 下這種大動態(tài)范圍直接觸發(fā)精度問題。排查后把預處理修正到和訓練一致問題立刻消失。所以記住先檢查預處理鏈路再懷疑 TensorRT 精度策略。6.4 一次奇怪的 5% 性能回退竟是因為 CPU 預處理成了瓶頸某次我優(yōu)化完 GPU 推理延遲降到 12ms但整個服務端到端延遲還是 60ms怎么都降不下去。一開始懷疑 TensorRT 沒生效后來用nvprof或nsight compute一看GPU 空閑時間占了 80%瓶頸根本不在這里。真正的問題是我的圖像解碼、resize、歸一化全在 CPU 上串行跑成了新瓶頸。解決思路是做預處理流水線并行用 OpenCV 的imread GPU 側的tf.image或者 CUDA 工具把 resize 和 normalize 移到 GPU 上或至少用多線程預取下一幀和當前幀的推理重疊。優(yōu)化之后端到端延遲從 60ms 降到 24msGPU 空閑時間也大幅減少。這個經(jīng)驗非常重要模型推理提速之后原來的次要瓶頸會變成主要瓶頸你必須重新審視整條鏈路。7. 從 TF-TRT 到多路并發(fā)部署一個可供參考的生產(chǎn)架構聊完單模型加速最后落到實際部署層面。熱詞里很多人搜gpu租用、gpu計算資源分配說明大家已經(jīng)意識到模型優(yōu)化完只是第一步怎么榨干 GPU 才是最終目的。這里給出我生產(chǎn)環(huán)境中用的一套相對成熟的思路供你參考。7.1 用 CUDA Stream 實現(xiàn)多路視頻并發(fā)推理多路視頻場景最大的特點是每一路都需要獨立處理但 GPU 計算可以共享。如果一路一路串行推理GPU 利用率很低T4 上 16 路 1080p25 檢測基本跑不贏。我的做法是引入 CUDA Stream 機制將多個 batch 的推理放到不同的 stream 上執(zhí)行讓它們并行提交給 GPU由 GPU 調度器按需分配計算資源。TensorRT 的引擎執(zhí)行本身是異步的配合 stream 就能實現(xiàn)第一路在算前處理時第二路同時在進行卷積計算的流水線效果。一個注意點多路并發(fā)不是簡單地把 batch 翻倍。每個 stream 里的推理仍然保持較小的 batch比如 2~4這樣既能讓 GPU 吃飽又不會因為單 batch 過大導致某一路的延遲抖動加劇。實測 T4 上 YOLO 640 輸入、FP16 引擎4 個 stream 每 stream batch 2整體吞吐比單流 batch 8 還要高約 15%原因就在于流之間可以提高整體的 kernel 重疊率。7.2 預熱、多進程和顯存隔離的經(jīng)驗值生產(chǎn)服務啟動后第一次推理特別慢這個是 TensorRT 引擎加載、cuDNN 初始化、CUDA context 創(chuàng)建疊加的結果。我的經(jīng)驗是在服務真正接受流量前, 先跑一次熱身推理加載引擎后立刻用一個 dummy 輸入跑幾幀讓 CUDA context 初始化完畢。這個熱身過程大約需要 500ms 到 2 秒不等但對線上延遲穩(wěn)定性非常重要。多進程部署時要注意每個進程都會創(chuàng)建獨立的 CUDA context 和 TensorRT engine 實例顯存占用是疊加的。所以我通常不用進程數(shù)去硬頂 CPU 核數(shù)而是根據(jù)顯存余量反推假設每進程占用 1.3GB一張 16GB 的 T4 卡單卡最多跑 10 個進程左右再多就要 OOM。更可控的方式是單進程多線程配合前面說的 CUDA Stream讓一個進程管理多路流減少顯存浪費。7.3 引擎文件的保存與跨機復用架構匹配就可用但別貪TensorRT 引擎文件.engine 或 .plan可以序列化保存但前面說了它綁定 GPU 架構和 TensorRT 版本。同一個集群里如果硬件完全一致那引擎文件直接拷過去加載即可如果硬件不一致必須在目標機上重新構建。為了平衡靈活性和加載速度我采用了一種混合策略保存 ONNX 中間文件在每臺部署機上首次啟動時自動構建引擎并緩存到本地磁盤以后直接加載緩存。這樣既不需要提前知道每臺機器的架構也不會每次啟動都重新構建。自動構建的邏輯可以做得簡單一點import os engine_cache ./engine_cache os.makedirs(engine_cache, exist_okTrue) def load_or_build_engine(onnx_path, precisionFP16): import hashlib key hashlib.md5((onnx_path precision trt.__version__).encode()).hexdigest()[:16] cache_path os.path.join(engine_cache, f{key}.engine) if os.path.exists(cache_path): with open(cache_path, rb) as f: return f.read() # 構建邏輯省略... engine build_engine(onnx_path, precision) with open(cache_path, wb) as f: f.write(engine) return engine這個方案的另一個好處是模型更新后ONNX 文件變了hash 自然不同會自動觸發(fā)重新構建不需要手動清理緩存。7.4 從單卡到多卡gpu計算資源分配的一個簡單框架當你單卡優(yōu)化完成接下來就是橫向擴展的問題。多卡環(huán)境下最簡單的任務分配方式是按路數(shù)切分比如 32 路視頻4 張 T4 卡每張卡分 8 路。路數(shù)和卡的映射可以先用固定公式做card_index stream_index % num_cards簡單但有效。如果想更精細可以引入一個簡單的調度器實時記錄每張卡的顯存占用和推理延遲把新請求分配到負載最低的卡上。在云上租用 GPU 時我習慣先把單卡能力測準能撐幾路、延遲多少然后倒推需要租幾卡。比如 100 路 1080p25 檢測單卡穩(wěn) 16 路那至少需要 7 卡考慮冗余和峰值余量我建議租 8 卡。別按單卡能跑 20 路這種理論值去算實測數(shù)據(jù)永遠比理論值靠譜。最后再分享一個實用的運維技巧GPU 推理服務上線后持續(xù)監(jiān)控顯存占用和卡上實際利用率的差異。你會發(fā)現(xiàn)當多路視頻并發(fā)不高時顯存占用率很低但利用率卻可能從 30% 跳到 90%這說明 TensorRT 已經(jīng)把計算的吞吐拉滿了。如果利用率一直壓在 30% 以下多半是 CPU 預處理、IO 或者調度邏輯卡了脖子別急著再優(yōu)化模型先往上下游找找。這條經(jīng)驗我用了很多年幾乎每次都能定位到真正的瓶頸。