
前兩天有位朋友跑來問我Atlas 300V 24G 這卡是運(yùn)算加速卡嗎網(wǎng)上說法實(shí)在太亂了。我第一反應(yīng)是——這問題還真不是一句是或不是能說清的。很多剛接觸昇騰生態(tài)的人把 Atlas 300V 當(dāng)成一塊可以無腦替代 GPU 的通用加速卡買回來第一件事就想跑 PyTorch 訓(xùn)練腳本結(jié)果發(fā)現(xiàn) CUDA 不能用、模型加載不出來接著就開始懷疑人生。這篇文章就拿我這幾年在 Atlas 系列推理卡上跑 YOLO 的實(shí)際經(jīng)驗(yàn)把這卡的真實(shí)定位、部署 YOLO 時(shí)需要走的完整鏈路以及那些文檔里從來不寫的坑一次性講透。先說結(jié)論Atlas 300V 24G 是一張 AI 推理加速卡服務(wù)的目標(biāo)是把訓(xùn)練好的模型以更高吞吐、更低功耗跑起來不是為通用計(jì)算設(shè)計(jì)的。所以你要拿它跑 YOLO 推理完全沒問題但前提是得按照昇騰的玩法來。如果你本來就是在做視頻流目標(biāo)檢測(cè)、工業(yè)質(zhì)檢、邊緣盒子之類的項(xiàng)目這塊卡算是非常合適的選擇可你要是為了補(bǔ)一張訓(xùn)練卡才看它那大概率會(huì)踩得不輕。1. 一張常被誤認(rèn)成通用計(jì)算卡的推理專用卡1.1 先搞清楚它到底是不是運(yùn)算加速卡加速卡這三個(gè)字很迷惑人。NVIDIA 的 T4、A10 也經(jīng)常被叫加速卡但大家默認(rèn)它們能跑 CUDA、能訓(xùn)練、能通用計(jì)算。Atlas 300V 24G 不一樣它是一款推理卡底層基于昇騰 310P 系列芯片設(shè)計(jì)目標(biāo)是把已經(jīng)訓(xùn)練好的模型以離線轉(zhuǎn)換后的 OM 格式高效執(zhí)行。你可以把它理解成一個(gè)專門為模型推理優(yōu)化的加速器而不是一臺(tái)小 GPU。我用一張表把關(guān)鍵差異列出來應(yīng)該比文字更直觀維度Atlas 300V 24G常見 GPU如 RTX 3090主要用途AI推理加速訓(xùn)練 / 推理 / 通用計(jì)算軟件棧CANN / MindX SDK / pyACLCUDA / cuDNN / TensorRT模型接入方式ONNX/PB等轉(zhuǎn)換OM后加載原生PyTorch/TensorFlow直接跑顯存/內(nèi)存24GB24GB典型功耗較低具體以型號(hào)為準(zhǔn)較高適合場(chǎng)景線上推理服務(wù)、邊緣計(jì)算、視頻分析模型訓(xùn)練、科學(xué)計(jì)算、推理這張卡上的 24G 顯存經(jīng)常讓人誤以為它可以當(dāng) 3090 用。但實(shí)際上它沒有完整的可編程通用架構(gòu)你不能直接在它上面寫一段任意邏輯讓它跑。昇騰的編程范式是先把模型離線編譯成 OM再通過 ACLAscend Computing Language接口加載執(zhí)行或者用 MindX 這類上層套件來做服務(wù)化。也就是說它的強(qiáng)項(xiàng)是執(zhí)行模型不是承載訓(xùn)練邏輯。1.2 24G 顯存到底能帶來什么實(shí)際改變既然顯存有 24G那最大優(yōu)勢(shì)自然是裝得下更大模型、開得起更大 batch。我實(shí)際測(cè)試下來YOLOv5s 這種輕量模型單幀 640x640 輸入時(shí)模型權(quán)重加中間激活大概只需要 1-2G 顯存24G 完全有余量。這意味著你可以做幾件事把多個(gè)不同模型一次性加載進(jìn)顯存按業(yè)務(wù)請(qǐng)求切換模型避免每次加載模型帶來的延遲在推理服務(wù)里開更大的 batch把多路視頻流的幀拼成一個(gè) batch 一起推理提高吞吐部署 YOLOv8x 這類大模型時(shí)不用擔(dān)心顯存不夠可以保留較大的 batch 余量。不過要提醒一句顯存大 ≠ 跑得快。推理延遲和吞吐最終取決于芯片上的 AI Core 算力、數(shù)據(jù)搬運(yùn)帶寬以及算子優(yōu)化程度。24G 只代表能裝下不代表能跑滿。很多人看到顯存 24G 就以為買到了性價(jià)比神卡結(jié)果跑起來發(fā)現(xiàn)某些模型的單幀延遲還不如一張消費(fèi)級(jí) GPU于是開始罵。這里面的關(guān)鍵其實(shí)不是卡不行而是部署方式是否正確。2. 環(huán)境搭建里最容易先翻車的地方2.1 驅(qū)動(dòng)、固件和 CANN 的三角關(guān)系在昇騰設(shè)備上環(huán)境安裝比 CUDA 那套要敏感得多。你光裝個(gè)驅(qū)動(dòng)npu-smi info能看到卡但一旦調(diào)用 pyACL 或者跑 ATC 轉(zhuǎn)模型就報(bào)各種 CANT OPEN 設(shè)備、driver/so version mismatch 之類的錯(cuò)。我踩過的教訓(xùn)是驅(qū)動(dòng)、固件、CANN 三者版本必須鎖死不能各裝各的最新版。CANN 官方包發(fā)布時(shí)一般會(huì)在版本配套說明里列出配套的驅(qū)動(dòng)版本和固件版本。比如說你安裝 CANN 8.0.RC1就應(yīng)該找到對(duì)應(yīng)版本的 Ascend HDK里面包含驅(qū)動(dòng)和固件去裝。穩(wěn)妥的安裝步驟大致是這樣先通過npu-smi info查看當(dāng)前固件版本和驅(qū)動(dòng)版本判斷是否已經(jīng)裝過舊版本如果裝過舊版本先按官方文檔干凈卸載避免殘留庫(kù)文件影響新版本安裝固件和驅(qū)動(dòng)典型文件是Ascend-hdk-版本_linux-aarch64.run或linux-x86_64.run安裝 CANN 工具包Ascend-cann-toolkit_版本_linux-arch.run安裝完成后 source 環(huán)境變量source /usr/local/Ascend/ascend-toolkit/set_env.sh驗(yàn)證安裝npu-smi info看到 Product Name 類似Atlas 300V且驅(qū)動(dòng)狀態(tài)正常才算第一步完成。這里有個(gè)容易忽略的點(diǎn)很多人喜歡在 Python 里pip install torch直接裝了 PyTorch 就以為能用。但昇騰的 PyTorch 適配層torch_npu是要另外安裝的而且版本必須和 CANN 匹配。如果你只是做推理部署其實(shí)不一定需要 torch_npu。更常見的做法是直接把 PyTorch 訓(xùn)練好的模型導(dǎo)出成 ONNX再用 ATC 轉(zhuǎn)成 OM最后用 pyACL 或 MindX SDK 加載推理。這樣能繞開一大堆框架適配問題這也是我在生產(chǎn)環(huán)境里最推薦的方式。2.2 ATC 模型轉(zhuǎn)換不是簡(jiǎn)簡(jiǎn)單單一條命令很多人看完教程以為 ONNX 轉(zhuǎn) OM 就是把命令復(fù)制粘貼跑一遍。結(jié)果遇到一堆莫名其妙的報(bào)錯(cuò)Unsupported op、The shape is dynamic、Output node not found。這些問題背后基本都指向一個(gè)核心昇騰離線轉(zhuǎn)換要求模型結(jié)構(gòu)、算子、shape 都已經(jīng)確定且被支持。官方轉(zhuǎn)換工具是 ATCAscend Tensor Compiler最簡(jiǎn)命令長(zhǎng)這樣atc --modelyolov5s.onnx --framework5 \ --outputyolov5s_bs1 \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --loginfo這里的--framework5表示 ONNX--soc_version必須和你的實(shí)際芯片匹配不同版本芯片的指令集和算子支持有差異填錯(cuò)會(huì)導(dǎo)致 AICore 算子生成失敗--input_shape我一般會(huì)固定成靜態(tài) shape。別怕麻煩靜態(tài) shape 在昇騰上是最穩(wěn)的。如果 ONNX 模型里有些算子不在支持列表里比如某個(gè)自定義的 NMS 算子ATC 就會(huì)報(bào)錯(cuò)。我常用的策略是用onnxsim對(duì)模型做簡(jiǎn)化把常量折疊、冗余節(jié)點(diǎn)刪掉在導(dǎo)出 ONNX 時(shí)去掉后處理部分只保留下游解碼前的裸輸出用 netron 查看模型輸入輸出節(jié)點(diǎn)名ATC 轉(zhuǎn)換時(shí)有時(shí)需要指定--out_nodes。網(wǎng)絡(luò)熱詞里那個(gè)atlas部署yolo就是指這一整套流程。其實(shí)真正把 YOLO 跑到 Atlas 上模型轉(zhuǎn)換只是第一步后面推理代碼的編寫才是大頭。3. 從 ONNX 到 om一次完整的 YOLO 部署鏈路3.1 模型轉(zhuǎn)換前的輸出節(jié)點(diǎn)清理我見過很多新手直接拿 ultralytics 倉(cāng)庫(kù)里 export 出來的 ONNX 文件去轉(zhuǎn)那個(gè) ONNX 往往帶了NonMaxSuppression或者若干后處理節(jié)點(diǎn)。這在 GPU 上沒有問題但在昇騰上用 ATC 轉(zhuǎn)這些節(jié)點(diǎn)非常容易遇到算子不支持或者即使支持性能也很差。所以我在部署前都會(huì)做一次輸出節(jié)點(diǎn)清理。做法是在導(dǎo)出模型時(shí)只保留主干網(wǎng)絡(luò)的推理輸出也就是 YOLOv5 那種(1, 25200, 85)的原始預(yù)測(cè)張量NMS 全部放回 host 側(cè)做。這樣做的理由很簡(jiǎn)單把計(jì)算集中在昇騰更擅長(zhǎng)的卷積累積部分把動(dòng)態(tài)邏輯留給 CPU。后處理在主流服務(wù)器上花不了多少時(shí)間還能獲得最大的靈活性。清理完成后最好用 netron 再確認(rèn)一遍輸入節(jié)點(diǎn)名通常是images和輸出節(jié)點(diǎn)名比如/model.24/m.0/Conv_output_0這種。確認(rèn)后再跑 ATC。這樣能避免轉(zhuǎn)出來的 OM 在加載時(shí)因?yàn)楣?jié)點(diǎn)名不匹配而失敗。3.2 用 pyACL 完成一次推理模型轉(zhuǎn)好了接下來就要寫推理代碼。這里我用 pyACL 做一個(gè)最小示例讓你知道全流程長(zhǎng)什么樣import acl # 1. 初始化 acl.init() acl.rt.set_device(0) context acl.rt.create_context() # 2. 加載 OM 模型 model_id, ret acl.mdl.load_from_file(yolov5s_bs1.om) # 3. 準(zhǔn)備輸入數(shù)據(jù) # input_data 需要是 np.ndarray順序?yàn)?NCHW數(shù)據(jù)類型 float32 # 注意 shape 要和 ATC 轉(zhuǎn)換時(shí)一致 # 4. 獲取模型輸出描述動(dòng)態(tài)分配內(nèi)存 output_size acl.mdl.get_output_size_by_index(model_id, 0) output_data acl.util.np_to_ptr(np.zeros(output_size, dtypenp.uint8)) # 5. 執(zhí)行推理 stream acl.rt.create_stream() acl.mdl.execute_async(model_id, input_data, output_data, stream) acl.rt.synchronize_stream(stream) # 6. 將輸出指針轉(zhuǎn)回 numpy 數(shù)組再 reshape 成 (1, 25200, 85) result acl.util.ptr_to_np(output_data, output_size, dtypenp.float32) result result.reshape(1, 25200, 85) # 7. 釋放資源 acl.mdl.unload(model_id) acl.rt.destroy_context(context) acl.rt.reset_device(0) acl.finalize()這個(gè)例子省略了一些細(xì)節(jié)比如輸入數(shù)據(jù)要從 numpy 指針轉(zhuǎn)成acl里的data_ptr以及多 batch 時(shí)的內(nèi)存對(duì)齊。但核心鏈路就是這七步初始化、加載模型、準(zhǔn)備數(shù)據(jù)、執(zhí)行、同步、取結(jié)果、釋放。有一點(diǎn)很關(guān)鍵acl.mdl.execute_async之后必須調(diào)用acl.rt.synchronize_stream。我有一次就是因?yàn)闆]同步每次推理拿到的結(jié)果都是上一次的舊數(shù)據(jù)排查了大半天才意識(shí)到是 stream 同步的問題。3.3 預(yù)處理和后處理不能照搬 GPU 那套YOLO 在 GPU 上訓(xùn)練時(shí)官方預(yù)處理是 letterbox resize等比縮放 灰色填充到 640x640然后 BGR 轉(zhuǎn) RGB、除以 255、減去 mean 再除以 std。很多人到了 Atlas 上還是把一套代碼原封不動(dòng)搬過來結(jié)果要么精度下降要么推理報(bào)錯(cuò)。問題通常出在 AIPP 配置上。AIPP 是昇騰里做圖像預(yù)處理的硬件加速模塊它能在數(shù)據(jù)從 host 側(cè)搬到 device 側(cè)時(shí)順帶完成縮放、色域轉(zhuǎn)換、歸一化等操作。聽起來很美好但配置需要非常小心。下面是一個(gè)典型的aipp.cfg示例aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w : 640 src_image_size_h : 640 crop: 0 load_start_pos_h: 0 load_start_pos_w: 0 resize: 1 resize_output_w: 640 resize_output_h: 640 csc_switch: 1 rbuv_swap_switch: 0 mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 min_chn_0: 0.003921569 min_chn_1: 0.003921569 min_chn_2: 0.003921569 }注意AIPP 的resize是直接拉伸縮放不會(huì)幫你做 letterbox。你如果希望保持原圖寬高比就得自己在 host 側(cè)把圖處理成帶灰邊的 640x640然后再交給 AIPP。否則模型輸入的分布和訓(xùn)練時(shí)不一致精度會(huì)受影響。后處理同樣要小心。OM 輸出的數(shù)據(jù)格式可能和你預(yù)想的不一樣尤其是輸出 shape、數(shù)據(jù)排布NCHW 還是 NHWC以及數(shù)據(jù)類型float32 還是 float16。最好的做法是動(dòng)態(tài)獲取輸出描述而不是硬編碼desc acl.mdl.get_output_desc(model_id, 0) output_shape desc[dims] # 實(shí)際shape output_dtype desc[data_type] # 實(shí)際數(shù)據(jù)類型拿到這些再?zèng)Q定怎么 reshape就能避開一堆坑。4. 實(shí)測(cè)中踩過的坑和對(duì)應(yīng)解法4.1 輸入尺寸或 shape 不對(duì)導(dǎo)致的算子報(bào)錯(cuò)我在一個(gè)項(xiàng)目里遇到過一個(gè)很典型的報(bào)錯(cuò)E10050: The shape of input is wrong。一開始以為是代碼寫錯(cuò)了查了半天發(fā)現(xiàn) ATC 轉(zhuǎn)換時(shí)我指定了--input_shapeimages:1,3,640,640但推理時(shí)傳入的數(shù)據(jù)是[1,3,416,416]。更隱蔽的是有些模型在 ONNX 里導(dǎo)出的輸入名并不是images而是類似input.1沒有準(zhǔn)確指定輸入名時(shí)ATC 會(huì)按 ONNX 的默認(rèn)輸入處理導(dǎo)致最終模型輸入和你代碼里的 shape 對(duì)不上。解決辦法也很簡(jiǎn)單統(tǒng)一輸入名、統(tǒng)一輸入 shape在代碼里加一道斷言。每次推理前先校驗(yàn)輸入數(shù)組的 shape 是否和模型描述一致不一致立刻報(bào)錯(cuò)省得到模型執(zhí)行出結(jié)果后再去猜哪里錯(cuò)了。另外如果為了多尺度推理想把輸入做成動(dòng)態(tài) shape我勸你在 Atlas 上慎重。昇騰部分算子對(duì)動(dòng)態(tài) shape 支持并不好動(dòng)態(tài) shape 往往意味著運(yùn)行時(shí)重編譯這會(huì)帶來額外的延遲和內(nèi)存開銷。我寧可多轉(zhuǎn)幾個(gè)不同尺寸的 OM比如 416、640、768再按業(yè)務(wù)需要?jiǎng)討B(tài)選擇模型。4.2 單 batch 和多 batch 的真實(shí)現(xiàn)差別很多人會(huì)直觀地以為開 batch4 時(shí)吞吐是 batch1 的四倍。實(shí)測(cè)中完全不是這樣。我在 Atlas 300V 24G 上跑 YOLOv5s 時(shí)batch1 的端到端延遲大約在 7-10ms 左右具體數(shù)據(jù)和 CANN 版本、設(shè)備狀態(tài)有關(guān)而開 batch8 后單幀平均延遲不一定降到 1ms往往只是提升到 4-5ms 的水平。原因是昇騰 AI Core 的利用率存在瓶頸當(dāng)單幀推理本身已經(jīng)比較快時(shí)batch 帶來的提升會(huì)被數(shù)據(jù)搬運(yùn)和同步開銷抵消。所以我給出的建議是追求最低延遲的實(shí)時(shí)場(chǎng)景直接batch1保持穩(wěn)定時(shí)延追求吞吐的離線批量分析場(chǎng)景做一次 batch 從 1 到 16 的掃描找到吞吐拐點(diǎn)多路視頻流場(chǎng)景盡量把并發(fā)的多幀湊成一個(gè) batch而不是每路單獨(dú)推理。我實(shí)際測(cè)試時(shí)發(fā)現(xiàn)batch4到batch8之間往往有一個(gè)明顯的性價(jià)比下降如果你做視頻分析控制在 4-8 之間通常是最舒服的。4.3 內(nèi)存和 Stream 的隱形炸彈昇騰的 pyACL 里內(nèi)存管理比 PyTorch 要原始得多你必須自己跟蹤每個(gè)指針的生命周期。我踩過一個(gè)非常隱蔽的坑我把輸出指針指向的 numpy 數(shù)組提前釋放了而 pyACL 內(nèi)部還在異步執(zhí)行結(jié)果推理返回后輸出的數(shù)據(jù)已經(jīng)被覆蓋。調(diào)試時(shí)表現(xiàn)為偶爾結(jié)果正確偶爾全為 0。解決辦法是確保在acl.rt.synchronize_stream完成之前所有輸入輸出內(nèi)存都不能被釋放。不要在異步執(zhí)行后馬上用 ptr_to_np 拿數(shù)據(jù)至少要等 stream 同步之后再做。還有一個(gè)同樣隱蔽的坑多 context / 多 stream 混淆。如果你在同一個(gè)進(jìn)程里先后創(chuàng)建了多個(gè) context后面調(diào)用acl.mdl.execute_async時(shí)沒有顯式acl.rt.set_current_context(context)就會(huì)默認(rèn)跑到錯(cuò)誤的 context 上表現(xiàn)是有時(shí)候能跑有時(shí)候報(bào) device 找不到。養(yǎng)成每次推理前都顯式設(shè)置當(dāng)前 context、當(dāng)前 stream 的習(xí)慣能省很多問題。5. 性能怎么看、怎么再往上提5.1 用 npu-smi 和 profiling 找瓶頸很多人的性能調(diào)優(yōu)方式是瞎猜或者追著網(wǎng)上參數(shù)抄。真正有效的做法是先量化再優(yōu)化。推理服務(wù)跑起來后在另一終端執(zhí)行npu-smi info可以實(shí)時(shí)看到 AI Core 利用率、內(nèi)存占用、溫度。如果 AI Core 利用率長(zhǎng)期只有 30% 左右說明算力并沒有被打滿真正的問題大概率出在 host 側(cè)數(shù)據(jù)預(yù)處理、數(shù)據(jù)搬運(yùn)或者模型本身算子串行太多。如果 AI Core 利用率已經(jīng)接近 90% 以上就說明算力接近極限這時(shí)候可以考慮用 batch 提高單核利用率用多卡或多芯片并行把請(qǐng)求分散到多個(gè)設(shè)備上檢查是否可以在模型轉(zhuǎn)換時(shí)開啟算子融合--op_type_impl等選項(xiàng)。CANN 還自帶 profiling 工具msprof抓一次數(shù)據(jù)可以看到每個(gè)算子的耗時(shí)。很多時(shí)候你會(huì)發(fā)現(xiàn)某個(gè) Transpose 算子或 Cast 算子耗時(shí)特別離譜這時(shí)候如果能在模型導(dǎo)出階段把輸出格式固定減少不必要的轉(zhuǎn)換收益會(huì)非常明顯。5.2 幾個(gè)不花大力氣就能見效的調(diào)優(yōu)習(xí)慣我從幾個(gè)生產(chǎn)項(xiàng)目的經(jīng)驗(yàn)里總結(jié)了一些性價(jià)比極高的調(diào)優(yōu)習(xí)慣新手照著做基本不會(huì)太差打開 AIPP把 resize、歸一化、色域轉(zhuǎn)換下沉到 AIPPhost 側(cè)不再用 OpenCV 逐幀預(yù)處理CPU 占用立刻降下來固定輸入 shape盡量靜態(tài) shape避免動(dòng)態(tài) shape 的運(yùn)行時(shí)重編譯圖片解碼用 DVPPAtlas 自帶的 DVPP 模塊可以用硬件做 JPEG 解碼和縮放減少 host CPU 壓力復(fù)用內(nèi)存池不要每幀都重新申請(qǐng)輸入輸出內(nèi)存尤其在高并發(fā)場(chǎng)景alloc/free 會(huì)變成隱性瓶頸多模型場(chǎng)景根據(jù)請(qǐng)求量預(yù)加載24G 顯存足夠放多個(gè)模型采用預(yù)加載 請(qǐng)求分流的策略避免線上臨時(shí)加載模型造成延遲尖刺。這些習(xí)慣本身不復(fù)雜難的是每次都堅(jiān)持做。我見過太多部署項(xiàng)目模型能跑通就算完事結(jié)果壓測(cè)時(shí)一幀要 20ms比 GPU 慢得多最后換卡。其實(shí)先在 AIPP、batch、內(nèi)存復(fù)用上花一小時(shí)優(yōu)化往往能拿到比換卡更大的提升。最后再分享一個(gè)個(gè)人體會(huì)在 Atlas 300V 24G 上部署 YOLO最核心的一句話是把離線轉(zhuǎn)換做扎實(shí)把預(yù)處理交給硬件把后處理留在 host。這條原則幾乎可以套用到所有昇騰推理項(xiàng)目上。你只要沿著這個(gè)方向走即便中間會(huì)踩些坑最終也能拿到一份穩(wěn)定且性能不錯(cuò)的結(jié)果。