:七類圖像分類模型部署與性能對比)
1. 為什么要在 RK3568 上折騰圖像分類模型RK3568 這顆芯片在邊緣計算圈子里熱度一直不低。四核 Cortex-A55 加一顆 0.8 TOPS 的 NPU支持 INT8 量化推理功耗控制在幾瓦級別價格又相對友好做智能門禁、工業(yè)質(zhì)檢、零售貨柜這類場景的開發(fā)者基本都會把它列入候選。但真到落地階段問題就來了手頭訓(xùn)好的圖像分類模型五花八門ResNet、MobileNet、EfficientNet、ViT 各有各的結(jié)構(gòu)到底哪個能在 RK3568 上跑得又快又準官方文檔給的數(shù)據(jù)往往只覆蓋少數(shù)幾個經(jīng)典網(wǎng)絡(luò)實際選型時還是得自己動手測一遍。我最近花了大概兩周時間把手上常見的幾類圖像分類模型在 RK3568 上完整跑了一輪部署實驗從模型轉(zhuǎn)換、量化、板端推理到精度和速度對比踩了不少坑也積累了一些官方文檔里不會寫的經(jīng)驗。這篇文章就是把整個實驗過程、數(shù)據(jù)結(jié)果和實操心得整理出來給正在做類似選型的同行一個參考。不管你是剛接觸 RKNN 工具鏈的新手還是已經(jīng)用過 RK3588 想了解 RK3568 差異的老手應(yīng)該都能從中找到有用的東西。需要提前說明的是實驗平臺是一塊標準的 RK3568 開發(fā)板4GB 內(nèi)存版本系統(tǒng)是官方 Ubuntu 20.04 固件RKNN-Toolkit2 用的是 1.6.0 版本NPU 驅(qū)動版本 0.8.2。不同固件和工具鏈版本可能會有差異具體數(shù)據(jù)僅供參考但方法論是通用的。2. 實驗整體設(shè)計與模型選型思路2.1 為什么選這幾類模型圖像分類模型發(fā)展到今天大致可以分成幾個流派。一類是以 ResNet 為代表的經(jīng)典卷積網(wǎng)絡(luò)結(jié)構(gòu)規(guī)整、算子標準對 NPU 友好一類是以 MobileNet、ShuffleNet 為代表的輕量級網(wǎng)絡(luò)專為移動端設(shè)計參數(shù)量和計算量都壓得很低還有一類是以 EfficientNet 為代表的復(fù)合縮放網(wǎng)絡(luò)用 NAS 思路平衡深度、寬度和分辨率最后是近兩年火起來的 ViT 系列基于注意力機制精度高但對算力和內(nèi)存帶寬要求也高。我選型的邏輯是每個流派挑一到兩個有代表性的模型覆蓋不同的參數(shù)量級和計算量級這樣對比出來的結(jié)論才有參考價值。具體清單如下模型參數(shù)量輸入尺寸流派選它的理由ResNet1811.7M224x224經(jīng)典卷積結(jié)構(gòu)標準作為基準線ResNet5025.6M224x224經(jīng)典卷積大模型在 NPU 上的表現(xiàn)MobileNetV23.4M224x224輕量級移動端經(jīng)典部署廣泛MobileNetV3-Small2.5M224x224輕量級更極致的輕量化ShuffleNetV22.3M224x224輕量級通道混洗算子的兼容性EfficientNet-B05.3M224x224復(fù)合縮放NAS 結(jié)構(gòu)的部署難度ViT-B/1686M224x224注意力機制驗證 Transformer 可行性這個組合基本能覆蓋大多數(shù)實際項目會遇到的模型類型。如果你手頭的模型不在這個列表里也可以參照這個分類找到最接近的參考對象。2.2 實驗要回答的三個核心問題整個實驗圍繞三個問題展開。第一是精度損失模型經(jīng)過 INT8 量化后Top-1 準確率會掉多少不同結(jié)構(gòu)的模型對量化的敏感度是否一樣第二是推理速度單幀推理耗時多少毫秒能否滿足實時性要求比如 30fps 需要單幀低于 33ms第三是部署難度模型轉(zhuǎn)換過程中會遇到哪些算子不支持的問題需要多少手工干預(yù)這三個問題對應(yīng)了實際選型時最關(guān)心的三個維度。精度決定能不能用速度決定好不好用部署難度決定要花多少人力。很多網(wǎng)上的評測只講速度不講精度或者只講精度不講部署成本參考價值有限。我這次盡量把三個維度都量化出來。2.3 測試數(shù)據(jù)集和評價標準精度測試用的是 ImageNet 驗證集的子集5000 張圖片覆蓋 1000 個類別。之所以不用完整 50000 張是因為板端推理速度有限完整跑一遍時間太長。5000 張的采樣保證了每個類別至少 5 張統(tǒng)計上足夠反映精度趨勢。評價指標用 Top-1 和 Top-5 準確率對比三個階段的數(shù)值PyTorch 原始模型、ONNX 轉(zhuǎn)換后模型、RKNN INT8 量化后模型。這樣能清楚看到精度損失發(fā)生在哪個環(huán)節(jié)。速度測試用板端 NPU 推理預(yù)熱 10 次后取 100 次推理的平均耗時排除首次加載和緩存的影響。3. 部署環(huán)境搭建與工具鏈配置3.1 PC 端環(huán)境準備RKNN 工具鏈分兩部分PC 端的 RKNN-Toolkit2 負責模型轉(zhuǎn)換和量化板端的 RKNPU2 運行時負責實際推理。PC 端建議用 Ubuntu 20.04 或 22.04Python 版本 3.8 到 3.10 之間。我一開始在 Windows 上試過工具鏈支持不完整量化功能經(jīng)常報錯后來換到 Ubuntu 才順利。安裝命令如下conda create -n rknn python3.9 conda activate rknn pip install rknn-toolkit21.6.0 pip install onnx1.14.0 pip install torch2.0.0 torchvision0.15.0這里有個坑要注意rknn-toolkit2 對 numpy 版本有要求如果環(huán)境里裝了 numpy 2.x 會報兼容性錯誤需要降到 1.24 以下。另外 onnx 版本也不能太新1.15 以上某些算子解析會出問題我實測 1.14.0 最穩(wěn)。板端環(huán)境相對簡單官方固件已經(jīng)帶了 RKNPU2 運行時只需要確認驅(qū)動版本cat /sys/kernel/debug/rknpu/version如果輸出類似RKNPU2: 0.8.2就說明驅(qū)動正常。如果版本太老建議刷最新固件老驅(qū)動對新算子的支持不全。3.2 模型導(dǎo)出與 ONNX 轉(zhuǎn)換PyTorch 模型轉(zhuǎn) ONNX 是第一步。以 ResNet18 為例import torch import torchvision.models as models model models.resnet18(pretrainedTrue) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, resnet18.onnx, input_names[input], output_names[output], opset_version11, dynamic_axesNone )opset_version 建議用 11這是 RKNN 工具鏈支持最完整的版本。用 12 或 13 有時會遇到算子映射問題。dynamic_axes 設(shè)為 None 是因為板端推理輸入尺寸固定動態(tài)軸反而會增加轉(zhuǎn)換復(fù)雜度。導(dǎo)出后建議用 onnxsim 做一次簡化去掉冗余算子pip install onnxsim onnxsim resnet18.onnx resnet18_sim.onnx這一步能減少后續(xù)量化時的算子數(shù)量提升轉(zhuǎn)換成功率。我測過幾個模型簡化后 ONNX 文件大小平均減少 15% 左右轉(zhuǎn)換時間也短一些。3.3 RKNN 模型轉(zhuǎn)換與量化配置轉(zhuǎn)換腳本的核心是量化配置。RKNN 支持兩種量化方式一是用校準數(shù)據(jù)集做 PTQ訓(xùn)練后量化二是加載量化感知訓(xùn)練后的模型。實際項目里 PTQ 用得最多因為不需要重新訓(xùn)練。from rknn.api import RKNN rknn RKNN(verboseTrue) rknn.config( mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrk3568, quantized_dtypeasymmetric_quantized-8, optimization_level3 ) rknn.load_onnx(modelresnet18_sim.onnx) rknn.build(do_quantizationTrue, datasetcalibration.txt) rknn.export_rknn(resnet18.rknn)這里的 mean 和 std 必須和訓(xùn)練時用的預(yù)處理參數(shù)一致否則精度會掉得莫名其妙。校準數(shù)據(jù)集準備 200 到 500 張圖片就夠從訓(xùn)練集里隨機采樣要覆蓋所有類別。我試過只用 50 張量化后精度掉了 3 個點加到 300 張后只掉 0.8 個點。optimization_level3是最高優(yōu)化級別會做一些算子融合和內(nèi)存優(yōu)化但偶爾會導(dǎo)致轉(zhuǎn)換失敗。如果遇到問題可以降到 2 試試。4. 各模型部署實操與精度速度實測4.1 經(jīng)典卷積網(wǎng)絡(luò)ResNet 系列ResNet18 和 ResNet50 的轉(zhuǎn)換過程最順利所有算子都被原生支持沒有遇到任何需要手工干預(yù)的地方。這得益于 ResNet 結(jié)構(gòu)規(guī)整主要是 Conv、BN、ReLU、MaxPool、GlobalAvgPool 和全連接這些都是 NPU 的標準算子。精度方面ResNet18 原始 PyTorch 模型 Top-1 是 69.8%轉(zhuǎn) ONNX 后 69.7%基本無損INT8 量化后 68.9%掉了 0.9 個點。ResNet50 原始 76.1%量化后 75.2%掉了 0.9 個點。這個損失幅度在可接受范圍內(nèi)說明 ResNet 系列對 INT8 量化不敏感。速度方面ResNet18 單幀推理 12.3msResNet50 是 31.7ms。ResNet18 能輕松跑到 30fps 以上ResNet50 剛好卡在 30fps 邊緣。如果對幀率要求高ResNet50 需要降輸入分辨率或者做模型剪枝。注意ResNet 的 BN 層在量化時會被融合進 Conv這是自動完成的不需要手動處理。但如果你的模型里有自定義 BN 變體可能需要檢查融合是否正確。4.2 輕量級網(wǎng)絡(luò)MobileNet 與 ShuffleNetMobileNetV2 的轉(zhuǎn)換也很順利深度可分離卷積在 RK3568 NPU 上有專門優(yōu)化。精度從原始的 71.9% 掉到 70.8%損失 1.1 個點。速度是亮點單幀只要 6.8ms能跑到 140fps 以上非常適合高幀率場景。MobileNetV3-Small 參數(shù)量更小但精度只有 67.4%量化后 66.2%。速度 4.2ms非???。這個模型適合對精度要求不高但幀率要求極高的場景比如簡單的有無檢測。ShuffleNetV2 遇到了點麻煩。通道混洗Channel Shuffle算子在 ONNX 里是用 Reshape Transpose Reshape 組合實現(xiàn)的RKNN 工具鏈在量化時對 Transpose 的處理有點問題第一次轉(zhuǎn)換報錯。解決辦法是在轉(zhuǎn)換前用 onnx-simplifier 把這三個算子融合成一個自定義算子或者手動改寫模型結(jié)構(gòu)用等效的 1x1 卷積替代。我用了前者融合后轉(zhuǎn)換成功。精度從 69.4% 掉到 67.8%損失 1.6 個點比 MobileNet 系列敏感一些。模型原始 Top-1量化后 Top-1精度損失單幀耗時幀率ResNet1869.8%68.9%-0.9%12.3ms81fpsResNet5076.1%75.2%-0.9%31.7ms31fpsMobileNetV271.9%70.8%-1.1%6.8ms147fpsMobileNetV3-Small67.4%66.2%-1.2%4.2ms238fpsShuffleNetV269.4%67.8%-1.6%5.9ms169fps4.3 復(fù)合縮放網(wǎng)絡(luò)EfficientNet-B0EfficientNet-B0 的轉(zhuǎn)換遇到了 Swish 激活函數(shù)的問題。Swish 是 x * sigmoid(x)ONNX 里拆成了 Sigmoid 和 Mul 兩個算子。RKNN 對 Sigmoid 的支持沒問題但量化時 Sigmoid 的輸出范圍不好確定導(dǎo)致精度損失偏大。第一次轉(zhuǎn)換后精度從 77.1% 掉到 73.5%損失 3.6 個點明顯比其他模型差。后來我查了 RKNN 的文檔發(fā)現(xiàn)可以在 config 里指定quantized_algorithmmmse用最小均方誤差算法做量化比默認的normal算法更適合有 Sigmoid 的網(wǎng)絡(luò)。改完后精度恢復(fù)到 75.8%損失 1.3 個點和其他模型差不多了。速度方面EfficientNet-B0 單幀 18.5ms約 54fps。這個速度中規(guī)中矩但考慮到它的精度比 ResNet18 高不少性價比還是不錯的。實操心得遇到精度損失異常大的模型先別急著換模型試試調(diào)整量化算法。mmse算法對包含 Sigmoid、Tanh 這類飽和激活函數(shù)的網(wǎng)絡(luò)效果明顯更好代價是量化時間會長一些。4.4 注意力機制ViT-B/16ViT 的部署是最折騰的。首先是模型太大86M 參數(shù)ONNX 文件 330MBRKNN 轉(zhuǎn)換時內(nèi)存占用很高我的 PC 有 16GB 內(nèi)存都差點不夠用。其次是結(jié)構(gòu)問題ViT 的核心是 Multi-Head Attention涉及大量的 MatMul 和 Softmax這些算子在 NPU 上的效率遠不如卷積。轉(zhuǎn)換過程倒沒有報錯但量化后精度從 81.8% 掉到 76.2%損失 5.6 個點是所有模型里最嚴重的。分析原因主要是 Attention 里的 Softmax 輸出分布比較集中INT8 量化后分辨率不夠?qū)е伦⒁饬?quán)重失真。速度更是慘不忍睹單幀 187ms只有 5fps 左右。這個速度基本沒法做實時推理只能用于離線批處理場景。模型原始 Top-1量化后 Top-1精度損失單幀耗時幀率EfficientNet-B077.1%75.8%-1.3%18.5ms54fpsViT-B/1681.8%76.2%-5.6%187ms5fpsViT 在 RK3568 上的表現(xiàn)說明Transformer 類模型目前還不適合這類邊緣 NPU。除非你有極致的精度需求且能接受離線推理否則還是老老實實用卷積網(wǎng)絡(luò)。5. 常見問題排查與避坑經(jīng)驗5.1 模型轉(zhuǎn)換失敗的高頻原因轉(zhuǎn)換失敗是最常見的問題我整理了一個排查表報錯信息原因解決辦法Unsupported op: XXX算子不支持用 onnx-simplifier 融合或改寫模型Quantize failed校準數(shù)據(jù)不足增加到 300 張以上覆蓋所有類別Out of memory模型太大增加 PC 內(nèi)存或分塊轉(zhuǎn)換Shape mismatch輸入尺寸不匹配檢查 config 里的輸入尺寸設(shè)置Mean/std error預(yù)處理參數(shù)錯誤核對訓(xùn)練時的歸一化參數(shù)其中算子不支持是最頭疼的。RKNN 1.6.0 支持的算子列表在官方文檔里有但實際轉(zhuǎn)換時經(jīng)常遇到列表里有但量化失敗的算子。我的經(jīng)驗是遇到問題先試 onnx-simplifier能解決 80% 的情況。剩下的 20% 需要手動改寫模型把不支持的算子用等效的支持算子替換。5.2 精度損失過大的調(diào)試思路精度損失超過 2 個點就需要排查了。我的調(diào)試順序是先確認 ONNX 模型本身的精度是否正常排除導(dǎo)出問題再檢查預(yù)處理參數(shù)是否一致然后嘗試不同的量化算法最后考慮增加校準數(shù)據(jù)量。有一個容易被忽略的點是校準數(shù)據(jù)的分布。如果你用 ImageNet 的圖片做校準但實際部署場景是工業(yè)相機拍的灰度圖量化后的精度在實際場景里會掉得很厲害。校準數(shù)據(jù)一定要貼近實際部署場景這是很多教程不會強調(diào)的。5.3 板端推理性能優(yōu)化技巧板端推理速度除了模型本身還受幾個因素影響。一是 CPU 頻率RK3568 默認是動態(tài)調(diào)頻推理時會自動升頻但如果散熱不好會降頻。建議加個散熱片實測能穩(wěn)定提升 10% 左右的幀率。二是內(nèi)存帶寬多個模型同時加載會爭搶帶寬建議串行推理。三是 NPU 核心數(shù)RK3568 只有一個 NPU 核心沒法像 RK3588 那樣多核并行。還有一個技巧是用 RKNN 的零拷貝接口避免數(shù)據(jù)在 CPU 和 NPU 之間來回拷貝。這個在官方例程里有示范能省 1 到 2ms 的拷貝時間對小模型來說占比不小。# 零拷貝推理示例 outputs rknn.inference(inputs[img], data_formatnhwc)用data_formatnhwc可以避免額外的格式轉(zhuǎn)換前提是你的輸入數(shù)據(jù)本來就是 NHWC 排列。5.4 模型選型的實戰(zhàn)建議根據(jù)這次實驗的數(shù)據(jù)我給幾個場景的選型建議。高幀率簡單場景選 MobileNetV3-Small4.2ms 的延遲基本沒有對手。精度和速度平衡選 MobileNetV2 或 EfficientNet-B0前者更快后者更準。需要高精度且能接受 30fps 選 ResNet50。ShuffleNetV2 部署麻煩除非有特殊需求否則不推薦。ViT 系列目前不建議在 RK3568 上做實時推理。另外提醒一點這些數(shù)據(jù)是在 224x224 輸入下測的。如果你把輸入分辨率提高到 320x320 或 416x416推理時間會按面積比例增加。比如 MobileNetV2 從 224 提到 320耗時大概從 6.8ms 漲到 13ms 左右。選型時要把分辨率因素考慮進去。6. 實驗數(shù)據(jù)匯總與橫向?qū)Ρ劝阉袛?shù)據(jù)匯總到一張表里方便橫向?qū)Ρ饶P蛥?shù)量原始 Top-1量化 Top-1精度損失耗時幀率部署難度MobileNetV3-Small2.5M67.4%66.2%-1.2%4.2ms238fps低ShuffleNetV22.3M69.4%67.8%-1.6%5.9ms169fps中MobileNetV23.4M71.9%70.8%-1.1%6.8ms147fps低ResNet1811.7M69.8%68.9%-0.9%12.3ms81fps低EfficientNet-B05.3M77.1%75.8%-1.3%18.5ms54fps中ResNet5025.6M76.1%75.2%-0.9%31.7ms31fps低ViT-B/1686M81.8%76.2%-5.6%187ms5fps高從這張表能看出幾個規(guī)律。輕量級網(wǎng)絡(luò)的精度損失普遍比大模型略高因為它們的特征表達能力本來就弱量化進一步壓縮了信息量。ResNet 系列對量化最魯棒損失都在 1 個點以內(nèi)。EfficientNet 需要調(diào)量化算法才能達到同等水平。ViT 是特例精度損失和速度都不理想。性價比最高的幾個選擇追求速度選 MobileNetV3-Small追求平衡選 MobileNetV2 或 EfficientNet-B0追求精度選 ResNet50。ResNet18 有點尷尬精度不如 EfficientNet-B0速度不如 MobileNetV2除非你的項目已經(jīng)在用 ResNet18 且不想換模型否則新項目不太推薦。7. 一些補充的實操細節(jié)校準數(shù)據(jù)集的制作有個小技巧。不要直接把圖片路徑寫進 txt 文件而是先用腳本把圖片統(tǒng)一 resize 到模型輸入尺寸并保存這樣轉(zhuǎn)換時不用反復(fù)解碼速度快很多。我一開始用原始圖片300 張校準花了 8 分鐘預(yù)處理后只要 2 分鐘。import cv2 import os for img_name in os.listdir(calib_images): img cv2.imread(fcalib_images/{img_name}) img cv2.resize(img, (224, 224)) cv2.imwrite(fcalib_resized/{img_name}, img)板端部署時建議把 RKNN 模型文件放在 eMMC 而不是 SD 卡上加載速度快 3 倍左右。RK3568 的 eMMC 讀取速度能到 200MB/s 以上SD 卡通常只有 40MB/s。模型文件雖然不大但加載時間也影響啟動速度。還有一點是關(guān)于多線程的。RK3568 的 NPU 是單核的多個線程同時調(diào)用 NPU 不會提升吞吐量反而會因為上下文切換增加延遲。正確的做法是用一個推理線程加一個預(yù)處理線程預(yù)處理和推理流水線化這樣能提升整體吞吐。我實測這種流水線方式比單線程串行快 20% 左右。最后說下溫度問題。RK3568 滿載推理時芯片溫度能到 70 度以上如果不加散熱會觸發(fā)降頻幀率掉 30% 都有可能。我加了一個 20x20mm 的鋁散熱片溫度穩(wěn)定在 55 度左右?guī)什▌雍苄?。這個成本不到兩塊錢但效果立竿見影強烈建議加上。關(guān)于模型量化后的精度驗證不要只看整體 Top-1還要看混淆矩陣。有些模型整體精度掉得不多但某些類別掉得很厲害如果這些類別恰好是你關(guān)心的那整體指標就有誤導(dǎo)性。我在測試時發(fā)現(xiàn) MobileNetV3-Small 在細粒度類別比如不同品種的狗上掉點明顯比粗粒度類別多如果你的應(yīng)用場景是細粒度分類選型時要特別注意。