RDK S100P的种种----yolov11量化模型在地瓜派上量化部署

原文发布于2025-12-27T16:27:09.891888+08:00
https://polar-bear.eu.org/2025/12/27/rdk-s100p-de-chong-chong/
与本人为同一作者,此平台非第一发布平台,原创、非搬运。

文中出现的参考链接为站外连接,请注意安全。

对于本文,需同样遵守:
文章作者: Ziyourufeng
文章链接: https://polar-bear.eu.org/2025/12/27/rdk-s100p-de-chong-chong/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 polar-bear~Blog!


资料

dtop

Jetson有Jtop,Linux有Htop,RDK也有Dtop! - 板卡使用 - 地瓜机器人论坛

模型性能调优 - OpenExplorer

参考教程

万字长文,学妹吵着要学的RDKS100模型量化及部署,你确定不学? - SkyXZ - 博客园

官方示例猴子脚本

rdk_model_zoo_s/samples/Vision/Ultralytics_YOLO/x86/export_monkey_patch.py at s100 · D-Robotics/rdk_model_zoo_s

官方示例量化脚本

rdk_model_zoo_s/samples/Vision/Ultralytics_YOLO/x86/mapper.py at s100 · D-Robotics/rdk_model_zoo_s

官方示例量化配置yaml

rdk_model_zoo_s/samples/Vision/ultralytics_YOLO_Detect/source/reference_yamls/config_ultralytics_YOLO_Detect_YUV420SP_NV12.yaml at s100 · D-Robotics/rdk_model_zoo_s

环境部署

万字长文,学妹一看就会的RDKS100模型量化及部署_rdk s100-CSDN博客

不推荐手动部署,建议直接bash ./run_docker.sh data/

模型量化

这部分容易出错,建议先跑跑官方示例练练手。

需要着重注意量化配置中,各种输入输出格式,颜色通道。

yolov8模型量化(示例)

待完善。。。

万字长文,学妹吵着要学的RDKS100模型量化及部署,你确定不学? - SkyXZ - 博客园

yolov11模型量化

首先准备yolov11模型

转onnx

# yolo11_to_onnx.py
from ultralytics import YOLO

# 加载你训练好的 YOLOv11 模型
pt_file = "best.pt"

model = YOLO(pt_file)

# 导出 ONNX(RDK S100 兼容模式)
model.export(
    format="onnx",
    imgsz=640,          # 必须和训练时一致
    batch=1,            # 固定 batch=1(RDK 推荐)
    opset=11,           # 必须为 11!
    dynamic=False,      # 关闭动态 shape(避免 BPU 不支持)
    simplify=True,      # 优化图(移除冗余节点)
    nms=False,          # 先不带 NMS(RDK BPU 可能不支持 NonMaxSuppression)
    device="cpu"        # 导出时用 CPU 避免 GPU 干扰
)

print(f"✅ YOLOv11 ONNX 已生成: {pt_file.replace('.pt', '.onnx')}")

hb_compile算子检测

使用hb_compile检测BPU算子支持,S100 用 nash-e,S100P 用 nash-m

hb_compile \
  --march nash-m \
  --model ./best.onnx \
  --input-shape images 1x3x640x640

算子测试表格中需要查看是否所有算子都被BPU,VPU支持,--表示算子被前后融合计算,是正常情况,需要留意的是是否存在CPU算子,性能损失见模型性能调优 - OpenExplorer

算子不支持

模型性能调优 - OpenExplorer

S100 Torch算子BPU约束列表 - OpenExplorer

算子不支持是BPU常态,若退回CPU执行,将会导致模型性能严重降低。

  • CPU算子处于模型中部
    对于CPU算子处于模型中部的情况,建议您优先尝试参数调整、算子替换或修改模型。
  • CPU算子处于模型首尾部
    对于CPU算子处于模型首尾部的情况,请参考以下示例,下面以量化/反量化节点为例:

邪恶C2PSA算子

例如在yolov11中,模型架构为

# YOLO11n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]] # 9
  - [-1, 2, C2PSA, [1024]] # 10

# YOLO11n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, False]] # 13

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 13], 1, Concat, [1]] # cat head P4
  - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)

  - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)

其中的C2PSA正是导致模型算子不能被bpu完全支持,对于处于中部的邪恶C2PSA算子只能使用替换模型的方式。

C2PSA(Cross Stage Partial with Pyramid Squeeze Attention)是YOLO11新增的核心模块,结合CSP结构与注意力机制:

  • CSP 结构 (Cross Stage Partial)****:特征分流,一部分走深层网络,一部分直接连接。 -> 所有方案都通过 C3k2 实现了这一点。
  • Pyramid (金字塔/多尺度感知):原模型通过不同核大小的卷积或分层处理来获取不同感受野。
  • PSA (Position-Sensitive Attention):位置敏感注意力,关注“哪里是重点”以及“哪个通道是重点”。

替换方案

纯 C3k2 方案

结构:[-1, 3, C3k2, [1024, True]]

优点:速度最快,完全无缝兼容 BPU。

缺点:严重缺失注意力机制。它只是单纯加深了网络,依靠卷积层的堆叠来“隐式”地学习特征,没有显式地告诉网络“去关注某个位置”。

还原度:50%。只保留了 CSP 和卷积提取,丢失了 C2PSA 的灵魂(Attention)。

C3k2 + CBAM 方案

结构:C3k2 + CBAM(k=7)

优点:CBAM 包含 CAM (通道注意力) 和 SAM (空间注意力)。其中 SAM 使用 7x7 卷积,能够提供较好的局部空间感知。

缺点:CBAM 的空间注意力是基于 7x7 局部窗口 的,它能看到周围一圈,但很难像 PSA 那样捕捉长距离(Long-range) 的依赖关系。

还原度:80%。补全了空间和通道注意力,但在“全局位置感知”上略逊一筹。

C3k2 + CoordAtt 方案 (理论最佳匹配)

结构:C3k2 + CoordAtt

核心优势:C2PSA 的核心词是 “Position-Sensitive” (位置敏感)。

CoordAtt 的原理:它通过分别对 X 轴 和 Y 轴 进行全局池化,将全局的空间信息编码进两个方向的向量中。

对比 PSA:这与 PSA 试图建立长距离位置依赖的目标高度一致。相比 CBAM 的 7x7 局部视野,CoordAtt 拥有全图视野 (Global View),能更好地感知物体在画面中的绝对位置。

BPU 兼容性:极佳(Pool + 1x1 Conv)。

还原度:90%。这是在功能原理上最接近 PSA 的 BPU 友好型算子。

C3k2 + DWConvblock + CBAM 方案 (结构最全)

结构:C3k2 (特征) + DWConv(7x7) (大感受野) + CBAM (注意力)

核心优势:这个组合是物理级堆叠。

用 C3k2 还原 CSP。

用 DWConvblock(7x7) 还原 Pyramid (大卷积核模拟多尺度/大感受野)。

用 CBAM 还原 Attention。

缺点:太重了。Backbone 末端本身通道数就大 (1024),再串联三个模块,计算量(FLOPs)和延迟(Latency)会显著增加。虽然功能最全,但可能导致 FPS 下降较多。

还原度:95% (功能上最全,但效率最低)。

替换算子后模型全部支持BPU运算。

替换模型后onnx导出

更换部分算子后,需要手动添加ultralytics不包含的class,由于ultralytics不包含手动修改的算子,所以需要修改前面的onnx导出代码。

import sys
import types
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import math
import numpy as np
import itertools
import einops
from einops import rearrange
from timm.models.layers import trunc_normal_

# 假设你需要用到 Conv 和 autopad,如果报错找不到,这里做一个简单的 mock
# 或者你可以从 ultralytics 导入它们
try:
    from ultralytics.nn.modules.conv import Conv, autopad
except ImportError:
    # 简单的 fallback 实现,防止报错
    def autopad(k, p=None, d=1):
        if d > 1:
            k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]
        if p is None:
            p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
        return p

    class Conv(nn.Module):
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
            super().__init__()
            self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), dilation=d, groups=g, bias=False)
            self.bn = nn.BatchNorm2d(c2)
            self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
        def forward(self, x):
            return self.act(self.bn(self.conv(x)))

# ==============================================================================
# 1. 在这里粘贴你提供的所有 Attention 类定义 (为了节省篇幅,我只列出 CoordAtt)
#    请务必把你上面发给我的那一大段代码完整粘贴替换下面的区域!
# ==============================================================================

# --------------- 粘贴开始 ---------------

# 这里必须包含 h_swish 和 h_sigmoid,因为 CoordAtt 用到了
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)

    def forward(self, x):
        return self.relu(x + 3) / 6

class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)

# 你的 CoordAtt 类
class CoordAtt(nn.Module):
    def __init__(self, inp, reduction=32):
        super(CoordAtt, self).__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))

        mip = max(8, inp // reduction)

        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()

        self.conv_h = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        identity = x

        n, c, h, w = x.size()
        x_h = self.pool_h(x)
        x_w = self.pool_w(x).permute(0, 1, 3, 2)

        y = torch.cat([x_h, x_w], dim=2)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)

        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)

        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()

        out = identity * a_w * a_h

        return out


class DWConvblock(nn.Module):
    "Depthwise conv + Pointwise conv"

    def __init__(self, in_channels, out_channels, k, s):
        super(DWConvblock, self).__init__()
        self.p = k // 2
        self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=k, stride=s, padding=self.p, groups=in_channels,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(in_channels)
        self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = F.relu(x)
        return x


# ... (请务必把 EMA, SimAM 等其他所有你可能用到的类都粘在这里,以防万一) ...
# 如果你确定只用了 CoordAtt,那上面这些就够了。如果不确定,建议全部粘贴。

# --------------- 粘贴结束 ---------------


# ==============================================================================
# 2. 核心欺骗步骤:构造虚拟模块
# ==============================================================================

# 创建一个虚拟的 module 对象
fake_attention_module = types.ModuleType('ultralytics.nn.modules.attention')

# 将定义好的类挂载到这个虚拟 module 上
fake_attention_module.CoordAtt = CoordAtt
fake_attention_module.h_swish = h_swish
fake_attention_module.h_sigmoid = h_sigmoid
# 如果还有其他类,例如 fake_attention_module.EMA = EMA

# 将虚拟 module 注入系统 sys.modules
# 这样 PyTorch 加载模型时寻找 'ultralytics.nn.modules.attention' 就会找到这里
sys.modules['ultralytics.nn.modules.attention'] = fake_attention_module


# 导入 DWConvblock 到 ultralytics.nn.modules.block 中
import ultralytics.nn.modules.block
ultralytics.nn.modules.block.DWConvblock = DWConvblock
# ==============================================================================
# 3. 正常执行 YOLO 导出逻辑
# ==============================================================================

from ultralytics import YOLO

if __name__ == '__main__':
    # 加载你训练好的 YOLOv11 模型
    pt_file = "model/yolo11_C2PSAtoC3k2_DWConvblock_CBAM-RGB/best.pt"  # ← 改成你的 .pt 路径

    model = YOLO(pt_file)

    # 导出 ONNX(RDK S100 兼容模式)
    model.export(
        format="onnx",
        imgsz=640,          # 必须和训练时一致
        batch=1,            # 固定 batch=1(RDK 推荐)
        opset=11,           # ⚠️ 必须为 11!
        dynamic=False,      # 关闭动态 shape(避免 BPU 不支持)
        simplify=True,      # 优化图(移除冗余节点)
        nms=False,          # 先不带 NMS(RDK BPU 可能不支持 NonMaxSuppression)
        device="cpu"        # 导出时用 CPU 避免 GPU 干扰
    )

    print(f"✅ YOLOv11 ONNX 已生成: {pt_file.replace('.pt', '.onnx')}")

生成校验数据

注意此处生成校验数据容易出错,请仔细核对。

import numpy as np
import cv2
import os
import glob
import random

def preprocess_image(image_path, target_size=(640, 640)):
    # 1. 读取 (BGR, 0-255)
    img = cv2.imread(image_path)
    if img is None:
        return None
  
    # 2. Resize
    img = cv2.resize(img, target_size)
  
    # 3. 转换色空间 BGR -> RGB 
    # (YOLO需要RGB,我们在脚本里转好,然后在 YAML 里告诉工具这是 RGB)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
  
    # 4. HWC 转 CHW (Layout: NCHW)
    img = img.transpose(2, 0, 1)
  
    # 5. 要除以 255.0!保持 0-1 的数值范围
    # 归一化
    img = img.astype(np.float32) / 255.0
  
    return img

# 创建保存目录
save_dir = "./calibration_data"
os.makedirs(save_dir, exist_ok=True)

# 获取图片
all_images = glob.glob(os.path.join("./", "*.jpg")) # 确保路径对

# 随机选取 
if len(all_images) > 100:
    image_list = random.sample(all_images, 100)
else:
    image_list = all_images

print(f"Start processing {len(image_list)} images...")

for i, img_path in enumerate(image_list):
    processed_data = preprocess_image(img_path)
    if processed_data is not None:
        # 保存
        np.save(os.path.join(save_dir, f"{i}.npy"), processed_data)

print("Done.")

hb_compile量化工具

直接量化,快速性能评测模式(开启fast-perf),会转为int8-NV12模型。(建议还是添加校验数据的手动校准,详细参考官方文档)

hb_compile --fast-perf \
           --model /open_explorer/data/best.onnx \
           --march nash-m \
           --input-shape images 1x3x640x640

自定义量化

创建yolo11_quantize.yaml

# 模型参数组
model_parameters:
  onnx_model: "best.onnx"
  march: "nash-m"                     # S100P 用 nash-m,S100 用 nash-e
  output_model_file_prefix: "yolo11_bgr"
  working_dir: "./model_output"

# 输入信息参数组
input_parameters:
  input_name: "images"                # ONNX 模型的输入节点名(用 netron 查看)
  input_type_train: "rgb"             # 校准数据是rgb
  input_type_rt: "bgr"                # 运行时也用 BGR(关键!)
  input_layout_train: "NCHW"          # YOLOv8/v11 通常是 NCHW
  input_shape: "1x3x640x640"          # 根据你的模型实际输入改
  input_batch: 1
  mean_value: "0 0 0"                 # 如果训练时没减均值,就设为 0
  scale_value: "0.003921568627451"    # = 1/255,如果训练时归一化到 [0,1]

# 校准参数组
calibration_parameters:
  cal_data_dir: "./calibration_data"        # 必须存在且包含校准图片(BGR 格式)
  # 强制指定一种校准方法,不要让它搜索
  calibration_type: 'max'     # 对于检测任务,max 通常最稳
  max_percentile: 0.99995
  per_channel: False          # YOLO 通常不需要逐通道量化,False 更快且兼容性好

# 编译参数组
compiler_parameters:
  compile_mode: "latency"
  core_num: 1
  optimize_level: "O2"
  jobs: 4                     # 并行编译线程数

允许hb_compile --config yolo11_quantize.yaml

  • Calibrated Cosine表示优化后模型(optimized_float_model.onnx)与校准后模型(calibrated_model.onnx)对应节点(Node)/输出Tensor(Output Tensor)的余弦相似度结果。
  • Quantized Cosine表示优化后模型(optimized_float_model.onnx)与模型量化后生成的定点模型(quantized_model.bc)对应节点(Node)/输出Tensor(Output Tensor)的余弦相似度结果。

注意此处余弦相似度,一般情况需要大于95%,若出现30%等异常低值,请检查测试集生成是否正确(图像通道对应,归一化对应,图片质量)

量化进阶

官方示例位于开发板/app/cdev_demo/bpu/02_detection_sample/02_ultralytics_yolo11路径下,使用的默认coco模型结构如下

2026-01-04 15:21:09,745 INFO ############# Model input/output info #############
2026-01-04 15:21:09,745 INFO NAME      TYPE   SHAPE            DATA_TYPE
2026-01-04 15:21:09,745 INFO --------- ------ ---------------- ---------
2026-01-04 15:21:09,745 INFO images_y  input  [1, 640, 640, 1] UINT8
2026-01-04 15:21:09,745 INFO images_uv input  [1, 320, 320, 2] UINT8
2026-01-04 15:21:09,745 INFO output0   output [1, 80, 80, 80]  FLOAT32
2026-01-04 15:21:09,745 INFO 499       output [1, 80, 80, 64]  INT32
2026-01-04 15:21:09,745 INFO 513       output [1, 40, 40, 80]  FLOAT32
2026-01-04 15:21:09,745 INFO 521       output [1, 40, 40, 64]  INT32
2026-01-04 15:21:09,745 INFO 535       output [1, 20, 20, 80]  FLOAT32
2026-01-04 15:21:09,745 INFO 543       output [1, 20, 20, 64]  INT32

输入为MIPI 摄像头/ISP 输出的原始视频流 NV12 (YUV420sp)

输出为原始特征图。 Box: DFL 分布 (64通道),Cls: 分类概率

切其最后检测头的原因可能是,YOLO 的 Detect 头包含大量的 Transpose(维度变换)、Softmax(指数运算)和巨大的 Concat(拼接)。不适宜BPU结构运算而且数值极度敏感不适合量化。

查看官方模型

参考教程

万字长文,学妹吵着要学的RDKS100模型量化及部署,你确定不学? - SkyXZ - 博客园

官方示例猴子脚本

rdk_model_zoo_s/samples/Vision/Ultralytics_YOLO/x86/export_monkey_patch.py at s100 · D-Robotics/rdk_model_zoo_s

官方示例量化脚本

rdk_model_zoo_s/samples/Vision/Ultralytics_YOLO/x86/mapper.py at s100 · D-Robotics/rdk_model_zoo_s

官方示例量化配置yaml

rdk_model_zoo_s/samples/Vision/ultralytics_YOLO_Detect/source/reference_yamls/config_ultralytics_YOLO_Detect_YUV420SP_NV12.yaml at s100 · D-Robotics/rdk_model_zoo_s

为了保持与官方示例一致,我们查看官方模型

hb_model_info yolo11n_detect_nashe_640x640_nv12.hbm

2026-01-04 16:39:47,687 INFO ############# Model input/output info #############
2026-01-04 16:39:47,687 INFO NAME      TYPE   SHAPE            DATA_TYPE
2026-01-04 16:39:47,687 INFO --------- ------ ---------------- ---------
2026-01-04 16:39:47,687 INFO images_y  input  [1, 640, 640, 1] UINT8
2026-01-04 16:39:47,687 INFO images_uv input  [1, 320, 320, 2] UINT8
2026-01-04 16:39:47,687 INFO output0   output [1, 80, 80, 80]  FLOAT32
2026-01-04 16:39:47,687 INFO 499       output [1, 80, 80, 64]  INT32
2026-01-04 16:39:47,687 INFO 513       output [1, 40, 40, 80]  FLOAT32
2026-01-04 16:39:47,688 INFO 521       output [1, 40, 40, 64]  INT32
2026-01-04 16:39:47,688 INFO 535       output [1, 20, 20, 80]  FLOAT32
2026-01-04 16:39:47,688 INFO 543       output [1, 20, 20, 64]  INT32
sunrise@ubuntu:~/main/2026/test_hbm/build$ ./test_hbm_info /opt/hobot/model/s100/basic/yolo11n_detect_nashe_640x640_nv12.hbm
[UCP]: log level = 3
[UCP]: UCP version = 3.7.3
[VP]: log level = 3
[DNN]: log level = 3
[HPL]: log level = 3
[UCPT]: log level = 6

============================================================
  UCP HBM模型测试工具
============================================================
  模型路径: /opt/hobot/model/s100/basic/yolo11n_detect_nashe_640x640_nv12.hbm
  测试推理: 否
  测试内存: 否

============================================================
  Step 1: 加载模型
============================================================
[BPU][[BPU_MONITOR]][281472937720128][INFO]BPULib verison(2, 1, 2)[0d3f195]!
[DNN] HBTL_EXT_DNN log level:6
[DNN]: 3.7.3_(4.2.11 HBRT)
  ✓ 模型加载成功, 耗时: 349 ms

============================================================
  Step 2: 获取模型名称列表
============================================================
  模型数量: 1
  模型[0]: yolo11n_detect_nashe_640x640_nv12

============================================================
  Step 3: 获取模型句柄
============================================================
  ✓ 已获取模型句柄: yolo11n_detect_nashe_640x640_nv12

============================================================
  Step 4: 获取输入输出数量
============================================================
  输入张量数量: 2
  输出张量数量: 6

============================================================
  Step 5: 获取输入张量详细属性
============================================================
  输入名称[0]: images_y

  [输入 Tensor 0]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 640, 640, 1)
  → 张量类型: 3 - HB_DNN_IMG_TYPE_NV12 (NV12图像格式)
  → 量化类型: 0 - NONE (无量化)
  → 量化轴: 0
  → 对齐后字节大小: -1 bytes
  → Stride信息: (-1, -1, 1, 1)
  → 元素总数: 409600
  输入名称[1]: images_uv

  [输入 Tensor 1]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 320, 320, 2)
  → 张量类型: 3 - HB_DNN_IMG_TYPE_NV12 (NV12图像格式)
  → 量化类型: 0 - NONE (无量化)
  → 量化轴: 0
  → 对齐后字节大小: -1 bytes
  → Stride信息: (-1, -1, 2, 1)
  → 元素总数: 204800

============================================================
  Step 6: 获取输出张量详细属性
============================================================
  输出名称[0]: output0

  [输出 Tensor 0]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 80, 80, 80)
  → 张量类型: 7 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 量化类型: 0 - NONE (无量化)
  → 量化轴: 0
  → 对齐后字节大小: 2048000 bytes
  → Stride信息: (2048000, 25600, 320, 4)
  → 元素总数: 512000
  输出名称[1]: 499

  [输出 Tensor 1]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 80, 80, 64)
  → 张量类型: 8 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数)
  → 量化类型: 1 - SCALE (缩放量化)
  → 量化轴: 3
  → 量化缩放信息:
    缩放数据长度: 64
    缩放数据 (前10个): 0.000601, 0.000602, 0.000556, 0.000525, 0.000421, 0.000437, 0.000292, 0.000345, 0.000311, 0.000240 ...
    零点偏移长度: 64
    零点偏移数据 (前10个): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ...
  → 对齐后字节大小: 1638400 bytes
  → Stride信息: (1638400, 20480, 256, 4)
  → 元素总数: 409600
  输出名称[2]: 513

  [输出 Tensor 2]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 40, 40, 80)
  → 张量类型: 7 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 量化类型: 0 - NONE (无量化)
  → 量化轴: 0
  → 对齐后字节大小: 512000 bytes
  → Stride信息: (512000, 12800, 320, 4)
  → 元素总数: 128000
  输出名称[3]: 521

  [输出 Tensor 3]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 40, 40, 64)
  → 张量类型: 8 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数)
  → 量化类型: 1 - SCALE (缩放量化)
  → 量化轴: 3
  → 量化缩放信息:
    缩放数据长度: 64
    缩放数据 (前10个): 0.000665, 0.000668, 0.000629, 0.000470, 0.000367, 0.000359, 0.000328, 0.000411, 0.000298, 0.000340 ...
    零点偏移长度: 64
    零点偏移数据 (前10个): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ...
  → 对齐后字节大小: 409600 bytes
  → Stride信息: (409600, 10240, 256, 4)
  → 元素总数: 102400
  输出名称[4]: 535

  [输出 Tensor 4]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 20, 20, 80)
  → 张量类型: 7 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 量化类型: 0 - NONE (无量化)
  → 量化轴: 0
  → 对齐后字节大小: 128000 bytes
  → Stride信息: (128000, 6400, 320, 4)
  → 元素总数: 32000
  输出名称[5]: 543

  [输出 Tensor 5]
  --------------------------------
  → 有效形状 (validShape):
    维度数量: 4
    形状: (1, 20, 20, 64)
  → 张量类型: 8 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数)
  → 量化类型: 1 - SCALE (缩放量化)
  → 量化轴: 3
  → 量化缩放信息:
    缩放数据长度: 64
    缩放数据 (前10个): 0.000820, 0.000811, 0.000706, 0.000627, 0.000631, 0.000693, 0.000700, 0.000729, 0.000585, 0.000458 ...
    零点偏移长度: 64
    零点偏移数据 (前10个): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ...
  → 对齐后字节大小: 102400 bytes
  → Stride信息: (102400, 5120, 256, 4)
  → 元素总数: 25600

============================================================
  Step 7: 获取模型描述信息
============================================================
  描述类型: HB_DNN_DESC_TYPE_JSON
  描述大小: 1628 bytes
  描述内容 (前500字符):
  {"BUILDER_VERSION": "3.3.11", "HBDK_VERSION": "4.1.17", "HBDK_RUNTIME_VERSION": null, "HORIZON_NN_VERSION": "2.1.9", "CAFFE_MODEL": null, "PROTOTXT": null, "ONNX_MODEL": "/open_explorer/ws_0508/ws_yolo11n/yolo11n.onnx", "MARCH": "nash-e", "LAYER_OUT_DUMP": "False", "LOG_LEVEL": null, "WORKING_DIR": "/open_explorer/ws_0508/ws_yolo11n/bpu_outputs", "MODEL_PREFIX": "yolo11n_detect_nashe_640x640_nv12", "OUTPUT_NODES": "", "REMOVE_NODE_TYPE": "", "REMOVE_NODE_NAME": "/model.23/cv2.0/cv2.0.2/Conv;/mod

============================================================
  Step 8: 格式分析
============================================================

----------------------------------------
  NV12输入格式分析
----------------------------------------
  ✓ 检测到NV12输入格式
  → 模式: 分离NV12 (Y和UV在不同tensor中)
  → Y平面 (Tensor 0):
      尺寸: 640x640
      大小: 409600 bytes
  → UV平面 (Tensor 1):
      尺寸: 320x320x2
      大小: 204800 bytes

----------------------------------------
  YOLO输出格式分析
----------------------------------------
  ✓ 检测到6输出YOLO格式 (可能是YOLOv8/v11 检测模型)
  → 典型布局: [cls_80x80, bbox_80x80, cls_40x40, bbox_40x40, cls_20x20, bbox_20x20]
  → 输出[0]: (1, 80, 80, 80) → 类别分数 (80 classes) @ 80x80 特征图 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 输出[1]: (1, 80, 80, 64) → bbox回归 (DFL, 4x16) @ 80x80 特征图 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数) (量化)
  → 输出[2]: (1, 40, 40, 80) → 类别分数 (80 classes) @ 40x40 特征图 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 输出[3]: (1, 40, 40, 64) → bbox回归 (DFL, 4x16) @ 40x40 特征图 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数) (量化)
  → 输出[4]: (1, 20, 20, 80) → 类别分数 (80 classes) @ 20x20 特征图 - HB_DNN_TENSOR_TYPE_F16 (16位浮点)
  → 输出[5]: (1, 20, 20, 64) → bbox回归 (DFL, 4x16) @ 20x20 特征图 - HB_DNN_TENSOR_TYPE_S32 (有符号32位整数) (量化)

============================================================
  Step 9: 释放资源
============================================================
  ✓ 资源释放完成

============================================================
  测试完成
============================================================
  所有测试通过!

其去除Detect头,并且保留p3,p4,p5输出精度,由cpu进行后处理。所以在导出onnx模型基础上,我们需要手动添加一步去除detect头部分,以保证Detect精度。

import sys
import types
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import math
import numpy as np
import itertools
import einops
from einops import rearrange
from timm.models.layers import trunc_normal_
from ultralytics.nn.modules import Detect

# Forward 函数,切去Detect层的cat和decode部分
def split_head_forward(self, x):
    """
    修改后的 Detect 层 Forward 函数。
    不进行 cat 和 decode,直接输出每个 stride 的 cls 和 box 特征图。
    """
    results = []
    # x 是 backbone/neck 输出的特征图列表 (P3, P4, P5 或 P3, P4)
    for i in range(self.nl): 
        # 输入特征 x[i]
  
        # 1. Cls 分支 (Classification)
        # cls: [B, nc, H, W] → [B, H, W, nc] (NHWC)
        cls_feat = self.cv3[i](x[i]).permute(0, 2, 3, 1).contiguous()
  

        # 2. Box 分支 (Regression)
        # bbox: [B, 64, H, W] → [B, H, W, 64] (NHWC)
        box_feat = self.cv2[i](x[i]).permute(0, 2, 3, 1).contiguous()

  
        # 将两个分支加入结果
        # 注意顺序:官方示例看起来是先 Cls 后 Box,或者反过来
        # 根据你的日志: output0(80ch float) 是 Cls, 499(64ch int32) 是 Box
        # 所以我们按这个顺序返回
        results.append(cls_feat)
        results.append(box_feat)
  
    # 返回扁平化的列表,导出 ONNX 时会变成多个输出节点
    return results




# 假设你需要用到 Conv 和 autopad,如果报错找不到,这里做一个简单的 mock
# 或者你可以从 ultralytics 导入它们
try:
    from ultralytics.nn.modules.conv import Conv, autopad
except ImportError:
    # 简单的 fallback 实现,防止报错
    def autopad(k, p=None, d=1):
        if d > 1:
            k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]
        if p is None:
            p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
        return p

    class Conv(nn.Module):
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
            super().__init__()
            self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), dilation=d, groups=g, bias=False)
            self.bn = nn.BatchNorm2d(c2)
            self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
        def forward(self, x):
            return self.act(self.bn(self.conv(x)))

# ==============================================================================
# 1. 在这里粘贴你提供的所有 Attention 类定义 (为了节省篇幅,我只列出 CoordAtt)
#    请务必把你上面发给我的那一大段代码完整粘贴替换下面的区域!
# ==============================================================================


# 这里必须包含 h_swish 和 h_sigmoid,因为 CoordAtt 用到了
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)

    def forward(self, x):
        return self.relu(x + 3) / 6

class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)

# 你的 CoordAtt 类
class CoordAtt(nn.Module):
    def __init__(self, inp, reduction=32):
        super(CoordAtt, self).__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))

        mip = max(8, inp // reduction)

        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()

        self.conv_h = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        identity = x

        n, c, h, w = x.size()
        x_h = self.pool_h(x)
        x_w = self.pool_w(x).permute(0, 1, 3, 2)

        y = torch.cat([x_h, x_w], dim=2)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)

        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)

        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()

        out = identity * a_w * a_h

        return out


class DWConvblock(nn.Module):
    "Depthwise conv + Pointwise conv"

    def __init__(self, in_channels, out_channels, k, s):
        super(DWConvblock, self).__init__()
        self.p = k // 2
        self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=k, stride=s, padding=self.p, groups=in_channels,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(in_channels)
        self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = F.relu(x)
        return x


# ... (请务必把 EMA, SimAM 等其他所有你可能用到的类都粘在这里,以防万一) ...
# 如果你确定只用了 CoordAtt,那上面这些就够了。如果不确定,建议全部粘贴。


# ==============================================================================
# 2. 核心欺骗步骤:构造虚拟模块
# ==============================================================================

# 创建一个虚拟的 module 对象
fake_attention_module = types.ModuleType('ultralytics.nn.modules.attention')

# 将定义好的类挂载到这个虚拟 module 上
fake_attention_module.CoordAtt = CoordAtt
fake_attention_module.h_swish = h_swish
fake_attention_module.h_sigmoid = h_sigmoid
# 如果还有其他类,例如 fake_attention_module.EMA = EMA

# 将虚拟 module 注入系统 sys.modules
# 这样 PyTorch 加载模型时寻找 'ultralytics.nn.modules.attention' 就会找到这里
sys.modules['ultralytics.nn.modules.attention'] = fake_attention_module


# 导入 DWConvblock 到 ultralytics.nn.modules.block 中
import ultralytics.nn.modules.block
ultralytics.nn.modules.block.DWConvblock = DWConvblock
# ==============================================================================
# 3. 正常执行 YOLO 导出逻辑
# ==============================================================================

from ultralytics import YOLO

if __name__ == '__main__':
    # 加载你训练好的 YOLOv11 模型
    pt_file = "model/yolo11_C2PSAtoC3k2_DWConvblock_CBAM-RGB/best.pt"  # ← 改成你的 .pt 路径

    model = YOLO(pt_file)

    # 替换 Detect 层的 forward 方法
    Detect.forward = split_head_forward

    # 导出 ONNX(RDK S100 兼容模式)
    model.export(
        format="onnx",
        imgsz=640,          # 必须和训练时一致
        batch=1,            # 固定 batch=1(RDK 推荐)
        opset=11,           # ⚠️ 必须为 11!
        dynamic=False,      # 关闭动态 shape(避免 BPU 不支持)
        simplify=True,      # 优化图(移除冗余节点)
        nms=False,          # 先不带 NMS(RDK BPU 可能不支持 NonMaxSuppression)
        device="cpu"        # 导出时用 CPU 避免 GPU 干扰
    )

    print(f"✅ YOLOv11 ONNX 已生成: {pt_file.replace('.pt', '.onnx')}")

P3 (Small / 80x80)

P4 (Medium / 40x40)

P5 (Large / 20x20)

工具链量化

检查数据集

import numpy as np
import os

cal_dir = "./calibration_data"
files = sorted([f for f in os.listdir(cal_dir) if f.endswith('.npy')])
print(f"Total calibration files: {len(files)}")

if len(files) == 0:
    print("❌ ERROR: No calibration files found!")
    exit(1)

# 检查前3个文件
for i, fname in enumerate(files[:3]):
    data = np.load(os.path.join(cal_dir, fname))
    print(f"\nFile {i}: {fname}")
    print(f"  Shape: {data.shape}")           # 应该是 (3, 640, 640)
    print(f"  Dtype: {data.dtype}")           # 应该是 float32
    print(f"  Value range: [{data.min():.2f}, {data.max():.2f}]")  # 应该是 [0, 255]
    print(f"  Mean per channel (RGB): R={data[0].mean():.1f}, G={data[1].mean():.1f}, B={data[2].mean():.1f}")
  
    # 验证值范围
    if data.min() < 0 or data.max() > 1:
        print("  ⚠️ WARNING: Value range incorrect!")
    if data.shape != (3, 640, 640):
        print("  ⚠️ WARNING: Shape incorrect!")
    if data.dtype != np.float32:
        print("  ⚠️ WARNING: Dtype incorrect!")

print("\n✅ Verification complete")

# 模型参数组
model_parameters:
  onnx_model: "best.onnx"
  march: "nash-m"                     # S100P 用 nash-m,S100 用 nash-e
  output_model_file_prefix: "yolo11"
  working_dir: "./model_output"
  # remove_node_name: "474;494;514"

# 输入信息参数组
input_parameters:
  input_name: "images"                # ONNX 模型的输入节点名(用 netron 查看)
  input_type_train: "rgb"             # 校准数据是rgb
  input_type_rt: "rgb"                # 运行时也用 BGR(关键!)
  input_layout_train: "NCHW"          # YOLOv8/v11 通常是 NCHW
  input_shape: "1x3x640x640"          # 根据你的模型实际输入改
  input_batch: 1
  scale_value: "0.003921568627451"    # = 1/255,如果训练时归一化到 [0,1]
  norm_type: 'data_scale'


# 校准参数组
calibration_parameters:
  calibration_type: 'default'
  cal_data_dir: "./calibration_data"        # 必须存在且包含校准图片
  cal_data_type: 'float32'
  quant_config: {"op_config": {"softmax": {"qtype": "int8"}}}

# 编译参数组
compiler_parameters:
  extra_params: {'input_no_padding': True, 'output_no_padding': True}
  compile_mode: "latency"
  core_num: 1
  optimize_level: "O2"
  jobs: 16                     # 并行编译线程数

模型推理

51hhh/RDK-S100p-BPU-yolo: RDK

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐