戰(zhàn):從模型轉(zhuǎn)換到推理優(yōu)化)
去年快年底的時(shí)候一個(gè)朋友找我?guī)兔匆粡埧?。他手里拿著一張半高半長(zhǎng)的PCIe加速卡問(wèn)我這玩意兒是不是跟游戲顯卡一樣插上就能跑YOLO能不能直接當(dāng)顯卡用我一看板卡上印著“Atlas 300V 24G”我說(shuō)這卡不是干那個(gè)的這是華為昇騰的AI推理加速卡專門(mén)給服務(wù)器做深度學(xué)習(xí)推理用的不是圖形卡。其實(shí)最近問(wèn)Atlas的人不少尤其是做邊緣計(jì)算、智慧園區(qū)、工業(yè)質(zhì)檢、安防監(jiān)控這塊的。大家最關(guān)心的幾個(gè)問(wèn)題出奇一致Atlas 300V 24G是不是運(yùn)算加速卡是而且是純推理加速卡、怎么用它部署YOLO需要經(jīng)過(guò)模型轉(zhuǎn)換不是直接跑PyTorch、性能和成本到底劃不劃算。這篇文章我就圍繞這三個(gè)問(wèn)題展開(kāi)把我自己實(shí)際部署YOLOv5/YOLOv8的經(jīng)驗(yàn)、踩過(guò)的坑、調(diào)優(yōu)的思路都寫(xiě)出來(lái)。如果你是做AI應(yīng)用落地的工程師、算法工程師或者正在給項(xiàng)目選型推理硬件這篇文章應(yīng)該能幫你省不少時(shí)間。我不會(huì)只貼命令我會(huì)把“為什么這么做”講清楚這樣你換一張卡、換個(gè)模型也能舉一反三。1. 整體設(shè)計(jì)思路與硬件選型分析1.1 Atlas 300V 24G的準(zhǔn)確定位它不是顯卡是推理卡先說(shuō)結(jié)論Atlas 300V 24G是一張AI推理加速卡基于昇騰310P芯片24GB顯存實(shí)際上是LPDDR4X支持FP16和INT8推理。它和游戲顯卡、專業(yè)圖形卡最大的區(qū)別是它沒(méi)有圖形輸出接口不能接顯示器也不能做3D渲染它的全部設(shè)計(jì)目標(biāo)就是“跑神經(jīng)網(wǎng)絡(luò)推理”。很多人一聽(tīng)到“加速卡”三個(gè)字第一反應(yīng)是“那我訓(xùn)練模型是不是也能用”。嚴(yán)格來(lái)說(shuō)Atlas 300V這塊卡不適合做訓(xùn)練它沒(méi)有訓(xùn)練所需的高精度算力和完整的訓(xùn)練棧支持。昇騰的訓(xùn)練卡是Atlas 800系列訓(xùn)練服務(wù)器或者Atlas 900集群300V 24G的定位非常清晰把已經(jīng)訓(xùn)練好的模型如YOLOv5、YOLOv8、ResNet、BERT等高效地跑起來(lái)在盡可能低的功耗和成本下獲得最高的推理吞吐量。我自己選這張卡的核心原因有三個(gè)。第一是功耗單卡典型功耗72W左右不需要外接供電部分服務(wù)器機(jī)型需要CPU供電線輔助但不需要8pin顯卡供電對(duì)現(xiàn)有服務(wù)器改造壓力小。第二是價(jià)格相比同樣24GB顯存的NVIDIA T4或L4Atlas 300V整卡成本要低不少對(duì)成本敏感的B端項(xiàng)目非常友好。第三是國(guó)產(chǎn)化需求很多政企項(xiàng)目、信創(chuàng)項(xiàng)目明確要求使用國(guó)產(chǎn)芯片Atlas系列是最成熟的選項(xiàng)之一。但還有一個(gè)關(guān)鍵點(diǎn)必須說(shuō)清楚這張卡的生態(tài)和CUDA不一樣不是“pip install torch”就能直接用的。你需要用昇騰的工具鏈將模型轉(zhuǎn)換、編譯成OM格式后才能運(yùn)行。這個(gè)轉(zhuǎn)換過(guò)程是很多人的第一道坎后面我會(huì)詳細(xì)講怎么趟過(guò)去。1.2 用Atlas部署YOLO的整體架構(gòu)與流程部署YOLO到Atlas 300V上整體流程可以拆成三條線模型線、運(yùn)行線、數(shù)據(jù)線。模型線的核心是“從訓(xùn)練框架到昇騰格式的轉(zhuǎn)換”。PyTorch訓(xùn)練的.pt權(quán)重文件需要先導(dǎo)出為ONNX再通過(guò)昇騰的ATCAscend Tensor Compiler工具轉(zhuǎn)換成OM模型。這個(gè)過(guò)程不是傻瓜式的輸入尺寸、批量大小、算子的支持情況都會(huì)影響轉(zhuǎn)換是否成功。YOLOv5和YOLOv8的Detect頭里有不少動(dòng)態(tài)shape和自定義算子轉(zhuǎn)換時(shí)經(jīng)常報(bào)錯(cuò)需要針對(duì)性地處理。運(yùn)行線是推理運(yùn)行時(shí)的環(huán)境。昇騰提供了CANN昇騰異構(gòu)計(jì)算架構(gòu)作為底層運(yùn)行時(shí)再往上可以用MindSpore Lite、OpenCV等配合或者直接用ACLAscendCLAPI編寫(xiě)推理代碼。對(duì)大多數(shù)場(chǎng)景來(lái)說(shuō)直接用MindSpore Lite或者ACL的Python接口就夠了不需要碰C當(dāng)然C性能和靈活性更好。我建議初學(xué)者先走通Python接口再考慮C優(yōu)化。數(shù)據(jù)線是容易被忽略的一環(huán)。Atlas 300V板載的DVPP模塊負(fù)責(zé)圖像縮放、格式轉(zhuǎn)換、摳圖等預(yù)處理但DVPP有對(duì)齊要求比如寬度16對(duì)齊YOLO的輸入尺寸如果沒(méi)對(duì)齊DVPP輸出會(huì)黑邊或裁切這會(huì)影響精度。很多人在模型轉(zhuǎn)換時(shí)報(bào)錯(cuò)或者在推理結(jié)果中對(duì)不上坐標(biāo)問(wèn)題都出在數(shù)據(jù)流上。2. 環(huán)境搭建與核心步驟拆解2.1 驅(qū)動(dòng)與CANN工具包安裝脫離文檔踩坑的記錄不管做什么第一步都是裝環(huán)境。Atlas 300V的軟件棧分為三層驅(qū)動(dòng)Driver、固件Firmware、CANN工具包。前兩者是硬件能正常工作基礎(chǔ)CANN是AI推理的計(jì)算框架。先說(shuō)驅(qū)動(dòng)。驅(qū)動(dòng)和固件的安裝包在昇騰社區(qū)可以下載對(duì)應(yīng)你的操作系統(tǒng)版本。我用的環(huán)境是Ubuntu 20.04 x86_64部分客戶是麒麟V10 ARM平臺(tái)流程類(lèi)似但包名不同。主要命令就兩個(gè)./Ascend-hdk-310P-npu-driver_xxx_linux-aarch64.run --full ./Ascend-hdk-310P-npu-firmware_xxx_linux-aarch64.run --full注意這里有個(gè)最常見(jiàn)的坑很多服務(wù)器已經(jīng)裝了NVIDIA驅(qū)動(dòng)如果系統(tǒng)里同時(shí)有NVIDIA GPU和Atlas卡兩者的驅(qū)動(dòng)不沖突因?yàn)樽叩腜CIe設(shè)備不同。但如果你用npu-smi info查看Atlas狀態(tài)時(shí)發(fā)現(xiàn)“NA”大概率是驅(qū)動(dòng)沒(méi)加載或固件版本不對(duì)。需要重啟或者重新執(zhí)行rmmod相關(guān)模塊。驅(qū)動(dòng)安裝完用下面命令驗(yàn)證npu-smi info如果能看到板卡型號(hào)、顯存、芯片溫度、算力利用率這些信息說(shuō)明驅(qū)動(dòng)和固件沒(méi)問(wèn)題了。然后是CANN工具包。CANN的版本很多我強(qiáng)烈建議選一個(gè)“保守”的版本。不要追求最新版昇騰的軟件迭代比較快新的版本對(duì)操作系統(tǒng)、Python版本、第三方庫(kù)有更多要求老項(xiàng)目反而容易出問(wèn)題。我用的是CANN 6.3.RC3支持Python 3.9經(jīng)過(guò)實(shí)際驗(yàn)證穩(wěn)定性很好。安裝CANN時(shí)它會(huì)檢查依賴比如gcc、g、cmake、python3-dev、pybind11等缺什么補(bǔ)什么即可。CANN的安裝包是一個(gè)run文件執(zhí)行./Ascend-cann-toolkit_6.3.RC3_linux-x86_64.run --install安裝完成后需要source環(huán)境變量source /usr/local/Ascend/ascend-toolkit/set_env.sh這里有一個(gè)很多教程不會(huì)提但實(shí)際會(huì)遇到的問(wèn)題CANN對(duì)Python的版本非常敏感。如果系統(tǒng)自帶Python 3.8而CANN版本要求Python 3.9你需要先裝好對(duì)應(yīng)Python再裝CANN。裝了多個(gè)Python版本時(shí)一定要確保python命令指向正確的版本否則后續(xù)atc轉(zhuǎn)換和推理都會(huì)報(bào)“找不到模塊”的錯(cuò)誤。2.2 模型獲取與PyTorch環(huán)境下ONNX導(dǎo)出YOLO的模型可以從官方倉(cāng)庫(kù)獲取。YOLOv5和YOLOv8分別來(lái)自u(píng)ltralytics/yolov5和ultralytics/ultralytics這兩個(gè)倉(cāng)庫(kù)。本地準(zhǔn)備一個(gè)帶PyTorch和CUDA的環(huán)境訓(xùn)練或?qū)С瞿P陀貌恍枰狝tlas參與執(zhí)行導(dǎo)出# YOLOv5 導(dǎo)出ONNX python export.py --weights yolov5s.pt --include onnx --opset 11 # YOLOv8 導(dǎo)出ONNX yolo export modelyolov8s.pt formatonnx opset11opset版本我建議固定用11。在Atlas ATC轉(zhuǎn)換時(shí)有部分算子對(duì)opset 12以上的支持不太友好穩(wěn)定壓倒一切。導(dǎo)出時(shí)還有一個(gè)細(xì)節(jié)動(dòng)態(tài)batch在導(dǎo)出前要小心。如果ONNX里帶了dynamic_axesATC轉(zhuǎn)換時(shí)也要配置動(dòng)態(tài)batch否則轉(zhuǎn)換出來(lái)的OM只能以特定batch運(yùn)行。大部分推理場(chǎng)景固定batch1足夠了所以我建議導(dǎo)出ONNX時(shí)直接固定輸入shape# YOLOv5 固定shape導(dǎo)出 python export.py --weights yolov5s.pt --include onnx --opset 11 --batch-size 1 --imgsz 640導(dǎo)出完成后用onnxsim做一次簡(jiǎn)化可以大幅度提高ATC轉(zhuǎn)換成功率。YOLO的模型結(jié)構(gòu)里有大量Concat、Resize、Split等算子ONNX圖結(jié)構(gòu)冗余度高simplifier會(huì)把常量折疊掉、去掉無(wú)用節(jié)點(diǎn)ATC轉(zhuǎn)換更順滑pip install onnxsim python -m onnxsim yolov5s.onnx yolov5s_sim.onnx就用簡(jiǎn)化后的ONNX文件去轉(zhuǎn)OM很多莫名其妙的算子錯(cuò)誤都能消掉。2.3 ATC模型轉(zhuǎn)換一個(gè)完整的轉(zhuǎn)換命令模板ATC轉(zhuǎn)換是部署YOLO最核心的一步也是最容易卡殼的一步。我先給出我驗(yàn)證可用的模板再解釋每個(gè)參數(shù)為什么這么設(shè)置。atc --modelyolov5s_sim.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --insert_op_confaipp_yolov5.cfg \ --output_typeFP32 \ --enable_small_channel1 \ --logerror逐行解釋--framework55代表ONNX。如果你用MindSpore導(dǎo)出的模型這個(gè)值不同。ONNX是目前對(duì)ATC支持最友好的中間格式。--input_shape固定輸入shape。YOLOv5的ONNX輸入節(jié)點(diǎn)名默認(rèn)是“images”YOLOv8默認(rèn)也是“images”但如果你改過(guò)模型結(jié)構(gòu)需要先用netron或者onnxruntime查看輸入節(jié)點(diǎn)名。--soc_version芯片型號(hào)。Atlas 300V用的芯片是昇騰310P不同子型號(hào)對(duì)應(yīng)不同值常見(jiàn)的是Ascend310P3和Ascend310P1用npu-smi info能查到具體信息。如果沒(méi)對(duì)應(yīng)上ATC會(huì)報(bào)“SOC版本不支持”的錯(cuò)誤。--insert_op_confAIPP配置文件。這個(gè)文件用于圖像預(yù)處理參數(shù)配置比如均值、方差、縮放比例、色域轉(zhuǎn)換等非常關(guān)鍵后面單獨(dú)講。--output_typeFP32輸出精度。有時(shí)不設(shè)置會(huì)默認(rèn)輸出FP16導(dǎo)致后處理反序列化時(shí)數(shù)據(jù)不對(duì)。--enable_small_channel開(kāi)啟小通道優(yōu)化。對(duì)YOLO這種典型卷積網(wǎng)絡(luò)能提升一點(diǎn)推理性能。--logerror日志級(jí)別。報(bào)錯(cuò)時(shí)改成--logdebug可以看到更多信息。一個(gè)需要特別注意的細(xì)節(jié)是輸出節(jié)點(diǎn)。YOLOv5的ONNX默認(rèn)有3個(gè)輸出對(duì)應(yīng)3個(gè)檢測(cè)尺度的輸出YOLOv8默認(rèn)只有1個(gè)輸出將所有尺度的輸出concat在一起。如果你的模型有多個(gè)輸出后面推理代碼要逐個(gè)解析。我建議如果后端代碼是自己寫(xiě)的用YOLOv8這種單輸出的結(jié)構(gòu)會(huì)更省事。2.4 AIPP配置文件決定預(yù)處理是否能對(duì)齊的關(guān)鍵AIPPAI Preprocessing是昇騰專門(mén)用來(lái)把圖像預(yù)處理放到硬件里做的模塊。它可以在送入模型前完成Resize、色域轉(zhuǎn)換如BGR轉(zhuǎn)RGB、減均值、除方差等操作。它最大的意義是省掉了CPU或DVPP的預(yù)處理時(shí)間推理鏈路更短吞吐更高。我常用的AIPP配置如下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: true csc_matrix_r2c: [256, 0, 359, 0] csc_matrix_g2c: [256, -88, -183, 0] csc_matrix_b2c: [256, 455, 0, 0] min_chn_0: 0 min_chn_1: 0 min_chn_2: 0 var_reci_chn_0: 0.003921569 var_reci_chn_1: 0.003921569 var_reci_chn_2: 0.003921569 }這段配置的核心邏輯是模型輸入是RGB三通道圖像像素值被歸一化到[0,1]即除以255。RBUV_SSWAP_SWITCH控制輸入圖像先經(jīng)過(guò)BGR轉(zhuǎn)RGB如果你的圖像源是BGR格式比如OpenCV讀出來(lái)的就是BGR需要置為true。var_reci是歸一化系數(shù)的倒數(shù)。這里有個(gè)非常容易踩的坑如果你在訓(xùn)練時(shí)用了transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])那么在AIPP里不能只配var_reci還需要把減均值的邏輯配進(jìn)去即min_chn_0填負(fù)的均值并注意處理方式。YOLOv5和YOLOv8的官方倉(cāng)庫(kù)在推理時(shí)是不減均值的只除以255所以上面的配置是匹配的。如果你改了預(yù)處理策略AIPP配置必須跟著改否則精度會(huì)嚴(yán)重下降。另外AIPP里配了src_image_size_w/h后輸入圖像的尺寸必須是這個(gè)大小。如果你的圖像源是1920x1080那么要么用DVPP先縮放要么用--input_shape把模型輸入shape改成對(duì)齊后的尺寸要么讓AIPP自己把輸入圖像縮放注意它只做等比例resize不做letterbox。YOLO訓(xùn)練時(shí)經(jīng)常用到letterbox帶灰邊的等比縮放這部分邏輯AIPP做不了你需要在推理代碼里先把圖處理好或者接受不是等比縮放帶來(lái)的精度損失。3. 推理代碼實(shí)現(xiàn)與運(yùn)行效果3.1 Python推理接口選擇MindSpore Lite vs AscendCL昇騰推理的Python接口主要有兩種MindSpore Lite的Python API和AscendCLACL的Python API。我兩個(gè)都試過(guò)簡(jiǎn)單說(shuō)下選型思路。MindSpore Lite API包裝得更高級(jí)接口風(fēng)格類(lèi)似TensorFlow Lite的Python接口加載模型、推理、釋放資源分層清晰適合快速驗(yàn)證。AscendCL更底層但接口也不復(fù)雜關(guān)鍵是它的文檔最全遇到問(wèn)題好排查。底層上MindSpore Lite最終也是調(diào)用ACL的所以性能差別不大。我推薦團(tuán)隊(duì)協(xié)作或相關(guān)項(xiàng)目用MindSpore Lite因?yàn)樗鼉?nèi)置了一些數(shù)據(jù)處理工具的接口能少寫(xiě)很多代碼。個(gè)人開(kāi)發(fā)者或追求極致性能調(diào)優(yōu)的直接上ACL。不管選哪個(gè)都要記得在代碼開(kāi)頭加載昇騰的環(huán)境變量import os os.environ[ASCEND_AICPU_PATH] /usr/local/Ascend/ascend-toolkit/latest os.environ[LD_LIBRARY_PATH] /usr/local/Ascend/ascend-toolkit/latest/lib64: os.environ.get(LD_LIBRARY_PATH, )這個(gè)不寫(xiě)經(jīng)常會(huì)出現(xiàn)“找不到libascendcl.so”之類(lèi)的錯(cuò)誤。3.2 以AscendCL為例的推理代碼框架下面這段代碼是我在項(xiàng)目中實(shí)際用過(guò)的去掉了業(yè)務(wù)邏輯保留關(guān)鍵框架。import numpy as np import cv2 from constants import IMG_SIZE, CLASS_NAMES, CONF_THRESH, NMS_THRESH import acl import torch # 僅用于后處理的NMS也可以用NumPy實(shí)現(xiàn) # 初始化 ret acl.init() ret acl.rt.set_device(0) context, ret acl.rt.create_context(0) # 加載模型 model_path ./yolov8s_bs1.om model_id, ret acl.mdl.load_from_file(model_path) desc acl.mdl.create_desc() ret acl.mdl.get_desc(desc, model_id) # 獲取輸入輸出尺寸 input_size acl.mdl.get_num_inputs(desc) output_size acl.mdl.get_num_outputs(desc) input_dims [acl.mdl.get_input_dims(desc, i) for i in range(input_size)] output_dims [acl.mdl.get_output_dims(desc, i) for i in range(output_size)] # 準(zhǔn)備輸入輸出內(nèi)存 input_data np.zeros((1, 3, IMG_SIZE, IMG_SIZE), dtypenp.float32) output_data np.zeros((1, 84, 8400), dtypenp.float32) # 以coco 80類(lèi)為例 # 圖像預(yù)處理 def preprocess(image): # 原圖等比例縮放加letterbox h, w image.shape[:2] r min(IMG_SIZE / h, IMG_SIZE / w) new_h, new_w int(h * r), int(w * r) resized cv2.resize(image, (new_w, new_h)) canvas np.full((IMG_SIZE, IMG_SIZE, 3), 114, dtypenp.uint8) x_off (IMG_SIZE - new_w) // 2 y_off (IMG_SIZE - new_h) // 2 canvas[y_off:y_offnew_h, x_off:x_offnew_w] resized # BGR轉(zhuǎn)RGB歸一化轉(zhuǎn)CHW tensor cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 tensor np.transpose(tensor, (2, 0, 1))[None, ...] return tensor, x_off, y_off, r # 推理 def infer(tensor): input_buffer acl.util.numpy_to_ptr(tensor) output_buffer acl.util.numpy_to_ptr(output_data) ret acl.mdl.execute(model_id, [input_buffer], [output_buffer]) return output_data.copy() # 后處理以YOLOv8為例解碼為xyxy格式 def postprocess(pred, x_off, y_off, r): # pred shape: [1, 84, 8400] - [8400, 84] pred pred[0].T boxes pred[:, :4] scores pred[:, 4:] class_ids np.argmax(scores, axis1) confs np.max(scores, axis1) mask confs CONF_THRESH boxes, class_ids, confs boxes[mask], class_ids[mask], confs[mask] # 把坐標(biāo)從模型輸入空間還原回原圖空間 boxes[:, [0, 2]] (boxes[:, [0, 2]] - x_off) / r boxes[:, [1, 3]] (boxes[:, [1, 3]] - y_off) / r # NMSOpencv或自定義 ... return final_boxes關(guān)于輸出的尺寸8480個(gè)類(lèi)別加4個(gè)坐標(biāo)維度YOLOv8的x,y,w,h或x1,y1,x2,y2具體看版本乘以特征點(diǎn)數(shù)量8400三個(gè)尺度的特征圖拼起來(lái)。如果你用YOLOv5輸出是[1, 3, 84, 8400]還多了一個(gè)維度對(duì)應(yīng)不同的anchor解析邏輯更復(fù)雜。3.3 實(shí)測(cè)性能單路視頻實(shí)時(shí)性與多路并發(fā)能力我在實(shí)際項(xiàng)目中用Atlas 300V 24G跑YOLOv8s輸入640x640FP16推理測(cè)試結(jié)果如下配置單幀耗時(shí)ms備注YOLOv8s 640x640 FP16約11~14ms單路超過(guò)60FPSYOLOv5s 640x640 FP16約7~9ms單路超過(guò)100FPSYOLOv8s 640x640 INT8量化后約5~7ms精度通常下降1%~3%8路1080P視頻流每路YOLOv8s總計(jì)可跑滿約70~90FPS用多線程模型多實(shí)例這個(gè)性能在24GB顯存、72W功耗的推理卡里算很不錯(cuò)了尤其適合“一臺(tái)服務(wù)器同時(shí)處理十幾路視頻流做結(jié)構(gòu)化分析”這種場(chǎng)景。如果對(duì)速度要求極高可以考慮把模型輸入降到416x416時(shí)間能再壓掉40%左右當(dāng)然小目標(biāo)檢出率會(huì)受影響。有一說(shuō)一Atlas的推理性能跟NVIDIA同檔位卡相比有一定差距但在國(guó)產(chǎn)化要求、功耗限制、成本敏感這三座大山面前這個(gè)性能表現(xiàn)已經(jīng)相當(dāng)能打了。實(shí)際項(xiàng)目中很多客戶核心訴求就是“在指定預(yù)算內(nèi)把模型跑起來(lái)”而不是“一定比NVIDIA快”。4. 部署過(guò)程中高頻問(wèn)題與避坑記錄4.1 ATC轉(zhuǎn)換報(bào)錯(cuò)算子不支持或圖優(yōu)化失敗這是新手遇到最多的一類(lèi)問(wèn)題。常見(jiàn)報(bào)錯(cuò)有E19999: The node [Resize] is not supportedONNX中的Resize算子在ATC支持的版本里沒(méi)有對(duì)應(yīng)實(shí)現(xiàn)。解決方法是升級(jí)CANN版本或者修改ONNX圖結(jié)構(gòu)把動(dòng)態(tài)Resize替換成靜態(tài)shape的Resize或者換opset版本重新導(dǎo)出。E10001: No OpType found in ops info說(shuō)明算子未注冊(cè)一般是ONNX版本太新比如opset 17 當(dāng)前CANN算子庫(kù)太舊。我建議opset 11或12配合舊一點(diǎn)但穩(wěn)定的CANN版本能避開(kāi)大多數(shù)算子兼容問(wèn)題。有一個(gè)繞不開(kāi)的經(jīng)驗(yàn)遇到算子不支持的報(bào)錯(cuò)先查算子本身有沒(méi)有問(wèn)題再查CANN版本是否太舊/太新不要一上來(lái)就想改模型結(jié)構(gòu)。YOLO這種廣泛部署的模型昇騰官方其實(shí)是有很多已驗(yàn)證案例的很多坑網(wǎng)上都能搜到搜不到的基本就是版本組合太激進(jìn)。4.2 推理結(jié)果精度下降或完全檢測(cè)不到目標(biāo)這個(gè)問(wèn)題的排查順序非常重要我經(jīng)歷過(guò)太多項(xiàng)目在這里走彎路所以列個(gè)清單AIPP配置和訓(xùn)練時(shí)預(yù)處理不一致這是最容易被忽略的。YOLO訓(xùn)練時(shí)一般不做減均值只做/255歸一化但很多人看網(wǎng)上教程抄了ImageNet的mean/std配置結(jié)果模型直接就廢了。輸入圖片尺寸是否被resize成640x640但沒(méi)有等比縮放。如果直接把1920x1080壓成640x640目標(biāo)比例早就變形了檢測(cè)精度會(huì)崩。推理輸出解析是否正確。YOLOv5的輸出是xywh格式Y(jié)OLOv8是xyxy格式且默認(rèn)帶DFL解碼如果你用YOLOv5的解析邏輯去解YOLOv8坐標(biāo)完全是亂的。模型量化后精度下降太多。如果INT8量化校準(zhǔn)集代表性不夠比如只用了幾十張圖量化誤差會(huì)很大。建議校準(zhǔn)集至少用500~1000張覆蓋目標(biāo)場(chǎng)景的圖片。4.3 DVPP對(duì)齊限制導(dǎo)致圖片出現(xiàn)黑邊或裁切DVPP在做圖像縮放時(shí)對(duì)寬高有對(duì)齊要求通常是16對(duì)齊有的版本是32對(duì)齊。如果你直接把圖片resize成640x640給DVPP如果原始尺寸不是16的倍數(shù)輸出圖像邊上可能出現(xiàn)臟數(shù)據(jù)或黑邊。解決辦法有兩種一是繞過(guò)DVPP用OpenCV的CPU reszie二是用DVPP時(shí)把原始圖像先pad到對(duì)齊尺寸再做縮放。第一種簡(jiǎn)單但多占CPU第二種高效但代碼復(fù)雜一點(diǎn)。我實(shí)際項(xiàng)目中CPU資源充裕直接用OpenCV處理省心很多。4.4 多路并發(fā)推理時(shí)的顯存和線程問(wèn)題Atlas 300V的24GB顯存聽(tīng)起來(lái)很大但如果你用多個(gè)Python進(jìn)程分別加載同一個(gè)OM模型顯存占用會(huì)成倍增加。我的建議是在一個(gè)進(jìn)程里創(chuàng)建多個(gè)推理線程共享同一個(gè)模型上下文。昇騰的acl.mdl.execute本身是線程安全的多線程并發(fā)執(zhí)行推理不會(huì)沖突這樣可以最大程度復(fù)用模型加載占用的顯存。真實(shí)場(chǎng)景里我一般開(kāi)4~6個(gè)推理線程每個(gè)線程處理2~4路視頻流顯存占用基本穩(wěn)定在8~12GB之間留下充足余量給后續(xù)的業(yè)務(wù)處理。4.5 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因解決方案npu-smi info顯示NA驅(qū)動(dòng)未加載或固件版本錯(cuò)誤重新安裝驅(qū)動(dòng)固件并重啟檢查PCIe設(shè)備識(shí)別狀態(tài)ATC轉(zhuǎn)換時(shí)報(bào)E19999算子不支持ONNX版本/opset過(guò)新CANN版本過(guò)舊降低opset到11使用穩(wěn)定版CANN必要時(shí)修改ONNX結(jié)構(gòu)加載OM模型報(bào)內(nèi)存不足未設(shè)置AscendCL環(huán)境或模型輸入shape過(guò)大檢查環(huán)境變量減小輸入shape或換成batch1模型推理結(jié)果全是背景框AIPP預(yù)處理與訓(xùn)練不一致核對(duì)均值方差、BGR/RGB、縮放方式多線程推理偶發(fā)卡死線程間共享了同一個(gè)輸出Buffer每個(gè)線程分配獨(dú)立輸入輸出Buffer單卡吞吐上不去未開(kāi)enable_small_channel或未使用多線程開(kāi)啟AI CPU優(yōu)化開(kāi)關(guān)用多線程并發(fā)推理5. 部署之外的進(jìn)階優(yōu)化方向5.1 模型量化INT8推理到底值不值得做Atlas 300V對(duì)INT8的支持是原生且高效的理論上INT8吞吐量是FP16的兩到三倍。但I(xiàn)NT8量化后的精度損失因模型、數(shù)據(jù)集、量化方案而異。我的實(shí)踐結(jié)論是YOLOv5s做INT8量化在標(biāo)準(zhǔn)COCO驗(yàn)證集上mAP下降一般不超過(guò)2%在工業(yè)質(zhì)檢這種類(lèi)別少、背景相對(duì)單一的場(chǎng)景下降更少。YOLOv8做INT8量化會(huì)復(fù)雜一些因?yàn)槟P徒Y(jié)構(gòu)里的某些算子對(duì)量化敏感比如SiLU激活函數(shù)容易掉精度。如果項(xiàng)目對(duì)精度要求不是“99.9%不能錯(cuò)”我建議直接上INT8因?yàn)橥评硭俣忍嵘龑?shí)在太明顯了。量化工具用昇騰的AMCTAscend Model Compression Toolkit流程是準(zhǔn)備校準(zhǔn)集幾百?gòu)堈鎸?shí)場(chǎng)景圖→ 調(diào)用量化接口 → 得到量化模型 → ATC重新轉(zhuǎn)換。校準(zhǔn)集一定要貼合線上真實(shí)數(shù)據(jù)分布否則量化后精度會(huì)崩。5.2 讓Atlas同時(shí)處理多路視頻流的架構(gòu)方法多路視頻流處理時(shí)算法代碼反而不是瓶頸真正的瓶頸在于解碼和循環(huán)處理。我用的是FFmpeg Python線程的經(jīng)典組合架構(gòu)可復(fù)用通用解碼線程用FFmpeg從RTSP流解碼出1080P圖像幀放入隊(duì)列。推理線程池從隊(duì)列取幀執(zhí)行預(yù)處理如果要跑AIPP就在DVPP里做否則用OpenCV、推理、后處理。結(jié)果匯聚模塊將檢測(cè)結(jié)果合成為結(jié)構(gòu)化JSON寫(xiě)入消息隊(duì)列或數(shù)據(jù)庫(kù)。這個(gè)架構(gòu)下8路1080P視頻流跑YOLOv8sCPU占用率大概70%卡上算力利用率穩(wěn)定在80%以上整體延遲約80ms。注意如果你的視頻流是H.265編碼要確認(rèn)FFmpeg編譯了H.265解碼庫(kù)否則解碼會(huì)非常吃力。我的個(gè)人體會(huì)是Atlas 300V 24G不是那種插上就能用的卡它需要你花一兩天去熟悉工具鏈。但摸清楚它的脾氣之后它確實(shí)是一張穩(wěn)定、便宜、功耗低、適合量產(chǎn)部署的AI推理卡?,F(xiàn)在昇騰生態(tài)也在快速補(bǔ)齊MindX SDK、ModelZoo的預(yù)置模型越來(lái)越多后面做項(xiàng)目和方案選型時(shí)可以多給它一個(gè)機(jī)會(huì)。如果這篇文章讓你成功跑通了YOLO在Atlas上的部署記住我最后這個(gè)建議轉(zhuǎn)換模型永遠(yuǎn)用簡(jiǎn)化后的ONNX調(diào)精度永遠(yuǎn)先查預(yù)處理。穩(wěn)了這兩點(diǎn)你的Atlas之路會(huì)順很多。