戰(zhàn)指南)
在圈子里混久了你會(huì)發(fā)現(xiàn)跑通一個(gè)模型只是起點(diǎn)真正讓人頭疼的是把它塞進(jìn)真實(shí)的生產(chǎn)環(huán)境推理延遲壓不下來顯存一上來就爆功耗卡著預(yù)算上限。我接手過的不少項(xiàng)目模型指標(biāo)刷得漂亮一到落地就被性能和資源卡脖子。Model-Optimizer這個(gè)詞兒就是干這個(gè)的它不是某個(gè)單一工具的名字而是覆蓋模型壓縮、加速、部署調(diào)優(yōu)的一整套思路。這篇文章我會(huì)結(jié)合自己多次模型優(yōu)化落地的實(shí)際經(jīng)歷從設(shè)計(jì)取舍講到具體操作流程把剪枝、量化、知識(shí)蒸餾、算子融合這些核心手段拆開揉碎再補(bǔ)上實(shí)戰(zhàn)里踩過的坑和排查技巧。不管你是剛接觸部署的新手還是正在為線上服務(wù)壓測(cè)撓頭的工程師這套方法論應(yīng)該都能直接用上。1. 模型優(yōu)化到底在解決什么問題1.1 先說清楚優(yōu)化指的是什么很多人一想到模型優(yōu)化第一反應(yīng)是調(diào)參、改損失函數(shù)、提升準(zhǔn)確率。但Model-Optimizer代表的場(chǎng)景恰恰相反它面對(duì)的是一個(gè)已經(jīng)訓(xùn)練好的、效果達(dá)標(biāo)的模型目標(biāo)是把它變輕、變快、變得更適合部署。這個(gè)過程不改變模型的語義功能但會(huì)顯著改變它的體積、計(jì)算量和內(nèi)存占用。舉個(gè)例子。我用ResNet-50做過一次圖像分類服務(wù)的優(yōu)化。原始模型文件約98MB單張顯卡上推理一張224x224的圖片大約需要12ms??雌饋磉€行但放到CPU服務(wù)器上、還要同時(shí)服務(wù)多個(gè)并發(fā)請(qǐng)求時(shí)延遲直接飆到80ms以上顯存占用也接近2GB。經(jīng)過一輪完整優(yōu)化后模型體積縮到26MBCPU推理延遲降到22ms顯存占用控制在400MB以內(nèi)精度損失不到一個(gè)百分點(diǎn)。這才是優(yōu)化真正該干的事。1.2 為什么要在訓(xùn)練之后再做一輪額外工作可能有人會(huì)問訓(xùn)練的時(shí)候就考慮部署不行嗎當(dāng)然可以神經(jīng)網(wǎng)絡(luò)架構(gòu)搜索NAS、輕量化網(wǎng)絡(luò)設(shè)計(jì)MobileNet、ShuffleNet就是這條路。但現(xiàn)實(shí)情況是業(yè)務(wù)團(tuán)隊(duì)通常沒有精力從零設(shè)計(jì)一個(gè)高效網(wǎng)絡(luò)更多時(shí)候是在已有模型基礎(chǔ)上迭代或者直接使用開源預(yù)訓(xùn)練模型。這就意味著模型的原始形態(tài)往往不是為部署場(chǎng)景準(zhǔn)備的。從工程角度看訓(xùn)練階段的目標(biāo)函數(shù)是精度越高越好而部署階段的目標(biāo)函數(shù)是在可接受的精度損失內(nèi)延遲和資源占用越低越好。兩個(gè)目標(biāo)天然存在張力。如果不在訓(xùn)練后專門做一輪優(yōu)化直接拿原始模型上線通常會(huì)遇到三類問題顯存不夠?qū)е路?wù)線程被OOM殺掉響應(yīng)時(shí)間超過業(yè)務(wù)SLA導(dǎo)致用戶側(cè)超時(shí)單機(jī)吞吐量太低需要堆更多機(jī)器成本直線上升。Model-Optimizer的核心價(jià)值就是在精度和性能之間幫你找到一個(gè)可接受的平衡點(diǎn)。1.3 優(yōu)化帶來的收益如何量化做優(yōu)化前我習(xí)慣先建立一個(gè)基線指標(biāo)表。這樣后續(xù)每一步改動(dòng)都能用數(shù)據(jù)說話。需要關(guān)注的核心指標(biāo)包括指標(biāo)項(xiàng)說明優(yōu)化前基線示例模型體積序列化后的文件大小直接影響存儲(chǔ)和加載時(shí)間98MB推理延遲單次請(qǐng)求從輸入到輸出的耗時(shí)分P50/P95等分位數(shù)CPU 80ms吞吐量單位時(shí)間處理的請(qǐng)求數(shù)通常用QPS衡量單機(jī)120內(nèi)存/顯存占用模型運(yùn)行時(shí)的峰值占用顯存1.9GB能耗單位請(qǐng)求的功耗嵌入式場(chǎng)景尤其關(guān)鍵8.5W優(yōu)化完成后我通常會(huì)生成一張同樣的表做對(duì)比。在很多實(shí)際項(xiàng)目中經(jīng)過量化加剪枝的組合拳模型體積可以壓縮到原來的1/4以下延遲降低50%以上誤差控制在1%以內(nèi)。這筆賬算清楚之后無論是說服團(tuán)隊(duì)投入時(shí)間還是選擇優(yōu)化方案心里都更有底氣。2. 核心優(yōu)化手段的取舍邏輯2.1 剪枝砍掉不重要的部分剪枝是最直觀的優(yōu)化手段神經(jīng)網(wǎng)絡(luò)的很多權(quán)重其實(shí)接近于零對(duì)最終輸出貢獻(xiàn)很小。把這些權(quán)重去掉模型自然就變小變快。剪枝分為結(jié)構(gòu)化剪枝和非結(jié)構(gòu)化剪枝。非結(jié)構(gòu)化剪枝會(huì)把權(quán)重矩陣中的稀疏值置零精度損失小但實(shí)際加速需要專門的稀疏計(jì)算庫支持否則內(nèi)存訪問還是按稠密矩陣走的效果有限。結(jié)構(gòu)化剪枝直接刪除整個(gè)卷積核或神經(jīng)元通道對(duì)硬件更友好但需要重新微調(diào)來恢復(fù)精度。我比較推薦的做法是訓(xùn)練時(shí)感知剪枝在模型訓(xùn)練過程中逐漸增加稀疏性約束訓(xùn)練結(jié)束后再一次性移除冗余結(jié)構(gòu)最后做一小段微調(diào)。這種方式比直接對(duì)訓(xùn)練好的模型做后訓(xùn)練剪枝穩(wěn)定很多。舉個(gè)例子對(duì)YOLOv5的檢測(cè)頭做結(jié)構(gòu)化剪枝時(shí)我按通道貢獻(xiàn)度排序保留前70%的通道微調(diào)5個(gè)epoch后mAP只掉了0.8%但推理速度提升了接近40%??车迷胶莼謴?fù)精度需要的微調(diào)數(shù)據(jù)和時(shí)間就越多所以剪枝比例一定要結(jié)合微調(diào)成本來定。2.2 量化用更少的位數(shù)表示權(quán)重和激活量化是另一個(gè)核心手段核心思路是用低精度數(shù)值替代高精度數(shù)值最典型的是把FP32變成INT8。FP32的模型權(quán)重有32位INT8只有8位理論上模型體積直接縮小4倍。同時(shí)INT8的矩陣運(yùn)算在CPU和GPU上都有高度優(yōu)化的指令集所以計(jì)算速度也會(huì)明顯提升。量化的難點(diǎn)在于精度損失。FP32到INT8數(shù)值表示范圍大幅縮小原來的小數(shù)值可能被直接截?cái)喑?。這時(shí)候有兩個(gè)關(guān)鍵選擇對(duì)稱量化和非對(duì)稱量化。對(duì)稱量化把浮點(diǎn)數(shù)值范圍映射到[-127,127]適用于權(quán)重分布相對(duì)對(duì)稱的場(chǎng)景非對(duì)稱量化則單獨(dú)計(jì)算正負(fù)方向的縮放因子對(duì)激活值的處理更友好。此外還有量化感知訓(xùn)練QAT和后訓(xùn)練量化PTQ兩條路線。QAT在訓(xùn)練時(shí)模擬量化的舍入誤差精度更好但需要重新訓(xùn)練PTQ只用少量校準(zhǔn)數(shù)據(jù)統(tǒng)計(jì)數(shù)值分布速度快適合大多數(shù)場(chǎng)景。我在實(shí)際項(xiàng)目里對(duì)BERT類文本模型做INT8 PTQ時(shí)校準(zhǔn)數(shù)據(jù)集用了500條樣本精度下降控制在0.5%左右。但如果校準(zhǔn)數(shù)據(jù)分布和線上數(shù)據(jù)差異大精度會(huì)掉得很難看。這個(gè)是必須提前預(yù)防的坑后面故障排查部分我會(huì)細(xì)說。2.3 知識(shí)蒸餾讓大模型教小模型蒸餾的思路和大模型壓縮不太一樣。它不是直接壓縮原來的模型而是訓(xùn)練一個(gè)新的、結(jié)構(gòu)更小的小模型讓小模型學(xué)習(xí)大模型的輸出行為。通常做法是把大模型的softmax輸出包含類別間的相對(duì)概率信息作為軟標(biāo)簽和小模型的真實(shí)標(biāo)簽損失一起訓(xùn)練。相比直接用真實(shí)標(biāo)簽訓(xùn)練軟標(biāo)簽攜帶的信息量更大小模型能學(xué)到更多暗知識(shí)。蒸餾適合的場(chǎng)景主要是你有資源訓(xùn)練一個(gè)精度很高的大模型但部署環(huán)境承受不了大模型的體積或者你希望換一個(gè)完全不同的網(wǎng)絡(luò)結(jié)構(gòu)比如從Transformer換到CNN這種情況下剪枝和量化都很難操作蒸餾幾乎是唯一選擇。我做過一個(gè)OCR場(chǎng)景用蒸餾把12層Transformer壓縮成4層字符識(shí)別準(zhǔn)確率只損失了2%推理速度提升了3倍多。要注意的是蒸餾訓(xùn)練本身有額外成本如果目標(biāo)模型只是想做INT8量化優(yōu)先考慮PTQ就夠了不必大動(dòng)干戈做蒸餾。2.4 算子融合與計(jì)算圖優(yōu)化算子融合和應(yīng)用層的關(guān)系更近是推理引擎做的事情。神經(jīng)網(wǎng)絡(luò)在計(jì)算圖上會(huì)有很多連續(xù)的細(xì)粒度算子比如Conv后面跟著BatchNorm和ReLU。傳統(tǒng)的執(zhí)行方式是每個(gè)算子單獨(dú)啟動(dòng)內(nèi)核讀寫一次內(nèi)存。算子融合把多個(gè)算子合并成一個(gè)內(nèi)核中間結(jié)果留在寄存器或緩存里省去多次內(nèi)存讀寫延遲自然下降。在TensorRT這類推理引擎里圖優(yōu)化是自動(dòng)做的但在自研推理管線里需要手動(dòng)標(biāo)記融合點(diǎn)。一個(gè)典型的融合序列是ConvBatchNormReLU。BN在推理時(shí)其實(shí)可以化簡成Conv的偏置項(xiàng)ReLU可以合并到激活函數(shù)中三者合成一個(gè)算子。優(yōu)化前一次前向需要三次內(nèi)核啟動(dòng)優(yōu)化后只需一次。層數(shù)越深的模型融合累積的收益越明顯。我實(shí)際測(cè)過一個(gè)80層的視覺模型僅靠算子融合就把推理時(shí)間縮短了18%。2.5 如何組合這些手段這幾個(gè)手段不是互相排斥的但它們有優(yōu)先級(jí)。我的經(jīng)驗(yàn)是多模態(tài)、大模型一類結(jié)構(gòu)復(fù)雜的模型先做通道剪枝和蒸餾再做量化最后交給推理引擎做圖優(yōu)化。因?yàn)榱炕瘜?duì)異常值敏感先剪枝掉冗余權(quán)重可以減少分布中的離群點(diǎn)讓量化誤差更小。蒸餾則適合在大模型上先把結(jié)構(gòu)變小后續(xù)量化壓力會(huì)小很多。如果項(xiàng)目時(shí)間緊只做量化和算子融合也能拿到大部分收益。具體組合方式可以用一個(gè)簡單表格做決策參考場(chǎng)景推薦組合原因CPU部署時(shí)間倉促PTQ量化算子融合成本低提速明顯GPU部署精度敏感QAT量化通道剪枝精度可控資源釋放多嵌入式邊緣設(shè)備結(jié)構(gòu)化剪枝INT8量化體積和能耗雙優(yōu)化模型結(jié)構(gòu)大型化蒸餾量化結(jié)構(gòu)徹底變小3. Model-Optimizer 工具選型與配置3.1 訓(xùn)練框架自帶能力 vs 獨(dú)立推理引擎優(yōu)化工具的選擇取決于你的部署鏈路。PyTorch和TensorFlow各自都有一些優(yōu)化能力比如PyTorch自帶torch.quantizationTensorFlow有TensorFlow Model Optimization Toolkit。這些和訓(xùn)練代碼集成度高適合快速實(shí)驗(yàn)。但當(dāng)你要真正上線尤其是在GPU上需要極致性能時(shí)獨(dú)立的推理引擎通常更值得考慮。TensorRT、ONNX Runtime、OpenVINO都是這個(gè)領(lǐng)域的常用選擇。Model-Optimizer這個(gè)命名在幾個(gè)推理引擎里都出現(xiàn)過。比如OpenVINO就內(nèi)置了Model Optimizer組件負(fù)責(zé)把訓(xùn)練好的模型轉(zhuǎn)換成中間表示并做圖優(yōu)化。Intel的這套工具鏈在CPU部署上非常成熟支持TensorFlow、PyTorch、ONNX等格式的導(dǎo)入。如果你的部署目標(biāo)是Intel CPU直接用它的Model Optimizer做轉(zhuǎn)換和優(yōu)化比手動(dòng)改模型省事得多。3.2 工具鏈選擇的基本原則我選工具鏈有四個(gè)原則。第一輸入格式是否兼容。最好統(tǒng)一用ONNX作為中間格式幾乎所有框架都能導(dǎo)出。第二是否支持目標(biāo)硬件。TensorRT主要服務(wù)NVIDIA GPUOpenVINO主打Intel CPU不能選錯(cuò)。第三是否支持混合精度。不同硬件對(duì)不同精度的支持差異很大不是所有工具都支持INT8或FP16。第四是否有持續(xù)維護(hù)和社區(qū)活躍度。這個(gè)看著虛實(shí)際能決定踩坑后能不能快速找到解決方案。還有個(gè)經(jīng)驗(yàn)不要在一開始就引入太多組件。很多平臺(tái)喜歡把各種優(yōu)化能力揉在一起配置復(fù)雜出了問題還很難定位。我現(xiàn)在的習(xí)慣是先用ONNX Runtime做一遍基礎(chǔ)優(yōu)化和基準(zhǔn)測(cè)試如果性能不達(dá)標(biāo)再引入TensorRT或OpenVINO針對(duì)硬件做二次優(yōu)化。這樣分層處理每一層的效果都能單獨(dú)量化排障時(shí)思路也清晰。3.3 完整配置流程示例以O(shè)penVINO的Model Optimizer為例典型的配置流程大概是準(zhǔn)備環(huán)境安裝openvino-dev工具包。將訓(xùn)練好的PyTorch模型導(dǎo)出為ONNX格式。使用mo命令進(jìn)行轉(zhuǎn)換指定輸入形狀和精度。比如執(zhí)行mo --input_model model.onnx --input_shape [1,3,224,224] --data_type FP32 --output_dir optimized。用benchmark_app工具跑一輪性能基準(zhǔn)對(duì)比轉(zhuǎn)換前后的延遲和吞吐。配置時(shí)要注意的一個(gè)點(diǎn)是靜態(tài)輸入形狀。推理引擎優(yōu)化圖結(jié)構(gòu)時(shí)往往依賴輸入形狀固定。如果你的服務(wù)支持動(dòng)態(tài)batch建議先嘗試設(shè)置動(dòng)態(tài)維度但要知道動(dòng)態(tài)shape會(huì)降低優(yōu)化力度。我的做法是能固定盡量固定業(yè)務(wù)方不接受的話再退而求其次用范圍限定的動(dòng)態(tài)shape。3.4 校準(zhǔn)數(shù)據(jù)的準(zhǔn)備方法做量化時(shí)校準(zhǔn)數(shù)據(jù)是繞不開的環(huán)節(jié)。校準(zhǔn)calibration就是統(tǒng)計(jì)激活值的數(shù)值范圍來確定量化的縮放因子。校準(zhǔn)數(shù)據(jù)集不需要帶標(biāo)簽只需要覆蓋真實(shí)場(chǎng)景的輸入分布。數(shù)量上我一般控制在300到1000條之間太多會(huì)拖慢校準(zhǔn)流程太少則統(tǒng)計(jì)不到極端值。具體做法可以是直接從驗(yàn)證集里隨機(jī)抽樣也可以從線上日志里撈一段真實(shí)請(qǐng)求輸入。如果是圖像模型記得做和訓(xùn)練時(shí)一致的預(yù)處理縮放、歸一化、通道順序否則輸入分布不匹配量化后精度會(huì)偏離。文本模型要特別注意輸入長度分布我之前遇到過用短文本校準(zhǔn)、長文本測(cè)試的情況激活值范圍估計(jì)偏差很大精度直接掉了5個(gè)百分點(diǎn)。提示校準(zhǔn)數(shù)據(jù)里一定要包含邊界情況。比如圖像里的純黑純白圖片、文本里的超長序列這些極端輸入產(chǎn)生的激活值范圍才是決定量化邊界的關(guān)鍵。4. 實(shí)操從PyTorch模型到線上服務(wù)的完整流程4.1 基線準(zhǔn)備與模型導(dǎo)出我習(xí)慣把優(yōu)化流程拆成五個(gè)階段每個(gè)階段都有明確的交付物。第一階段是基線準(zhǔn)備。加載一個(gè)訓(xùn)練好的模型用驗(yàn)證集測(cè)出它的原始精度和性能指標(biāo)記錄下來作為對(duì)照。接著把模型的權(quán)重文件轉(zhuǎn)成中間格式我用得最多的是ONNX。PyTorch導(dǎo)出ONNX時(shí)有一個(gè)值得注意的點(diǎn)動(dòng)態(tài)軸。如果你在導(dǎo)出時(shí)設(shè)置了動(dòng)態(tài)batch導(dǎo)出的ONNX文件會(huì)包含動(dòng)態(tài)shape信息靈活性好但優(yōu)化空間小。反過來使用固定shape導(dǎo)出很多推理引擎可以直接做常量折疊、算子融合性能更好。我的做法是按業(yè)務(wù)場(chǎng)景的實(shí)際batch大小固定shape比如線上單次推理固定batch1導(dǎo)出時(shí)就寫死。4.2 圖優(yōu)化與靜態(tài)分析拿到ONNX文件后先做一輪圖優(yōu)化和靜態(tài)分析。用ONNX Runtime自帶的graph optimization就能完成大部分工作包括算子融合、常量折疊、冗余節(jié)點(diǎn)消除。這一步不需要重新訓(xùn)練也不需要額外數(shù)據(jù)純粹是計(jì)算圖級(jí)別的優(yōu)化安全無副作用。圖優(yōu)化完成后我會(huì)用一個(gè)工具腳本統(tǒng)計(jì)計(jì)算圖中的算子類型和數(shù)量。比如看到大量Conv、BatchNorm、Relu節(jié)點(diǎn)就知道量化時(shí)這幾類算子會(huì)占大頭。如果發(fā)現(xiàn)某些自定義算子推理引擎不支持就得提前處理要么用官方算子重寫要么在引擎中注冊(cè)自定義實(shí)現(xiàn)。這一步不處理好后面轉(zhuǎn)換時(shí)會(huì)出現(xiàn)大量fallback導(dǎo)致整個(gè)模型退回到CPU執(zhí)行加速效果全無。4.3 量化與剪枝的具體操作算力充足時(shí)我會(huì)優(yōu)先做QAT量化。具體做法是在PyTorch中給模型插入fake quant節(jié)點(diǎn)用訓(xùn)練數(shù)據(jù)繼續(xù)訓(xùn)練幾個(gè)epoch讓模型適應(yīng)量化的舍入誤差。epoch數(shù)不用多3到5個(gè)即可重點(diǎn)是讓BN統(tǒng)計(jì)量重新收斂。這里有個(gè)小技巧QAT前先把模型設(shè)為eval模式跑一遍校準(zhǔn)數(shù)據(jù)更新BN的running_mean和running_var效果比直接訓(xùn)練更好而且穩(wěn)定。如果時(shí)間不允許直接走PTQ。操作步驟是準(zhǔn)備校準(zhǔn)數(shù)據(jù)遍歷模型各層記錄激活值分布計(jì)算縮放因子。PyTorch中可以用torch.quantization.prepare和convert快速完成。我試過一種改進(jìn)方式校準(zhǔn)完后在量化模型上把最后一層分類器保留為FP16其余層轉(zhuǎn)INT8。這樣做精度損失稍微小一點(diǎn)實(shí)現(xiàn)成本不高是個(gè)劃算的折中。剪枝方面最簡單的切入點(diǎn)是卷積層的通道維度。使用torch.prune中的結(jié)構(gòu)化剪枝方法指定剪枝比例后重新導(dǎo)出ONNX。剪枝后模型結(jié)構(gòu)會(huì)變化下游層的輸入通道數(shù)也要跟著調(diào)整PyTorch會(huì)自動(dòng)處理但ONNX導(dǎo)出后最好檢查一下網(wǎng)絡(luò)圖確認(rèn)沒有無效的維度變化。我見過自動(dòng)剪枝后ONNX圖里出現(xiàn)Reshape節(jié)點(diǎn)和Transpose節(jié)點(diǎn)冗余的情況手動(dòng)清理后推理速度又提升了5%。4.4 部署環(huán)境的適配與實(shí)測(cè)優(yōu)化完成后到部署還有一步適配推理引擎。TensorRT以O(shè)NNX為輸入支持FP32、FP16、INT8精度。構(gòu)建Engine時(shí)有一個(gè)選項(xiàng)要留意workspace size。它決定TensorRT能用多少顯存來做layer選擇。設(shè)置太小會(huì)讓部分算子選不到最優(yōu)實(shí)現(xiàn)設(shè)置太大則會(huì)占滿顯存影響其他服務(wù)的部署。我一般設(shè)為1GB起步按模型大小調(diào)整。部署后的驗(yàn)證要分兩層。第一層是單請(qǐng)求延遲測(cè)試用固定輸入跑幾百次統(tǒng)計(jì)P50和P95延遲。第二層是并發(fā)壓測(cè)用模擬流量逐步加壓觀察QPS和顯存占用曲線。Found a critical problem here:在壓測(cè)時(shí)經(jīng)常會(huì)遇到CPU和GPU使用率不均衡的情況。例如數(shù)據(jù)預(yù)處理用CPU導(dǎo)致CPU成為瓶頸GPU反而在等待數(shù)據(jù)。這種情況下優(yōu)化模型本身已經(jīng)沒有意義要解決的是數(shù)據(jù)管線和預(yù)處理并行度的問題。這個(gè)觀察點(diǎn)很值得記錄下來。4.5 優(yōu)化完成的驗(yàn)收標(biāo)準(zhǔn)驗(yàn)收要看三個(gè)維度缺一不可精度、性能、穩(wěn)定性。精度維度要對(duì)比優(yōu)化前后的top1/top5準(zhǔn)確率、mAP或具體業(yè)務(wù)指標(biāo)性能維度看延遲和吞吐提升幅度穩(wěn)定性維度則要跑長時(shí)間壓測(cè)觀察是否有緩慢的內(nèi)存增長、延遲抖動(dòng)、偶發(fā)超時(shí)。我經(jīng)常在驗(yàn)收表里多留一項(xiàng)回滾成本。優(yōu)化方案如果不可回滾比如直接改動(dòng)了原模型權(quán)重一旦有問題會(huì)很被動(dòng)。所以我通常保留一份優(yōu)化前的模型文件并設(shè)置一個(gè)開關(guān)能快速在原模型和優(yōu)化后模型之間切換。線上流量小的時(shí)候灰度切過去觀察幾小時(shí)確認(rèn)無誤再全量。這個(gè)流程看著繁瑣但能避免很多上線事故。5. 常見問題與排查技巧實(shí)錄5.1 量化后精度明顯掉得離譜這是我被問到最多的問題。INT8量化后精度掉超過3%通常不是量化本身的問題而是校準(zhǔn)數(shù)據(jù)出了問題。我遇到過的典型情況包括用了帶標(biāo)簽的訓(xùn)練數(shù)據(jù)做校準(zhǔn)而訓(xùn)練數(shù)據(jù)的預(yù)處理流程和線上不一致校準(zhǔn)數(shù)據(jù)太單一激活值范圍集中在很小區(qū)域模型中有對(duì)數(shù)值范圍極其敏感的層比如檢測(cè)模型的回歸頭。排查思路是逐層對(duì)比激活值分布。我寫過一個(gè)小工具輸入ONNX模型在量化前后各跑一遍相同數(shù)據(jù)輸出每層的輸出分布直方圖。找到差異最大的層針對(duì)那一層做特殊處理要么保留FP16要么單獨(dú)調(diào)整量化范圍。用這個(gè)方法幫一個(gè)目標(biāo)檢測(cè)項(xiàng)目把量化后mAP損失從6%降到了1.2%。5.2 轉(zhuǎn)換后模型運(yùn)行速度反而更慢優(yōu)化后變慢這事兒不罕見。有一次我把一個(gè)模型轉(zhuǎn)成TensorRT INT8后單次延遲反而比FP32慢。后來定位發(fā)現(xiàn)模型里有一些小算子如shape操作、gather沒有被TensorRT高效支持運(yùn)行時(shí)會(huì)從GPU回退到CPU執(zhí)行?;赝瞬僮鞯拈_銷遠(yuǎn)大于普通GPU內(nèi)核所以整體延遲反而上升。解決方法是使用TensorRT的profiling工具查看每個(gè)節(jié)點(diǎn)的執(zhí)行時(shí)間和執(zhí)行設(shè)備。找到標(biāo)著CPU執(zhí)行的節(jié)點(diǎn)手動(dòng)改計(jì)算圖把不支持的操作改寫成支持的形式或者用插件實(shí)現(xiàn)。還有一件事需要檢查模型是否存在動(dòng)態(tài)shape如果不固定shape引擎無法做完整的層融合性能也會(huì)打折。5.3 顯存占用居高不下顯存優(yōu)化和模型體積、延遲是兩個(gè)維度。即使模型權(quán)重文件很小推理時(shí)顯存占用也可能很高因?yàn)榧せ钪祱D占用了大量空間。典型的解決策略包括減小batch size、使用激活值檢查點(diǎn)、調(diào)整workspace size、或者減少同時(shí)并發(fā)的推理流。BN層在推理時(shí)合并進(jìn)卷積后激活值內(nèi)存也會(huì)下降不少。有一個(gè)技巧很實(shí)用在TensorRT里將推理流數(shù)量從默認(rèn)值調(diào)低。默認(rèn)情況下TensorRT會(huì)為一個(gè)模型創(chuàng)建多個(gè)CUDA stream來最大化并行度但這會(huì)導(dǎo)致顯存占用成倍增加。如果顯存緊張把stream數(shù)降到1吞吐會(huì)有輕微下降但顯存占用可能直接減半。這是很多部署文檔里沒有細(xì)講的經(jīng)驗(yàn)。5.4 推理引擎報(bào)錯(cuò)無法解析模型ONNX模型在推理引擎轉(zhuǎn)換時(shí)報(bào)錯(cuò)常見原因有三類使用了引擎不支持的算子版本模型中存在動(dòng)態(tài)shape導(dǎo)致的維度推理失敗某些自定義算子沒有被注冊(cè)。針對(duì)第一類解決方法是使用opset版本兼容性腳本在導(dǎo)出ONNX時(shí)選擇引擎支持的較低版本。第二類盡量固定輸入shape或者為動(dòng)態(tài)維度設(shè)定合理的范圍。第三類只能重寫自定義算子沒有更好的辦法。注意不同推理引擎對(duì)ONNX算子集的支持程度不一樣。一個(gè)模型能在ONNX Runtime正常跑不代表在TensorRT里一定也能成。轉(zhuǎn)換前先看官方支持的算子列表能省很多調(diào)試時(shí)間。5.5 常見問題速查表問題表現(xiàn)可能原因處理建議量化后精度大跌校準(zhǔn)數(shù)據(jù)與線上分布不匹配更新校準(zhǔn)集納入邊界樣本轉(zhuǎn)換后變慢算子回退到CPU執(zhí)行查看profiling重寫不支持算子顯存占用過高并發(fā)流過多或shape動(dòng)態(tài)減少stream數(shù)固定shape模型加載緩慢未做圖優(yōu)化節(jié)點(diǎn)冗余先跑基礎(chǔ)圖優(yōu)化精度有損但無法復(fù)現(xiàn)量化存在隨機(jī)性固定隨機(jī)種子對(duì)比多次實(shí)驗(yàn)業(yè)務(wù)請(qǐng)求偶發(fā)超時(shí)預(yù)處理/后處理耗時(shí)不穩(wěn)定預(yù)熱模型優(yōu)化數(shù)據(jù)管線6. 一些經(jīng)驗(yàn)層面的建議回頭看我做過的大大小小十幾個(gè)優(yōu)化項(xiàng)目最深的體會(huì)是模型優(yōu)化是在預(yù)算、時(shí)間、精度三者之間找平衡不存在一個(gè)萬能方案。每次動(dòng)手前先明確一個(gè)數(shù)字目標(biāo)——延遲要降到多少、顯存要控制在多少、精度損失不能超過多少三個(gè)數(shù)字寫清楚后續(xù)所有決策都有據(jù)可依。沒有目標(biāo)做優(yōu)化很容易在手段之間反復(fù)橫跳最后什么都優(yōu)化了又什么都沒優(yōu)化透。另外一個(gè)經(jīng)驗(yàn)是想清楚給誰優(yōu)化。同一個(gè)模型跑在不同硬件上最優(yōu)方案完全不同。CPU上可能OpenVINO加INT8才是最優(yōu)解GPU上TensorRT加FP16往往更香邊緣設(shè)備則要把模型體積和功耗放在第一位。優(yōu)化流程沒有標(biāo)準(zhǔn)答案但方法論是通用的先量化基線再選手段組合然后快速驗(yàn)證、逐步調(diào)優(yōu)最后灰度上線。這套流程跑順之后新模型再進(jìn)來最多一兩天就能出一版可用的優(yōu)化結(jié)果。最后再送一個(gè)建議優(yōu)化之后別忘了補(bǔ)充文檔把校準(zhǔn)數(shù)據(jù)來源、量化參數(shù)、剪枝比例、壓測(cè)結(jié)果全部記錄下來。這和寫代碼要留注釋一個(gè)道理。很多時(shí)候模型幾天后就要迭代如果沒有一份完整的優(yōu)化記錄又得從頭開始摸索參數(shù)。我自己的經(jīng)驗(yàn)是優(yōu)化記錄文檔寫得好下一次迭代能省至少30%的溝通和調(diào)試時(shí)間。