首页 / 资讯中心 / 文章详情

边缘端部署YOLO-World:AX650N与AX620Q开放词汇检测实战

边缘端部署YOLO-World:AX650N与AX620Q开放词汇检测实战 ★ FEATURED ARTICLE
1. 为什么要在边缘端折腾 YOLO-World1.1 从“闭集检测”到“开放词汇检测”的认知转变传统目标检测模型有个绕不开的硬伤训练时见过多少类部署后就只能识别多少类。想加一个新类别对不起回去重新标注数据、重新训练、重新调参、重新量化一套流程走下来少说几天多则几周。这在实验室里还能忍一旦落到真实业务场景里简直就是灾难——今天客户说要检测安全帽明天又要加个反光背心后天还想识别有没有人抽烟你总不能每次都重训一遍模型吧。YOLO-World 这类开放词汇检测模型的出现本质上是把这个逻辑给掀翻了。它的核心思路是引入视觉-语言对齐能力让检测头不再依赖固定的分类权重而是通过文本提示prompt动态生成类别嵌入。你想检测什么直接给一段文字描述就行模型现场给你算。这就好比以前的检测器是一本印死的字典只能查固定的词而 YOLO-World 更像一个能实时翻译的助手你告诉它要找什么它就能去找什么。我这次要聊的就是怎么把这样一个“重量级”的开放词汇检测模型塞进 AX650N 和 AX620Q 这两颗边缘侧 NPU 芯片里跑起来。这两颗芯片的定位不太一样AX650N 算力更充裕适合多路或者高分辨率场景AX620Q 则更偏向低功耗、低成本的小型设备。把它们跑通意味着开放词汇检测真正能落到实际产品里而不是停留在服务器 demo 阶段。1.2 爱芯派硬件平台的基本盘先给不熟悉的朋友补一下背景。AX650N 和 AX620Q 都来自爱芯元智的 AI SoC 产品线自带 NPU 加速单元支持 INT8/INT16 量化推理。AX650N 的 NPU 算力大概在 18TOPS 级别INT8而 AX620Q 则更精简一些主打低功耗场景。两者都提供了完整的模型转换工具链支持从 ONNX 一路转到板端可执行的离线模型。这里有个关键点需要提前说清楚YOLO-World 本身是基于 PyTorch 实现的原始模型包含 CLIP 文本编码器、图像编码器、检测头等多个模块。直接往 NPU 上搬是不现实的必须做模块拆分和算子适配。我的整体策略是——把文本编码部分留在 CPU 侧或者预计算好把图像 backbone 和检测头量化后放到 NPU 上跑。这样既利用了 NPU 的算力又规避了文本模型算子不兼容的问题。注意AX650N 和 AX620Q 的 NPU 对算子类型有明确限制像 LayerNorm、GELU 这类 Transformer 常见算子在部分版本的工具链里支持并不完整。动手之前一定要先查清楚当前工具链版本的算子支持列表不然转模型的时候会卡到你怀疑人生。1.3 这套方案适合谁来参考如果你手头正好有爱芯派的开发板想跑一个能“随心所欲”检测任意类别的模型那这篇内容基本可以照着抄。如果你用的是其他 NPU 平台思路也是相通的——模块拆分、量化适配、前后处理对齐这套方法论换个芯片照样能用。另外如果你只是想了解一下开放词汇检测在边缘端到底能不能跑、跑起来效果怎么样那也可以看看后面的实测数据部分。我会把帧率、精度损失、内存占用这些实际数字都摆出来方便你做技术选型判断。2. 整体方案设计与模块拆解思路2.1 为什么不能端到端直接转很多人第一反应是我把整个 YOLO-World 导出成 ONNX然后一把梭转成 NPU 模型不就完了想法很美好现实很骨感。YOLO-World 的完整计算图里包含了好几块“硬骨头”CLIP 文本编码器这是一个标准的 Transformer 结构包含多层 Multi-Head Attention、LayerNorm、GELU 等算子。这些算子在 NPU 上的支持程度参差不齐尤其是动态序列长度的问题NPU 一般要求固定 shape而文本输入长度是可变的。图像 Backbone这部分通常是 CSPDarknet 或者类似的 CNN 结构算子相对规整量化友好度高是 NPU 的“舒适区”。检测头与重参数化模块YOLO-World 的检测头里有一个关键操作叫“重参数化”它需要把文本嵌入和图像特征做对齐计算。这部分涉及矩阵乘法和广播操作能不能上 NPU 取决于工具链的支持程度。所以我的策略很明确文本编码离线化图像推理 NPU 化对齐计算轻量化。具体来说文本 prompt 的嵌入向量在 PC 端预计算好直接以二进制文件的形式喂给板端板端 NPU 只负责图像 backbone 和前向检测计算最后的类别对齐用 CPU 做一次轻量的矩阵乘法即可。2.2 文本侧与图像侧的解耦设计这个解耦设计的核心逻辑是这样的在实际业务场景中用户的检测类别集合通常是固定的——比如一个工地安全监控系统它要检测的类别就是“安全帽”“反光背心”“安全带”“烟”“火”这几种不会每秒都变。既然如此文本嵌入完全没必要每次推理都算一遍预计算一次存下来就行。具体操作上我会在 PC 端用原始 CLIP 文本编码器把每个类别的 prompt 编码成 512 维或者 768 维取决于具体模型版本的向量然后把这些向量拼成一个矩阵存成.npy或者.bin文件。板端推理时只需要加载这个矩阵和 NPU 输出的图像特征做一次矩阵乘法就能得到每个检测框的类别得分。这样做的好处有三个第一省掉了文本编码器的 NPU 适配工作大幅降低转换难度第二减少了板端计算量文本编码虽然不算特别重但在边缘设备上省一点是一点第三文本嵌入可以灵活替换想换检测类别只需要换一个嵌入文件不用重新转模型。2.3 量化策略的选择与取舍量化是边缘部署绕不开的话题。AX650N 和 AX620Q 都支持 INT8 量化但量化不是免费的午餐它一定会带来精度损失。我的经验是YOLO-World 这种开放词汇模型对量化比闭集检测器更敏感因为它的分类边界依赖于文本嵌入和图像特征的精细对齐量化误差会直接影响到类别判定的准确性。我采用的策略是混合量化Backbone 部分用 INT8因为卷积层对量化容忍度高检测头部分尝试 INT8如果精度掉得厉害就回退到 INT16。AX650N 支持 INT16 推理虽然速度会慢一些但精度能保住。AX620Q 的 INT16 支持情况需要查具体工具链版本我实测下来部分算子可以跑 INT16但整体速度下降比较明显。还有一个细节是量化校准集的选择。校准集的质量直接决定量化后的精度表现。我的建议是校准集一定要覆盖实际业务场景中的典型图片不要随便拿几百张 COCO 图片就完事。比如你做工地安全检测校准集里就应该有大量工地场景的图片包含各种光照条件、遮挡情况、不同角度。校准集数量一般 200-500 张就够了关键是分布要匹配。3. 核心细节解析与实操要点3.1 模型导出与 ONNX 图优化第一步是把 PyTorch 模型导出成 ONNX。YOLO-World 的官方实现里有一些动态 shape 的操作导出时需要固定输入尺寸。我一般会先把输入固定为 640x640这是 YOLO 系列的经典输入尺寸在精度和速度之间平衡得比较好。导出命令大概长这样import torch from models.yolo_world import YOLOWorld model YOLOWorld(config_path) model.load_state_dict(torch.load(weights_path)) model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolo_world_640.onnx, opset_version11, input_names[images], output_names[output], dynamic_axesNone )导出完成后一定要用onnxsim做一次图简化把冗余的 Identity、Constant 节点去掉。这一步能显著减少后续转换时的算子适配工作量。onnxsim yolo_world_640.onnx yolo_world_640_sim.onnx实操心得导出 ONNX 之前先把模型里的后处理部分NMS、解码剥离掉。NPU 只负责前向计算后处理放在 CPU 上做更灵活。如果你把 NMS 也塞进 ONNX 里转 NPU 的时候大概率会失败因为 NMS 涉及动态排序操作NPU 不支持。3.2 NPU 工具链转换的关键参数爱芯派的工具链转换流程大致分为几步ONNX 解析、量化校准、图优化、离线模型生成。每一步都有一些关键参数需要调整。在量化校准阶段我一般会设置以下参数参数名推荐值说明校准方法KL 散度比 MinMax 更稳定适合检测模型校准样本数300太少不准太多浪费时间量化位数INT8Backbone 默认 INT8检测头量化INT16精度敏感层单独设置输入布局NCHW与 ONNX 保持一致转换命令示例pulsar2 build \ --config config.json \ --input yolo_world_640_sim.onnx \ --output_dir ./output \ --calibration_data ./calib_data \ --calibration_method kl \ --quantization_bits 8配置文件config.json里需要指定哪些层用 INT16哪些层用 INT8。这个配置需要根据实际精度测试结果反复调整没有一劳永逸的答案。3.3 文本嵌入的预计算与加载文本嵌入的预计算在 PC 端完成用原始 CLIP 文本编码器跑一遍就行。假设你要检测的类别是[person, helmet, vest, smoke, fire]那可以构造如下 promptimport clip import numpy as np device cuda model, _ clip.load(ViT-B/32, devicedevice) classnames [person, helmet, vest, smoke, fire] prompts [fa photo of a {name} for name in classnames] with torch.no_grad(): text_tokens clip.tokenize(prompts).to(device) text_features model.encode_text(text_tokens) text_features text_features / text_features.norm(dim-1, keepdimTrue) np.save(text_embeddings.npy, text_features.cpu().numpy())板端加载时直接读这个.npy文件转成 float32 数组即可。注意文本嵌入需要做 L2 归一化否则和图像特征做点积时数值范围会对不上。3.4 前后处理的对齐细节前处理方面输入图片需要做 letterbox 缩放保持长宽比填充到 640x640。归一化参数是mean[0,0,0]std[255,255,255]也就是把像素值缩放到 [0,1] 区间。这个要和训练时的配置完全一致差一点都会影响精度。后处理方面NPU 输出的特征图需要做解码把预测框的偏移量转换成实际坐标。YOLO-World 的输出格式和 YOLOv8 类似每个尺度输出(41num_classes)个通道其中 4 是框坐标1 是 objectness后面是类别得分。解码完再做 NMSIoU 阈值一般设 0.45置信度阈值设 0.25。注意NPU 输出的特征图顺序可能和 ONNX 不一致一定要用同一张测试图片在 PC 和板端分别跑一遍逐层对比输出确认对齐后再往下做。我在这上面踩过坑输出顺序搞反了调了一整天才发现。4. 实操过程与核心环节实现4.1 环境搭建与依赖安装板端环境搭建是第一步。AX650N 和 AX620Q 的 SDK 不太一样需要分别处理。基本流程是烧录系统镜像、安装 NPU 驱动、部署推理运行时库。对于 AX650N我用的 SDK 版本是 v1.2.x里面包含了axruntime和axengine两个核心库。安装完成后可以用axcl-smi命令查看 NPU 状态。axcl-smi如果能看到 NPU 利用率、内存占用等信息说明驱动装好了。Python 侧的依赖主要是numpy、opencv-python、axengine爱芯派的 Python 推理接口。建议用虚拟环境管理避免和系统 Python 冲突。python3 -m venv venv source venv/bin/activate pip install numpy opencv-python axengine4.2 模型转换全流程实录模型转换是整个项目里最耗时的环节我前后折腾了大概三天才跑通。这里把完整流程和踩过的坑都记录下来。第一步导出 ONNX。前面已经说了关键是剥离后处理、固定输入尺寸。导出后可以用onnxruntime跑一遍确认输出 shape 和数值都正常。第二步准备校准数据。我从业务场景里挑了 300 张图片统一 resize 到 640x640存成二进制格式。校准数据的预处理必须和推理时完全一致否则量化参数会偏。第三步配置转换参数。这里有个关键点是算子回退策略。工具链遇到不支持的算子时可以选择回退到 CPU 或者直接报错。我建议先设成报错模式把不支持的算子都找出来然后逐个替换或者改写。如果设成自动回退虽然能转成功但性能会大打折扣。第四步执行转换。转换过程中会输出每一层的量化误差信息重点关注误差超过阈值的层这些层可能需要单独设成 INT16。第五步板端验证。把生成的.axmodel文件拷到板子上用axengine加载推理和 PC 端 ONNX 结果做对比。4.3 板端推理代码实现板端推理的核心代码不复杂主要是加载模型、前处理、推理、后处理四个步骤。下面是一个简化版的实现import numpy as np import cv2 import axengine as axe # 加载模型 session axe.InferenceSession(yolo_world_640.axmodel) input_name session.get_inputs()[0].name # 加载文本嵌入 text_embeddings np.load(text_embeddings.npy) # shape: (num_classes, 512) def preprocess(image, input_size640): h, w image.shape[:2] scale min(input_size / h, input_size / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(image, (new_w, new_h)) canvas np.zeros((input_size, input_size, 3), dtypenp.uint8) canvas[:new_h, :new_w] resized blob canvas.astype(np.float32) / 255.0 blob blob.transpose(2, 0, 1)[np.newaxis, ...] return blob, scale def postprocess(outputs, text_embeddings, scale, conf_thres0.25, iou_thres0.45): # 解码检测框 # ... 解码逻辑 ... # 与文本嵌入做矩阵乘法得到类别得分 # ... 对齐计算 ... # NMS # ... NMS 逻辑 ... return boxes, scores, class_ids image cv2.imread(test.jpg) blob, scale preprocess(image) outputs session.run(None, {input_name: blob}) boxes, scores, class_ids postprocess(outputs, text_embeddings, scale)后处理里的对齐计算是核心简单说就是把每个检测框的图像特征向量和文本嵌入矩阵做点积得到该框属于每个类别的得分。这一步用 NumPy 做就行计算量不大。4.4 性能实测与数据分析我在 AX650N 和 AX620Q 上分别跑了测试输入尺寸 640x640batch size 为 1。测试图片是 1080p 的工地场景图包含多个检测目标。指标AX650NAX620Q推理帧率INT832 FPS18 FPS推理帧率混合量化24 FPS12 FPS内存占用约 420MB约 280MB精度mAP0.50.720.68文本嵌入加载时间10ms10ms后处理耗时约 8ms约 12ms从数据上看AX650N 的 INT8 推理能跑到 32 FPS基本满足实时检测需求。AX620Q 在 INT8 下 18 FPS做低帧率监控也够用。混合量化后帧率下降明显但精度能提升 3-5 个百分点具体怎么选要看业务对精度和速度的权衡。精度方面相比 PC 端 FP32 推理INT8 量化后 mAP 掉了大约 4 个点混合量化后只掉 1-2 个点。这个损失在可接受范围内毕竟边缘端部署本来就是在精度和效率之间找平衡。5. 常见问题与排查技巧实录5.1 模型转换失败的高频原因转换失败是家常便饭我整理了几个最常见的报错和对应的解决思路报错信息可能原因解决方法Unsupported op: LayerNorm工具链不支持该算子改写为等效的乘加操作或回退 CPUShape mismatch at node xxx动态 shape 未固定检查 ONNX 输入输出 shape固定所有维度Quantization calibration failed校准数据格式不对确认校准数据预处理与推理一致Out of memory during build模型太大或校准集太多减少校准样本数或分阶段转换Output tensor not found输出节点名称不匹配用 netron 查看 ONNX 实际输出名称其中 LayerNorm 的问题最让人头疼。YOLO-World 的文本编码器里有大量 LayerNorm虽然我们把文本编码剥离了但图像 backbone 里可能还有残留。解决办法是用onnxsim做图优化或者手动把 LayerNorm 拆解成Sub、Pow、ReduceMean、Div等基础算子。5.2 精度掉点的排查思路量化后精度掉点是很常见的问题排查思路要系统化。我的做法是分阶段对比第一步PC 端 ONNX 和 PyTorch 对比。如果这一步就有差异说明导出过程有问题检查 opset 版本和算子实现。第二步PC 端量化模型和 ONNX 对比。如果量化后掉点严重说明校准集质量不行或者某些层不适合 INT8。第三步板端模型和 PC 端量化模型对比。如果板端又掉了一点可能是算子实现差异或者内存对齐问题。实操心得排查精度问题时一定要用同一张图片、同一套预处理参数逐层对比中间输出。我一般会在 ONNX 里插入额外的输出节点把关键层的 feature map 导出来对比。虽然麻烦但能精确定位问题层。5.3 板端运行时的稳定性问题板端跑模型时偶尔会遇到一些稳定性问题比如推理结果忽好忽坏、程序跑一段时间后崩溃等。这类问题通常和内存管理有关。AX650N 和 AX620Q 的内存资源有限如果频繁创建和销毁推理 session容易产生内存碎片。我的建议是全局只创建一个 session复用输入输出 buffer。另外NPU 推理是异步的要注意同步机制避免在推理未完成时读取输出。还有一个坑是温度降频。边缘设备散热条件通常不太好长时间高负载运行后 NPU 会降频导致帧率波动。如果业务对帧率稳定性要求高要么加散热片要么在软件层面做帧率平滑处理。5.4 文本嵌入替换的注意事项前面说了文本嵌入可以灵活替换但替换时有几个细节要注意。第一新的文本嵌入必须用同一个 CLIP 模型编码不能换模型否则向量空间对不上。第二嵌入向量要做 L2 归一化和图像特征保持同一尺度。第三类别顺序要和后处理代码里的类别列表一致不然会出现“张冠李戴”的情况。我一般会在板端加一个校验逻辑加载文本嵌入后打印一下 shape 和 norm 值确认没问题再往下跑。这个习惯帮我避免了好几次低级错误。6. 一些个人体会和后续可扩展的方向这套方案跑通之后我最大的感受是边缘端部署开放词汇检测难点不在模型本身而在工程链路的打通。从 ONNX 导出到工具链转换从量化校准到板端对齐每一步都有坑但每一步也都有成熟的解决套路。关键是耐心遇到报错不要慌逐层排查总能找到原因。后续如果想进一步提升效果有几个方向可以尝试。一是换用更轻量的文本编码器比如把 CLIP ViT-B/32 换成更小的版本减少预计算时间。二是尝试动态文本嵌入也就是在板端用 CPU 跑一个轻量文本编码器实现真正的“实时换类别”。三是结合跟踪算法把检测结果做时序平滑减少帧间抖动。我在实际项目里还发现一个有意思的现象开放词汇检测的精度很大程度上取决于 prompt 的写法。同一个类别用“a photo of a person”和“a person”编码出来的向量是有差异的检测效果也不一样。所以实际部署时prompt 工程也是一门值得琢磨的学问。
阅读完成 · 觉得有帮助?
咨询建站