換到工程化推理)
最近有幾個做視覺的朋友問我Atlas 300V 24G到底是不是運算加速卡能不能拿來部署YOLO。這個問題挺典型——Atlas在華為昇騰產(chǎn)品線里既指整機、也指板卡還經(jīng)常被拿來和GPU對比新手一聽就懵。我花了大約三天把YOLOv5完整部署到Atlas 300V Pro 24G上跑通中間經(jīng)歷了模型轉(zhuǎn)換、算子報錯、結(jié)果全黑、顯存反復拉滿這些坑。這篇就把整個流程、參數(shù)選擇、排查經(jīng)驗一次性寫清楚給準備在Atlas類推理卡上做目標檢測的同學當參考。1. 先搞清楚Atlas 300V 24G到底是什么卡1.1 它不是顯卡是專用AI推理加速卡很多人聽到“24G顯存”第一反應是這塊卡像RTX 3090一樣可以隨便訓練模型。實際不是。Atlas 300V Pro 24G是華為昇騰系列里的AI推理加速卡核心是一個NPU神經(jīng)網(wǎng)絡處理器針對神經(jīng)網(wǎng)絡計算做了大量專用電路優(yōu)化擅長干高吞吐的模型推理但它不是通用的可編程GPU很多CUDA生態(tài)里習以為常的操作在它上面并不直接支持。怎么理解這個區(qū)別可以類比GPU像一臺通用機床什么零件都能車NPU像一條專用流水線只加工你提前設計好的零件但加工速度極快。所以Atlas這張卡最適合的場景是模型已經(jīng)訓練好、需要大規(guī)模部署推理的環(huán)節(jié)比如視頻監(jiān)控目標檢測、工業(yè)質(zhì)檢、園區(qū)安防、智慧交通這些。你拿它去從頭訓練YOLO那會很別扭但拿它來跑已經(jīng)訓練好的YOLO模型尤其是多路視頻流同時做目標檢測反而是它的主場。這張卡還有一個加分項板載了專屬的視頻編解碼模塊可以硬件解碼H.264/H.265視頻流不用CPU軟解做視頻分析類的項目非常省心。所以熱詞里“atlas 300v 24g是運算加速卡嗎”答案是肯定的但更準確的說法是它是一張偏推理、偏視頻處理場景的AI加速卡。1.2 為什么在Atlas上部署YOLO要先轉(zhuǎn)變思路在GPU上部署YOLO大家習慣把PyTorch模型直接加載到顯存里跑或者導出TensorRT引擎。Atlas這套生態(tài)的邏輯不太一樣它要求先把訓練好的模型離線編譯成一種名為OMOffline Model的格式再通過CANN運行時加載執(zhí)行。這個差異是整個部署流程里最需要扭轉(zhuǎn)思路的地方。OM模型是靜態(tài)編譯產(chǎn)物編譯時就要把輸入shape、算子類型、芯片型號這些信息都定好運行時不支持動態(tài)算子插拔。好處是運行時開銷低、性能穩(wěn)定壞處是靈活性下降比如你想隨便改輸入分辨率就得重新編譯一次模型。理解了這個底層邏輯后面所有操作就順理成章了——先導出ONNX再用ATC工具轉(zhuǎn)成OM最后寫代碼加載OM做推理。這不是繞路而是NPU推理卡的標準工作流。2. 部署前準備驅(qū)動固件與CANN軟件棧2.1 裝機與配套版本檢查我踩過的第一個坑就是安裝順序和版本配套。Atlas這塊板卡到手后不是插上PCIe槽就能用還要裝三樣東西NPU驅(qū)動、固件、CANN工具包。這三者之間有嚴格的版本配套關(guān)系不能各裝各的。選版本時一定要去昇騰社區(qū)的“版本配套表”里查清楚驅(qū)動版本對應哪個固件版本對應哪個CANN版本再對應你打算用的PyTorch/ONNX算子版本。我一開始圖省事裝了最新驅(qū)動結(jié)果CANN不認npu-smi info能看見卡但跑ATC時直接報算子編譯失敗。折騰半天最后老老實實按照配套表重裝了一遍才正常。另外要注意硬件環(huán)境。Atlas 300V Pro 24G是PCIe接口的板卡服務器主板至少要留一個PCIe x16物理插槽功耗和散熱也要確認電源功率夠、機箱風道能帶走熱量。開發(fā)階段我用一臺塔式工作站插單卡完全不涉及多卡互聯(lián)環(huán)境簡單很多。2.2 安裝順序和關(guān)鍵環(huán)境變量安裝順序建議嚴格按這個來先裝NPU驅(qū)動再裝固件最后裝CANN Toolkit。驅(qū)動和固件一般有對應的run包或deb包執(zhí)行后會寫入/usr/local/Ascend/driver和/usr/local/Ascend/firmware。CANN Toolkit解壓后路徑通常在/usr/local/Ascend/ascend-toolkit。裝完CANN后最容易被忽略的是環(huán)境變量。每次開終端或?qū)懛漳_本都要source一下source /usr/local/Ascend/ascend-toolkit/set_env.sh這個腳本會把LD_LIBRARY_PATH、PYTHONPATH、ASCEND_HOME_PATH等關(guān)鍵變量設置好。如果沒source你importacl庫時會直接報找不到動態(tài)庫或者運行時提示libascendcl.so: cannot open shared object file這類問題十有八九就是環(huán)境變量沒配對。強烈建議在系統(tǒng)級配置里寫上比如在/etc/profile.d/下新建一個腳本開機自動source省得每次部署服務都要重新找路徑。2.3 用npu-smi確認設備正常安裝完成后第一件事就是確認設備狀態(tài)。npu-smi info類似NVIDIA的nvidia-smi能查看卡的工作狀態(tài)、溫度、顯存占用、驅(qū)動版本。npu-smi info正常輸出里能看到Atlas 300V Pro 24G的信息芯片狀態(tài)應該是“OK”。如果這里都看不到卡或者芯片狀態(tài)異常不要往下走先檢查驅(qū)動是否加載成功、PCIe設備是否被識別必要時重新插拔板卡或引導驅(qū)動模塊。驅(qū)動加載可以看內(nèi)核模塊lsmod | grep drv_pcie如果驅(qū)動模塊沒加載可以用npu-smi info的報錯信息排查通常是版本不匹配或系統(tǒng)內(nèi)核頭文件缺失。我習慣在裝機階段就確認好這一步因為它決定了后面所有環(huán)節(jié)能不能進行。3. YOLO模型轉(zhuǎn)換從PyTorch權(quán)重到OM模型3.1 為什么要轉(zhuǎn)OM前面說過Atlas推理卡運行的是OM離線模型不是PyTorch權(quán)重。OM模型是ATC工具根據(jù)目標芯片生成的靜態(tài)指令集有點像給NPU寫好的“機器碼”。運行時按圖執(zhí)行調(diào)度開銷極低。轉(zhuǎn)換時ATC要做幾件事解析ONNX圖結(jié)構(gòu)把PyTorch里的動態(tài)計算圖轉(zhuǎn)成靜態(tài)計算圖把ONNX算子映射成CANN算子做算子融合、內(nèi)存復用等優(yōu)化最終生成適配指定芯片型號的OM文件。所以OM模型和你手上的芯片型號強綁定。在Atlas 300V Pro上編譯的OM換到另一顆昇騰芯片上大概率跑不了需要重新轉(zhuǎn)換。3.2 ONNX導出要點我用的是YOLOv5sPyTorch權(quán)重先導出ONNX。YOLOv5官方倉庫自帶導出腳本python export.py --weights yolov5s.pt --include onnx --opset 11這里有幾個細節(jié)要注意。第一--opset不建議設太高CANN對ONNX算子支持有版本邊界過高的opset可能引入CANN不認識的算子。我實測opset 11比較穩(wěn)。第二導出前把模型輸入固定。YOLOv5默認導出的是動態(tài)batch版本輸入shape帶-1維度。ONNX動態(tài)shape不是不能轉(zhuǎn)OM但會帶來額外的動態(tài)shape處理初學者很容易在這里出錯。更穩(wěn)的做法是在導出腳本里固定輸入尺寸比如imgsz640后面ATC轉(zhuǎn)換也固定成1,3,640,640鏈路最簡單。第三不要在ONNX里導出NMS算子。YOLOv5原版導出的ONNX后處理部分是裸的候選框輸出NMS要自己做。某些倉庫會集成NMS算子但在NPU上這些算子未必兼容而且整個NMS放硬件上做輸出形狀無法固定反而不利于性能優(yōu)化。我的習慣是模型只做主干推理NMS和后處理全部在CPU側(cè)完成。3.3 ATC轉(zhuǎn)換命令與參數(shù)說明拿到ONNX后用ATC工具轉(zhuǎn)換atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_om \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --insert_op_confaipp.cfg \ --output_typeFP32 \ --logerror逐個解釋這些參數(shù)--model輸入的ONNX文件路徑。--framework5表示ONNX1表示MindSpore0表示TensorFlow我們這里用ONNX就是5。--input_shape固定輸入shape。images是ONNX輸入節(jié)點的名稱YOLOv5導出后一般叫images可以用Netron打開ONNX確認。形狀寫1,3,640,640表示batch1、3通道、高640寬640。--soc_version目標芯片型號字符串必須和你的卡對應。這里的Ascend310P3只是示例Atlas 300V Pro到底是哪個字符串一定要去查你這張卡對應CANN版本的《產(chǎn)品型號-昇騰軟件版本配套表》。填錯會在編譯階段直接報錯或者生成一個根本無法加載的OM。--insert_op_confAIPP配置文件路徑稍后詳細講。--output_type指定模型輸出數(shù)據(jù)類型。目標檢測后處理一般用FP32精度夠用。--logerror日志級別只打印錯誤信息。轉(zhuǎn)換失敗時再看debug日志。3.4 配置AIPP把預處理搬進硬件AIPP是Atlas硬件圖像預處理模塊可以在推理前自動完成縮放、裁剪、色域轉(zhuǎn)換、歸一化。YOLO的預處理需要在CPU端做resize、減均值除方差、RGB轉(zhuǎn)BGR這些如果全部在CPU做多路視頻時CPU容易成瓶頸。AIPP配置好后這些操作交給硬件CPU只負責讀圖。我用的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 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 }這里input_format: RGB888_U8表示輸入是RGB三通道、每個通道8bitvar_reci_chn_*是1/255即歸一化系數(shù)對應YOLO訓練時的歸一化到0-1操作。rbuv_swap_switch控制是否交換R和B通道如果你的PYTORCH訓練代碼輸入是RGB順序而JPG讀進來默認是BGR就需要在這里控制換通道。這個細節(jié)很多人會漏結(jié)果推理結(jié)果出來坐標對但類別全錯。AIPP還有更大的作用——支持直接送原始分辨率大圖讓硬件完成等比縮放和居中填充這樣CPU端不需要自己實現(xiàn)letterbox。不過這樣配置稍微復雜我建議第一版還是CPU端把圖resize好喂給AIPP只做歸一化跑通再優(yōu)化。4. 用pyACL寫推理代碼跑通YOLO4.1 初始化和加載模型CANN的Python接口叫pyACL代碼風格和CUDA的C接口很像都是先初始化、設置設備、創(chuàng)建stream再加載模型執(zhí)行。先看初始化這一段import acl def init(): ret acl.init() assert ret 0, acl.init failed ret acl.rt.set_device(0) # 使用第0張卡 ret acl.rt.set_context(0) # 創(chuàng)建上下文 context acl.rt.create_context(0) ret acl.rt.create_stream(0) # 創(chuàng)建stream這段代碼有幾個容易踩的坑acl.init()通常只需要調(diào)用一次進程結(jié)束前要記得調(diào)用acl.finalize()釋放set_device(0)的設備編號要和npu-smi info里看到的卡序號對應多卡時尤其注意。模型加載用acl.mdl.load_from_filemodel_id acl.mdl.load_from_file(yolov5s_om.om) model_desc acl.mdl.create_desc() acl.mdl.get_desc(model_desc, model_id)加載成功后必須拿到模型描述信息里面包含輸入、輸出的shape和大小。這些信息后面申請內(nèi)存時要用。4.2 準備輸入數(shù)據(jù)與執(zhí)行推理輸入數(shù)據(jù)要先轉(zhuǎn)成numpy數(shù)組再拷貝到設備側(cè)內(nèi)存。YOLOv5預處理后的輸入是1,3,640,640、每個像素值范圍0-1的float32數(shù)組。import numpy as np input_data np.zeros((1, 3, 640, 640), dtypenp.float32) # 把resize后的圖像按HWC轉(zhuǎn)CHW、歸一化之后填入input_data _, input_size acl.mdl.get_input_size_by_index(model_id, 0) input_buffer, ret acl.rt.malloc(input_size, 2) # 設備側(cè)內(nèi)存2是ACL_MEM_MALLOC_NORMAL_ONLY acl.rt.memcpy(input_buffer, input_size, input_data.tobytes(), input_size, 1) # 1是H2D拷貝這一步最容易出問題的是input_size。它是bytes大小不是元素個數(shù)。YOLOv5s的輸入是1*3*640*640*4字節(jié)float32也就是4915200字節(jié)。不要想當然寫死最好從get_input_size_by_index動態(tài)獲取避免以后換模型就崩。執(zhí)行推理stream_id acl.rt.get_stream() ret acl.mdl.execute(stream_id, model_id, [input_buffer], [output_buffer])acl.mdl.execute是同步接口執(zhí)行完需要從輸出buffer拷回主機側(cè)。異步版本acl.mdl.execute_async后面做多路并發(fā)時再考慮。4.3 后處理坐標解碼與NMSYOLOv5輸出形狀是1, 25200, 85表示有25200個候選框每個候選框85個值——前4個是坐標(x, y, w, h)或(x1, y1, x2, y2)第5個是objectness置信度后面80個是COCO各類別概率。拿到原始輸出后要做三件事坐標解碼、置信度過濾、NMS。坐標解碼要看你導出的模型輸出格式。如果輸出是中心坐標加寬高(xywh)要先轉(zhuǎn)成xyxy格式boxes[:, 0] cx - w / 2 boxes[:, 1] cy - h / 2 boxes[:, 2] cx w / 2 boxes[:, 3] cy h / 2然后再縮放回原始圖像尺寸。別忘了letterbox的padding要減掉否則框會整體偏移。置信度過濾一般把閾值設在0.3-0.5之間太低會有大量假陽性。NMS我用最簡單的torchvision.ops.nms或者OpenCV的cv2.dnn.NMSBoxes都行關(guān)鍵是NMS的IoU閾值YOLOv5默認是0.45。這塊沒什么黑科技但性能調(diào)優(yōu)時要注意25200個候選框逐類別做NMS比較花CPU時間如果連續(xù)跑多路視頻后處理CPU占用會很高后面我會講怎么優(yōu)化。5. 工程化部署的提速與避坑5.1 固定shape優(yōu)先動態(tài)batch謹慎使用ATC轉(zhuǎn)換時可以選擇固定一個輸入shape也可以用--dynamic_batch_size支持多個batch。動態(tài)batch的代價是模型體積變大、推理啟動時間變長而且部分算子融合策略會被禁用。我的觀點很直接第一版項目一律固定shape。先讓整個鏈路跑通、結(jié)果正確再去折騰動態(tài)shape。如果后續(xù)有多batch需求明確兩個batch就編譯兩個輸入shape的模型不要用過于寬泛的動態(tài)范圍。實際上大多數(shù)邊緣推理場景都有固定的視頻路數(shù)和固定分辨率動態(tài)shape的收益遠小于它帶來的復雜度。5.2 多進程多路并發(fā)怎么搭Atlas 300V Pro 24G有24GB顯存單路YOLOv5s推理占顯存約2-3GB理論上一張卡能跑多路。但千萬不要把所有視頻流塞進同一個進程里串行推理。更合理的做法是一個進程處理一路視頻或者把幀收集到隊列后按batch推理。實際項目中我用的是“一卡多進程”模式每個視頻流一個進程進程內(nèi)做解碼、預處理、推理、后處理。顯存和NPU算力由驅(qū)動做調(diào)度。這樣隔離性最好單路崩潰不影響其他路。如果希望高峰期吞吐更高再在一路視頻內(nèi)把多幀拼成一個batch用batch4的OM模型做批量推理。多進程模式下要留意acl.init和set_device在每個進程內(nèi)都要調(diào)用一次不要圖省事在父進程初始化后fork子進程否則子進程的設備上下文狀態(tài)不可控容易出現(xiàn)推理超時。5.3 量化、DVPP和算子融合的提速空間部署穩(wěn)定之后性能優(yōu)化有幾條路第一INT8量化。YOLOv5s用FP16/FP32推理已經(jīng)不錯但INT8能把吞吐再提一截。CANN生態(tài)里有AMCT模型壓縮工具支持后訓練量化用一批校準圖片統(tǒng)計量化因子。量化后模型體積變小、推理速度接近翻倍代價是精度可能掉0.5-2個點。是否需要取決于你的業(yè)務精度底線。第二DVPP硬解碼。前面說的視頻編解碼模塊不是擺設。用dvpp接口直接解碼視頻流輸出YUV幀再做AIPP轉(zhuǎn)RGB能省掉CPU軟解和顏色空間轉(zhuǎn)換的開銷。實測下來8路1080P視頻流的解碼CPU占用能從60%降到10%以內(nèi)。不過DVPP對輸入格式和分辨率對齊有要求比如寬高要偶數(shù)對齊這些細節(jié)得翻CANN的DVPP文檔。第三算子融合。ATC編譯時默認會做常見融合比如ConvBNReLU融合。如果發(fā)現(xiàn)某些算子在圖上單獨存在導致性能瓶頸可以看CANN提供的算子融合配置項但一般用默認設置就夠了。不要為了優(yōu)化而優(yōu)化先量化再動融合。6. 常見問題排查速查表下面這些是部署過程中高頻踩坑整理成速查表遇到類似報錯直接按表查。現(xiàn)象可能原因解決辦法acl.init失敗錯誤碼507008驅(qū)動/固件/CANN版本不匹配設備文件權(quán)限異常內(nèi)核模塊未加載檢查npu-smi info是否能正常顯示確認驅(qū)動和固件版本重新加載drv_pcie模塊按配套表重裝ATC報錯EZ0101算子不支持ONNX算子無法映射到CANN算子降低opset版本升級CANN查看日志定位具體算子改網(wǎng)絡結(jié)構(gòu)或換成等價算子ATC報錯SOC版本不匹配--soc_version填錯查產(chǎn)品型號對應表用npu-smi info確認芯片類型推理結(jié)果全0或坐標偏移輸入通道順序錯誤、letterbox padding沒去掉、歸一化系數(shù)不對、輸出shape理解錯打印輸入和輸出shape核對預處理鏈路每個環(huán)節(jié)用一張圖逐層對比輸出推理結(jié)果類別錯亂但坐標正確RGB/BGR通道交換配置錯誤在AIPP配置或預處理中交換R和B通道保持訓練時輸入一致設備顯存不足0x506010單卡顯存被多路進程占滿顯存未釋放減小batch檢查是否有資源泄漏用npu-smi info監(jiān)控顯存占用DVPP解碼報錯輸入分辨率或編碼格式不符合DVPP要求確認寬高偶數(shù)對齊確認輸入是H.264/H.265裸流或符合要求的封裝查文檔對齊要求模型加載失敗提示文件損壞或版本不符OM文件與目標芯片型號不匹配用當前芯片型號重新ATC編譯OM其中推理結(jié)果全0這個問題我調(diào)試了最久。最后發(fā)現(xiàn)是預處理代碼里np.transpose寫錯了維度順序圖像從HWC轉(zhuǎn)CHW時通道順序變成了不入流的結(jié)果模型吃進去的完全是噪聲。所以排查這個問題時建議先用一張純色圖跑推理如果輸出也和純色圖對不上說明預處理鏈路有硬傷先修預處理再看模型。環(huán)境類問題里最隱蔽的是“驅(qū)動裝了、CANN裝了、但acl.init還是失敗”。我當時查了半天才發(fā)現(xiàn)是用戶權(quán)限不夠設備節(jié)點只能root訪問。普通用戶跑推理前要給設備節(jié)點加訪問權(quán)限或者在服務里以合適用戶運行。生產(chǎn)環(huán)境建議用昇騰容器鏡像鏡像里已經(jīng)配好權(quán)限和環(huán)境變量能少踩很多坑。7. 部署之外把Atlas當成正式生產(chǎn)設備來管理項目跑通只是第一步真正把它放上生產(chǎn)再看還有一些管理層面的經(jīng)驗值得記下來。首先是開機自檢和恢復。服務器重啟后驅(qū)動不一定自動加載npu-smi info可能暫時看不到卡。生產(chǎn)環(huán)境建議做啟動后的設備自檢腳本檢測到設備異常就觸發(fā)告警而不是等推理服務超時了才發(fā)現(xiàn)。其次是顯存監(jiān)控。多路進程共享一張卡時顯存分配不均衡會互相影響。我在實際部署中給每個進程指定了顯存上限策略并在監(jiān)控面板上持續(xù)觀察每路占用的顯存曲線。一旦某一路異常上漲一般是圖像分辨率突變或模型加載了額外buffer及時重啟該路進程不影響其他路。最后是容器化部署。昇騰提供了配套的CANN容器鏡像把驅(qū)動、固件、CANN都封裝好宿主機只需要裝驅(qū)動。我后來把推理服務打成鏡像內(nèi)部初始化時再source環(huán)境變量代碼統(tǒng)一管理換機器部署時拉鏡像就能跑比手工裝環(huán)境省太多時間。唯一的注意點是容器要映射設備節(jié)點啟動時加--device/dev/davinci0和對應管控節(jié)點否則容器里看不到卡。整套流程走下來我最大的感受是Atlas這套東西不是拿來即用的即插即型生態(tài)它需要你花一兩天時間把環(huán)境、格式、參數(shù)這些基礎(chǔ)事情磨平。但只要鏈路通了它在多路推理場景下的穩(wěn)定性和性能是很能打的。給剛開始接觸的同學一個建議第一版不求多路、不求動態(tài)、不求INT8只用固定shape、單路、FP32把全流程跑通。跑通了你對整個鏈路的理解就建立起來了后面加容器、加并發(fā)、加量化都是水到渠成的事。如果一上來就想直接跑一個復雜的多路動態(tài)模型每個環(huán)節(jié)都在報錯反而容易勸退。