资料

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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

1
2
3
4
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中,模型架构为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 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导出代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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')}")

生成校验数据

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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模型。(建议还是添加校验数据的手动校准,详细参考官方文档)

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

自定义量化

创建yolo11_quantize.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 模型参数组
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模型结构如下

1
2
3
4
5
6
7
8
9
10
11
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

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

1
hb_model_info yolo11n_detect_nashe_640x640_nv12.hbm

1
2
3
4
5
6
7
8
9
10
11
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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)

工具链量化

检查数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 模型参数组
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 # 并行编译线程数

模型推理