ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ZCU104上使用Vitis-AI部署YOLOv5:从模型量化到DPU运行全流程

ZCU104上使用Vitis-AI部署YOLOv5:从模型量化到DPU运行全流程 做FPGA端的深度学习部署Vitis-AI基本是绕不开的一条技术路线。这篇文章把我最近在ZCU104上跑通YOLOv5的完整过程记录下来从Pytorch训练好的模型权重开始到量化校准再到编译成DPU可执行的xmodel每一步都展开讲清楚。代码我已经整理开源想直接照着做的朋友可以到我的GitHub仓库里找仓库里的脚本和文章里用到的完全一致。内容适合刚接触Vitis-AI、准备把目标检测模型部署到Zynq UltraScale平台的开发者参考也适合已经在Xilinx生态里摸爬滚打、想快速评估YOLOv5在DPU上效果的同行。这个系列我打算写三篇第一篇就是现在这篇重点解决模型怎么从Pytorch变成DPU能跑的xmodel这一核心问题。很多朋友卡在这个环节要么是Docker环境起不来要么是量化时算子不兼容报错要么是编译完发现精度掉得没法看。这些坑我基本都踩了一遍文章里会逐个说明原因和解决办法。1. 整体方案设计与选型思路1.1 为什么选ZCU104 Vitis-AI而不是 Jetson 或嵌入式NPU做边缘端目标检测硬件方案其实很多。NVIDIA Jetson系列生态成熟跑YOLOv5几乎零门槛TensorRT加速效果也出色但功耗和整板成本都比较高而且在严苛的工业环境下散热和可靠性不一定满足要求。嵌入式NPU比如瑞芯微RK3588、算能BM1684性价比很高但模型适配受限于各类工具链想做自定义图像采集、协议解析、电机控制这类逻辑时NPU的通用性就不够。ZCU104的优势在于它是Zynq UltraScale MPSoCPS侧有四个ARM A53核PL侧有可编程逻辑DPU相当于在PL里例化出来的专用CNN加速IP。这意味着卷积推理可以卸载到DPU上而图像采集、帧处理、NMS后处理、外部接口通信这些完全可以在PS侧自己控制。如果你需要的是一个既能跑AI、又能灵活控制外设的边缘计算平台ZCU104就是比Jetson更适合的选择。Vitis-AI是AMD/Xilinx官方提供的AI推理加速工具链它解决的问题很直接你不用自己写RTL去实现卷积也不用费劲学HLS去折腾矩阵运算只需要把训练好的模型丢给工具链量化、编译之后就能在DPU上跑起来。整个流程从Pytorch模型到最终在板卡上运行链路非常清晰而且官方镜像把所有依赖都封装好了很大程度上降低了FPGA开发的门槛。当然它的学习曲线并没有完全消失——量化原理、算子支持范围、DPU架构选择这些知识如果不懂遇到问题还是会一头雾水。1.2 端到端流程拆解训练、量化、编译、部署整个技术路径可以分成四个阶段训练阶段在Pytorch框架下完成YOLOv5模型的训练得到浮点权重文件这一步有官方YOLOv5仓库可以用也可以在自己数据集上微调。量化阶段用Vitis-AI提供的vai_q_pytorch工具以少量真实图片作为校准集统计网络各层激活值的动态范围把FP32参数映射到INT8。编译阶段用vai_c_xir工具把量化后的模型编译成DPU指令流的xmodel文件这一阶段会指定具体的DPU架构例如ZCU104上的DPUCZDX8G。部署阶段在ZCU104上运行Vitis AI Runtime加载xmodel执行推理PS侧CPU负责图像预处理和结果后处理。文章后面所有内容都围绕这条主线展开。我在实际开发中比较建议先跑通一个最简单的分类模型再上YOLOv5这种结构复杂的检测模型这样可以把工具链不熟和模型适配问题分开排查。当然如果你已经有一定基础直接按本文流程操作也没有问题。2. 环境准备与必备概念2.1 主机端环境搭建Ubuntu Docker Vitis-AI镜像Vitis-AI官方推荐在Docker容器里完成量化和编译原因很简单不同版本的工具链依赖差异很大Docker镜像把CUDA、Pytorch、nndct等一堆依赖固定好避免污染主机环境也方便版本切换。我自己的习惯是单独划分一个工作目录把模型文件、数据集、输出产物都挂载进容器这样即使容器重建也不会丢数据。主机环境建议Ubuntu 18.04或20.04先安装Docker然后拉取官方镜像。Vitis-AI 1.4是比较经典的稳定版本新项目也可以考虑2.5或3.0版本但不同版本的命令和算子支持会有差异本文以1.4版本为例说明# 拉取镜像根据实际网络环境选择合适的源 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环境conda activate vitis-ai-pytorch这一步很关键Vitis-AI容器里同时内置了TensorFlow2、TensorFlow1、Pytorch、Caffe等多个环境如果不激活pytorch环境就执行vai_q_pytorch命令会直接提示找不到命令。我用的是1.4版本自带的python3.7 pytorch 1.8组合实际测试下来和官方YOLOv5 v5.0版本的代码兼容性比较好。2.2 核心概念速通量化、编译、DPU、xmodel在动手之前一定要把几个基本概念搞明白否则后面连错误提示都看不懂。量化就是把浮点模型转成定点模型。FP32精度下每个参数占4字节而DPU的DSP单元做INT8定点乘加运算更高效FP32转INT8之后模型体积缩小到原来的四分之一推理速度大幅提升。量化的数学本质是找一个浮点实数r和整数q之间的映射关系q round(r / scale) zero_point其中scale和zero_point是根据每一层参数的取值范围算出来的。Vitis-AI在量化时会在校准阶段统计激活值的分布从而确定合适的scale和zero_point。编译不是普通程序员理解的源码变机器码在Vitis-AI里编译是把已经量化的模型进一步转换成DPU的指令流同时做算子融合和内存调度优化。DPU认识的不是PyTorch的算子图而是一套专用的AI指令最终产物是xmodel文件。DPU是Xilinx提供的可配置CNN加速IP核在ZCU104上用的典型配置是DPUCZDX8G。你可以在Vivado里例化DPU核设置不同的架构参数比如能放几个卷积核、支持多大的输入图像然后综合出FPGA比特流。软件侧的模型编译必须和硬件侧的DPU架构匹配否则编译出来的xmodel在板子上根本加载不了。2.3 YOLOv5模型准备与针对性预处理YOLOv5本身是Pytorch框架下非常成熟的目标检测工程模型主体由CSPDarknet骨干网络、PANet特征融合层和YOLO检测头组成。准备模型时我建议直接用官方v5.0分支训练自己的权重或者先用官方预训练权重yolov5s.pt走通整个流程后续再换自定义数据。量化编译阶段用到的模型文件是PyTorch的.pt/.pth权重不是ONNX这一点要特别注意。有一个针对Vitis-AI的关键修改必须在量化前完成YOLOv5默认使用SiLU激活函数也就是Swish但DPUCZDX8G对SiLU的支持很有限量化和编译时大概率会报Unsupported Op错误。我的处理方式是把模型结构中的SiLU全部替换成ReLU虽然理论上会让模型表达能力略降一些但实际测试下来精度损失很小而且能顺利在DPU上部署。如果你是自己训练模型建议在训练阶段就直接把激活函数换掉比如在模型定义里把SiLU改成ReLU再训练这样从源头避免麻烦。输入图像的预处理也必须和训练时保持一致。YOLOv5在预处理时会对图像做letterbox等比缩放后填充灰边然后归一化到0到1之间量化校准阶段给模型喂的数据必须完全按这个流程来否则量化统计出的激活值范围是不准确的最终部署精度会受到牵连。3. 量化流程详解从FP32到INT83.1 为什么必须做量化以及它带来的收益ZCU104上的DPU本质上就是一个定点运算引擎INT8是它的核心工作精度。如果不做量化直接把FP32模型扔给工具链编译编译根本不会通过即便强行部署也会因为计算资源开销巨大而性能惨不忍睹。量化带来的收益体现在三个层面一是模型体积压缩一个70MB左右的FP32模型量化后约17.5MB对片上存储有限的嵌入式设备来说这个差别很关键二是推理速度提升INT8的定点乘加在DSP上可以流水线执行实测在ZCU104上YOLOv5s的推理延迟比FP32模拟运行快好几倍三是功耗降低FPGA上INT8运算的单位能耗远低于浮点运算。当然量化不是免费的它一定带来精度损失。所以整个量化流程里最重要的事情就是控制精度损失这是Vitis-AI使用中最核心的经验之一。量化误差主要来源于参数和激活值在转INT8时的舍入误差以及极端分布数值被截断。Vitis-AI的解决方案是在校准阶段用真实数据来统计每一层的动态范围从而确定合适的量化参数让舍入误差尽量小。3.2 校准数据集的选取直接决定量化精度校准集是量化时用来统计激活值分布的小批量真实图片。很多人在这一步不重视随手拿几张图就校准了结果量化后模型几乎不可用。根据我的测试和官方建议校准集一般取200到500张左右比较合适图片要覆盖真实场景中的典型情况不同光照、不同目标尺寸、不同背景复杂度的样本都尽量包含。如果太少统计出的数值范围有偏差如果太多校准时间长而且收益会递减。我自己在项目里通常先从验证集中随机抽样300张图片再手动检查一遍图片多样性确保不会集中在某几个场景。同时要注意校准图片的预处理必须和训练完全一致。比如YOLOv5训练时用了letterbox和归一化那校准阶段也必须是同样的操作不能让模型看到与训练时差异很大的数据分布。校准集的载体路径我放在一个文本文件里每行一张图片的路径。后面量化命令会读取这个文件。组batch时建议batch_size设为1避免多张图统计时个别异常值对量化参数造成干扰。3.3 量化执行使用vai_q_pytorch一行命令完成Vitis-AI针对Pytorch模型的量化工具是vai_q_pytorch它封装了nndct的量化器。在激活好vitis-ai-pytorch环境后基本用法如下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拆开说几个重要参数。--model指定的是浮点权重文件路径--input_size是模型输入尺寸YOLOv5默认640x640按batch,channel,height,width的顺序写--dataset是刚才说的校准图片路径列表文件--data_preprocess是一个Python脚本路径里面实现和训练阶段一致的数据预处理逻辑--save_dir是输出目录量化产物会写到这个目录下。如果你希望更精细地控制量化过程也可以直接使用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数据上执行forward让工具统计各层激活范围 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 量化后模型精度评估如何科学地确认精度损失量化完成不等于任务结束必须对量化后的模型做一轮精度评估和目标检测的mAP指标一起对比。我的流程是在验证集上分别跑浮点模型和量化模型算出两套mAP如果差值在可接受范围内比如mAP0.5下降不超过2%就继续编译部署如果精度掉得厉害就需要返工。不同任务对精度损失的容忍度不同目标检测一般比图像分类更敏感。我自己的经验是如果mAP0.5下降超过5%说明校准集可能没有覆盖足够多的场景或者模型里某些层对量化特别敏感。你可以使用Vitis-AI提供的量化分析工具或日志查看每一层的量化敏感度优先保留对精度影响大的层为更高精度其他层用INT8。当然更简单的做法是先尝试把校准集从200张增加到500张再重新量化一遍很多时候问题就解决了。4. 模型编译与部署产物生成4.1 编译命令详解指定ZCU104架构量化出来的模型还要通过编译变成DPU实际执行的文件。ZCU104开发板上我们需要指定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支持哪些运算、内存带宽、指令长度等参数。如果你用的是其他板卡比如ZCU102或KV260就要换对应路径下的JSON文件千万不能混用。--net_name就是生成的xmodel网络名称--output_dir决定编译产物保存路径。编译完成后目录里会生成类似yolov5s_zcu104.xmodel的文件。这个文件就是最终要拷到ZCU104上加载的模型文件。编译阶段的核心工作是对计算图做算子融合和内存规划比如把卷积后面的批归一化和ReLU融合到卷积运算内部这在DPU上是硬件级别的优化能显著减少指令数和内存访问次数。4.2 DPU硬件配置与架构匹配问题编译时指定的架构必须与ZCU104里实际例化的DPU核保持一致。如果你在Vivado里创建DPU核时选择了DPUCZDX8G_ISA1_B4096那么arch路径下对应的JSON也要匹配这个指令集架构ISA。如果软件编译的ISA和硬件DPU的ISA不匹配板卡运行时加载xmodel会直接报错这在Vitis-AI里是非常典型的部署错误。常见的ZCU104配置有B4096和B512等这些参数描述了DPU中并发计算单元的数量可以理解为DPU的算力档位。B4096计算能力强占用FPGA资源多最高时钟频率可能上不去B512资源占用小频率更稳但算力相对低。对于YOLOv5s来说B4096是比较均衡的选择INT8下的理论算力能跑到2TOPS左右实际帧率视输入尺寸而定。DPU频率的设置也影响性能。我在Vivado配置DPU核时选择300MHz或者更高的频率但如果PL侧的时序收敛不好就降到270MHz否则系统跑起来可能有偶发错误排查起来非常痛苦。Vivado综合后会用报时序问题的路径这时候通过降低DPU频率来收时序是最快的方法。4.3 输出产物说明xmodel、指示文件与部署准备编译输出的xmodel文件是二进制格式包含DPU指令流和模型权重数据。除此之外编译日志和元信息文件也值得关注。日志里的total instruction count和total memory footprint可以用来粗略估算推理延迟和资源占用。我在项目里会用一张表对比不同输入尺寸下的编译结果配置项建议值备注输入尺寸640x640精度优先速度会慢一些输入尺寸320x320速度优先精度会下降DPU架构B4096平衡资源占用和算力编译架构文件ZCU104.json必须匹配硬件DPU配置量化方式INT8对称默认配置大多数场景足够部署到ZCU104之前还需要把对应版本的Vitis AI RuntimeVART放到板卡上。官方的Petalinux镜像里已经预编译好了runtime也可以自己编译。runtime版本必须和编译时的Vitis-AI版本严格对齐否则会出现版本不匹配的报错。这一步比较容易忽略很多人在板子上跑不起来xmodel检查后发现是runtime版本和主机编译工具链版本对不上。5. 常见问题与排查技巧实录5.1 算子不支持SiLU激活函数引发的不兼容这是我在量化时遇到的第一个报错Unsupported Ops: aten::silu。原因在前面说过ZCU104上的DPUCZDX8G指令集不直接支持SiLU激活。解决办法是把YOLOv5模型里所有SiLU替换成ReLU。需要注意替换的位置不止一处YOLOv5的CSPDarknet骨干里有多处SiLU如果不小心漏掉一个量化还是会报错。建议在修改后用脚本扫描一遍模型定义确保没有遗漏。5.2 量化后精度下降严重从校准集和预处理两个方向排查量化后mAP掉点通常有以下几类原因校准集规模太小校准图片预处理与训练不一致模型里有某些层对量化非常敏感输入图像尺寸和训练时不匹配。我的排查顺序是先扩充校准集数量再检查预处理代码然后考虑对敏感层做更高精度的保护最后才考虑改模型结构。大多数情况到第二步就能解决我遇到过一次因为dataloader里忘了做letterbox而是直接resize导致校准数据分布偏移量化后精度惨不忍睹排查了半天才定位到预处理的问题。5.3 编译错误架构文件版本与容器版本不匹配如果你在编译时发现报错提示JSON架构文件里没有对应算子定义通常是Vitis-AI容器版本和arch文件版本不匹配。解决方法是重新拉取和arch文件配套的容器版本或者直接在容器里使用默认路径下的arch文件而不是到处拷贝网上找来的JSON。这个坑在多人协作或者在网上搜索教程时很容易踩因为同一个ZCU104的JSON在不同版本里的字段有细微差异。5.4 部署时加载xmodel失败版本对齐和ISA匹配是重点板卡端最常见的问题是加载xmodel失败。首先是runtime版本必须和主机端Vitis-AI版本一致其次就是前面反复强调的DPU ISA匹配。如果平台报错提示xmodel is not compatible with the DPU基本可以确定是编译时的arch配置和板上DPU硬件配置不一致。另外检查一下ZCU104镜像里是否已经正确加载了DPU驱动裸机部署和Petalinux部署在这个环节略有不同。5.5 性能不达标不只是DPU频率的问题如果部署后帧率不理想不只是调高DPU频率就能解决。模型输入尺寸、DPU配置、PS侧预处理代码的写法、是否合理使用多线程都会影响整体性能。我建议在硬件允许的前提下把YOLOv5的输入从640x640降到416x416测试一下通常延迟能下降接近一半对很多检测场景来说精度依然够用。另外预处理如果全部放在PS侧串行执行会成为瓶颈建议使用NEON优化或者直接改成多线程并行。排查以上问题时我习惯先把问题拆成主机端和板卡端两段来定位。量化、编译阶段的报错留在Docker里反复验证部署阶段的报错再到板子上调试不要混在一起排查否则思路很容易乱。再说一个非常容易踩的坑Vitis-AI官方文档里推荐的Docker镜像非常大如果网速不好拉取时间很长。我一般会在同一个镜像基础上维护一个自己的tag把数据集、脚本、编译产物都放在挂载目录里这样每次启动容器不需要重新下载镜像也不要频繁提交新镜像既省时间又避免容器膨胀。如果你按着这个流程走通了你会发现Vitis-AI本身并没有想象中那么神秘核心就是量化、编译、部署三步。真正花时间的地方全在细节上算子兼容性、校准集质量、预处理一致性、版本对齐。这一篇让你在主机端得到了可部署的xmodel。下一步就是在ZCU104上加载它跑起推理了这部分涉及VART API的调用、YOLOv5检测头的输出解析、NMS后处理的实现内容比量化编译还要细碎一些我放到系列第二篇里详细讲。我个人在实际项目里的体会是先把模型侧这一关理顺了runtime的工程量其实不大真正花时间的往往是在调试输入图像显示不对、坐标偏移这类不起眼的问题上。
返回列表