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