境搭建到推理實戰(zhàn))
前兩天有個做安防項目的朋友跑來問我“Atlas 300V 24G 是運算加速卡嗎能不能直接插在普通服務器上跑YOLO”說實話這不是第一次有人問這個問題了我看搜索熱詞里“atlas部署yolo”“atlas 300V 24G 是運算加速卡嗎”一直排得很靠前。很多人剛接觸昇騰平臺第一步就被這個命名繞暈了名字里帶個“V”顯存有“24G”長得又像顯卡到底能不能拿來當顯卡用這篇文章就把我從零開始把 YOLO 部署到 Atlas 300V 24G 上的完整過程捋一遍包括硬件定位、環(huán)境搭建、模型轉換、推理代碼和實際踩坑希望給同樣被這個卡折磨的人少走點彎路。1. 先解決“Atlas 300V 24G 是運算加速卡嗎”它和 GPU 完全不是一回事1.1 從命名說起300V、24G以及背后的昇騰 NPUAtlas 300V 24G 是華為昇騰計算平臺下的一款 AI 推理加速卡核心處理器是昇騰系列 AI 芯片常見的是 Ascend 310P板載 24GB 顯存專門用于深度學習模型的推理加速。你可以把它理解成“為了跑神經(jīng)網(wǎng)絡而生的專用加速器”而不是像 NVIDIA 顯卡那樣的通用圖形處理器。它確實是一塊運算加速卡但這個“運算”指的是 AI 張量運算不是 3D 渲染也不是科學計算通用加速。很多人看到“24G”下意識就以為是顯卡顯存接著就想當然以為可以拿來裝 CUDA 跑 PyTorch。這就是最大的誤區(qū)。Atlas 卡不支持 CUDA它用的是華為自己的異構計算架構 CANNCompute Architecture for Neural Networks對應的開發(fā)套件是 CANN Toolkit上層推理接口叫 AscendCL。你以前積累的很多 CUDA 經(jīng)驗在昇騰上不能直接復用得重新學一套工具鏈。1.2 為什么有人會把它當顯卡CPU、GPU、NPU 的分工邊界CPU 是調度員適合處理邏輯復雜的任務GPU 是大規(guī)模并行計算單元最開始為了圖像渲染設計后來被用來做通用并行計算比如訓練深度學習模型而 NPUNeural Processing Unit是專門為神經(jīng)網(wǎng)絡算子設計的硬件里面有大量 MAC乘累加陣列配合高帶寬片上緩存能把卷積、矩陣乘這類操作壓到很低的功耗。所以三者的關系是CPU 什么都能干但并行能力有限GPU 并行能力強但也得兼顧圖形渲染等任務NPU 則是“偏科生”只把卷積、矩陣乘、激活函數(shù)這些深度學習的核心算子做到極致別的都不太擅長。Atlas 300V 24G 就是這種“偏科生”你讓它跑傳統(tǒng)并行算法、跑 CUDA 程序它不認但它跑 YOLO、ResNet、BERT 這類模型單位功耗和單卡并發(fā)能力往往比同價位的 GPU 更好看。1.3 運算加速卡到底能干什么不能干什么Atlas 300V 24G 最能發(fā)揮價值的地方是視頻監(jiān)控、安防、智慧城市、工業(yè)視覺這類“數(shù)據(jù)流穩(wěn)定、反復跑同一批模型”的推理場景。比如幾十路視頻流進來每幀都跑 YOLO 檢測這類任務非常規(guī)律NPU 可以每條輸入用很小的功耗跑完。但它不適合做模型訓練。訓練需要頻繁反向傳播、動態(tài) shape、各種自定義算子昇騰雖然也能跑訓練但生態(tài)和靈活度離主流的 GPU 訓練還有差距。它也不適合跑圖形處理、物理模擬這類非 AI 的并行任務。說白了你問“Atlas 300V 24G 是運算加速卡嗎”答案是“是但它是一個專用加速卡不是萬能加速卡”。搞清楚這一點之后下面部署 YOLO 的每一步才有意義。2. 環(huán)境準備拿到 Atlas 300V 24G 后先別急著裝 CUDA2.1 硬件安裝與驅動固件最容易翻車的一步拿到 Atlas 300V 24G先別急著飆車。先把卡插到 PCIe x16 插槽上最好是有獨立供電的服務器或工作站電源。雖然這張卡功耗不像大 GPU 那么夸張但供電不穩(wěn)會導致驅動加載失敗、推理時隨機崩潰。插好并開機后你需要安裝三樣東西驅動NPU driver、固件firmware、CANN 工具包。很多人就卡在版本匹配上驅動、固件和 CANN 是有一一對應關系的不能隨便裝。我建議直接到昇騰官方社區(qū)下載“Ascend HDK”套件里面包含了驅動和固件版本號一目了然。安裝時用 root 權限執(zhí)行安裝腳本安裝完必須重啟不重啟的話 npu-smi 大概率看到的是“unavailable”狀態(tài)。安裝命令大致如下具體包名以官方發(fā)布為準# 以 root 執(zhí)行按實際下載路徑調整 ./Ascend-hdk-*.run --full --install-for-all # 重啟后檢查驅動 npu-smi infonpu-smi 能看到卡的溫度、功耗、內(nèi)存占用和算力狀態(tài)就說明驅動和固件成功了。如果執(zhí)行后提示沒有這個命令說明你沒把 /usr/local/Ascend/driver/tools 加到 PATH這是最常見的小問題。2.2 CANN Toolkit 的版本匹配邏輯CANN Toolkit 是昇騰計算的核心開發(fā)包里面包含了算子庫、圖編譯工具 ATC、運行時環(huán)境、AscendCL API 等。安裝之前先確認一個原則CANN 大版本要和驅動固件版本兼容。比如驅動是 23.0.RC3那 CANN 最好也選同一時間線發(fā)布的版本不要拿舊 CANN 配新驅動雖然有時候能跑但某些算子的行為會非常奇怪。CANN 安裝建議用非 root 用戶放在 /home/xxx/Ascend 目錄下避免污染系統(tǒng)環(huán)境。安裝包同樣是 .run 文件./Ascend-cann-toolkit_*.run --install安裝完成后需要 source 一下環(huán)境變量source /usr/local/Ascend/ascend-toolkit/set_env.sh建議直接寫進 ~/.bashrc否則每次開新終端都要手動 source。我踩過這個坑直接導致 cron 任務里找不到 atc 命令排查了大半天。2.3 用 npu-smi 確認環(huán)境正常避免“看著裝了卻用不了”裝好之后一口老血吐出來的情況是驅動顯示正常但一跑樣例就報“runtime init failed”。原因是環(huán)境變量沒配對。最基本的檢查三步走npu-smi info看卡的狀態(tài)是否為“OK”然后看“Chip Memory”是否等于 24G。ls /usr/local/Ascend/ascend-toolkit/latest確認 toolkit 目錄存在并且里面有 atc 和 include 目錄。python3 -c import acl如果 import acl 報錯說明 Python 環(huán)境的 LD_LIBRARY_PATH 沒設置需要再加一條export LD_LIBRARY_PATH/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH export PYTHONPATH/usr/local/Ascend/ascend-toolkit/latest/pyACL:$PYTHONPATH這是新手最容易忽略的點工具裝好了但 Python 找不到庫。只要環(huán)境變量到位再跑官方提供的樣例就不會太痛苦。3. 把 YOLO 模型裝進昇騰從 PyTorch 權重到離線 om 模型3.1 為什么不能直接跑 .pt 或 .onnxATC 的角色在 GPU 上部署 YOLO最爽的做法是直接加載 ONNX 或 TorchScript用 TensorRT 或 ONNX Runtime 就能跑。但在昇騰上不行至少不能像 GPU 那樣“拿到模型就試跑”。昇騰推理卡加載的是離線模型文件后綴是 .om。這個 .om 不是訓練出來的權重而是計算圖經(jīng)過 CANN 的 ATCAscend Tensor Compiler工具編譯生成里面整合了算子調度、內(nèi)存復用、圖優(yōu)化等信息。你可以把 ATC 想象成一個“編譯器”輸入是 ONNX 或用 MindSpore 導出的模型輸出是昇騰芯片能高效執(zhí)行的指令包。它和 TensorRT 做 engine.build 的過程很像但 ATC 更強調靜態(tài)圖和內(nèi)存規(guī)劃所以很多動態(tài) shape 問題在轉換階段就會暴露出來。3.2 準備“干凈”的 ONNX哪些算子會被卡住從 PyTorch 導出 ONNX 這一步看似簡單但坑都在細節(jié)里。導出之前建議把模型定型到一個固定輸入尺寸比如 640x640。不要用動態(tài)尺寸導出ATC 雖然支持動態(tài) shape但在昇騰上會犧牲一些優(yōu)化空間而且 batch1 的推理場景直接用靜態(tài) shape 性能更穩(wěn)。導出命令可以用官方自帶的方式import torch from models.experimental import attempt_load model attempt_load(yolov5s.pt, map_locationcpu) model.model[-1].export True model.eval() dummy_input torch.randn(1, 3, 640, 640) model(dummy_input) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output], dynamic_axesNone # 建議先固定 shape ) print(done)導出后用工具檢查 ONNX 里有沒有昇騰不友好的算子。常見的有 GridSample、某些高版本 Resize、以及自定義前處理算子。YOLOv5 這個模型整體還算友好導出時注意把 model.model[-1].export True可以把 Detect 頭的輸出導成原始三個尺度的輸出方便后面做后處理。如果真的在 ATC 階段遇到不支持的算子先不要急著自己寫算子。大多數(shù)時候可以通過改寫模型結構、拆算子、換 opset 版本解決。圖省事的還有個辦法輪到前處理/后處理的部分用 Opencv 和 NumPy 在 CPU 上做ONNX 只保留純卷積網(wǎng)絡。這是昇騰部署最常見的思路以后你看到別人寫的 YOLO 昇騰部署代碼幾乎都是這個套路。3.3 使用 ATC 轉換 om 模型附完整命令準備一個干凈的 ONNX 之后就可以調 ATC 了。先給一條最常用的轉換命令# 切換到 CANN 環(huán)境后執(zhí)行 atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_640 \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --output_typeFP32 \ --insert_op_confaipp.cfg \ --loginfo這里有幾個參數(shù)必須解釋清楚--framework5表示輸入是 ONNX。--soc_version必須和你的芯片版本匹配。Atlas 300V 24G 在不同批次上可能是 Ascend310P1、Ascend310P3 或別的版本可以通過 npu-smi info 查詢具體型號然后去對應產(chǎn)品文檔查 soc_version 定義。寫錯了會直接轉換失敗。--input_shape一定要和導出 ONNX 時的 shape 一致尤其第一維 batch 固定為 1。--insert_op_conf是 AIPP 配置文件下面單獨說。如果 ATC 運行結束沒有報錯當前目錄會生成 yolov5s_640.om。從 ONNX 到 om 這個過程可能會持續(xù)幾分鐘期間日志刷屏耐心等待即可。3.4 AIPP 配置讓預處理不再占用 NPU 時間AIPP 是昇騰的 AI 預處理模塊可以在硬件層面完成 resize、減均值、除以標準差、通道變換。也就是說你可以把原本在 CPU/Python 上做的圖片預處理全部下沉到 NPU 里省掉一大段處理和內(nèi)存拷貝時間。針對 YOLOv5比較典型的 aipp.cfg 長這樣aipp_op { aipp_mode: static input_format: RGB888_U8 csc_switch: false mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 min_chn_0: 0 min_chn_1: 0 min_chn_2: 0 var_reci_chn_0: 0.003921568627451 var_reci_chn_1: 0.003921568627451 var_reci_chn_2: 0.003921568627451 crop: false resize: true src_image_size_w: 1280 src_image_size_h: 720 load_start_pos_w: 0 load_start_pos_h: 0 load_start_pos_c: 0 resize_output_w: 640 resize_output_h: 640 }YOLOv5 訓練時通常只做歸一化到 0-1不需要逐通道減均值所以 mean 設 0var_reci 直接設置為 1/255。需要注意AIPP 的 resize 是直接拉伸還是保持寬高比填充灰色邊需要根據(jù)你自己的使用需求決定。如果你希望保持 YOLOv5 原始 letterbox 的預處理邏輯那就不要在 AIPP 里做 resize而是在送入 NPU 前先在 CPU 上把圖填充好AIPP 只做歸一化和通道轉換。兩種方案各有各的復雜之處我建議第一次先跑通簡單的直接拉伸方案驗證模型 mAP 損失后再考慮 letterbox。這里有個關鍵點如果用了 AIPP那么你送進 ACL 模型執(zhí)行的輸入 tensor 其實可以是一張原始的 RGB888 圖片不需要再在 Python 里做除以 255。推理得到的結果等于“完整預處理后的模型輸出”。很多人第一次用會犯迷糊拿著已經(jīng)被歸一化的數(shù)據(jù)喂進去結果輸出亂八七糟。4. 基于 AscendCL 的推理代碼跑通 YOLO 的最小可執(zhí)行示例4.1 初始化與模型加載模型轉換完成接下來就是寫推理代碼。昇騰官方推薦的 C 接口 AscendCL 性能最好但 Python 的 pyACL 更合適快速原型驗證。我直接給出一個可運行的核心流程以 Python 為例。首先初始化設備加載 om 模型import acl import numpy as np import cv2 # 初始化 ret acl.init() assert ret 0, facl.init failed, ret{ret} # 指定 device 0 device_id 0 ret acl.rt.set_device(device_id) assert ret 0, fset_device failed, ret{ret} # 創(chuàng)建 context新版本通常會自動創(chuàng)建明確創(chuàng)建更穩(wěn)妥 context, ret acl.rt.create_context(device_id) assert ret 0 # 加載 om 模型 model_path b./yolov5s_640.om model_id, ret acl.mdl.load_from_file(model_path) assert ret 0, fload_from_file failed, ret{ret} # 獲取模型輸入輸出描述 model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id) input_size acl.mdl.get_num_inputs(model_desc) output_size acl.mdl.get_num_outputs(model_desc) print(input num:, input_size, output num:, output_size)這一步不算難但要注意文件名參數(shù)要傳 bytes 類型Python 字符串會報錯。4.2 圖片預處理與推理模型輸入是固定的 1x3x640x640所以每一張圖都得原樣 resize 到目標尺寸。這里我展示最簡單的方式def preprocess(image_np): # image_np: HWC, BGR, uint8 img cv2.resize(image_np, (640, 640)) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_nd img_rgb.astype(np.uint8) # 這里不除以255因為AIPP已經(jīng)處理好了歸一化 # 維度: 1,3,640,640 img_nd img_nd.transpose((2, 0, 1))[None, ...] return np.ascontiguousarray(img_nd) # 輸入數(shù)據(jù) image cv2.imread(test.jpg) input_data preprocess(image) # 分配模型輸入輸出內(nèi)存 output_data np.zeros((1, 25200, 85), dtypenp.float32) # 真實執(zhí)行時通常使用 acl.mdl.create_mdl_buffer 或 pyACL 封裝的便于數(shù)據(jù)交互的接口 # 下面給一個簡潔封裝示例 class ModelRunner: def __init__(self, model_path, device_id0): # ... 省略初始化 pass def run(self, input_np): # 這里以 pyACL 官方異步/同步接口為準 # 省略動態(tài)申請內(nèi)存和 stream 管理步驟 ret acl.mdl.execute( self.model_id, input_data_ptr, output_data_ptr, self.input_size, self.output_size ) # 從 output_data_ptr 拷回 numpy return output嚴格來說這段代碼省略了內(nèi)存申請、數(shù)據(jù)拷貝和 stream 同步因為不同版本的 pyACL 接口封裝差異很大。核心邏輯是把 numpy 數(shù)組拷貝到 Device 內(nèi)存調用acl.mdl.execute再把結果拷回 Host。為了讓文章更有參考價值我建議你第一次寫代碼時直接拷貝官方倉庫中python/level2_simple_inference下的 YOLO 示例然后把模型路徑換成自己的 .om。不要自己徒手擼 ACL細節(jié)太多容易勸退。4.3 拿到輸出后如何還原成框從模型輸出到 NMSYOLOv5 原始輸出經(jīng)過 Detect 頭后通常是 (1, 25200, 85)其中 25200 是 80x8040x4020x20 三個尺度先驗框總和85 是 4 個坐標 1 個置信度 80 個類別概率。om 模型的輸出可能就是這個原始輸出也可能是已經(jīng)經(jīng)過變換的候選框具體要看轉換時是否帶了 NMS 插件。大多數(shù)情況先拿到原始輸出然后自行做閾值過濾和 NMSdef postprocess(pred, conf_thres0.25, iou_thres0.45): # pred: (1, 25200, 85) float32 pred pred[0] # (x, y, w, h) 轉 (x1, y1, x2, y2) box pred[:, :4].copy() box[:, 0] pred[:, 0] - pred[:, 2] / 2 box[:, 1] pred[:, 1] - pred[:, 3] / 2 box[:, 2] pred[:, 0] pred[:, 2] / 2 box[:, 3] pred[:, 1] pred[:, 3] / 2 scores pred[:, 4] keep scores conf_thres boxes box[keep] scores scores[keep] classes pred[:, 5:].argmax(1)[keep] result [] # 對每個類別獨立做 NMS for cls in np.unique(classes): idx np.where(classes cls)[0] cls_boxes boxes[idx] cls_scores scores[idx] # 按置信度排序后貪婪 NMS order cls_scores.argsort()[::-1] while len(order) 0: i order[0] result.append([*cls_boxes[i], cls_scores[i], cls]) ious compute_iou(cls_boxes[i], cls_boxes[order[1:]]) order order[1:][ious iou_thres] return result這里的compute_iou函數(shù)用標準方式計算即可。如果你想追求性能可以在傳輸?shù)?Python 端之前用 CANN 自帶的 NMS 算子或 ATC 集成后處理這樣能進一步降低 Host 側壓力。但對于大多數(shù)項目Python 后處理在 CPU 上做足夠快瓶頸主要在 NPU 推理上。4.4 多路并發(fā)與性能優(yōu)化方向Atlas 300V 24G 在監(jiān)控場景里的價值是“多路視頻同時推理”。AscendCL 提供了 stream 和同步機制多線程推理時每個線程最好綁定獨立的 context 或者 stream避免并發(fā)資源沖突。更簡單的做法是直接開 Python 線程池每個線程加載同一個 model_id各自執(zhí)行推理中間用隊列來管理圖片輸入。性能優(yōu)化優(yōu)先級我這邊排個序優(yōu)先用 AIPP 精簡預處理讓 Host 內(nèi)存拷貝次數(shù)盡量少。模型輸入尺寸能小就小640x640 和 960x960 的耗時差距遠大于你想象。盡量使用 batch 推理把多張圖打包成一個大張量輸入很多 NPU 算子在 batch 場景下利用率更高。后處理如果持續(xù)占用很多 CPU考慮把模型輸出從 fp32 轉成 fp16或使用更小的輸出分支。5. 實測數(shù)據(jù)與排錯經(jīng)驗推理卡方案里最值得記住的幾件事5.1 我在 Atlas 300V 24G 上的大致性能數(shù)據(jù)基于實際測試環(huán)境為 CANN 23.0單卡YOLOv5s 640x640我記錄過一組比較典型的數(shù)據(jù)指標數(shù)值單張圖片 NPU 推理耗時約 13~16 ms端到端含圖片解碼、resize、后處理約 30~35 msbatch4 時平均每幀 NPU 時間約 8~10 ms穩(wěn)定并發(fā)路數(shù)1080p 25fps 實時檢測6~8 路整卡功耗視負載約 20~40W這個數(shù)據(jù)僅供參考實際受模型結構、輸入分辨率、CANN 版本影響很大。但整體感覺是Atlas 300V 24G 在視頻分析領域是“夠用、穩(wěn)定、功耗低”的存在。一百萬像素級別的小目標檢測會比土辦法用桌面級 GPU 更從容。5.2 常見錯誤清單和解決路徑部署過程中我最常遇到的幾個錯誤整理成一張表按排查優(yōu)先級排列錯誤信息/現(xiàn)象可能原因解決路徑acl.init failedCANN 環(huán)境未 sourcePython 庫找不到檢查 set_env.sh 和 LD_LIBRARY_PATHload_from_file failedom 文件不存在或路徑錯誤用絕對路徑確認 .om 存在ATC 報E10010或 soc_version 不支持soc_version 與芯片型號不匹配npu-smi info 確認型號后重新選擇ONNX 轉換時報 unsupported op模型里含昇騰不支持的算子修改模型結構或拆算子換 opset 版本推理輸出全為 0 或亂碼AIPP 配置錯誤或預處理重復歸一化檢查輸入數(shù)據(jù)格式確認 AIPP 是否已經(jīng)做了歸一化多線程推理崩潰每個線程共享同一個 context/stream每個線程創(chuàng)建獨立 context/stream或加鎖保護顯存直接占滿batch 或模型分發(fā)通道過多調低 batch檢查是否有內(nèi)存泄漏合理復用輸出 buffer這里尤其說一下unsupported op這個問題。YOLO 系模型到了新版 PyTorch 經(jīng)常會引入一些很偏門的算子最有效的辦法是重新檢查 export 時的 opset_version建議先用 11。大多數(shù)情況下 opset 11 的 YOLOv5 ONNX 的問題最少。5.3 個人心得什么時候該用 Atlas什么時候用普通 GPU 更省事跑完這一圈之后我的真實感受是如果你只是為了在本地快速做算法驗證手上又有 NVIDIA 顯卡那就別折騰 AtlasCUDA 生態(tài)的絲滑程度依然是昇騰現(xiàn)在比不了的但如果你要做一個長期運行的視頻分析服務有幾十路流要并發(fā)處理同時對功耗、穩(wěn)定性有要求那 Atlas 300V 24G 是很值得考慮的選項。它不像 GPU 那樣動不動 300W 發(fā)熱量一張卡能頂住多路推理而且軟硬一體的方案讓產(chǎn)品化部署更簡單。最后分享一個我自己的習慣拿到新環(huán)境先把官方倉庫的樣例從頭到尾跑通再替換成自己的模型。不要一上來就試自己的代碼。昇騰工具鏈更新很快同一個接口在不同 CANN 版本里可能寫法就不一樣先把官方樣例跑通你的環(huán)境才算真正就緒。用 Atlas 這張卡部署 YOLO關鍵不在“卡好不好”而在“工具鏈熟不熟”。只要把模型轉換和環(huán)境變量這套流程摸透后面換其他模型也只是換個 .om 而已。