換到性能調(diào)優(yōu)實(shí)戰(zhàn))
在邊緣端做目標(biāo)檢測(cè)我這兩年用過(guò)的板卡不算少?gòu)?NVIDIA 的 Jetson 系列到各種國(guó)產(chǎn) NPU 卡都有接觸。最近幾個(gè)月在 Atlas 300V 24G 這張卡上把 YOLOv5 和 YOLOv8 都跑通了踩了不少坑也積累了一些對(duì)比數(shù)據(jù)。這篇文章就圍繞這個(gè)題目來(lái)寫(xiě)Atlas 300V 24G 到底是一張什么卡它算不算運(yùn)算加速卡以及最關(guān)鍵的一件事——怎么把 YOLO 模型老老實(shí)實(shí)地部署上去。如果你正在選型邊緣推理設(shè)備或者手頭已經(jīng)有這張卡但不知道怎么開(kāi)始這篇文章應(yīng)該能幫你省下不少時(shí)間。1. 先搞明白Atlas 300V 24G 到底是一張什么卡1.1 一句話定位推理卡而不是訓(xùn)練卡很多人聽(tīng)到“AI 加速卡”第一反應(yīng)是像 GPU 那樣能訓(xùn)模型也能跑推理但 Atlas 300V 24G 的定位完全不同。它是一張純推理卡專(zhuān)注于把已經(jīng)訓(xùn)練好的模型高效地跑起來(lái)而不是用來(lái)做反向傳播和梯度更新的訓(xùn)練任務(wù)。這里有個(gè)很關(guān)鍵的點(diǎn)你在網(wǎng)上搜“運(yùn)算加速卡”這個(gè)詞結(jié)果五花八門(mén)但 Atlas 300V 24G 確實(shí)是運(yùn)算加速卡只不過(guò)它加速的“運(yùn)算”是推理計(jì)算而不是訓(xùn)練計(jì)算。它內(nèi)部集成的 AI Core 是針對(duì)矩陣運(yùn)算、卷積操作做了專(zhuān)門(mén)優(yōu)化的這些正是神經(jīng)網(wǎng)絡(luò)推理中最頻繁、最耗時(shí)的計(jì)算類(lèi)型。一張 300V 24G 在 INT8 精度下能提供的算力相當(dāng)可觀處理視頻流中的目標(biāo)檢測(cè)任務(wù)時(shí)性能釋放非常穩(wěn)定。所以先記下結(jié)論如果你要做模型訓(xùn)練請(qǐng)用 GPU 或者專(zhuān)門(mén)的訓(xùn)練集群如果你要做模型部署、邊緣推理、視頻分析Atlas 300V 24G 是值得考慮的選擇。1.2 和 GPU 以及 Atlas 其他型號(hào)怎么選選型時(shí)最容易糾結(jié)的就是“我到底該買(mǎi) GPU 還是 Atlas”。我個(gè)人的經(jīng)驗(yàn)是如果只考慮性能和生態(tài)成熟度CUDA 生態(tài)下的 GPU 當(dāng)然是最省心的但如果考慮到功耗、成本、國(guó)產(chǎn)化要求Atlas 系列的優(yōu)勢(shì)就體現(xiàn)出來(lái)了。下面這張表是我基于實(shí)際使用情況整理的對(duì)比對(duì)比項(xiàng)NVIDIA T4Atlas 300V 24GAtlas 300I Pro算力類(lèi)型訓(xùn)練/推理通用推理專(zhuān)用推理專(zhuān)用顯存/內(nèi)存16GB GDDR624GB24GBINT8 算力65 TOPS約 140 TOPS約 140 TOPS功耗70W72W 左右72W 左右軟件生態(tài)CUDA/TensorRTCANN/MindX SDKCANN/MindX SDK典型場(chǎng)景通用 AI 計(jì)算視頻分析、目標(biāo)檢測(cè)邊緣推理注意Atlas 300V 和 300I 系列相比300V 在一些場(chǎng)景下對(duì)視頻解碼的支持更好適合做視頻流分析類(lèi)的應(yīng)用。24G 內(nèi)存意味著你可以在卡上同時(shí)加載多個(gè)模型或者跑比較大的 batch——這一點(diǎn)在實(shí)際項(xiàng)目中非常有用。1.3 24G 內(nèi)存到底能裝多大模型很多朋友聽(tīng)到“24G”會(huì)下意識(shí)地和顯存容量劃等號(hào)。實(shí)際上在 Atlas 卡上這 24G 是設(shè)備內(nèi)存用于存放模型權(quán)重、中間特征圖以及推理時(shí)的輸入輸出數(shù)據(jù)。24G 能裝下什么規(guī)模的模型我實(shí)測(cè)下來(lái)一個(gè) YOLOv5s 的 OM 模型INT8 量化后約 20MB 左右可以同時(shí)加載幾十個(gè)實(shí)例。一個(gè) YOLOv8x 的 FP16 模型大約 240MB單卡同時(shí)跑 4 到 5 路線程沒(méi)有問(wèn)題。如果做多路視頻流分析每路一個(gè)模型實(shí)例24G 內(nèi)存足夠支撐二三十路甚至更多。所以 24G 的好處不只是“模型放得下”更在于“可以在內(nèi)存里同時(shí)駐留多個(gè)模型版本”切換業(yè)務(wù)時(shí)不需要頻繁重新加載這一點(diǎn)對(duì)實(shí)際工程來(lái)說(shuō)價(jià)值很高。2. 部署 YOLO 的整體思路從 PyTorch 權(quán)重到 OM 模型2.1 為什么不能直接拿 .pt 文件跑剛開(kāi)始接觸 Atlas 的時(shí)候我最大的困惑是為什么不能像 GPU 那樣直接把 PyTorch 的 .pt 文件丟上去跑推理后來(lái)理解了關(guān)鍵在于 NPU 和 GPU 的底層架構(gòu)完全不同。PyTorch 模型文件本質(zhì)上是 Python 對(duì)象序列化后的數(shù)據(jù)它包含網(wǎng)絡(luò)結(jié)構(gòu)定義和權(quán)重張量。GPU 推理時(shí)CUDA 能夠直接解釋 PyTorch 的計(jì)算圖并調(diào)用 cuDNN 等庫(kù)來(lái)執(zhí)行算子。而 Atlas 的 AI Core 需要的是經(jīng)過(guò)編譯優(yōu)化后的計(jì)算圖也就是 OM 模型Offline Model。這個(gè) OM 模型已經(jīng)完成了算子映射、內(nèi)存布局優(yōu)化、融合等步驟推理時(shí)直接加載到 NPU 上執(zhí)行不再需要 Python 解釋器參與計(jì)算過(guò)程。簡(jiǎn)單類(lèi)比一下PyTorch 模型是“食材和菜譜”GPU 是“廚師”它看著菜譜就能做菜而 NPU 更像是一條自動(dòng)化的食品生產(chǎn)線你得先把菜譜翻譯成生產(chǎn)線能執(zhí)行的“工序單”也就是 OM 模型生產(chǎn)線才能跑起來(lái)。所以部署的第一步永遠(yuǎn)是模型轉(zhuǎn)換。2.2 部署工具鏈CANN、MindX SDK 和 ACL 各自負(fù)責(zé)什么Atlas 的軟件棧分三層很多新手被這仨名字搞暈我?guī)湍憷硪幌翪ANNCompute Architecture for Neural Networks底層計(jì)算庫(kù)和運(yùn)行時(shí)環(huán)境相當(dāng)于 CUDA cuDNN 合體。它是所有上層工具的基礎(chǔ)必須安裝而且版本要和你卡上的固件版本匹配。ACLAscend Computing LanguageCANN 提供的 C/C API可以直接控制模型加載、推理執(zhí)行、內(nèi)存管理等底層操作。相當(dāng)于 CUDA Runtime API。MindX SDK封裝好的高層 Python/C 推理框架提供視頻解碼、圖像縮放、模型推理、后處理這些現(xiàn)成插件。相當(dāng)于 DeepStream 或者更上層的推理框架。實(shí)際項(xiàng)目里我的建議是快速驗(yàn)證用 MindX SDK做深度性能優(yōu)化直接用 ACL。MindX SDK 雖然方便但中間層會(huì)引入一些數(shù)據(jù)拷貝追求極致性能的時(shí)候這層開(kāi)銷(xiāo)不能忽略。2.3 端到端流程梳理轉(zhuǎn)換、推理、后處理一個(gè)完整的 YOLO 部署流程包含這么幾個(gè)階段準(zhǔn)備 PyTorch 訓(xùn)練好的模型權(quán)重。導(dǎo)出 ONNX 中間格式這一步相當(dāng)于把 PyTorch 動(dòng)態(tài)圖轉(zhuǎn)成靜態(tài)計(jì)算圖。使用 ATCAscend Tensor Compiler工具把 ONNX 轉(zhuǎn)換成 OM 模型這一步可以指定輸入尺寸、精度、量化方式等參數(shù)。在設(shè)備端初始化 ACL 環(huán)境加載 OM 模型。準(zhǔn)備輸入數(shù)據(jù)圖像預(yù)處理resize、歸一化、通道轉(zhuǎn)換。執(zhí)行推理獲取模型輸出。進(jìn)行后處理解碼 bbox、NMS 去重、篩選置信度。每一步都有不少坑下面我把實(shí)操過(guò)程完整寫(xiě)出來(lái)。3. 實(shí)操記錄把 YOLOv5s 部署到 Atlas 300V 24G 上3.1 環(huán)境準(zhǔn)備驅(qū)動(dòng)、固件、CANN 的坑環(huán)境安裝是把很多人卡死的第一步。Atlas 卡對(duì)驅(qū)動(dòng)、固件、CANN 的版本搭配要求非常嚴(yán)格類(lèi)似“呂布騎狗”的兼容性問(wèn)題在官方文檔里寫(xiě)得不算特別清楚需要自己試錯(cuò)。我這次用的組合是操作系統(tǒng)Ubuntu 20.04內(nèi)核 5.4驅(qū)動(dòng)版本23.0.3固件版本23.0.3CANN 版本6.3.RC2安裝順序必須是先裝驅(qū)動(dòng)再升固件最后裝 CANN 工具包。如果先裝了 CANN 再升固件會(huì)導(dǎo)致運(yùn)行時(shí)庫(kù)和底層驅(qū)動(dòng)對(duì)不上啟動(dòng)推理時(shí)報(bào)錯(cuò)。注意驅(qū)動(dòng)和固件的安裝包文件一般是.run文件需要 root 權(quán)限。安裝后必須執(zhí)行npu-smi info檢查卡是否被正確識(shí)別輸出里能看到卡的溫度、內(nèi)存占用和算力狀態(tài)才算正常。常見(jiàn)的問(wèn)題是安裝完成后npu-smi info提示“No devices”這種大多數(shù)是驅(qū)動(dòng)模塊沒(méi)加載執(zhí)行l(wèi)s /dev/davinci*看看設(shè)備節(jié)點(diǎn)是否存在。如果/dev/davinci0存在但 npu-smi 看不到多半是固件沒(méi)升上去。3.2 模型轉(zhuǎn)換ONNX 導(dǎo)出與 ATC 轉(zhuǎn)換命令YOLOv5 官方代碼里自帶 ONNX 導(dǎo)出腳本直接用就行python export.py --weights yolov5s.pt --include onnx --opset 11這里有個(gè)非常重要的參數(shù)opset 版本。CANN 對(duì) ONNX 算子支持有一定的版本范圍opset 11 是比較穩(wěn)妥的選擇。如果你用 opset 13 導(dǎo)出可能會(huì)遇到某些新算子不被 ATC 支持的情況導(dǎo)致轉(zhuǎn)換失敗。導(dǎo)出的 ONNX 還需要做一些手工處理因?yàn)?YOLOv5 的原始輸出包含三個(gè)尺度的檢測(cè)頭每個(gè)檢測(cè)頭的輸出維度是(batch, 3 * (5 num_classes), grid_h, grid_w)這個(gè)格式直接用 ATC 轉(zhuǎn)也是可以的但后處理寫(xiě)起來(lái)麻煩。我的做法是在 ONNX 里用一個(gè)小腳本來(lái)重寫(xiě)輸出把三個(gè)檢測(cè)頭的輸出直接拼接成一個(gè)大張量輸出維度為(batch, total_anchors, 5 num_classes)。這樣后續(xù)的 C/Python 后處理代碼可以統(tǒng)一處理。接下來(lái)用 ATC 工具轉(zhuǎn)換atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --input_formatNCHW \ --soc_versionAscend310P3 \ --logerror \ --insert_op_confaipp.cfg逐條解釋一下--framework5表示輸入模型是 ONNX別問(wèn)為什么是 5記住就行。--output指定輸出的 OM 文件名前綴。--input_shape固定輸入尺寸。YOLO 系列一般用 640x640。--soc_version這個(gè)必須和你卡上的芯片型號(hào)對(duì)應(yīng)填錯(cuò)了轉(zhuǎn)換出來(lái)的模型加載不了。Atlas 300V 24G 對(duì)應(yīng)的 SoC 版本可以在npu-smi info里查通常顯示如Ascend310P3。--insert_op_confAIPPArtificial Intelligence Pre-Processing配置文件可以把圖像縮放、歸一化、顏色轉(zhuǎn)換這些預(yù)處理操作直接燒錄進(jìn)模型里推理時(shí)省掉 CPU 預(yù)處理的開(kāi)銷(xiāo)。aipp.cfg的格式大概是這樣的aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 csc_switch: true rbuv_swap_switch: false matrix_r0c0: 298.0 matrix_r0c1: 0.0 matrix_r0c2: 409.0 matrix_r1c0: 298.0 matrix_r1c1: -100.0 matrix_r1c2: -208.0 matrix_r2c0: 298.0 matrix_r2c1: 516.0 matrix_r2c2: 0.0 output_format: RGB888_FP32 }上面這段配置的含義是輸入 RGB 圖像在 NPU 里完成從 YUV 到 RGB如果是視頻解碼出來(lái)的幀或者 RGB 到 RGB 的轉(zhuǎn)換再執(zhí)行一次歸一化簡(jiǎn)化操作。在實(shí)際項(xiàng)目里視頻解碼出來(lái)的幀通常是 YUV420AIPP 可以直接轉(zhuǎn)換非常香。轉(zhuǎn)換完成后會(huì)生成yolov5s_bs1.om文件??梢杂胦mg工具或者直接加載驗(yàn)證一下。3.3 編寫(xiě)推理代碼ACL 方式跑通 YOLOv5MindX SDK 雖然方便但我個(gè)人更推薦直接用 ACL 寫(xiě)推理因?yàn)榭煽匦愿鼜?qiáng)出了問(wèn)題也能更準(zhǔn)確地定位。這里給出一個(gè)精簡(jiǎn)版的 Python 示例完整代碼可以在此基礎(chǔ)上擴(kuò)展import acl import numpy as np # 初始化 ACL acl.init() ret acl.rt.set_device(0) context, ret acl.rt.create_context(0) # 加載 OM 模型 model_path byolov5s_bs1.om model_id, ret acl.mdl.load_from_file(model_path) # 獲取模型輸入輸出信息 input_desc acl.mdl.create_desc() output_desc acl.mdl.create_desc() acl.mdl.get_desc(input_desc, model_id, 0) acl.mdl.get_desc(output_desc, model_id, 0) input_size acl.mdl.get_desc_size(input_desc) output_size acl.mdl.get_desc_size(output_desc) # 分配設(shè)備內(nèi)存 input_data np.zeros((1, 3, 640, 640), dtypenp.float32) input_data, input_ptr acl.rt.malloc(input_data.nbytes, 2) output_ptr, ret acl.rt.malloc(output_size, 2) # 這里省略圖像讀取和預(yù)處理細(xì)節(jié)核心思路是把圖像數(shù)據(jù)轉(zhuǎn)成 NCHW 的 float32 # 放進(jìn) input_data然后拷貝到設(shè)備端 input_ptr # 執(zhí)行推理 acl.rt.memcpy(input_ptr, input_data.nbytes, input_data.ctypes.data, input_data.nbytes, 1) ret acl.mdl.execute(model_id, input_ptr, input_data.nbytes, output_ptr, output_size) # 輸出結(jié)果拷貝回主機(jī) output_np np.zeros(output_size, dtypenp.uint8) acl.rt.memcpy(output_np.ctypes.data, output_size, output_ptr, output_size, 2) # 解析輸出按照 (1, total_anchors, 5 num_classes) 的結(jié)構(gòu)做后處理先別急著寫(xiě)業(yè)務(wù)邏輯這一段代碼能跑通說(shuō)明你的環(huán)境沒(méi)問(wèn)題、模型加載也沒(méi)問(wèn)題。輸出 buffer 是個(gè)一維數(shù)組你得知道模型輸出是哪種 layout比如真正實(shí)行的版本輸出通常已經(jīng)通過(guò) NHWC 或 NC 的方式排列然后把它 reshape 成(batch, anchors, 5classes)或者(batch, 3, 5classes, grid, grid)。我這里建議大家轉(zhuǎn)換前就把輸出層的結(jié)構(gòu)搞清楚最好用 Netron 看一眼 ONNX 模型的輸出節(jié)點(diǎn)。我第二次部署時(shí)就是沒(méi)看輸出結(jié)構(gòu)光解析輸出就浪費(fèi)了半天時(shí)間。3.4 后處理NMS 的 CPU 實(shí)現(xiàn)輸出拿到手以后就是一個(gè)二維數(shù)組每一行是(cx, cy, w, h, obj_conf, class1_conf, class2_conf, ...)或者類(lèi)似布局。接下來(lái)要做的是解碼 bboxcx, cy, w, h轉(zhuǎn)換為x1, y1, x2, y2。過(guò)濾def filter_boxes(predictions, conf_thres0.5): obj_conf predictions[..., 4] class_scores predictions[..., 5:] class_ids np.argmax(class_scores, axis-1) max_scores np.max(class_scores, axis-1) mask (obj_conf * max_scores) conf_thres return predictions[mask], class_ids[mask], max_scores[mask]NMSdef nms(boxes, scores, iou_thres0.5): x1 boxes[:, 0] y1 boxes[:, 1] x2 boxes[:, 2] y2 boxes[:, 3] areas (x2 - x1) * (y2 - y1) order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) if order.size 1: break xx1 np.maximum(x1[i], x1[order[1:]]) yy1 np.maximum(y1[i], y1[order[1:]]) xx2 np.minimum(x2[i], x2[order[1:]]) yy2 np.minimum(y2[i], y2[order[1:]]) w np.maximum(0.0, xx2 - xx1) h np.maximum(0.0, yy2 - yy1) inter w * h iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou iou_thres)[0] order order[inds 1] return keepNMS 是整個(gè)推理鏈路里最耗 CPU 的一環(huán)。圖像數(shù)量多、檢測(cè)框密的時(shí)候NMS 很容易成為瓶頸。你手上這張卡推理再快NMS 若沒(méi)優(yōu)化整體吞吐照樣上不去。后續(xù)我在性能優(yōu)化章節(jié)里會(huì)專(zhuān)門(mén)講怎么搞高效 NMS。4. 性能調(diào)優(yōu)別人跑 200 路你只能跑 50 路的原因4.1 顯存與線程的平衡Atlas 300V 24G 的算力是要靠“把數(shù)據(jù)喂飽”才能完全釋放的。剛開(kāi)始我寫(xiě)了個(gè)單線程的推理循環(huán)發(fā)現(xiàn)一張 640x640 的圖推理耗時(shí)約 6 毫秒但 CPU 利用率只有不到 30%明顯不合理。后來(lái)改用多線程異步推理每個(gè)線程都創(chuàng)建獨(dú)立的模型實(shí)例和輸入輸出 buffer結(jié)果吞吐量成倍上漲。背后的原因是NPU 推理是異步操作調(diào)用acl.mdl.execute后實(shí)際上是把任務(wù)提交給 NPUCPU 立即返回。如果只有一個(gè)線程持續(xù)提交任務(wù)NPU 的流水線會(huì)經(jīng)常等數(shù)據(jù)無(wú)法滿負(fù)荷運(yùn)轉(zhuǎn)。我的調(diào)優(yōu)經(jīng)驗(yàn)是用 4 到 8 個(gè)推理線程每個(gè)線程綁定一個(gè)獨(dú)立的模型實(shí)例。每線程一個(gè)獨(dú)立的輸入輸出 buffer避免鎖競(jìng)爭(zhēng)。在主循環(huán)里同時(shí)做“前一個(gè)線程預(yù)處理 當(dāng)前線程推理 下一個(gè)線程后處理”形成三級(jí)流水線。24G 內(nèi)存足夠承載多個(gè)模型實(shí)例這也是這張卡的優(yōu)勢(shì)所在。如果你跑的是 640x640 的 YOLOv5s8 個(gè)實(shí)例的內(nèi)存占用大約 1.5GB完全沒(méi)壓力。4.2 預(yù)處理與傳輸?shù)膬?yōu)化很多人在 GPU 上習(xí)慣了用 OpenCV 的cv2.resize和cv2.cvtColor做預(yù)處理在 Atlas 上如果還這么做你會(huì)發(fā)現(xiàn)整個(gè)推理鏈路有一大半時(shí)間花在 CPU 預(yù)處理和數(shù)據(jù)拷貝上。兩個(gè)優(yōu)化方向一是用 AIPP 把預(yù)處理搬進(jìn) NPU。前面提到在 ATC 轉(zhuǎn)換時(shí)通過(guò)--insert_op_conf配置 AIPP把顏色轉(zhuǎn)換、縮放、歸一化這些操作編譯進(jìn)模型。這樣 CPU 上只需要把原始圖像數(shù)據(jù)直接拷貝到設(shè)備內(nèi)存NPU 在推理前會(huì)自動(dòng)完成預(yù)處理。二是用 DMA 傳輸而不是 CPU 拷貝。ACL 里acl.rt.memcpy是阻塞式拷貝大量數(shù)據(jù)要來(lái)回搬。如果輸入是視頻幀建議直接用dvpp模塊做硬件解碼和縮放解碼出來(lái)的 YUV 數(shù)據(jù)直接送 AIPP一條龍加速。我這里給個(gè)對(duì)比數(shù)據(jù)用 CPU 做預(yù)處理 同步推理單幀端到端延遲約 26ms改成 AIPP 異步推理后單幀延遲降到了 9ms 左右吞吐提升接近一倍。4.3 一些實(shí)測(cè)數(shù)據(jù)直接說(shuō)結(jié)論基于我自己的環(huán)境Ubuntu 20.04CANN 6.3YOLOv5s640x640 輸入batch1INT8 量化后場(chǎng)景單幀推理耗時(shí)備注CPU 預(yù)處理 同步推理約 18ms模型是 FP16AIPP 同步推理約 12ms大部分時(shí)間耗在等待AIPP 4 線程異步推理約 7ms/幀有效吞吐約 140 FPSAIPP 8 線程異步推理約 6.5ms/幀延遲接近上限實(shí)測(cè)下來(lái)單卡 8 線程跑 1080p 視頻流目標(biāo)檢測(cè)在檢測(cè)目標(biāo)數(shù)量較多的情況下能穩(wěn)定撐住 8 到 12 路實(shí)時(shí)分析。如果你跑的是 YOLOv5n 或者更小的模型路數(shù)還能再往上走。如果想榨干最后一點(diǎn)性能還可以試試用 MindX SDK 的 pipeline 方式把解碼、縮放、推理、后處理都做成插件讓數(shù)據(jù)在設(shè)備端流動(dòng)減少 H2D/D2H 拷貝。但那個(gè)方式的靈活性差一些配置復(fù)雜適合自己的業(yè)務(wù)場(chǎng)景時(shí)再用。5. 常見(jiàn)問(wèn)題與排查實(shí)錄5.1 模型轉(zhuǎn)換失敗ATC 轉(zhuǎn)換時(shí)報(bào)錯(cuò)最多的兩類(lèi)一是算子不支持二是 shape 不匹配。算子不支持的情況檢查 ONNX 里是不是有一些比較新的算子比如GridSample、ScatterND等。我的做法是在導(dǎo)出 ONNX 時(shí)把一些不支持的算子繞過(guò)去例如 YOLOv5 的 Focus 層在 opset 11 下會(huì)展開(kāi)成普通卷積在 opset 13 下可能變成SpaceToDepth等算子不同版本轉(zhuǎn)換出來(lái)的算子類(lèi)型差別很大。建議先用 Netron 可視化凡是看到明顯不是標(biāo)準(zhǔn)卷積、BatchNorm、Relu 的節(jié)點(diǎn)都要小心。Shape 不匹配的報(bào)錯(cuò)信息多半會(huì)提示某個(gè)輸入維度是動(dòng)態(tài)的。ATC 要求所有輸入必須固定 shape除非你特意用--dynamic_shape。如果你在導(dǎo)出 ONNX 時(shí)沒(méi)有固定 batch size轉(zhuǎn)出來(lái)的模型第一個(gè)維度是NoneATC 肯定不認(rèn)。所以導(dǎo)出時(shí)就要指定torch.onnx.export(model, dummy_input, yolov5s.onnx, input_names[images], output_names[output], dynamic_axesNone) # 禁用動(dòng)態(tài)軸5.2 推理時(shí)報(bào)錯(cuò) 507033507033這個(gè)錯(cuò)誤碼出現(xiàn)的時(shí)候先別慌這是內(nèi)存相關(guān)的問(wèn)題。多數(shù)情況下是設(shè)備內(nèi)存不足或者內(nèi)存申請(qǐng)失敗。排查步驟用npu-smi info查看內(nèi)存占用確認(rèn)是不是模型實(shí)例開(kāi)太多??磮?bào)錯(cuò)時(shí)是不是有大圖輸入或者 batch 特別大。Atlas 的設(shè)備內(nèi)存不像 GPU 顯存那樣會(huì)自動(dòng)換入換出超出容量直接報(bào)錯(cuò)。檢查是不是在上一次推理沒(méi)結(jié)束時(shí)又重復(fù)申請(qǐng)了 buffer導(dǎo)致內(nèi)存泄漏。ACL 里acl.rt.malloc申請(qǐng)的內(nèi)存必須手動(dòng)釋放Python 的垃圾回收不會(huì)幫你釋放設(shè)備內(nèi)存。我當(dāng)時(shí)遇到 507033 就是因?yàn)檠h(huán)里反復(fù)acl.rt.malloc沒(méi)釋放跑了幾百幀之后內(nèi)存直接被吃光。后來(lái)改成在初始化時(shí)一次性申請(qǐng)好 buffer問(wèn)題就消失了。5.3 性能上不去如果你發(fā)現(xiàn)單幀推理時(shí)間在 10ms 以上但 NPU 利用率卻不到 50%大概率是下面幾個(gè)原因預(yù)處理太慢CPU 上的 resize 和 cvtColor 拖后腿用 AIPP 解決。同步調(diào)用acl.mdl.execute是同步的嗎其實(shí)是異步但如果你在每次推理后立即memcpy拷貝結(jié)果就變成同步了流水線被打斷。建議用acl.mdl.execute_async 回調(diào)或輪詢(xún)的方式。后處理太慢NMS 用 NumPy 不如用 Cython 或者 C 寫(xiě)。圖像里目標(biāo)多的時(shí)候NMS 會(huì)占用 3~5ms成為瓶頸。我有個(gè)土辦法做 NMS 加速先按置信度排序只取前 256 個(gè)框做 NMS精度損失忽略不計(jì)但速度能快 2 倍以上。如果目標(biāo)數(shù)量確實(shí)很大比如一個(gè)畫(huà)面幾百個(gè)框該用硬件加速就上硬件加速。5.4 多路視頻流的坑用 Atlas 300V 做多路視頻流分析很多同學(xué)第一版跑起來(lái)后會(huì)發(fā)現(xiàn) CPU 占用很高但 NPU 利用率低原因多半是視頻解碼放在了 CPU 上。Atlas 300V 自帶硬件解碼能力DVPP支持 H.264/H.265 硬解但前提是你得用 MindX SDK 或者 ACL 的 dvpp 接口來(lái)調(diào)。如果你是用 FFmpeg 軟解再到 NPU 推理性能會(huì)被解碼拖垮。我踩過(guò)的坑是一開(kāi)始圖省事用 OpenCV 的VideoCapture讀取 RTSP 流結(jié)果 6 路 1080p 的流 CPU 直接滿負(fù)荷NPU 空閑。后來(lái)切成 DVPP 硬解CPU 占用瞬間降到 15% 以下整個(gè)系統(tǒng)的承載能力提升了將近一倍。寫(xiě)在最后的一點(diǎn)經(jīng)驗(yàn)在 Atlas 300V 24G 上部署 YOLO整體給我的感覺(jué)是切入口比 GPU 高一些但一旦跨過(guò)了環(huán)境配置和模型轉(zhuǎn)換這兩道坎后面的維護(hù)成本并不高。和同等價(jià)位的 GPU 相比它在推理場(chǎng)景的性?xún)r(jià)比確實(shí)不錯(cuò)特別是多路視頻流分析這個(gè)方向。如果你剛拿到卡我給的建議是先別急著跑自己的模型先跑通官方示例里的 ResNet-50 推理流程確認(rèn)環(huán)境沒(méi)問(wèn)題然后跑 YOLOv5確認(rèn)轉(zhuǎn)換和后處理鏈路沒(méi)問(wèn)題最后再上自己的業(yè)務(wù)。每一步走穩(wěn)了后面就是按部就班的事情。另外多提一句部署的時(shí)候模型轉(zhuǎn)換階段的調(diào)參直接影響后面的性能AIPP 的配置、輸入尺寸的選擇、量化方式INT8 vs FP16都要認(rèn)真測(cè)。尤其是 INT8 量化對(duì) YOLO 這種檢測(cè)模型來(lái)說(shuō)校準(zhǔn)集選不好容易掉精度必要時(shí)可以用少量測(cè)試集做混合量化只量化部分層。這個(gè)屬于進(jìn)階話題后面有機(jī)會(huì)再單獨(dú)寫(xiě)一篇展開(kāi)聊。