
做FPGA端的深度學習部署Vitis-AI基本是繞不開的一條技術路線。這篇文章把我最近在ZCU104上跑通YOLOv5的完整過程記錄下來從Pytorch訓練好的模型權重開始到量化校準再到編譯成DPU可執(zhí)行的xmodel每一步都展開講清楚。代碼我已經(jīng)整理開源想直接照著做的朋友可以到我的GitHub倉庫里找倉庫里的腳本和文章里用到的完全一致。內(nèi)容適合剛接觸Vitis-AI、準備把目標檢測模型部署到Zynq UltraScale平臺的開發(fā)者參考也適合已經(jīng)在Xilinx生態(tài)里摸爬滾打、想快速評估YOLOv5在DPU上效果的同行。這個系列我打算寫三篇第一篇就是現(xiàn)在這篇重點解決模型怎么從Pytorch變成DPU能跑的xmodel這一核心問題。很多朋友卡在這個環(huán)節(jié)要么是Docker環(huán)境起不來要么是量化時算子不兼容報錯要么是編譯完發(fā)現(xiàn)精度掉得沒法看。這些坑我基本都踩了一遍文章里會逐個說明原因和解決辦法。1. 整體方案設計與選型思路1.1 為什么選ZCU104 Vitis-AI而不是 Jetson 或嵌入式NPU做邊緣端目標檢測硬件方案其實很多。NVIDIA Jetson系列生態(tài)成熟跑YOLOv5幾乎零門檻TensorRT加速效果也出色但功耗和整板成本都比較高而且在嚴苛的工業(yè)環(huán)境下散熱和可靠性不一定滿足要求。嵌入式NPU比如瑞芯微RK3588、算能BM1684性價比很高但模型適配受限于各類工具鏈想做自定義圖像采集、協(xié)議解析、電機控制這類邏輯時NPU的通用性就不夠。ZCU104的優(yōu)勢在于它是Zynq UltraScale MPSoCPS側(cè)有四個ARM A53核PL側(cè)有可編程邏輯DPU相當于在PL里例化出來的專用CNN加速IP。這意味著卷積推理可以卸載到DPU上而圖像采集、幀處理、NMS后處理、外部接口通信這些完全可以在PS側(cè)自己控制。如果你需要的是一個既能跑AI、又能靈活控制外設的邊緣計算平臺ZCU104就是比Jetson更適合的選擇。Vitis-AI是AMD/Xilinx官方提供的AI推理加速工具鏈它解決的問題很直接你不用自己寫RTL去實現(xiàn)卷積也不用費勁學HLS去折騰矩陣運算只需要把訓練好的模型丟給工具鏈量化、編譯之后就能在DPU上跑起來。整個流程從Pytorch模型到最終在板卡上運行鏈路非常清晰而且官方鏡像把所有依賴都封裝好了很大程度上降低了FPGA開發(fā)的門檻。當然它的學習曲線并沒有完全消失——量化原理、算子支持范圍、DPU架構選擇這些知識如果不懂遇到問題還是會一頭霧水。1.2 端到端流程拆解訓練、量化、編譯、部署整個技術路徑可以分成四個階段訓練階段在Pytorch框架下完成YOLOv5模型的訓練得到浮點權重文件這一步有官方Y(jié)OLOv5倉庫可以用也可以在自己數(shù)據(jù)集上微調(diào)。量化階段用Vitis-AI提供的vai_q_pytorch工具以少量真實圖片作為校準集統(tǒng)計網(wǎng)絡各層激活值的動態(tài)范圍把FP32參數(shù)映射到INT8。編譯階段用vai_c_xir工具把量化后的模型編譯成DPU指令流的xmodel文件這一階段會指定具體的DPU架構例如ZCU104上的DPUCZDX8G。部署階段在ZCU104上運行Vitis AI Runtime加載xmodel執(zhí)行推理PS側(cè)CPU負責圖像預處理和結(jié)果后處理。文章后面所有內(nèi)容都圍繞這條主線展開。我在實際開發(fā)中比較建議先跑通一個最簡單的分類模型再上YOLOv5這種結(jié)構復雜的檢測模型這樣可以把工具鏈不熟和模型適配問題分開排查。當然如果你已經(jīng)有一定基礎直接按本文流程操作也沒有問題。2. 環(huán)境準備與必備概念2.1 主機端環(huán)境搭建Ubuntu Docker Vitis-AI鏡像Vitis-AI官方推薦在Docker容器里完成量化和編譯原因很簡單不同版本的工具鏈依賴差異很大Docker鏡像把CUDA、Pytorch、nndct等一堆依賴固定好避免污染主機環(huán)境也方便版本切換。我自己的習慣是單獨劃分一個工作目錄把模型文件、數(shù)據(jù)集、輸出產(chǎn)物都掛載進容器這樣即使容器重建也不會丟數(shù)據(jù)。主機環(huán)境建議Ubuntu 18.04或20.04先安裝Docker然后拉取官方鏡像。Vitis-AI 1.4是比較經(jīng)典的穩(wěn)定版本新項目也可以考慮2.5或3.0版本但不同版本的命令和算子支持會有差異本文以1.4版本為例說明# 拉取鏡像根據(jù)實際網(wǎng)絡環(huán)境選擇合適的源 docker pull xilinx/vitis-ai:1.4.130 # 啟動容器掛載工作目錄 docker run -it \ -v /home/user/vitis_ai_workspace:/workspace \ xilinx/vitis-ai:1.4.130 \ bash進入容器后激活Pytorch環(huán)境conda activate vitis-ai-pytorch這一步很關鍵Vitis-AI容器里同時內(nèi)置了TensorFlow2、TensorFlow1、Pytorch、Caffe等多個環(huán)境如果不激活pytorch環(huán)境就執(zhí)行vai_q_pytorch命令會直接提示找不到命令。我用的是1.4版本自帶的python3.7 pytorch 1.8組合實際測試下來和官方Y(jié)OLOv5 v5.0版本的代碼兼容性比較好。2.2 核心概念速通量化、編譯、DPU、xmodel在動手之前一定要把幾個基本概念搞明白否則后面連錯誤提示都看不懂。量化就是把浮點模型轉(zhuǎn)成定點模型。FP32精度下每個參數(shù)占4字節(jié)而DPU的DSP單元做INT8定點乘加運算更高效FP32轉(zhuǎn)INT8之后模型體積縮小到原來的四分之一推理速度大幅提升。量化的數(shù)學本質(zhì)是找一個浮點實數(shù)r和整數(shù)q之間的映射關系q round(r / scale) zero_point其中scale和zero_point是根據(jù)每一層參數(shù)的取值范圍算出來的。Vitis-AI在量化時會在校準階段統(tǒng)計激活值的分布從而確定合適的scale和zero_point。編譯不是普通程序員理解的源碼變機器碼在Vitis-AI里編譯是把已經(jīng)量化的模型進一步轉(zhuǎn)換成DPU的指令流同時做算子融合和內(nèi)存調(diào)度優(yōu)化。DPU認識的不是PyTorch的算子圖而是一套專用的AI指令最終產(chǎn)物是xmodel文件。DPU是Xilinx提供的可配置CNN加速IP核在ZCU104上用的典型配置是DPUCZDX8G。你可以在Vivado里例化DPU核設置不同的架構參數(shù)比如能放幾個卷積核、支持多大的輸入圖像然后綜合出FPGA比特流。軟件側(cè)的模型編譯必須和硬件側(cè)的DPU架構匹配否則編譯出來的xmodel在板子上根本加載不了。2.3 YOLOv5模型準備與針對性預處理YOLOv5本身是Pytorch框架下非常成熟的目標檢測工程模型主體由CSPDarknet骨干網(wǎng)絡、PANet特征融合層和YOLO檢測頭組成。準備模型時我建議直接用官方v5.0分支訓練自己的權重或者先用官方預訓練權重yolov5s.pt走通整個流程后續(xù)再換自定義數(shù)據(jù)。量化編譯階段用到的模型文件是PyTorch的.pt/.pth權重不是ONNX這一點要特別注意。有一個針對Vitis-AI的關鍵修改必須在量化前完成YOLOv5默認使用SiLU激活函數(shù)也就是Swish但DPUCZDX8G對SiLU的支持很有限量化和編譯時大概率會報Unsupported Op錯誤。我的處理方式是把模型結(jié)構中的SiLU全部替換成ReLU雖然理論上會讓模型表達能力略降一些但實際測試下來精度損失很小而且能順利在DPU上部署。如果你是自己訓練模型建議在訓練階段就直接把激活函數(shù)換掉比如在模型定義里把SiLU改成ReLU再訓練這樣從源頭避免麻煩。輸入圖像的預處理也必須和訓練時保持一致。YOLOv5在預處理時會對圖像做letterbox等比縮放后填充灰邊然后歸一化到0到1之間量化校準階段給模型喂的數(shù)據(jù)必須完全按這個流程來否則量化統(tǒng)計出的激活值范圍是不準確的最終部署精度會受到牽連。3. 量化流程詳解從FP32到INT83.1 為什么必須做量化以及它帶來的收益ZCU104上的DPU本質(zhì)上就是一個定點運算引擎INT8是它的核心工作精度。如果不做量化直接把FP32模型扔給工具鏈編譯編譯根本不會通過即便強行部署也會因為計算資源開銷巨大而性能慘不忍睹。量化帶來的收益體現(xiàn)在三個層面一是模型體積壓縮一個70MB左右的FP32模型量化后約17.5MB對片上存儲有限的嵌入式設備來說這個差別很關鍵二是推理速度提升INT8的定點乘加在DSP上可以流水線執(zhí)行實測在ZCU104上YOLOv5s的推理延遲比FP32模擬運行快好幾倍三是功耗降低FPGA上INT8運算的單位能耗遠低于浮點運算。當然量化不是免費的它一定帶來精度損失。所以整個量化流程里最重要的事情就是控制精度損失這是Vitis-AI使用中最核心的經(jīng)驗之一。量化誤差主要來源于參數(shù)和激活值在轉(zhuǎn)INT8時的舍入誤差以及極端分布數(shù)值被截斷。Vitis-AI的解決方案是在校準階段用真實數(shù)據(jù)來統(tǒng)計每一層的動態(tài)范圍從而確定合適的量化參數(shù)讓舍入誤差盡量小。3.2 校準數(shù)據(jù)集的選取直接決定量化精度校準集是量化時用來統(tǒng)計激活值分布的小批量真實圖片。很多人在這一步不重視隨手拿幾張圖就校準了結(jié)果量化后模型幾乎不可用。根據(jù)我的測試和官方建議校準集一般取200到500張左右比較合適圖片要覆蓋真實場景中的典型情況不同光照、不同目標尺寸、不同背景復雜度的樣本都盡量包含。如果太少統(tǒng)計出的數(shù)值范圍有偏差如果太多校準時間長而且收益會遞減。我自己在項目里通常先從驗證集中隨機抽樣300張圖片再手動檢查一遍圖片多樣性確保不會集中在某幾個場景。同時要注意校準圖片的預處理必須和訓練完全一致。比如YOLOv5訓練時用了letterbox和歸一化那校準階段也必須是同樣的操作不能讓模型看到與訓練時差異很大的數(shù)據(jù)分布。校準集的載體路徑我放在一個文本文件里每行一張圖片的路徑。后面量化命令會讀取這個文件。組batch時建議batch_size設為1避免多張圖統(tǒng)計時個別異常值對量化參數(shù)造成干擾。3.3 量化執(zhí)行使用vai_q_pytorch一行命令完成Vitis-AI針對Pytorch模型的量化工具是vai_q_pytorch它封裝了nndct的量化器。在激活好vitis-ai-pytorch環(huán)境后基本用法如下vai_q_pytorch quantize \ --model ./float/yolov5s.pth \ --input_size 1,3,640,640 \ --batch_size 1 \ --dataset ./data/calib_list.txt \ --data_preprocess ./data/calib_dataloader.py \ --save_dir ./quant_res拆開說幾個重要參數(shù)。--model指定的是浮點權重文件路徑--input_size是模型輸入尺寸YOLOv5默認640x640按batch,channel,height,width的順序?qū)?-dataset是剛才說的校準圖片路徑列表文件--data_preprocess是一個Python腳本路徑里面實現(xiàn)和訓練階段一致的數(shù)據(jù)預處理邏輯--save_dir是輸出目錄量化產(chǎn)物會寫到這個目錄下。如果你希望更精細地控制量化過程也可以直接使用Vitis-AI提供的Pytorch API來做from pytorch_nndct.apis import torch_quantizer # 校準階段 quantizer torch_quantizer(calib, float_model, input_tensor, device) quant_model quantizer.quant_model # 在若干batch數(shù)據(jù)上執(zhí)行forward讓工具統(tǒng)計各層激活范圍 run_calibration(quant_model) # 測試階段 quantizer torch_quantizer(test, float_model, input_tensor, device) quant_model quantizer.quant_model evaluate(quant_model) # 導出xmodel quantizer torch_quantizer(deploy, float_model, input_tensor, device) quant_model quantizer.quant_model quantizer.export_xmodel()API方式的好處是可以在量化前后分別加載模型做精度評估這在排查量化導致精度嚴重下降的問題時很有用。命令行方式則更適合標準化流程我個人的項目里兩種方式都保留排查問題用API方式正式流程用命令行方式。等命令跑完quant_res目錄下會生成quantized_model.xmodel等文件。這里要特別提醒有些版本的vai_q_pytorch在量化模式下不會生成xmodel需要在deploy模式下才能導出不過最新版命令行工具一般會自動完成這一步。3.4 量化后模型精度評估如何科學地確認精度損失量化完成不等于任務結(jié)束必須對量化后的模型做一輪精度評估和目標檢測的mAP指標一起對比。我的流程是在驗證集上分別跑浮點模型和量化模型算出兩套mAP如果差值在可接受范圍內(nèi)比如mAP0.5下降不超過2%就繼續(xù)編譯部署如果精度掉得厲害就需要返工。不同任務對精度損失的容忍度不同目標檢測一般比圖像分類更敏感。我自己的經(jīng)驗是如果mAP0.5下降超過5%說明校準集可能沒有覆蓋足夠多的場景或者模型里某些層對量化特別敏感。你可以使用Vitis-AI提供的量化分析工具或日志查看每一層的量化敏感度優(yōu)先保留對精度影響大的層為更高精度其他層用INT8。當然更簡單的做法是先嘗試把校準集從200張增加到500張再重新量化一遍很多時候問題就解決了。4. 模型編譯與部署產(chǎn)物生成4.1 編譯命令詳解指定ZCU104架構量化出來的模型還要通過編譯變成DPU實際執(zhí)行的文件。ZCU104開發(fā)板上我們需要指定DPUCZDX8G架構。在Vitis-AI容器里編譯命令如下vai_c_xir \ --xmodel ./quant_res/quantized_model.xmodel \ --arch /opt/vitis_ai/compiler/arch/dpuv2/ZCU104/ZCU104.json \ --net_name yolov5s_zcu104 \ --output_dir ./compiled_model--xmodel指定量化模型的路徑--arch指定DPU架構描述文件這個JSON文件告訴編譯器ZCU104上DPU支持哪些運算、內(nèi)存帶寬、指令長度等參數(shù)。如果你用的是其他板卡比如ZCU102或KV260就要換對應路徑下的JSON文件千萬不能混用。--net_name就是生成的xmodel網(wǎng)絡名稱--output_dir決定編譯產(chǎn)物保存路徑。編譯完成后目錄里會生成類似yolov5s_zcu104.xmodel的文件。這個文件就是最終要拷到ZCU104上加載的模型文件。編譯階段的核心工作是對計算圖做算子融合和內(nèi)存規(guī)劃比如把卷積后面的批歸一化和ReLU融合到卷積運算內(nèi)部這在DPU上是硬件級別的優(yōu)化能顯著減少指令數(shù)和內(nèi)存訪問次數(shù)。4.2 DPU硬件配置與架構匹配問題編譯時指定的架構必須與ZCU104里實際例化的DPU核保持一致。如果你在Vivado里創(chuàng)建DPU核時選擇了DPUCZDX8G_ISA1_B4096那么arch路徑下對應的JSON也要匹配這個指令集架構ISA。如果軟件編譯的ISA和硬件DPU的ISA不匹配板卡運行時加載xmodel會直接報錯這在Vitis-AI里是非常典型的部署錯誤。常見的ZCU104配置有B4096和B512等這些參數(shù)描述了DPU中并發(fā)計算單元的數(shù)量可以理解為DPU的算力檔位。B4096計算能力強占用FPGA資源多最高時鐘頻率可能上不去B512資源占用小頻率更穩(wěn)但算力相對低。對于YOLOv5s來說B4096是比較均衡的選擇INT8下的理論算力能跑到2TOPS左右實際幀率視輸入尺寸而定。DPU頻率的設置也影響性能。我在Vivado配置DPU核時選擇300MHz或者更高的頻率但如果PL側(cè)的時序收斂不好就降到270MHz否則系統(tǒng)跑起來可能有偶發(fā)錯誤排查起來非常痛苦。Vivado綜合后會用報時序問題的路徑這時候通過降低DPU頻率來收時序是最快的方法。4.3 輸出產(chǎn)物說明xmodel、指示文件與部署準備編譯輸出的xmodel文件是二進制格式包含DPU指令流和模型權重數(shù)據(jù)。除此之外編譯日志和元信息文件也值得關注。日志里的total instruction count和total memory footprint可以用來粗略估算推理延遲和資源占用。我在項目里會用一張表對比不同輸入尺寸下的編譯結(jié)果配置項建議值備注輸入尺寸640x640精度優(yōu)先速度會慢一些輸入尺寸320x320速度優(yōu)先精度會下降DPU架構B4096平衡資源占用和算力編譯架構文件ZCU104.json必須匹配硬件DPU配置量化方式INT8對稱默認配置大多數(shù)場景足夠部署到ZCU104之前還需要把對應版本的Vitis AI RuntimeVART放到板卡上。官方的Petalinux鏡像里已經(jīng)預編譯好了runtime也可以自己編譯。runtime版本必須和編譯時的Vitis-AI版本嚴格對齊否則會出現(xiàn)版本不匹配的報錯。這一步比較容易忽略很多人在板子上跑不起來xmodel檢查后發(fā)現(xiàn)是runtime版本和主機編譯工具鏈版本對不上。5. 常見問題與排查技巧實錄5.1 算子不支持SiLU激活函數(shù)引發(fā)的不兼容這是我在量化時遇到的第一個報錯Unsupported Ops: aten::silu。原因在前面說過ZCU104上的DPUCZDX8G指令集不直接支持SiLU激活。解決辦法是把YOLOv5模型里所有SiLU替換成ReLU。需要注意替換的位置不止一處YOLOv5的CSPDarknet骨干里有多處SiLU如果不小心漏掉一個量化還是會報錯。建議在修改后用腳本掃描一遍模型定義確保沒有遺漏。5.2 量化后精度下降嚴重從校準集和預處理兩個方向排查量化后mAP掉點通常有以下幾類原因校準集規(guī)模太小校準圖片預處理與訓練不一致模型里有某些層對量化非常敏感輸入圖像尺寸和訓練時不匹配。我的排查順序是先擴充校準集數(shù)量再檢查預處理代碼然后考慮對敏感層做更高精度的保護最后才考慮改模型結(jié)構。大多數(shù)情況到第二步就能解決我遇到過一次因為dataloader里忘了做letterbox而是直接resize導致校準數(shù)據(jù)分布偏移量化后精度慘不忍睹排查了半天才定位到預處理的問題。5.3 編譯錯誤架構文件版本與容器版本不匹配如果你在編譯時發(fā)現(xiàn)報錯提示JSON架構文件里沒有對應算子定義通常是Vitis-AI容器版本和arch文件版本不匹配。解決方法是重新拉取和arch文件配套的容器版本或者直接在容器里使用默認路徑下的arch文件而不是到處拷貝網(wǎng)上找來的JSON。這個坑在多人協(xié)作或者在網(wǎng)上搜索教程時很容易踩因為同一個ZCU104的JSON在不同版本里的字段有細微差異。5.4 部署時加載xmodel失敗版本對齊和ISA匹配是重點板卡端最常見的問題是加載xmodel失敗。首先是runtime版本必須和主機端Vitis-AI版本一致其次就是前面反復強調(diào)的DPU ISA匹配。如果平臺報錯提示xmodel is not compatible with the DPU基本可以確定是編譯時的arch配置和板上DPU硬件配置不一致。另外檢查一下ZCU104鏡像里是否已經(jīng)正確加載了DPU驅(qū)動裸機部署和Petalinux部署在這個環(huán)節(jié)略有不同。5.5 性能不達標不只是DPU頻率的問題如果部署后幀率不理想不只是調(diào)高DPU頻率就能解決。模型輸入尺寸、DPU配置、PS側(cè)預處理代碼的寫法、是否合理使用多線程都會影響整體性能。我建議在硬件允許的前提下把YOLOv5的輸入從640x640降到416x416測試一下通常延遲能下降接近一半對很多檢測場景來說精度依然夠用。另外預處理如果全部放在PS側(cè)串行執(zhí)行會成為瓶頸建議使用NEON優(yōu)化或者直接改成多線程并行。排查以上問題時我習慣先把問題拆成主機端和板卡端兩段來定位。量化、編譯階段的報錯留在Docker里反復驗證部署階段的報錯再到板子上調(diào)試不要混在一起排查否則思路很容易亂。再說一個非常容易踩的坑Vitis-AI官方文檔里推薦的Docker鏡像非常大如果網(wǎng)速不好拉取時間很長。我一般會在同一個鏡像基礎上維護一個自己的tag把數(shù)據(jù)集、腳本、編譯產(chǎn)物都放在掛載目錄里這樣每次啟動容器不需要重新下載鏡像也不要頻繁提交新鏡像既省時間又避免容器膨脹。如果你按著這個流程走通了你會發(fā)現(xiàn)Vitis-AI本身并沒有想象中那么神秘核心就是量化、編譯、部署三步。真正花時間的地方全在細節(jié)上算子兼容性、校準集質(zhì)量、預處理一致性、版本對齊。這一篇讓你在主機端得到了可部署的xmodel。下一步就是在ZCU104上加載它跑起推理了這部分涉及VART API的調(diào)用、YOLOv5檢測頭的輸出解析、NMS后處理的實現(xiàn)內(nèi)容比量化編譯還要細碎一些我放到系列第二篇里詳細講。我個人在實際項目里的體會是先把模型側(cè)這一關理順了runtime的工程量其實不大真正花時間的往往是在調(diào)試輸入圖像顯示不對、坐標偏移這類不起眼的問題上。