境配置到調(diào)優(yōu)實(shí)踐)
1. Atlas 300V到底是什么卡——先說清楚再動(dòng)手1.1 一張容易被誤解的運(yùn)算加速卡先說個(gè)我自己的經(jīng)歷。以前做邊緣端視頻分析項(xiàng)目客戶給了一臺(tái)Atlas 300V 24G問我能不能在上面跑YOLO。我當(dāng)時(shí)第一反應(yīng)是這不就一塊NPU加速卡嗎跟GPU差不多裝個(gè)CUDA跑起來就行。結(jié)果真正動(dòng)手才發(fā)現(xiàn)這張卡既不是純粹的GPU也不是普通的推理卡它背后的軟件棧和部署邏輯跟CUDA那一套完全是兩碼事。Atlas 300V 24G從硬件規(guī)格上看板載24GB顯存準(zhǔn)確說是內(nèi)存主打視頻解析和AI推理場(chǎng)景功耗大概72W左右不需要額外供電線插上PCIe就能用。很多人拿它跟顯卡比其實(shí)它更接近專用的AI推理加速器核心計(jì)算單元是達(dá)芬奇架構(gòu)的AI Core而不是CUDA核心。這意味著你不能直接在它上面跑PyTorch的GPU版本必須借助華為的CANNCompute Architecture for Neural Networks工具鏈把模型轉(zhuǎn)換成它認(rèn)識(shí)的后綴為.om的格式。搞清楚這個(gè)定位非常重要。因?yàn)檫@個(gè)從GPU思路遷移到NPU思路的認(rèn)知轉(zhuǎn)變決定了你后面是順利部署還是反復(fù)踩坑。簡單理解你在NVIDIA生態(tài)里用的是CUDA cuDNN TensorRT這一路在Atlas生態(tài)里對(duì)應(yīng)的是CANN AscendCL MindSpore或者通過ONNX中轉(zhuǎn)ATC轉(zhuǎn)換器。名字不同功能類似但接口、轉(zhuǎn)換流程、優(yōu)化手段統(tǒng)統(tǒng)要換一套。1.2 它和GPU在工作原理上的本質(zhì)差異很多實(shí)驗(yàn)室的項(xiàng)目組第一次接觸Atlas時(shí)習(xí)慣性把它當(dāng)顯卡用pip install torch然后model.cuda()結(jié)果發(fā)現(xiàn)根本不行。原因在于NPU的架構(gòu)設(shè)計(jì)思路和GPU有本質(zhì)不同——GPU有幾千個(gè)輕量級(jí)核心做并行計(jì)算適合通用并行任務(wù)Atlas的AI Core則是專門為神經(jīng)網(wǎng)絡(luò)算子設(shè)計(jì)的它擅長的是矩陣乘累加運(yùn)算對(duì)激活函數(shù)、歸一化這類算子的實(shí)現(xiàn)也要走專門的硬件指令。另一個(gè)區(qū)別是內(nèi)存管理方式。在GPU上torch.cuda.mem_get_info()能查到顯存占用但Atlas的AI Core內(nèi)存管理和設(shè)備內(nèi)存不是一回事。你通過AscendCL接口申請(qǐng)的內(nèi)存要經(jīng)過aclrtMalloc來分配而不是cudaMalloc。這個(gè)差異在模型轉(zhuǎn)換和推理的時(shí)候特別明顯——模型大小接近24G上限時(shí)不是你看到的卡上還有空間就能跑還要考慮AI Core內(nèi)部緩沖、算子中間張量、DVPP預(yù)處理占用的內(nèi)存等等。還有一點(diǎn)是算子支持的粒度。TensorRT能把很多算子融合成插件Atlas的ATC轉(zhuǎn)換器也有類似能力但它對(duì)算子的支持有一個(gè)白名單機(jī)制。你訓(xùn)練時(shí)可能用了某個(gè)很新的激活函數(shù)在GPU上跑得好好的ATC轉(zhuǎn)換時(shí)直接報(bào)Unsupported Op這時(shí)候就得想辦法繞過去。這不是Atlas不行而是NPU生態(tài)對(duì)算子種類支持還在趕超途中部署前必須有這個(gè)心理預(yù)期。2. 部署前必須確認(rèn)的三件事2.1 驅(qū)動(dòng)、固件和CANN的版本匹配Atlas 300V部署YOLO最容易翻車的不是模型本身而是環(huán)境版本不匹配。這個(gè)問題幾乎每個(gè)初轉(zhuǎn)過來的人都會(huì)遇到明明照著官方文檔裝了驅(qū)動(dòng)跑npu-smi info也能看到卡但一跑推理進(jìn)程就報(bào)E49999、E10009之類的錯(cuò)誤碼。查了一圈才發(fā)現(xiàn)是固件版本和CANN版本對(duì)不上。以Atlas 300V 24G為例官方對(duì)驅(qū)動(dòng)、固件、CANN的版本有一套明確的配套關(guān)系。這些組件在華為的支持網(wǎng)站上有單獨(dú)下載需要保證固件如Ascend-hdk-300v-npu_xxx.run和驅(qū)動(dòng)如Ascend-hdk-300v-npu-driver_xxx.run以及CANN toolkit的版本在同一個(gè)兼容列表里。版本之間互相鎖定不能只升級(jí)其中一個(gè)。這里有個(gè)小建議部署前先登錄服務(wù)器用npu-smi info查看當(dāng)前固件版本然后對(duì)照CANN的版本說明文檔。如果裝新版CANN會(huì)要求你升級(jí)固件而固件升級(jí)又需要重啟機(jī)器并且可能影響正在運(yùn)行的其他業(yè)務(wù)最好在業(yè)務(wù)低峰期做。我有一個(gè)習(xí)慣就是把每個(gè)版本的驅(qū)動(dòng)、固件、CANN的安裝包和對(duì)應(yīng)兼容關(guān)系截圖存檔下次部署同類機(jī)器直接照抄省掉大量試錯(cuò)時(shí)間。需要注意如果你用的是Atlas 300V這種PCIe形態(tài)的卡安裝驅(qū)動(dòng)后會(huì)生成/dev/davinci0設(shè)備節(jié)點(diǎn)同時(shí)還會(huì)有一個(gè)/dev/davinci_manager節(jié)點(diǎn)。檢查這兩個(gè)設(shè)備節(jié)點(diǎn)是否存在是判斷驅(qū)動(dòng)是否裝好的最直接方式。2.2 模型轉(zhuǎn)換前置工作導(dǎo)出ONNX與算子檢查Atlas部署YOLO的標(biāo)準(zhǔn)鏈路是PyTorch/YOLOv5權(quán)重→導(dǎo)出ONNX→通過ATC工具轉(zhuǎn)換成OM格式→用AscendCL或MindSpore推理。為什么中間要隔一層ONNX因?yàn)锳TC轉(zhuǎn)換器不能直接吃PyTorch的權(quán)重文件ONNX是神經(jīng)網(wǎng)絡(luò)模型的標(biāo)準(zhǔn)中間表示相當(dāng)于一個(gè)翻譯官。這一步看起來簡單但對(duì)YOLO模型來說有些細(xì)節(jié)必須處理干凈。YOLOv5導(dǎo)出ONNX時(shí)模型默認(rèn)包含NMS非極大值抑制后處理邏輯有的導(dǎo)出腳本會(huì)在推理圖中加入一堆自定義算子。Atlas的ATC轉(zhuǎn)換器對(duì)這類自定義操作支持不是特別好建議導(dǎo)出ONNX時(shí)把NMS去掉把后處理放到宿主機(jī)的Python代碼里去寫。這樣OM模型只負(fù)責(zé)前向推理輸出的是原始的預(yù)測(cè)張量比如[batch, 25200, 85]這種NMS交給CPU側(cè)處理。另外導(dǎo)出ONNX后建議先用onnxsim做一次圖優(yōu)化把常量折疊、冗余節(jié)點(diǎn)合并掉。這個(gè)操作能明顯提升后續(xù)ATC轉(zhuǎn)換的成功率。接著用python -m onnxruntime.tools.check_onnx_model或者Netron可視化檢查一遍確認(rèn)輸出名稱和shape是你預(yù)期的。如果發(fā)現(xiàn)模型里有Einsum、Roll、NonMaxSuppression這類算子大概率在ATC轉(zhuǎn)換Stage 2時(shí)會(huì)報(bào)錯(cuò)最好提前規(guī)避。2.3 ATC轉(zhuǎn)換的參數(shù)選擇ATC轉(zhuǎn)換是Atlas部署流程的核心環(huán)節(jié)。命令行格式大概是atc --modelyolov5s.onnx --framework5 \ --outputyolov5s_om --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --insert_op_confaipp.cfg \ --output_typeFP32這里的--soc_version必須對(duì)應(yīng)Atlas 300V的算力型號(hào)。Atlas 300V 24G我在環(huán)境里看到的是Ascend310P3但不同批次可能略有差異最好用npu-smi info看芯片型號(hào)再確認(rèn)。--framework5表示ONNX--input_shape要跟導(dǎo)出ONNX時(shí)的輸入形狀一致YOLO模型通常固定到640x640輸入。AIPPArtificial Intelligence Pre-Processing配置是Atlas特有的預(yù)處理語法用于圖像縮放、色域轉(zhuǎn)換、歸一化等操作。一個(gè)常見的AIPP配置長這樣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 matrix_r0c0: 256 matrix_r0c1: 0 matrix_r0c2: 359 matrix_r1c0: 256 matrix_r1c1: -88 matrix_r1c2: -183 matrix_r2c0: 256 matrix_r2c1: 0 matrix_r2c2: 0 input_format: RGB888_U8 }實(shí)際上很多人第一次并不在ATC階段插入AIPP而是先跑通裸模型推理確認(rèn)OM轉(zhuǎn)換無誤再考慮前置處理的優(yōu)化。我覺得這個(gè)思路是對(duì)的因?yàn)锳IPP配置錯(cuò)一行輸出結(jié)果就全偏了排查起來非常麻煩。先跑通、再優(yōu)化這是后文會(huì)說到的調(diào)優(yōu)策略里最重要的一條。如果你的輸入圖片不是固定尺寸可以參考動(dòng)態(tài)shape配置但動(dòng)態(tài)shape對(duì)性能影響較大一般建議優(yōu)先固定輸入尺寸。YOLO對(duì)輸入分辨率不敏感固定640x640完全夠用。3. 完整部署鏈路實(shí)踐從PyTorch權(quán)重點(diǎn)到推理卡3.1 環(huán)境準(zhǔn)備CANN安裝和虛擬環(huán)境搭建在服務(wù)器上部署時(shí)我傾向于用獨(dú)立的Python虛擬環(huán)境避免污染系統(tǒng)Python。Atlas的推理接口現(xiàn)在推薦使用mindspore或者pyaclPython AscendCL其中pyacl是直接封裝C接口的庫功能更底層也更靈活。安裝過程大概是# 安裝CANN toolkit ./Ascend-cann-toolkit_7.0.0_linux-aarch64.run --install # 配置環(huán)境變量 source /usr/local/Ascend/ascend-toolkit/set_env.sh # 創(chuàng)建Python虛擬環(huán)境 python3 -m venv atlas_env source atlas_env/bin/activate # 安裝pyacl pip install pyacl --find-links /usr/local/Ascend/ascend-toolkit/latest/pydev/需要注意CANN toolkit安裝完之后環(huán)境變量一定要source進(jìn)當(dāng)前shell否則后面的atc命令、pyacl庫都會(huì)找不到。這里還有一個(gè)容易忽略的細(xì)節(jié)Atlas 300V在x86和arm架構(gòu)服務(wù)器上都有對(duì)應(yīng)的CANN包下載時(shí)要選對(duì)架構(gòu)。很多人在華為統(tǒng)一下載頁上看到一堆.run包上面寫了linux-aarch64和linux-x86_64下載錯(cuò)了裝不上白折騰一晚上。3.2 宿主機(jī)圖像預(yù)處理與推理代碼下面的示例代碼演示了用pyacl加載OM模型、執(zhí)行推理并做簡單的輸出處理。這個(gè)代碼不包含AIPP預(yù)處理完全在CPU側(cè)完成OpenCV將圖像resize到640x640并轉(zhuǎn)為RGB。import numpy as np import cv2 from pyacl.acl_infer import AclNet, AclLiteImage # 模型路徑 MODEL_PATH ./yolov5s_om.om INPUT_SIZE (640, 640) # 初始化推理上下文 net AclNet(device_id0, model_pathMODEL_PATH) # 讀取圖片并做預(yù)處理 img cv2.imread(test.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, INPUT_SIZE, interpolationcv2.INTER_LINEAR) img img / 255.0 img img.astype(np.float32) # 調(diào)整維度為 NCHW input_data np.expand_dims(img.transpose(2, 0, 1), axis0) # 推理 result net(aicl_imageinput_data) # 輸出后處理省略關(guān)鍵看 result 中是否有預(yù)期shape print(result.shape)這段代碼里需要注意一個(gè)細(xì)節(jié)net()傳入的數(shù)據(jù)類型必須與ATC轉(zhuǎn)換時(shí)OM模型期望的輸入類型一致。如果轉(zhuǎn)換時(shí)--output_typeFP32那推理時(shí)輸入張量必須是float32如果模型前向輸入是uint8且配合AIPP數(shù)據(jù)就不能歸一化到0-1而應(yīng)直接傳0-255的原始像素值。這個(gè)坑我在早期部署時(shí)踩過顏色偏差和檢測(cè)效果差到懷疑人生。3.3 后處理邏輯的完整度ONNX導(dǎo)出時(shí)去掉NMS后OM模型的原始輸出是一個(gè)大張量。以YOLOv5s 640x640輸入為例原始輸出shape為[1, 25200, 85]其中25200是三個(gè)特征層80x80、40x40、20x20的候選框總數(shù)85是[cx, cy, w, h, obj_conf, 80類cls_conf]的組合。后處理階段需要做閾值過濾一般conf0.25、類別置信度提取、解碼框坐標(biāo)、NMS。這部分在CPU上做如果做視頻流實(shí)時(shí)推理要注意NMS的耗時(shí)不能拖后腿。建議直接用OpenCV自帶的cv2.dnn.NMSBoxes實(shí)現(xiàn)幾百個(gè)框的速度完全夠用。boxes, scores, class_ids [], [], [] for pred in result[0]: classes_scores pred[5:] class_id np.argmax(classes_scores) score classes_scores[class_id] * pred[4] if score 0.25: cx, cy, w, h pred[:4] boxes.append([cx - w/2, cy - h/2, w, h]) scores.append(float(score)) class_ids.append(class_id) indices cv2.dnn.NMSBoxes(boxes, scores, score_threshold0.25, nms_threshold0.45)如果你跟著這一步走通了你會(huì)發(fā)現(xiàn)Atlas 300V推理YOLO本身不復(fù)雜復(fù)雜的是理解和應(yīng)對(duì)整個(gè)工具鏈的細(xì)節(jié)。跑通是起點(diǎn)優(yōu)化才是大頭。4. 真實(shí)性能實(shí)測(cè)與調(diào)優(yōu)方向4.1 一張卡的實(shí)測(cè)數(shù)據(jù)我在Atlas 300V 24G上跑YOLOv5s模型640x640輸入FP32推理單張圖片的端到端耗時(shí)情況如下階段耗時(shí)備注CPU預(yù)處理resize轉(zhuǎn)色歸一化約2ms實(shí)際取決于輸入圖片大小NPU推理純模型前向約7-9ms單batch固定shapeCPU后處理閾值篩選NMS約1-2ms取決于候選框數(shù)量總端到端延遲約10-13ms折合約77-100 FPS這個(gè)數(shù)據(jù)是單stream推理的結(jié)果實(shí)際項(xiàng)目中的幀率還會(huì)受到數(shù)據(jù)搬運(yùn)、Python GIL、宿主CPU性能等因素影響。我這里用的宿主CPU是Xeon 4210R如果CPU性能太弱預(yù)處理和后處理反而會(huì)成為瓶頸。如果你把batch從1調(diào)整為4推理卡上的并行效率會(huì)有明顯提升單張攤銷耗時(shí)能降到4-5ms左右但端到端延遲會(huì)累加數(shù)據(jù)攢批的時(shí)間。所以高吞吐和低延遲在Atlas上確實(shí)是個(gè)取舍看業(yè)務(wù)側(cè)有多少容忍度。4.2 固定shape與動(dòng)態(tài)shape之間的取舍我在最初的部署中用了動(dòng)態(tài)shape配置因?yàn)閾?dān)心輸入尺寸不固定。實(shí)際測(cè)試下來動(dòng)態(tài)shape會(huì)讓ATC生成的OM模型在推理時(shí)做很多動(dòng)態(tài)計(jì)算單幀耗時(shí)會(huì)比固定shape高出20%到30%。后來我改成固定640x640輸入推理速度穩(wěn)定了很多。另一個(gè)影響性能的關(guān)鍵參數(shù)是--buffer_optimize。ATC轉(zhuǎn)換時(shí)加上--buffer_optimizeoff_optimize可以關(guān)閉部分內(nèi)存復(fù)用優(yōu)化這在排查非法內(nèi)存訪問問題時(shí)有用但部署到生產(chǎn)環(huán)境不要加這個(gè)參數(shù)保持默認(rèn)就好。固定shape的代價(jià)是輸入圖片需要做letterbox處理保留下黑邊并調(diào)整到640x640。這樣檢測(cè)精度和均勻性都靠譜只要后處理時(shí)把坐標(biāo)換算回原始圖就行。換算邏輯就是等比縮放加平移寫起來不麻煩。4.3 多路視頻流場(chǎng)景的部署思路Atlas 300V 24G的一個(gè)典型使用場(chǎng)景是視頻解析服務(wù)器比如8路到16路的攝像頭視頻流同時(shí)做目標(biāo)檢測(cè)。每路視頻流獨(dú)立走解碼→縮放→推理→后處理鏈路。這種場(chǎng)景下單純跑Python腳本很難達(dá)到高并發(fā)更適合用C寫推理服務(wù)或者用Python多進(jìn)程 獨(dú)立的AscendCL context隔離。在Python里aclrtSetDevice和aclrtCreateContext允許每個(gè)進(jìn)程綁定到指定設(shè)備。多進(jìn)程方式比多線程更安全因?yàn)槊總€(gè)進(jìn)程都有獨(dú)立的Python GIL不會(huì)相互阻塞。我實(shí)際測(cè)試過8個(gè)進(jìn)程同時(shí)推理每路視頻流的幀率基本穩(wěn)定在20-30FPS總體吞吐相當(dāng)可觀。還有一個(gè)細(xì)節(jié)是DVPP硬件解碼。Atlas 300V內(nèi)置了視頻解碼模塊支持H.264/H.265硬解碼但要在Atlas平臺(tái)使用DVPP需要通過aclvdec接口走完整的視頻解碼流程代碼復(fù)雜度明顯提升。如果視頻流路數(shù)少直接用OpenCV的cv2.VideoCapture軟解也是可以的路數(shù)多時(shí)再考慮把解碼也扔到卡上。5. 我在部署Atlas 300V時(shí)踩過的坑5.1 24G顯存明明是夠的卻被OOM第一次在Atlas 300V上跑YOLOv8一個(gè)較大尺寸的版本模型轉(zhuǎn)換成功推理時(shí)在初始化階段直接OOM。npu-smi顯示內(nèi)存占用才沒多少這里的關(guān)鍵是AI Core的workspace內(nèi)存和算子的中間buffer占用并不反映在顯存用量上。解決思路有兩個(gè)一是換用更小的模型結(jié)構(gòu)比如YOLOv5s而非YOLOv5x或者減少輸入分辨率到416x416二是用ATC轉(zhuǎn)換時(shí)加上--memory_reuse1參數(shù)讓算子間復(fù)用內(nèi)存降低峰值占用。這兩個(gè)方法我在實(shí)際項(xiàng)目中都用過搭配起來能從帶不動(dòng)變成穩(wěn)跑。5.2 ONNX算子和ATC的脾氣不對(duì)付我遇到過的報(bào)錯(cuò)信息大概有這些報(bào)錯(cuò)/現(xiàn)象根因解決方案E13005 Unsupported OpONNX圖里有ATC不支持的算子onnxsim優(yōu)化、修改導(dǎo)出腳本規(guī)避該算子E19999 Internal Error模型輸入shape和ATC指定的不匹配核對(duì)--input_shape導(dǎo)出時(shí)固定batch轉(zhuǎn)換OK但推理結(jié)果全為0或NaN輸入預(yù)處理和AIPP配置不對(duì)檢查歸一化值域、通道順序有一次我導(dǎo)出的YOLOv5 ONNX里帶了一個(gè)Focus模塊的切片算子ATC死活不支持。后來發(fā)現(xiàn)在YOLOv5官方倉庫的新版本中Focus已經(jīng)被替換成普通卷積換版本就好。還有一次是模型用了Mish激活函數(shù)ATC轉(zhuǎn)成OM后推理結(jié)果的置信度全亂掉最終我把Mish手工替換成SiLU才解決精度損失可以忽略。所以如果你在轉(zhuǎn)換時(shí)卡住最好的排查姿勢(shì)是先把模型簡化到最原始的卷積BNReLU結(jié)構(gòu)確認(rèn)鏈路通再逐步加回復(fù)雜算子定位到具體是哪個(gè)算子觸發(fā)報(bào)錯(cuò)。這個(gè)方法有點(diǎn)笨但排查效率非常高。5.3 幀率上不去的隱藏瓶頸有的項(xiàng)目推理耗時(shí)只有8ms但整路視頻流就是跑不到預(yù)期的幀率。排查下來發(fā)現(xiàn)瓶頸在數(shù)據(jù)讀取上用cv2.VideoCapture拉RTSP流CPU軟解每秒只能處理十幾幀整體幀率被拖垮了。這時(shí)就把解碼鏈路改掉RTSP流用FFmpeg拉流轉(zhuǎn)成NV12格式再通過DVPP硬解和縮放。雖然剛開始寫DVPP代碼很繁瑣但效果立竿見影。還有一點(diǎn)是Python的cv2.imread在高并發(fā)下會(huì)因?yàn)镚IL互相等待多路視頻場(chǎng)景盡量繞開Python層做圖片讀取或者直接用C封裝一個(gè)預(yù)處理服務(wù)。另外aclrtMemcpy的同步/異步模式也需要留意。同步拷貝會(huì)在數(shù)據(jù)搬運(yùn)時(shí)阻塞當(dāng)前線程異步拷貝配合aclrtSynchronizeStream則可以做到數(shù)據(jù)搬運(yùn)和NPU計(jì)算重疊。一開始圖省事用同步模式性能自然上不去改成異步之后端到端吞吐提升非常明顯。5.4 環(huán)境變量和權(quán)限相關(guān)的碎坑最后說幾個(gè)不起眼但會(huì)卡你半天的問題運(yùn)行推理的用戶必須在HwHiAiUser用戶組或者在root下運(yùn)行否則/dev/davinci0的設(shè)備權(quán)限不夠會(huì)報(bào)錯(cuò)。如果發(fā)現(xiàn)npu-smi info能看到卡但推理時(shí)找不到設(shè)備先確認(rèn)ls /dev/davinci*設(shè)備節(jié)點(diǎn)是否存在有時(shí)驅(qū)動(dòng)安裝后需要重啟才能生成完整節(jié)點(diǎn)。CANN的環(huán)境變量source之后不要輕易切換Python版本或虛擬環(huán)境否則LD_LIBRARY_PATH指向的庫可能對(duì)不上。日志排錯(cuò)時(shí)ASCEND_GLOBAL_LOG_LEVEL1能打出debug級(jí)別的CANN日志報(bào)錯(cuò)信息會(huì)詳細(xì)得多。默認(rèn)的error級(jí)別往往只給你一個(gè)錯(cuò)誤碼查起來太痛苦。我自己在實(shí)際項(xiàng)目里把這一切理順之后Atlas 300V 24G在視頻結(jié)構(gòu)化、安全帽檢測(cè)這類場(chǎng)景中跑得非常穩(wěn)。用一塊低功耗卡替代以前雙GPU的算力方案機(jī)房功耗和制冷壓力都小了很多。雖然NPU生態(tài)不像CUDA那么完善但只要把版本、算子、內(nèi)存管理這幾關(guān)摸透它完全能成為邊緣推理和視頻分析場(chǎng)景里很能打的選擇。最后再分享一個(gè)小技巧如果你打算長期用Atlas做YOLO部署建議維護(hù)一份自己的算子白名單文檔記錄哪些PyTorch算子能在ATC階段順利轉(zhuǎn)換、哪些需要繞路處理。這個(gè)文檔會(huì)隨著你做的項(xiàng)目越多越有價(jià)值遇到新模型時(shí)基本能快速判斷部署的難度和時(shí)間。每個(gè)人的踩坑路徑可能不同但一個(gè)團(tuán)隊(duì)里多一份這樣的記錄后輩同事能少熬好幾個(gè)通宵。