ARTICLE DETAIL

资讯详情

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

Zynq UltraScale+上部署YOLOv8:INT8量化与DPU实战全记录

Zynq UltraScale+上部署YOLOv8:INT8量化与DPU实战全记录 去年接了个项目要在Xilinx Zynq UltraScale上把YOLOv8实时目标检测跑起来而且功耗、体积、时延都有硬指标。说实话一开始我心里也没底——YOLOv8在GPU上部署几乎一条龙但在FPGA这条链路上从Vivado、Vitis AI到PetaLinux每一层都有坑等着你。整个项目做完最大的体感是FPGA跑YOLOv8真正的瓶颈根本不是卷积算力够不够而是模型端、量化端、硬件通路端这三条线怎么完美咬合。这篇文章把从选型、模型准备、INT8量化到片上实现的全过程梳理了一遍重点放在量化技巧和实测坑位上给准备在Zynq UltraScale上做目标检测的同行一份可以直接参考的实战记录。1. 选型逻辑为什么是Zynq UltraScale而不是GPU、不是纯逻辑1.1 三条部署路线的账怎么算拿到需求先别急着写代码先把账算清楚。我当时对比了三条路线英伟达GPU方案、纯FPGA逻辑实现CNN的方案、Zynq UltraScale异构SoC方案。GPU方案的优势是生态成熟PyTorch训练完转TensorRT几乎一键跑通但问题也很直接功耗高、体积大、时延抖动不好控。做工业设备的人都懂很多现场根本没有给GPU服务器留位置甚至整机功耗预算就那么几十瓦。纯FPGA方案也就是不用任何现成的NPU/DPU软核自己用HLS或RTL把YOLOv8的网络结构搭出来这个工程量巨大。YOLOv8里光C2f模块、SPPF、上采样、Detect头这些异构算子用纯逻辑实现一遍并优化好没有半年下不来而且后期改模型结构基本等于重做。Zynq UltraScale走的是第三路线PL侧例化一个DPU软核IP来专门跑卷积PS侧用自带的Cortex-A53四核跑Linux、做调度和后处理。这样等于一手抓专用算力一手留通用处理能力YOLOv8这种卷积为主少量逻辑的网络结构正好匹配。我最终选了这块说实话用过之后觉得这个架构确实是为嵌入式视觉量身设计的。1.2 DPU软核到底是什么DPU全称Deep Processing Unit是Xilinx Vitis AI里针对Zynq UltraScale提供的一个可综合IP核。它不像GPU那样是一块固化的硬件而是在Vivado里例化后通过FPGA逻辑实现的软核本质是一套可配置算力大小的卷积加速引擎。DPU内部有若干PE阵列每个PE按配置好的ISA同时执行多个INT8乘加操作。常见的配置有B800、B2304、B4096数字越大代表每周期能做的乘加次数越多占用LUT和DSP资源也越多。DPU支持的算子包括普通卷积、深度可分离卷积、池化、ReLU、sigmoid等YOLOv8的backbone和neck基本全是这类结构所以DPU能覆盖。但这里有个关键点DPU不支持NMS和非极大值抑制相关的后处理算子。YOLOv8的Detect头是anchor-free的解码结构输出坐标、置信度和类别概率之后解码加NMS这一步必须在PS端的ARM核上用C自己实现。这也是为什么网上跑YOLOv5的教程多、跑YOLOv8的相对少——YOLOv8的检测头后处理比YOLOv5更绕一点。1.3 算力估算先算明白再选型号选具体型号前我用一句话做了估算YOLOv8n在640x640输入下浮点算力约8.7 GFLOPs降到512x512输入后约5.6 GFLOPs换算成MAC累加量大概是2.8 GMACs。DPU以B4096配置跑在300MHz时理论峰值是4096 MAC/周期×300MHz≈1.23 TMACs对应约2.46 TOPS的INT8算力。听起来2.8 GMACs只要两三毫秒就算完了但实际完全不是这么回事。DPU实际跑起来要受DDR带宽、片上RAM容量、层间数据搬运效率的制约利用率能到15%到20%就很不错了。所以真实推理时间估算应该在20到40毫秒量级这决定了最终能做到25到30FPS这个挡位。这个预期是合理的也符合工业视觉里实时检测的常规需求。如果客户非要60FPS那就得考虑DPU多核或者直接上更高端的版本这属于方案层面的调整。2. 模型端准备从PyTorch训练到可部署ONNX的关键处理2.1 数据集标注与训练FPGA端部署和GPU端最大的区别是模型一旦定下来就不适合频繁改结构所以数据集质量直接影响后期效果。我用LabelMe做了标注导出为JSON格式后再写脚本转成YOLO训练需要的txt格式。数据集目录结构建议整理成标准YOLO格式dataset/ ├─ images/ │ ├─ train/ │ └─ val/ ├─ labels/ │ ├─ train/ │ └─ val/训练环境我用的是Ubuntu 20.04装的Ultralytics框架。如果手头没有GPUCPU版本也能训小数据集几百张图、100个epoch也能出结果就是慢一些。以下是我的训练命令pip install ultralytics yolo detect train datadataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0训练时有个容易被忽略的点我直接用yolov8n.pt做预训练权重迁移而不是从一个随机初始化的模型开始。YOLOv8n虽然有9个输出层但预训练模型已经学会了通用特征提取迁移到自己的数据集上收敛速度快得多。训练完记得看results.png里的损失函数曲线如果train_loss和val_loss差距越来越大那就是过拟合了需要加数据增强或减少epoch。2.2 ONNX导出时的大坑别把后处理带进去模型训练完之后要把best.pt导出成ONNX格式。命令很简单yolo export modelbest.pt formatonnx opset12但这里埋着FPGA部署的第一个大坑。Ultralytics在导出ONNX时默认会把Detect头的解码逻辑一起导出于是导出的ONNX里就会包含一堆Reshape、Transpose、Concat、Sigmoid算子。这些算子如果全部送给Vitis AI编译很可能编译不通过或者就算编译过了DPU跑起来效率也极低。我的做法是导出后主动砍头用Netron看ONNX图找到最后一个卷积层通常是Detect头里最后一个Conv的输出张量名然后用onnx.utils.extract_model截断只保留backbone加neck加检测头卷积的部分。后处理全部留在C代码里自己做。import onnx onnx.utils.extract_model( yolov8n.onnx, yolov8n_cut.onnx, input_names[images], output_names[/model.22/conv_output_0] )这个操作让我少走了很多弯路。如果这一步不做后面Vitis AI编译、DPU部署都会因为算子不支持而反复报错一旦编译过了才发现模型有问题排查成本高得多。2.3 输入规格与预处理约定YOLOv8训练时默认使用letterbox保持纵横比把图像缩放后填充到正方形。这个letterbox策略在部署时也必须保持一致否则精度会明显下降。很多新手在GPU部署时用torchvision的resize直接把图拉成正方形精度掉一截还找不到原因FPGA上这个问题更突出因为一旦量化后输入分布的细微偏差都会被放大。常见的预处理链条是读取图像帧、letterbox缩放、BGR转RGB、HWC转CHW、除以255归一化。这条链在GPU上用CUDA kernel做毫无压力但在ARM核上全用浮点数跑会很费时间。所以到后期我把它改成定点化实现同时把BGR转RGB和HWC转CHW在一次遍历中完成。这部分细节在后面第4章展开讲。需要在模型准备阶段就定好的还有一件重要的事输入预处理和训练时保持一致校准量化时也要用同一套预处理方案。这三者割裂是量化后精度暴跌最常见的原因。3. INT8量化实操从校准集选择到敏感层处理3.1 为什么FPGA部署必须INT8很多人问Zynq UltraScale不是有DSP48E2可以做浮点乘加吗为什么非要量化到INT8原因很直接浮点运算在FPGA上消耗的资源是INT8的好几倍尤其卷积这种大规模乘加密集型运算用浮点DSP资源根本不够看。INT8部署让DSP48E2的吞吐翻倍也大幅压缩了权重和中间激活值在DDR和片上RAM之间的搬运量——带宽在FPGA推理里比算力更金贵。量化本质就是把浮点权重和激活值映射到INT8整数范围。每个张量会统计出一个scale和zero_point部署时把浮点输入乘上scale转成整数参与卷积输出再乘回scale还原成浮点。这一步感知上很简单实际操作中校准集选不好精度损失可以大到无法接受。3.2 校准集怎么选才靠谱Vitis AI 3.x的vai_q_pytorch提供了PTQ训练后量化能力流程是先加载浮点模型用一批校准数据跑一遍前向统计每层激活值的分布然后确定量化参数。校准集的选择是整个量化流程里回报率最高也最容易被低估的一环。我的经验教训是不要直接从训练集里随手抓几百张图当校准集。校准集需要满足几个条件覆盖部署场景的光照条件、目标尺度、遮挡情况至少300到500张最好不超过1000张太多没意义太少覆盖不足与训练集不重叠否则会有乐观偏差必须包含实际的负样本也就是没有目标只有背景的图否则模型在真机上会疯狂误检我们第一次量化后用验证集测mAP只掉了1.8个点感觉还不错。结果到现场一跑漏检率明显升高后来分析发现就是校准集几乎全是干净图片目标大、光线好、背景简单完全没覆盖到现场光线昏暗、目标密集遮挡的情况。后来重新从现场录制的视频里抽了几百帧作为校准集重新量化后现场的mAP直接涨了3个点左右。这件事让我彻底长了记性校准集的分布就是模型的标尺标尺不准部署必然翻车。3.3 量化后精度评估与敏感层回退量化完成后用验证集分别测浮点模型和INT8模型的mAP50和mAP50-95对比精度损失。一般掉1到2个点可以接受掉3个点以上就需要介入。我的处理顺序是先检查预处理是否一致。训练时letterbox、缩放比例、归一化方式有没有和校准、评估完全统一。用vai_q_pytorch的敏感层分析功能把每一层单独设置成不量化逐个观察对mAP的影响。把影响最大的那几层回退到浮点或者更高位宽看精度恢复情况。从经验看以下层最容易成为敏感层第一层卷积因为输入图像的原始分布信息直接从这里进网络检测头附近的卷积层输出层的量化误差会直接反映到box和class预测上数值分布范围特别大的层比如某些卷积之后经过上采样或者Concat特征图数值尺度差异很大INT8表示能力不足如果只回退少量敏感层就能恢复精度就优先做。如果敏感层太多、回退后性能也达不到要求那就只能上QAT量化感知训练。QAT是在训练过程中模拟量化误差让网络权重慢慢适应INT8的离散表示精度通常比PTQ好但要额外的GPU资源和训练时间。我的建议是PTQ走不通再上QAT不要一上来就QAT没必要。3.4 量化到编译的衔接量化完成后会产出一个带量化参数的模型文件然后通过vai_c_xir编译成DPU能直接执行的指令集模型。vai_c_xir --xmodel quantized_model.xmodel --arch dpu_b4096_zynqu.json -o yolov8n_int8.xmodel这里有个坑arch.json的架构描述必须和Vivado工程里实际例化的DPU核配置完全一致。比如你在硬件里配的是B4096编译时却用了B2304的arch文件编译能通过但放到板卡上一跑就会报架构不匹配或者直接非法指令。这类问题排查起来特别隐蔽因为错误信息往往不在DPU这一层而是在运行时Runner初始化阶段。4. 片上实现DPU核配置、视频通路与多线程调度4.1 Vivado里例化DPU核的要点硬件工程方面我用Vivado的Block Design搭了一个最小系统PS端挂DDR4控制器和UARTPL端例化DPU IP核通过AXI接口连到PS的DDR地址空间。DPU IP的配置界面里需要选几个关键参数Convolution Parallel也就是算力配置我选了B4096片上RAM大小这个决定DPU能缓存多少中间特征图太小会频繁访问DDR时钟域配置DPU的工作时钟一般可以跑到300MHz左右但如果时序紧张降频到250MHz反而更稳定还有一点容易被忽略DPU核和整个系统的复位必须做同步释放。FPGA里复位信号如果处理不好会出现亚稳态问题DPU这种带状态机的模块尤其敏感我在Vivado里用了proc_sys_reset IP来生成同步复位释放信号保证所有模块在同一个时钟边沿进入确定状态。很多跑起来偶尔卡死的问题最后都查到了复位释放不同步上。4.2 视频采集到显示的数据通路我的整体数据通路是这样设计的摄像头传感器通过MIPI CSI-2接口进PL经过CSI-2 RX IP解包后由Demosaic和色彩空间转换模块输出RGB再用VDMA把图像帧写到DDR的环形缓冲区。PS端的应用从DDR里取帧做完预处理把输入张量交给DPU RunnerDPU推理完写回DDR然后PS端读取输出张量做解码和NMS最后把可视化结果叠加到HDMI显示通道上。这里VDMA的多帧缓冲设计很关键。我配了4帧环形缓冲摄像头持续往DDR写应用从最新的帧开始处理尽量避免读到半帧导致图像撕裂。VDMA的行缓冲和突发长度也值得调行缓冲太小会导致带宽浪费突发长度太长又可能和DPU的DDR访问抢占带宽这个需要结合实际时序做权衡。4.3 VART部署与定点化预处理PS端应用我用的C和Vitis AI Runtime API核心流程是创建Runner、获取输入输出张量、填充输入、执行异步推理、等待完成、解析输出。伪代码如下auto runner vart::Runner::create_runner(yolov8n_int8.xmodel, run); auto input_tensors runner-get_input_tensors(); auto output_tensors runner-get_output_tensors(); // 准备输入 buffer uint64_t input_addr input_tensors[0]-get_data_physical_address(); memcpy((void*)input_addr, preprocessed_data, input_size); // 执行推理 auto job_id runner-execute_async({input_tensors}, {output_tensors}); runner-wait(job_id.first, -1); // 读取输出做解码 NMS一开始我是在ARM核上直接用浮点方式做图像缩放和归一化的结果发现整个链路跑下来帧率始终上不去。用perf一查ARM核的CPU占用率接近100%大部分时间都花在预处理上DPU反而经常闲着等输入。后来我把resize的浮点坐标全部改成定点化表示并让BGR转RGB、HWC转CHW、除以255在同一个循环里一次完成。这个改动直接让预处理时间从10毫秒级别降到了5到6毫秒整体帧率提升非常明显。4.4 流水线并行调度单帧串行计算是影响帧率的最大敌人采一帧、处理一帧、显示一帧每一步都在等上一步完成。我改成四线程环形流水线采集线程、预处理线程、推理调度线程、后处理线程各自独立通过环形缓冲区衔接。最关键的是让DPU异步执行。VART的execute_async提交后不用同步等待CPU可以在DPU计算的同时去处理上一帧的NMS和当前帧的预处理。经过这一步优化端到端吞吐直接从24FPS提到了30FPS左右这个提升不是靠DPU变快而是靠让所有硬件都在干活。5. 实测性能与三个坑5.1 项目实测数据最终部署配置如下Zynq UltraScale ZU5EV平台DPU核B4096配置跑300MHzPetaLinux 2023.1系统Vitis AI 3.x工具链模型是YOLOv8n用自己的数据集训练后导出截断再做INT8量化输入分辨率512x512。我实际测量了各环节的耗时阶段耗时图像采集MIPIVDMA约3ms预处理letterbox定点化归一化约6ms输入拷贝与DPU推理约33ms输出解码NMS约4ms端到端单帧约46ms在四线程流水线下整体吞吐稳定在30FPS左右。精度方面Float32模型在验证集上mAP50是0.782PTQ量化后是0.761掉了约2.1个点加入现场校准图重新量化后恢复到0.769。5.2 坑一ONNX带了后处理结构编译期没问题运行时疯狂报错这个坑是在项目初期踩的。当时第一次编译竟然通过了但把编译好的xmodel在板卡上运行时Runner初始化直接失败报错信息指向一个不存在的算子。排查了两天才发现是导出的ONNX里带了Detect头的Reshape和Transpose结构DPU编译时自动把这些算子调度到了CPU侧去执行但CPU侧没有对应的算子实现于是运行时崩溃。解决方式就是第2.2节讲的导出后砍掉后处理结构让DPU只跑卷积主干。这个经验后来成了我做FPGA部署YOLO系列模型的固定套路不管YOLOv5还是YOLOv8先砍后处理再送编译。5.3 坑二校准集太干净现场检测直接失效这个前面详细说过校准集选的是相对理想的样本导致量化参数严重偏离现场分布。最严重的一次INT8模型在测试集上mAP只掉了1.5个点到了现场同一场景下置信度全面偏低很多目标直接漏检。后来我录了现场视频抽帧300张作为校准集重新量化才把问题解决。这件事给我的启发是做FPGA部署一定要提前跟现场使用者确认成像环境光线、视角、目标大小、环境复杂度这些维度直接影响校准集的选择。5.4 坑三DPU没跑满CPU预处理成了新瓶颈项目中期阶段性能始终卡在22FPS左右。一开始我以为瓶颈在DPU算力不够又是调DPU频率又是看DDR带宽占用结果都正常。后来用perf才发现ARM核的四核里有一个核心占用率100%就是负责图像预处理的线程。我把预处理从浮点改成定点后CPU占用降了30%DPU终于开始满负荷运转帧率也上来了。这里想多说一句FPGA系统做性能优化一定不能只看PL侧的资源利用率PS侧的CPU、DDR带宽、AXI总线的冲突任何一个都可能成为瓶颈。一个系统要跑得快不是单个模块快而是整条流水线的匹配度高。5.5 可继续扩充的优化方向项目到这里基本收尾了如果后面还要深入我会从这几个方向做一是把图像预处理进一步下沉到PL侧用逻辑实现resize和归一化把PS侧CPU的时间省出来二是考虑使用DPU多核实例让不同层在不同DPU核上并行跑三是在PS侧做更高效的NMS并行化或者针对特定场景把NMS改成更宽松的近似算法。另外如果板卡有PCIE接口可以扩展成FPGA加速卡形态配合上位机使用这也是工业应用里很常见的部署方式。最后聊几句我个人的体会。FPGA加YOLOv8这套组合最大的价值在于把目标检测从GPU服务器里搬了出来嵌入到功耗受限、体积受限、时延要求高的工业设备里。但价值的背后是代价这个项目的复杂度和GPU部署完全不在一个量级训练、量化、编译、硬件、驱动、算法七条线交错在一起任何一个环节版本不匹配都会产生难以排查的问题。我强烈建议大家在动手前把Vitis AI版本、PetaLinux版本、DPU核版本、板卡BSP版本全部记录下来如果条件允许最好用官方推荐的组合能省掉大量莫名其妙的问题。施工顺序上也一定先拿最小的模型、单张图片跑通全链路再逐步上视频流和实时优化——这个节奏看起来慢实际是最快的路径。
返回列表