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