AXU9EG深度学习部署全流程:Vitis AI量化到DPU推理实践 手里这块黑金AXU9EG开发板我折腾了差不多两周才把一条完整的深度学习部署链路跑通。如果你也想在AXU9EG上做模型部署又不想从一堆手册和英文文档里找答案那这篇全流程解析应该能帮你省下大量时间。这篇文章会从硬件架构、环境搭建、模型量化编译、PL端加速器搭建一直讲到PS端调用DPU跑通推理每一步我都会把当时踩过的坑和最终留下的方案写清楚。AXU9EG是Xilinx Zynq UltraScale MPSoC系列核心型号XCZU9EG属于异构SoC一边是四核ARM Cortex-A53加双核Cortex-R5的PS端另一边是可编程逻辑PL端。深度学习部署在这个平台上最典型的路子就是用Vitis AI工具链把训练好的模型量化、编译成DPU能读的xmodel再在PL端例化DPU加速器最后在PS端用Python或C调用完成实时推理。整条链路涉及的环节非常多任何一个环节对不上版本都会卡很久。所以下面我把整个流程拆成几块按实际操作顺序讲。1. 全流程思路与硬件选型分析1.1 AXU9EG为什么适合深度学习部署很多人对FPGA做深度学习第一反应是难第二反应是功耗低。实际上Zynq UltraScale这块芯片把ARM和FPGA绑在一起天然适合做边缘端模型部署。AXU9EG开发板上的XCZU9EGPL侧有274K左右的逻辑单元约274K严格讲是274K个系统逻辑单元另外还有大量DSP Slice、Block RAM以及UltraRAM。单说DPU核在ZU9EG上例化3到4个通用配置的DPUCZDX8G没有太大压力INT8推理性能可以到每秒1.5万亿次到2万亿次左右。这个算力虽然比不上高端GPU但在板级功耗只有几十瓦的嵌入式平台里已经相当能打。PS侧的四核Cortex-A53跑Embedded Linux处理网络通信、图片输入、结果输出都很顺手。实际部署时ARM端跑调度FPGA端跑卷积和全连接运算两边分工明确这也是我最终选这套方案的核心原因。如果你手头已经有了一块AXU9EG不想让它只当普通ARM开发板用深度学习部署应该是最能物尽其用的方向。1.2 部署方案选型Vitis AI、PYNQ还是手写RTL在AXU9EG上跑深度学习主流方案有三类。第一类是使用Xilinx官方Vitis AI工具链加DPU IP这也是官方更新最频繁、资料最多、性能最稳的方案。第二类是使用PYNQ把PL端打包成overlay用Python控制FPGA资源优点是好上手底层还是同一个DPU。第三类是自己在RTL里手写卷积加速器工作量巨大适合教学或专门定制一般情况下不建议碰。我最终选择Vitis AI加PYNQ的组合模型转换和编译用Vitis AI工具链板端运行环境用PYNQ镜像把DPU的bitstream做成overlayPython直接加载xmodel。这样做的好处是中间不涉及太多底层硬件细节又可以保证DPU跑在比较高的性能档位。纯手写RTL虽然能让你在面试时吹一整晚但要交付一个可用的部署流程效率远不如官方的DPU方案。1.3 全流程路线图整个流程可以分成五段准备模型、量化编译、PL端搭建、PS端运行、调试优化。第一步是准备一个训练好的模型PyTorch和TensorFlow都可以Vitis AI工具链都支持只是导入路径不同。第二步是在Docker容器里完成量化和编译输出xmodel文件。第三步是在Vivado里搭建带DPU的硬件工程生成bitstream和XSA。第四步是在开发板上搭好Linux环境把bitstream和xmodel加载进去通过Python或C接口跑推理。第五步是处理各种性能和精度问题。这一整套链路里最容易卡死人的是版本匹配。Xilinx的Vitis AI、Vivado、PYNQ镜像、DPU IP四个东西之间都有对应关系用错版本经常会出现类似“kernel not found”或“xclbin file format version mismatch”的报错。所以后面我会单独讲版本匹配的经验。2. 环境搭建先让开发板跑起来2.1 开箱准备与启动要素黑金AXU9EG开发板是一块大板接口非常全开机前建议先检查这几样12V电源适配器最好是官方配的电流要足够一张TF卡建议16GB以上Class 10一条Micro USB转串口线用于观察启动日志一根网线用于SSH和传输文件。你会发现串口在调试阶段几乎是离不开的尤其是第一次启动出问题的时候。启动介质方面AXU9EG支持从QSPI Flash、SD卡、eMMC等多种方式启动。为了方便更换系统和调试我一般用SD卡启动。SD卡里只需要放好启动镜像拨动开发板上的启动模式拨码开关到SD卡模式上电后串口就能看到U-Boot和Linux的启动日志。黑金官方资料包里通常会有现成的BOOT.bin、image.ub和Ubuntu根文件系统第一次上手不要自己从头交叉编译直接用官方提供的它们先把板子跑起来后面再慢慢深挖。2.2 制作启动SD卡给开发板挂载Ubuntu“给开发板挂载Ubuntu”这个操作本质是把Ubuntu根文件系统放进SD卡分区让U-Boot引导硬件后通过内核挂载rootfs启动系统。我在这上面犯过一个小错SD卡只有一个分区结果把BOOT.bin和rootfs全都塞进去了系统完全启动不了。AXU9EG的启动SD卡建议分两个区第一个分区是FAT32用来放BOOT.bin、image.ub等启动文件第二个分区是EXT4用来放Ubuntu根文件系统。具体操作流程是在Ubuntu主机上插入TF卡用fdisk或GParted分区。第一个分区格式化成FAT32挂载后把官方资料包里的BOOT.bin、image.ub、如还有设备树文件一并拷进去。第二个分区格式化成EXT4然后解压根文件系统压缩包比如ubuntu-rootfs.tar.gz命令类似sudo tar -xzf ubuntu-rootfs.tar.gz -C /mnt/rootfs这里有个关键点解压之前用mount命令把第二分区挂载到/mnt/rootfs解压完成后一定要sync再卸载分区否则文件缓存没落盘拔卡后大概率启动失败。检查根文件系统是否已经“挂载”成功最直观的方式是启动后输入lsb_release -a如果能看到Ubuntu版本号说明整个系统已经正常跑起来了。2.3 配置开发环境串口、SSH与交叉编译开发板启动后先通过串口登录。串口参数一般是115200波特率、8位数据位、1位停止位、无校验具体以黑金资料里的说明为准。Windows下可以用PuTTY或者SSCOMLinux下用minicom或者screen。登录后第一件事是看IP地址用ifconfig或ip addr查一下网口IP然后就可以用SSH远程连接了后面传文件、跑脚本都方便很多。如果你的需求不只是跑PYNQ还想自己写一些C/C底层程序那就需要配置交叉编译环境。AXU9EG的PS端是ARMv8-A架构在Ubuntu主机上安装gcc-aarch64-linux-gnu交叉编译器然后编写CMake或者Makefile编译时指定这套工具链就可以生成能在开发板上运行的程序。不过就深度学习部署而言大多数时候Python就够用了交叉编译只有在做C性能优化时才需要。3. 模型量化与编译把PyTorch模型变成DPU能吃的xmodel3.1 为什么要量化FPGA和低精度有什么关系DPU核内部针对INT8做了深度优化卷积、全连接、池化这类密集计算在INT8下的吞吐量远高于FP32。原因不复杂INT8的数据位宽只有32位浮点的四分之一同样的DSP资源和带宽可以塞进更多的并行计算。所以把FP32模型转成INT8模型是FPGA部署最核心的一步。这一过程会带来一定精度损失但通过量化感知训练或校准数据集在分类、检测这类任务里精度损失通常能控制在1%到3%以内。我在第一次尝试时直接把PyTorch模型导出成ONNX然后强行编译结果DPU完全不吃。原因是我跳过了量化步骤。Vitis AI工具链中的vai_q_pytorch就是专门做量化的模块它会读取一个小的校准数据集统计每层激活值的范围然后把浮点权重和激活映射到INT8。这个校准集不需要很大每个类别几十张图就够了但一定要和训练数据的分布一致否则量化后精度会明显变差。3.2 搭建Vitis AI Docker工具链环境Vitis AI工具链的运行方式是以Docker容器为主。主机上先安装Docker然后拉取对应版本的Vitis AI镜像。Xilinx官方有一个Dockerfile和预编译镜像建议按照官方文档里的版本对应关系拉取指定tag不要随便用latest。进入容器时要把包含模型和数据的目录挂载进去这样容器里面才能访问到你的文件例如docker run -it -v /home/user/vitis_ai_workspace:/workspace \ xilinx/vitis-ai-gpu:latest bash进入容器后需要先执行conda激活脚本然后进入对应的Python环境例如vitis-ai-pytorch。这一步如果不做直接执行vai_q_pytorch会提示找不到命令。我当时就卡在这里以为镜像没装好折腾了很久才发现是环境变量没激活。每次进入容器都要重新source一下所以建议把这句命令写在脚本里省得每次手敲。3.3 量化、编译与xmodel生成过程以PyTorch的ResNet18分类模型为例量化流程大致是这样先加载训练好的PyTorch模型并设置为推理模式实例化vai_q_pytorch的Quantizer然后把校准数据集逐一喂给模型让量化器统计激活范围。校准完成后调用deploy得到量化后的模型。这一步的输出不是xmodel而是一个通过导出得到的onnx或xir图之后还要交给Vitis AI编译器。编译命令大致长这样vai_c_xir -x quantized_model.xmodel -a arch.json -o output_dir -n resnet18其中arch.json是DPU架构配置文件必须和你后面在PL端例化的DPU版本完全对应。DPUCZDX8G和DPUCZDX8H这类配置不一样搞错了编译可能成功但加载时会报错。编译完成后output目录下就会生成resnet18.xmodel这个文件就是最终部署时用的模型文件。很多人在这一步报错常见原因有模型里有DPU不支持的算子比如某些自定义OP、循环结构、动态shape或者校准数据集数量太少导致量化统计不稳定。我的经验是先用官方提供的一些标准模型把流程跑通再去适配自己的复杂模型。你如果有一个带自定义层的模型尽量在导出前把自定义层替换成标准算子或者把不支持的层迁移到ARM端做后处理不要和DPU较劲。4. PL端搭建DPU加速器4.1 用Vivado Block Design搭DPU模型编译完成后下一步是给DPU做一个硬件“载体”。在Vivado中新建工程选择XCZU9EG芯片型号使用Block Design方式搭建。先添加Zynq UltraScale MPSoC IP完成PS侧基础配置包括DDR、UART、SD、GPIO、ENET这些外设。然后添加DPU IP这一步需要先把Vitis AI库中的DPU IP文件目录加到Vivado的IP仓库里否则在IP Catalog里搜不到DPU。DPU IP配置里有几个关键参数Number of Cores表示例化几个核心核心数越多并行能力越强但资源占用也越大DPU Config的架构类型要选择对应的arch我用的DPUCZDX8G。最保守的方式是先配置一个核心把DSP和BRAM消耗控制在50%左右留出余量。连接好时钟和复位后还需要把DPU的中断接到PS端这样ARM侧才能收到推理完成的信号。整个Block Design连接完成后先跑综合再跑实现最后生成比特流。4.2 导出硬件并准备Overlay生成比特流后Vivado会输出一个XSA文件里面包含了硬件描述和比特流信息。如果你使用PYNQ方式部署还需要额外生成一个包含bitstream的overlay包。具体做法是使用Vitis软件或者PYNQ的构建脚本把XSA打包成pynq_dpu.bit和相关的hwh文件放到开发板上的指定目录。在PYNQ里overlay就是控制PL端的一把钥匙加载后FPGA就运行起来了。这个过程也容易出问题最常见的是DPU IP版本和Vitis AI工具链版本不匹配。比如工具链是1.4版本Vivado里却用了2.5版本的DPU IP编译出来的xmodel或者bitstream在运行时就会出奇怪错误。所以我在第一次搭建时就吃了这个亏后来干脆把每个组件的版本号列了一张对照表严格遵守后才稳定跑通。4.3 资源利用评估DPU的资源占用和配置直接相关我实测过一个少核配置在ZU9EG上用掉的资源大概如下资源类型一个DPU核占用比例两个DPU核占用比例估算LUT约20%-25%约40%-45%FF约10%-15%约20%-25%BRAM约30%-35%约60%-70%DSP约25%约50%这个比例因版本和优化级别不同会有浮动但整体趋势是这样。如果你后面还要在PL端放自己的逻辑比如图像预处理IP、视频采集接口建议先留好资源余量不要一上来就把核数拉满。资源评估最好在Vivado的Implement后看综合后的报告只能作为初步参考。5. 在PS端调用DPU跑推理5.1 安装PYNQ与DPU运行环境板端运行环境我选择使用PYNQ镜像而不是手动逐个安装XRT和DPU驱动。PYNQ的好处是已经把很多底层驱动和Python库打包好了烧到SD卡启动后直接打开Jupyter Notebook或SSH进入就能开始加载overlay。你只需要准备一张足够大的SD卡把PYNQ镜像像写普通系统镜像一样写入SD卡启动方式跟前面做Ubuntu启动卡类似。启动后确认环境正常可以先执行一个简单的overlay加载测试比如加载带有LED或开关的例程。这一步能排除底层硬件链路问题。如果连最简单的overlay都加载不了那大概率是bitstream版本和当前PYNQ内核版本不匹配优先检查这两点。如果overlay加载正常再把DPU专用overlay放进去整套运行环境就算通了。5.2 用Python加载xmodel跑一次推理在PYNQ环境里调用DPU最直接的方式是用pynq_dpu库。先加载overlay再load_model把上一步生成的xmodel交给DPU runner之后就能喂数据拿结果。核心代码大概是这个风格from pynq_dpu import DpuOverlay overlay DpuOverlay(dpu.bit) overlay.load_model(resnet18.xmodel) dpu overlay.runner input_tensors dpu.get_input_tensors() output_tensors dpu.get_output_tensors()这里需要特别注意输入张量的形状和数据类型。DPU接收的输入通常是INT8格式而不是你在GPU上用的FP32。另外输入图像的布局可能是NHWC不是PyTorch里默认的NCHW。我一开始在预处理时没有做通道顺序调整和归一化尺寸也转换错了结果推理出来的分类结果简直是随机的。这个事儿看着不复杂但非常容易翻车建议自己封装一个预处理函数把resize、RGB/BGR转换、归一化、NHWC排列全部固定下来。预处理完成后执行dpu.execute拿到输出通过softmax或argmax就能得到分类结果。整个流程跑通后单张图片的处理时间在几十毫秒到一两百毫秒级别具体看模型大小和DPU配置。我自己跑的ResNet18在单个DPU核、100MHz左右时钟下一张224x224图片大概在十几毫秒到三十毫秒之间比纯A53上跑能快一个数量级。5.3 性能分析与进一步优化基础流程跑通之后重点就变成了性能。影响推理延迟的主要因素有几个DPU时钟频率、输入数据的拷贝开销、预处理是否在CPU端串行执行、推理线程是否被系统调度拖累。AXU9EG上DPU的时钟频率通常可以设到300MHz左右甚至更高但前提是时序能收敛。我在Vivado里把DPU时钟频率提高后看到推理时间下降明显但这需要综合实现通过如果时序不过降回安全频率更稳妥。减少数据拷贝开销最直接的方案是用PYNQ的ContiguousArray或者直接在PL端做图像预处理让数据不要从DDR来回倒腾。我早期每次推理都先把图像从文件读出来再做缩放和归一化这部分延迟比DPU本身还高。后来我把预处理改成多线程流水线一部分线程负责读取和预处理另一部分线程负责DPU推理整体吞吐提升了一倍还多。如果做视频流检测还可以把N帧图像做成一个batch一起推理DPU对batch的处理效率更高。6. 常见问题与排查实录6.1 启动卡住串口无输出或无日志开发板一上电串口什么日志都没有这种情况先检查三件事电源是不是真的给上了启动拨码开关是不是拨到了SD卡模式串口线是不是接对并设置了正确波特率。AXU9EG的串口连接方式比较常规但不同批次可能有细微差别如果换了几根线都没有输出建议看板卡指示灯确定电源和启动状态。如果串口有U-Boot输出但启动Linux时卡住优先怀疑SD卡分区或根文件系统解压不完整重新格式化后再次写入。不要用从别人那边拷来的镜像直接混用分区结构不同也会导致挂载失败。6.2 模型编译失败或编译后加载报错这类问题九成出在版本匹配或模型算子兼容性上。编译失败时先看报错里提示哪个算子不支持去Vitis AI支持算子列表里核对。如果模型里有动态shape比如用了可变尺寸输入DPU编译器通常不支持需要把输入固定成固定的分辨率和batch大小。加载报错则要注意xmodel的编译参数是否和当前DPU硬件配置对应。例如你在arch.json里指定了一个四核DPU架构实际硬件只有一个核载入时就会报错。最简单的排查方法是回到官方例程用官方编译好的xmodel加载如果这个能跑说明你的模型生成环节有问题。6.3 推理结果不对准确率暴跌模型在GPU上表现很好放到AXU9EG上结果就乱了最常见的原因就是预处理不一致。DPU输入是INT8你在量化流程中看到的均值、方差、缩放系数在部署时必须原样复现。哪怕只是RGB通道顺序反了分类准确率也会直接掉到接近随机水平。建议把整个预处理链路写成一个独立的函数先在PC上用同一张图片对比PyTorch输出再放到开发板上跑如果两边结果不一致一步步打印中间张量定位是归一化、通道顺序还是数据格式的问题。另外输出层的后处理也不要忽略。DPU输出可能不是标准的softmax概率而是一些原始score有些模型还带有偏置或anchor这些都需要跟训练时代码保持一致。我踩过最隐蔽的坑是输出需要额外除以一个缩放因子这一点在官方模型文档里写得很轻描淡写但没做的话结果就完全不对。6.4 推理速度达不到预期如果你发现DPU推理时间比官方例程慢很多可以按这个顺序排查。先看overlay是否在每次推理前都重复加载如果加载bitstream和模型反复出现在循环里会把大量时间浪费在IO上。接着看CPU端是否被其他任务占满比如大量文件读写、Jupyter后台轮询都会挤占ARM侧的资源。然后是预处理如果图像缩放用的是纯Python代码性能会非常差建议用OpenCV等优化库。最后再检查DPU时钟是否在运行是否因为时序收敛问题被系统降低到很低的频率这种情况在Vivado实现后查看report会给到实际频率。6.5 Overlay加载失败报XRT错误在PYNQ下加载overlay出现类似xclbin/uio错误时先确认当前PYNQ镜像和bitstream是不是同一个版本的Vitis AI生成出来的。XRT的用户态驱动和内核驱动如果和bitstream不匹配加载时就会崩溃。更新PYNQ镜像或重新生成bitstream二选一。同时在开发板上检查dmesg日志看DPU驱动有没有正常 probe。如果驱动没有起来可以手动加载驱动模块或重启开发板再试。很多时候拔掉其他USB设备、重新拔插电源反而能解决奇怪的初始化问题因为这个板子的电源管理对时序比较敏感加载PL端bitstream时需要瞬间大电流。最后再分享一点实操体会从我个人的使用感受来说黑金AXU9EG深度学习部署的难度不在于某一个单独环节而在于整条链路太长每个环节都埋着“版本一致”的雷。第一次做的时候我建议你不要试图把所有步骤一次走完先跑官方例程确认硬件、工具链、环境都没问题再一步步替换成自己的模型。只要跑通一次后面的项目就是不停地替换模型和调整预处理整个套路会越用越顺。如果你手头有特别复杂或者训练方式特殊的模型不要轻易幻想DPU能直接吃下一切。把模型拆成适合硬件实现的形态把不支持的算子放到ARM端处理这本来就是把深度学习模型部署到嵌入式平台必须接受的现实。AXU9EG的优势是灵活性极高PS和PL你可以随意分配任务这和固定架构的NPU开发板完全是两种玩法。希望这篇基于实际踩坑整理的全流程解析能让你少走一些弯路。