RDK S100P的种种----yolov11量化模型在地瓜派上量化部署
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! - 板卡使用 - 地瓜机器人论坛
参考教程
万字长文,学妹吵着要学的RDKS100模型量化及部署,你确定不学? - SkyXZ - 博客园
官方示例猴子脚本
官方示例量化脚本
rdk_model_zoo_s/samples/Vision/Ultralytics_YOLO/x86/mapper.py at s100 · D-Robotics/rdk_model_zoo_s
官方示例量化配置yaml
环境部署
万字长文,学妹一看就会的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
算子不支持
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/mapper.py at s100 · D-Robotics/rdk_model_zoo_s
官方示例量化配置yaml
为了保持与官方示例一致,我们查看官方模型
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 # 并行编译线程数

模型推理
更多推荐
所有评论(0)