:從模型轉(zhuǎn)到部署避坑指南)
不知道有多少朋友和我一樣第一次聽到“Atlas 300V 24G”這個名字的時候下意識會把它和NVIDIA的A100、4090這類GPU劃等號。畢竟名字里帶個“V”參數(shù)表里赫然寫著24G怎么看都像是一塊“國產(chǎn)大顯存顯卡”??烧娴鹊侥惆阉宓椒?wù)器上準備拿它訓(xùn)個模型、跑個訓(xùn)練任務(wù)的時候才發(fā)現(xiàn)事情沒那么簡單。這塊卡真正的舞臺在推理側(cè)而不是訓(xùn)練側(cè)。我用Atlas系列硬件做深度學(xué)習(xí)推理部署有一段時間了踩過不少坑也積累了一些比較順手的經(jīng)驗。今天這篇就圍繞“Atlas 300V 24G”和“YOLO部署”這兩件事把這塊卡的定位、部署流程、模型轉(zhuǎn)換細節(jié)、以及最常見的報錯和排查方法一次性講清楚。如果你剛拿到這塊卡或者正在糾結(jié)“為什么我照著GPU那套流程怎么都跑不起來”那你來對地方了。1. Atlas 300V 24G到底是張什么卡先說結(jié)論Atlas 300V 24G是一塊AI推理加速卡不是用來做通用計算更不適合直接訓(xùn)練大模型。它搭載的是昇騰310P芯片板載24GB內(nèi)存主打的是高算力、高吞吐、低功耗的云端或邊緣推理場景。很多人被“24G”這個數(shù)字誤導(dǎo)了覺得這差不多是消費級旗艦卡的水平結(jié)果拿到手一看跑個PyTorch訓(xùn)練腳本直接各種報錯或者速度感人就開始懷疑卡是不是壞了。實際上判斷一張卡適不適合你的任務(wù)不能只看顯存大小要看它的架構(gòu)設(shè)計目標(biāo)是什么。維度Atlas 300V 24G310P常規(guī)GPU如A10/4090設(shè)計定位推理加速訓(xùn)練/推理通用核心架構(gòu)昇騰AI Core達芬奇架構(gòu)CUDA Core / Tensor Core軟件棧CANN昇騰計算語言CUDA cuDNN主要場景云端推理、視頻分析、CV模型大批量處理模型訓(xùn)練、科學(xué)計算、通用計算顯存類型LPDDR4X板載不可擴充GDDR6/6X部分卡可擴充編程方式C/Python調(diào)用ACL接口或用MindSpore/ONNX轉(zhuǎn)OMCUDA/C/Python生態(tài)更廣我這么打個比方GPU是“全能運動員”訓(xùn)練、推理、渲染、計算都能干但你讓它干推理的時候很多算力和顯存帶寬其實是浪費的而Atlas 300V這種昇騰推理卡是“專項選手”你讓它跑訓(xùn)練它的強項發(fā)揮不出來但你要是讓它跑高并發(fā)的推理任務(wù)——尤其是YOLO這類目標(biāo)檢測模型——它的性價比和吞吐量會讓你眼前一亮。那個熱搜問題“atlas 300v 24g 是運算加速卡嗎”答案是是運算加速卡但更準確地說是AI推理運算加速卡。它不承擔(dān)圖形渲染也不適合跑需要頻繁動態(tài)shape變化的訓(xùn)練邏輯。弄清楚這一點后面所有部署思路就不會跑偏。1.1 硬件形態(tài)與接口理解Atlas 300V 24G在物理形態(tài)上是一張標(biāo)準全高全長PCIe卡接口是PCIe 4.0 x16。服務(wù)器上插好之后你通過npu-smi命令能看到卡的基本狀態(tài)這個命令就相當(dāng)于GPU那邊的nvidia-smi。我習(xí)慣拿到卡之后先跑一遍npu-smi info確認四件事卡是否正常上電、狀態(tài)為“ok”芯片溫度是否在合理范圍待機一般不會超過50℃驅(qū)動版本和固件版本是否匹配板載內(nèi)存是否識別為24G。這四件事如果有一件不對后面裝CANN華為昇騰的AI計算框架和跑推理的時候大概率會出幺蛾子。特別是驅(qū)動和固件版本不匹配的問題我見過很多次癥狀就是npu-smi info能顯示卡但一跑程序就報設(shè)備不可用、初始化失敗。這類問題通常可以重裝或升級固件解決但一定要以官方配套文檔為準不能拿著一個驅(qū)動版本瞎升級。1.2 推理卡的算力指標(biāo)怎么看看推理卡的算力不能只看顯存和“TOPS”數(shù)字得看它對應(yīng)什么精度、什么輸入分辨率。Atlas 300V 24G標(biāo)稱的INT8算力還是比較可觀的在YOLOv5s這類輕量模型上單卡的吞吐量往往能達到幾百FPS具體數(shù)值取決于預(yù)處理方式、batch大小和輸入分辨率。但如果你拿它的FP16算力去對標(biāo)GPU那就沒意義了因為推理卡在真實業(yè)務(wù)中絕大多數(shù)時候跑的是INT8量化模型少數(shù)場景跑FP16極少人會在推理卡上跑FP32。換句話說你買這塊卡就是要做好“量化部署”的心理準備的。關(guān)于量化后面模型轉(zhuǎn)換那一節(jié)我會仔細講。2. 部署YOLO前的環(huán)境準備很多人在Atlas上部署YOLO失敗十有八九不是代碼寫錯而是環(huán)境沒準備好。昇騰的軟件棧和CUDA那一套差異很大你不能用“裝個GPU驅(qū)動、裝個CUDA、裝個PyTorch”的慣性思維去弄。2.1 主機側(cè)和卡側(cè)軟件棧的對應(yīng)關(guān)系昇騰推理環(huán)境的軟件棧大致分三層驅(qū)動與固件NPU firmware driver負責(zé)讓操作系統(tǒng)識別到硬件是上層所有軟件的底座CANN toolkit昇騰計算語言提供運行時、算子庫、圖編譯功能相當(dāng)于“CUDA cuDNN”的合體推理引擎/框架你可以直接用ACLAscendCL編程也可以用MindSpore或者通過ONNX轉(zhuǎn)OM后用mxVision/msame等工具。這三層必須版本配套不是“越新越好”。我自己的經(jīng)驗是選定一套經(jīng)過驗證的組合之后就不要頻繁升級尤其是不要在項目中期升級CANN。昇騰的軟件迭代確實很快但配套矩陣復(fù)雜度也很高升級一次可能讓你多出好幾天工作量。以我現(xiàn)在用的這套為例驅(qū)動固件版本適配CANN 7.0的配套版本CANN toolkit7.0.RC1操作系統(tǒng)Ubuntu 20.04 x86_64Python3.8裝驅(qū)動和固件的時候注意Atlas 300V 24G屬于300V系列部分驅(qū)動包名和300I/300I Pro不一樣下錯包是常見錯誤下載時留意產(chǎn)品名全稱。2.2 環(huán)境變量配置安裝完成之后最容易被忽略的就是環(huán)境變量。每次打開終端要跑推理前都需要把CANN的so庫、工具鏈路徑加進去。我一般會把這些寫進~/.bashrc核心配置類似這樣source /usr/local/Ascend/ascend-toolkit/set_env.sh export ASCEND_DEVICE_ID0 export CPU_ARCHx86_64ASCEND_DEVICE_ID對應(yīng)的是你用的是第幾張昇騰卡從0開始。多卡機器上跑之前先看npu-smi info確認邏輯設(shè)備ID否則代碼里寫死device_id0實際去跑別的卡數(shù)據(jù)流和性能都會有怪毛病。還有一個容易踩的坑Ascend的工具包有多個目錄比如/usr/local/Ascend/driver、/usr/local/Ascend/ascend-toolkit、/usr/local/Ascend/nnrt。如果你是純推理部署只裝driver和nnrt或者叫cann toolkit的runtime部分就夠了如果你還要做模型轉(zhuǎn)換ATC那得裝完整的toolkit。別為了省空間只裝runtime到轉(zhuǎn)OM模型的時候到處缺工具更頭疼。3. 模型轉(zhuǎn)換PyTorch模型到OM模型的關(guān)鍵環(huán)節(jié)在GPU上部署YOLO通常就是PyTorch權(quán)重拿來直接加載或者轉(zhuǎn)成ONNX、TensorRT的engine。在昇騰上主流路徑是PyTorch — ONNX — OMOMOffline Model是昇騰的離線模型格式像TensorRT的engine但又不完全一樣。ATC工具負責(zé)把ONNX模型轉(zhuǎn)成OM這一步是整個部署里最考驗經(jīng)驗的地方。3.1 導(dǎo)出ONNX時的注意事項很多人卡在第一步PyTorch模型導(dǎo)出ONNX時沒注意“動態(tài)維度”的處理。YOLO這類檢測模型輸入shape通常是[N, C, H, W]其中N是batch size。為了速度我強烈建議導(dǎo)出ONNX時固定shape不要搞動態(tài)維度。原因很簡單昇騰的ATC在編譯模型時會做很多算子融合和內(nèi)存布局優(yōu)化如果模型輸入是動態(tài)shape編譯器沒法做極致的靜態(tài)內(nèi)存規(guī)劃性能和穩(wěn)定性都會受影響。實際業(yè)務(wù)中就算你想支持動態(tài)batch也建議在業(yè)務(wù)邏輯層做padding把不同batch的請求湊成固定大小輸入而不是讓模型本身去動態(tài)適配。給一個我常用的YOLOv5導(dǎo)出腳本片段import torch from models.experimental import attempt_load model attempt_load(yolov5s.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[outputs], dynamic_axesNone # 固定shape ) print(export done)這里有個容易被忽略的細節(jié)opset_version不要圖新用12以上有些版本導(dǎo)出的ONNX結(jié)構(gòu)ATC解析的時候會報“不支持的op”。我踩過的版本組合是opset 12結(jié)果ATC報一個看不懂的算子錯誤后來退回opset 11就順了。3.2 ATC轉(zhuǎn)換的關(guān)鍵參數(shù)解析轉(zhuǎn)換命令大概是這樣的atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_om \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --input_formatNCHW \ --output_typeFP16 \ --insert_op_confaipp.cfg逐個參數(shù)解釋--framework55表示ONNX這個是ATC的固定枚舉值別改。--soc_versionAscend310P3這個必須和你的卡對應(yīng)。Atlas 300V 24G對應(yīng)的soc version是Ascend310P3但我見過有人拿Ascend310或者Ascend310P1去轉(zhuǎn)轉(zhuǎn)出來的OM能加載但性能很差或者干脆跑不起來。怎么確認用npu-smi info看芯片型號再對照CANN文檔里的“產(chǎn)品型號與soc_version對照表”。--input_shapeimages:1,3,640,640固定batch為1。如果業(yè)務(wù)上想用batch4或者batch8要在這里同時改并且導(dǎo)出ONNX時的dummy input也要改成對應(yīng)的大小且最好用torch.onnx.export時固定好。--output_typeFP16昇騰推理卡上性價比最高的精度是FP16和INT8。如果對精度要求高可以先跑FP16之后再嘗試INT8量化。FP32在推理場景下基本沒必要吃內(nèi)存又慢。--insert_op_confaipp.cfgAIPPAI Preprocessing是昇騰特有的預(yù)處理配置可以把圖像縮放、減均值、除方差、色域轉(zhuǎn)換等操作融合進模型里省掉一部分主機側(cè)預(yù)處理的開銷。這是提升端到端性能的核心手段后面細說。3.3 AIPP配置與預(yù)處理融合YOLO模型的預(yù)處理包括resize到640x640、歸一化除以255、RGB順序調(diào)整。常規(guī)做法是在Python端用OpenCV做再把處理好的tensor喂給模型。這在小batch場景沒問題但要追求高吞吐就得把預(yù)處理下沉到AIPP里。一個典型的AIPP配置文件長這樣aipp_op { aipp_mode: static input_format: YUV420SP_U8 src_image_size_h: 1080 src_image_size_w: 1920 csc_switch: true rbuv_swap_switch: false matrix_r0c0: 256 matrix_r0c1: 0 matrix_r0c2: 359 matrix_r1c0: 256 matrix_r1c1: -88 matrix_r1c2: -183 matrix_r2c0: 256 matrix_r2c1: 456 matrix_r2c2: 0 input_bias_0: 0 input_bias_1: 128 input_bias_2: 128 crop: true load_start_pos_h: 0 load_start_pos_w: 0 crop_size_h: 1080 crop_size_w: 1920 resize: true resize_h: 640 resize_w: 640 }但這個配置有個前提輸入給卡的圖像格式必須是YUV420SP因為昇騰的JPEG解碼硬件輸出就是YUV420SP。如果你在主機側(cè)已經(jīng)用OpenCV讀成BGR的RGB圖了那AIPP配置里的input_format要對應(yīng)改成RGB888_U8csc_switch可以關(guān)掉。AIPP配置不是三言兩語能說完的我的建議是第一版先不要開AIPP用Python端OpenCV做預(yù)處理把模型跑通驗證精度沒問題之后再回頭優(yōu)化AIPP。一上來就搞AIPP一旦結(jié)果不對你根本分不清是預(yù)處理問題還是模型轉(zhuǎn)換問題。3.4 后處理輸出解析YOLO模型轉(zhuǎn)成OM之后輸出不再是PyTorch里的張量那么直觀。用ACL推理時模型輸出可能是一個或三個輸出節(jié)點取決于你導(dǎo)出ONNX時是否把head部分包含進去。如果是YOLOv5原版模型轉(zhuǎn)出來的通常有三個輸出shape分別為[1, 3, 80, 80, 85]、[1, 3, 40, 40, 85]、[1, 3, 20, 20, 85]以80類COCO為例。你要手動做解碼先算grid、算anchor偏移、做sigmoid、再乘stride映射回原圖坐標(biāo)最后做NMS。這塊邏輯和GPU上推理基本一樣代碼可以直接復(fù)用之前寫過的YOLO后處理邏輯。唯一要小心的是昇騰推理輸出的內(nèi)存布局可能是ND格式也就是學(xué)術(shù)界說的“排布”可能和PyTorch里不一樣建議用aclmdlGetOutputDesc確認好每個輸出的shape和dtype再拉數(shù)據(jù)。4. 推理部署實操記錄環(huán)境、模型轉(zhuǎn)換都就緒后就到了真正跑推理的環(huán)節(jié)。昇騰推理有幾種調(diào)用方式從底層到高層分別是ACL接口C/Python、mxVision基于ACL封裝的Python/C推理框架、msame命令行推理工具。實戰(zhàn)中我推薦按“msame驗證模型 — Python接口調(diào)通流程 — C優(yōu)化性能”這個路線推進。4.1 先用msame驗證模型msame是昇騰自帶的模型推理工具用法很類似TensorRT的trtexec它可以加載OM模型、喂入輸入數(shù)據(jù)、輸出推理結(jié)果。拿到新轉(zhuǎn)好的OM模型我會第一時間用msame跑一遍確認模型能不能正常加載、輸入輸出是否合理。常見的msame命令msame --modelyolov5s_om.om \ --inputtest.bin \ --output./out \ --outfmtBIN \ --loop10test.bin是原始輸入數(shù)據(jù)注意必須是和模型輸入shape一致的二進制數(shù)據(jù)比如模型輸入是1,3,640,640那這個bin就是1*3*640*640個float16或float32數(shù)值具體看你的--output_type設(shè)置。如果msame這一關(guān)過了說明模型轉(zhuǎn)換沒問題后面寫代碼出問題大概率是你自己的處理邏輯有bug。這一招幫我省了很多排查時間。4.2 Python ACL推理最小示例用Python寫ACL推理代碼模板相對固定。核心流程是初始化ACL環(huán)境和設(shè)備加載OM模型獲取輸入輸出信息申請輸入輸出內(nèi)存把數(shù)據(jù)拷入設(shè)備內(nèi)存執(zhí)行推理拉取輸出數(shù)據(jù)。一個能跑通的最小示例框架import acl import numpy as np # 初始化 ret acl.init() ret acl.rt.set_device(0) # 加載模型 model_id acl.mdl.load_from_file(yolov5s_om.om) model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id) # 獲取輸入輸出尺寸 input_size acl.mdl.get_input_size_by_index(model_desc, 0) output_num acl.mdl.get_num_outputs(model_desc) output_size acl.mdl.get_output_size_by_index(model_desc, 0) # 申請設(shè)備內(nèi)存 input_ptr, input_mem acl.rt.malloc(input_size, 2) output_ptr, output_mem acl.rt.malloc(output_size, 2) # 準備輸入數(shù)據(jù)假設(shè)是NCHW的float16 data np.random.randn(1, 3, 640, 640).astype(np.float16) acl.rt.memcpy(input_ptr, input_size, data.tobytes(), input_size, 1) # 1表示H2D # 執(zhí)行推理 ret acl.mdl.execute(model_id, input_ptr, input_size, output_ptr, output_size) # 拉取輸出 output_data acl.rt.memcpy(output_size, output_ptr, output_size, 2) # 2表示D2H output np.frombuffer(output_data, dtypenp.float16).reshape(...) # 釋放資源 acl.rt.free(input_ptr) acl.rt.free(output_ptr) acl.mdl.unload(model_id) acl.rt.reset_device(0) acl.finalize()這個示例里輸入數(shù)據(jù)用的是隨機數(shù)真實業(yè)務(wù)中你要把圖像通過解碼、resize、歸一化后轉(zhuǎn)成float16或uint8的NCHW數(shù)據(jù)喂進去。注意模型如果是FP16權(quán)重輸入數(shù)據(jù)也建議轉(zhuǎn)成FP16再傳入否則會有隱式類型轉(zhuǎn)換的性能損耗。4.3 Python接口的性能瓶頸上面這套Python ACL代碼能把功能跑通但性能一定不是最優(yōu)的瓶頸主要在幾個地方Python側(cè)的圖像解碼和resize如果你用OpenCV的cv2.imreadcv2.resize每張圖的耗時可能在幾毫秒到十幾毫秒不等。這個開銷對于追求“單卡幾百FPS”的目標(biāo)來說幾乎是致命的。內(nèi)存拷貝acl.rt.memcpy每次都要把數(shù)據(jù)從主機傳到設(shè)備如果頻繁申請、釋放內(nèi)存會有不小的系統(tǒng)調(diào)用開銷。建議提前申請好內(nèi)存池反復(fù)復(fù)用。推理排隊ACL的執(zhí)行模式有同步和異步兩種。Python接口天然適合同步模式但同步模式下CPU等NPU算完這段時間CPU是空閑的。高性能部署要么用C多線程要么在Python里用多線程把預(yù)處理和推理流水線化。我實測下來同樣的OM模型Python版本能做到的功能驗證沒問題但單路吞吐往往只有C多線程版本的1/3到1/2。如果你的業(yè)務(wù)并發(fā)要求不高比如每秒處理幾十張圖Python完全夠用但凡要上生產(chǎn)、追求高并發(fā)建議直接把C的推理引擎拉起來。4.4 C多線程推理的架構(gòu)思路C AC