BiSeNet图像分割模型
2. 方案简介
2.1 算法流程介绍
1. 模型背景
BiSeNet(Bilateral Segmentation Network) 是一款面向 实时语义分割任务 的高效神经网络模型,由 百度研究院(Baidu Research) 的研究团队于 2018 年在论文《BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation》中首次提出,并发表在 ECCV 2018(欧洲计算机视觉大会) 上。该模型旨在解决语义分割中常见的精度与速度难以兼得的问题,提出了一种创新的双分支结构,兼顾了高分辨率细节与语义感受野的表达能力。
BiSeNet 的核心思想是通过两个功能互补的分支实现语义信息与空间信息的协同融合:
- Spatial Path(空间路径):以较少的下采样层保留图像的空间分辨率和细节信息;
- Context Path(语义路径):使用主干网络(如 ResNet 或 Xception)进行较大步长的快速下采样,提取全局上下文语义信息;
- 两者的输出通过**Feature Fusion Module(特征融合模块)**进行融合,实现高效的信息整合与精细预测。
该结构有效解决了传统语义分割模型中存在的以下问题:
- 一方面,高语义感受野的深层特征往往丢失了边界和细节信息;
- 另一方面,浅层网络虽能保留空间结构,但缺乏抽象语义能力。
通过双路径设计,BiSeNet 同时具备了空间细节表达力与语义抽象能力,显著提升了在实时应用中的实用性。尤其在移动端部署和自动驾驶场景中,BiSeNet 在不牺牲准确率的前提下,实现了**高帧率(FPS)**的实时推理。
此外,BiSeNet 还引入了如下关键机制:
- Attention Refinement Module(注意力优化模块):增强重要通道的表达能力;
- Global Average Pooling:加强上下文路径的感受野范围;
- 轻量化设计:支持多种主干网络替换,适应不同硬件平台的资源约束。
BiSeNet 在多个语义分割数据集(如 Cityscapes、CamVid)中表现出色,具备如下优势:
- 实时推理能力强:即便在高分辨率图像上也能维持 60 FPS 以上;
- 结构清晰简洁:便于嵌入式平台部署和硬件加速;
- 融合效果优越:有效整合空间与语义信息,提高边界预测精度;
- 可扩展性强:Context Path 可适配多种轻量主干网络(如 MobileNetV2、ShuffleNet 等)。
BiSeNet 是一款速度与精度兼具的高效语义分割模型,特别适合自动驾驶、机器人视觉和边缘计算等对实时性有高要求的场景。其双路径设计理念也对后续分割架构(如 SFNet、STDC 等)产生了广泛影响。
2. 算法流程图
graph TD
A[输入图像] --> B[前处理]
B --> C[BiSeNet模块]
C --> D[后处理]
3. 前处理(Host端CPU处理)
前处理流程:
关键操作:
PicPre img(img_path, cv::IMREAD_COLOR); // 图像加载
img.Resize({ netinfo.i_cubic[0].h, netinfo.i_cubic[0].w }, PicPre::LONG_SIDE).rPad(); // 调整尺寸 + Padding填充
Tensor img_tensor = CvMat2Tensor(img.dst_img, network); // OpenCV Mat转Tensor
dmaInit(run_sim, netinfo.ImageMake_on, img_tensor, device); // DMA传输初始化
具体处理流程:
- 图像加载:通过 OpenCV 读取原始图像(
cv::IMREAD_COLOR保持 RGB 格式)- 示例:输入
img_path为"/data/street.png",图像尺寸为 1024×2048(高×宽)
- 示例:输入
- 尺寸变换与补边:
Resize:以 长边为基准(此处为宽 = 2048),将图像缩放,使其长边匹配目标尺寸(如输入为 512×1024,则长边为1024)- 缩放比例 = 1024 / 2048 = 0.5
- 缩放后图像尺寸为 512×1024
rPad:在 短边方向(此处为高 = 512) 进行对称补零,补成最终输入尺寸(如目标为 512×1024,实际已满足则无需额外 padding)- 若目标为正方形输入(如 1024×1024),则:
- 当前图像已为 512×1024,需要在高方向补齐至1024,高度补 512 像素(上下各补 256)
- 最终尺寸为 1024×1024
- 张量转换:
CvMat2Tensor完成以下处理:- 数据类型转换:
uint8 → float32 - 像素归一化(例如除以 255.0)
- 通道顺序调整:如 BGR → RGB(若模型要求)
- 添加 Batch 维度:转换后张量尺寸为
[1, 3, H, W],如[1, 3, 1024, 1024]
- 数据类型转换:
- DMA准备:
dmaInit函数配置 DMA 传输,将图像张量从主机内存拷贝至 NPU 设备端,用于硬件加速推理。
4.BiSeNet模型结构
5. 后处理(Host端CPU处理)
后处理机制:
关键操作:
// 数据回传
auto host_tensor = result_tensor[0].to(HostDevice::MemRegion());
// 结果解析(多通道float/单通道int8)
if (output_channel == 1) {
auto tensor_data = (int8_t*)host_tensor.data().cptr();
net_result_mat = cv::Mat(h,w,CV_8SC1,tensor_data);
} else {
auto tensor_data = (float*)host_tensor.data().cptr();
net_result_mat = cv::Mat(h,w,CV_32FC19,tensor_data);
}
// 尺寸调整
cv::resize(net_result_mat, resultMat, cv::Size(orig_w,orig_h), cv::INTER_LINEAR);
// 类别映射与可视化
map_plot(resultMat, img_id_mat, img_color_mat, img.dst_img);
具体处理流程:
-
数据回传
to(HostDevice::MemRegion())将NPU输出的分割张量复制到Host内存- 示例输出shape为
[1,512,512,19](Cityscapes的19类分割任务)
-
结果解析
- 根据输出类型(float多通道/int8单通道)转换数据指针
- 构建OpenCV矩阵:多通道用
CV_32FC19,单通道用CV_8SC1
-
尺寸调整
- 将网络输出resize到原始图像尺寸(如1024x2048)
- 使用双线性插值保持边缘平滑
-
类别映射
-
map_plot函数核心操作:解释// 像素级类别索引→调色板颜色 for(auto i : img_id){ if(i >= 19) color = [0,0,0]; // 未知类别用黑色 else color = PALETTE_CITYSCAPES[i]; // 按预设调色板着色 }
-
-
结果保存
- Windows/Linux路径差异化处理
- 保存两种结果:
img_id_mat:单通道类别ID图(可用于量化分析)img_color_mat:三通道伪彩色图(直观可视化)
2.2 硬件资源分配
1. 硬件映射策略
2. 硬件资源分配方案总表
| ** 硬件分配** | 代码/模块对应位置 | 关键变更说明 | ||
|---|---|---|---|---|
| 1. CPU前处理 | ||||
| - 图像加载 | OpenCV读取图像文件 | CPU | cv::imread(img_path) |
新增补边处理环节 |
| - 尺寸变换 | 动态Resize(保持长宽比) | CPU | cv::resize(..., INTER_LINEAR) |
支持非方形输入 |
| - 补边处理 | 四周Padding填充(常量值) | CPU | cv::copyMakeBorder() |
新增预处理步骤 |
| - 张量转换 | uint8转float32/归一化 | CPU | convertTo(CV_32F, 1/255.0) |
归一化合并到转换 |
| 2. ImageMake加速 | ||||
| - DMA传输初始化 | 主机内存→NPU内存 | FPGA | dmaInit(host_ptr, npu_ptr, size) |
硬件自动完成数据对齐 |
| 3. NPU推理 | ||||
| - 双分支卷积计算 | 细节分支+语义分支并行 | NPU | session.forward() |
新增双分支支持 |
| - BGALayer特征融合 | 动态加权融合双分支特征 | NPU | 编译器自动优化 | 新增硬件融合模块 |
| 4. CPU后处理 | ||||
| - 数据回传 | NPU内存→主机内存 | FPGA/CPU | tensor.to(HostDevice::MemRegion()) |
支持int8/float多格式 |
| - 结果解析 | 原始数据转OpenCV矩阵 | CPU | cv::Mat(..., data_ptr) |
自动识别单通道/多通道 |
| - 尺寸调整 | 还原至原始图像尺寸 | CPU | cv::resize(..., orig_h, orig_w) |
双线性插值保持边缘 |
| - 类别映射 | 像素索引→Cityscapes调色板 | CPU | map_plot()中的PALETTE_CITYSCAPES |
新增未知类别处理(填充黑色) |
| - 掩膜渲染 | 伪彩色结果叠加原始图 | CPU | cv::addWeighted() |
可调节透明度 |
3. 硬件-软件交互示意图
3. 模型导出说明
BiSeNet 主干
1)基础卷积算子:
- 常规卷积:
nn.Conv2d直接实现 - 深度可分离卷积:通过
groups参数实现(groups=in_channels) - 批归一化:
nn.BatchNorm2d直接实现 - 激活函数:
- ReLU:
nn.ReLU(inplace=True)
- ReLU:
2)双分支特有算子:
- 细节分支(Detail Branch):
- 多阶段
ConvBNReLU堆叠(k=3, s=2下采样 +k=3, s=1细化)
- 多阶段
- 语义分支(Segment Branch):
- StemBlock:双路下采样(
Conv+MaxPool) + 特征融合 - GELayer:
GELayerS2:带下采样的深度可分离卷积块(含残差连接)GELayerS1:无下采样的深度可分离卷积块
- StemBlock:双路下采样(
3)特征增强模块:
- CEBlock(Context Embedding):
- 全局平均池化 + 1x1 卷积 + 3x3 卷积
- BGALayer(Bilateral Guided Aggregation):
- 双分支特征对齐(
深度卷积+上采样) + 融合卷积
- 双分支特征对齐(
4)分割头算子:
- SegmentHead:
ConvBNReLU扩展通道 →Dropout→ 1x1 卷积输出 → 双线性上采样
5)BiSeNet 与 Icraft 硬件支持算子对应表
| BiSeNetV2 功能组件 | PyTorch 实现方式 | Icraft 对应算子组合 |
|---|---|---|
| 基础卷积算子 | ||
| 常规卷积 | nn.Conv2d |
Conv2d |
| 深度可分离卷积 | nn.Conv2d(groups=in_channels) |
Conv2d(设置 groups=in_channels) |
| 批归一化 | nn.BatchNorm2d |
BatchNorm2d |
| ReLU激活 | nn.ReLU(inplace=True) |
ReLU 或 Hardtanh |
| 细节分支(Detail Branch) | ||
| 多阶段卷积堆叠 | ConvBNReLU(Conv+BN+ReLU) |
Conv2d + BatchNorm2d + ReLU |
| 语义分支(Segment Branch) | ||
| StemBlock双路下采样 | ConvBNReLU + MaxPool + 特征融合 |
Conv2d + BatchNorm2d + ReLU + MaxPool2d + Add |
| GELayerS2(带下采样) | 深度可分离卷积 + 残差连接 | Conv2d(groups>1) + BatchNorm2d + Add |
| GELayerS1(无下采样) | 深度可分离卷积 + 残差连接 | Conv2d(groups>1) + BatchNorm2d + Add |
| 特征增强模块 | ||
| CEBlock全局上下文 | GAP → 1x1 Conv → 3x3 Conv |
AvgPool2d + Conv2d + Conv2d |
| BGALayer特征对齐 | 深度卷积 + 上采样 + 融合 | Conv2d(groups>1) + Upsample + Add |
| 分割头(SegmentHead) | ||
| 输出卷积 | ConvBNReLU → Dropout → 1x1 Conv → 上采样 |
Conv2d + Dropout + Upsample |
| 特殊操作 | ||
| 双线性上采样 | F.interpolate(mode='bilinear') |
Upsample |
| 特征拼接 | torch.cat |
Concat |
| 通道注意力(可选) | SE模块 |
AvgPool2d + Conv2d + Hardsigmoid |
3.3 导出过程和1_scripts中的脚本运行说明
直接加载模型,只导出BiSeNet 主干,前后处理不保存在在导出模型中。
├── 0_infer.py >框架下推断脚本
├── 1_save.py >trace模型保存onnx/pt脚本
├── 2_save_infer.py >onnx/pt推断脚本
-
0_infer.py
- 可以推理一张图(或其他格式数据)并得到最终结果,目的是为了快速跑通推理代码,为后续剥离/移植前后处理做准备。为了实现快速不能用整个数据集作为输入。
- 用法: python 0_infer.py
-
1_save.py
-
保存模型,导出的模型默认存放至
2_compile/fmodel路径下,如../2_compile/fmodel/Bisenet_512x1048.pt -
用法: python 1_save.py
-
导出过程修改点
-
修改原因:
-
- 分析parsed后的模型最后一层,可以看到最后一层是Upsample算子,因为要使用segpost FPGA硬算子加速,要求最后一层是普通卷积。所以在Upsample后面再加一个恒等卷积。
-
-
修改方法
-
# -------- 包裹模型并添加恒等卷积 -------- class BiSeNetWithIdentityConv(nn.Module): def __init__(self, base, n_classes): super().__init__() self.base = base self.final_conv = nn.Conv2d(n_classes, n_classes, kernel_size=1, bias=False) # 默认 groups=1 # 初始化为恒等变换 (单位矩阵形式) with torch.no_grad(): self.final_conv.weight.zero_() # 所有权重先清零 for i in range(n_classes): self.final_conv.weight[i, i, 0, 0] = 1.0 # 只保留对角线为1 def forward(self, x): out = self.base(x)[0] out = self.final_conv(out) return out # -------- 包裹并导出 -------- model = BiSeNetWithIdentityConv(base_model, cfg.n_cats).to(device)
-
-
修改后的结构
-
- 最后一层是一个恒等卷积(不需训练)。
-
-
-
-
2_save_infer.py
- 用修改后保存的模型做前向推理,目的是为了与0_infer的结果对比,确保导出静态模型的正确性。
- 用法: python 2_save_infer.py
4. cpu处理部分c++工程移植说明
4.1 cpu环节实现的功能
CPU(Host 端)承担了模型部署与推理流程中大量关键的前处理、后处理和控制逻辑。
1)配置与初始化(控制逻辑)
- 读取 YAML 配置文件:
- 包括模型路径、仿真与实卡运行开关、是否可视化输出、是否保存结果等。
- 加载图像路径、类别标签文件、网络输入尺寸等信息。
- 构建模型路径、打开设备并加载模型:
- 根据配置动态拼接 JSON 模型路径和 RAW 参数路径。
- 判断是否在仿真(sim)还是实际设备(实卡)上运行。
- 加载网络结构并初始化
Session和Device。
2)前处理(Preprocess)
这是典型的 在 CPU 上运行的图像前处理操作,目标是将原始图片转为模型输入格式:
- 图像读取:
- 使用 OpenCV 加载图像为 BGR 格式。
- 尺寸变换与补边:
Resize:以 长边为基准(此处为宽 = 2048),将图像缩放,使其长边匹配目标尺寸(如输入为 512×1024,则长边为1024)- 缩放比例 = 1024 / 2048 = 0.5
- 缩放后图像尺寸为 512×1024
rPad:在 短边方向(此处为高 = 512) 进行对称补零,补成最终输入尺寸(如目标为 512×1024,实际已满足则无需额外 padding)- 若目标为正方形输入(如 1024×1024),则:
- 当前图像已为 512×1024,需要在高方向补齐至1024,高度补 512 像素(上下各补 256)
- 最终尺寸为 1024×1024
- 图像转换为张量:
- 将裁剪后的图像转为 NCHW 格式、float 类型张量,用于输入模型(
CvMat2Tensor())。
- 将裁剪后的图像转为 NCHW 格式、float 类型张量,用于输入模型(
- 输入 DMA 传输:
- 将 CPU 中的图像张量传输给设备端执行推理(
dmaInit())。
- 将 CPU 中的图像张量传输给设备端执行推理(
3)模型执行控制
- 执行模型前向推理:
- 由 CPU 调用
session.forward()启动一次推理流程,控制输入和获取输出。 - 返回的是在设备端计算完成的结果张量。
- 由 CPU 调用
- 设备端输出转回 Host 内存:
- 将设备端输出张量(float*)搬运回 CPU 内存空间供后处理使用(
to(HostDevice::MemRegion()))。
- 将设备端输出张量(float*)搬运回 CPU 内存空间供后处理使用(
4)后处理(Postprocess)
这些操作也是由 CPU 进行的,主要包括:
- 数据回传
to(HostDevice::MemRegion())将NPU输出的分割张量复制到Host内存。- 这里使用segpost FPGA硬算子加速。
- 示例输出shape为
[1,512,1024,19](Cityscapes的19类分割任务)
- 结果解析
- 根据输出类型(float多通道/int8单通道)转换数据指针
- 构建OpenCV矩阵:多通道用
CV_32FC19,单通道用CV_8SC1
- 尺寸调整
- 将网络输出resize到原始图像尺寸(如1024x2048)
- 使用双线性插值保持边缘平滑
- 类别映射
- 将每个类别用不同颜色表示。
4.2 各环节耗时
命令行输入icraft run ./config/BiSeNet_8.toml,以仿真int8量化后的模型为例,单一输入图像对理时间输如下:
# BiSeNet_8.toml文件中
[run]
log_time = true
log_io = true
dump_format = "SFB"
json = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_BY.json"
raw = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_BY.raw"
input = "./qtset/cityscapes/frankfurt_000000_000294_leftImg8bit.png"
log_path = "./logs/"
D:\dtpw\Desktop\icraft_workplace\bisenet\2_compile>icraft run ./config/BiSeNet_8.toml
[06/30/25 16:30:10.426] [I] ImageMake Version is 3.8.1.0
SegPost Version is 3.8.1.0
[06/30/25 16:30:10.496] [W] Turn on both dump and calctime will get wrong timing results.
Running : [██████████████████████████████████████████████████ ] 100% [00m:21s]
Input 0 In: Out: (0|-1,-1,-1,-1)
Resize 1 In: (0|-1,-1,-1,-1) Out: (1|1,512,1024,3)
ImageMake 476 In: (1|1,512,1024,3) Out: (1083|1,1,512,1024,4)
HardOp 710 In: (1083|1,1,512,1024,4) Out: (344|1,1,512,1024,32)
SegPost 477 In: (344|1,1,512,1024,32) Out: (1084|1,512,1024,1)
Output 177 In: (1084|1,512,1024,1) Out:
[06/30/25 16:30:32.490] [I] network wall: 0.0146 ms
Load Json Time: 64.859 ms
Load Raw Time: 77.009 ms
opId: 0 opType: Input wall: 16.212 ms mem: 0.000 ms hard: 0.000 ms other: 16.212 ms
opId: 1 opType: Resize wall: 52.321 ms mem: 0.000 ms hard: 0.000 ms other: 52.321 ms
opId: 476 opType: ImageMake wall: 131.286 ms mem: 0.000 ms hard: 0.000 ms other: 131.286 ms
opId: 710 opType: HardOp wall: 19446.349 ms mem: 0.000 ms hard: 0.000 ms other: 19446.349 ms
opId: 477 opType: SegPost wall: 1977.115 ms mem: 0.000 ms hard: 0.000 ms other: 1977.115 ms
opId: 177 opType: Output wall: 0.013 ms mem: 0.000 ms hard: 0.000 ms other: 0.013 ms
################# SUMMARY #################
OpNum 1 OpType HardOp total_wall: 19446.349 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 19446.349 ms
OpNum 1 OpType ImageMake total_wall: 131.286 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 131.286 ms
OpNum 1 OpType Input total_wall: 16.212 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 16.212 ms
OpNum 1 OpType Output total_wall: 0.013 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 0.013 ms
OpNum 1 OpType Resize total_wall: 52.321 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 52.321 ms
OpNum 1 OpType SegPost total_wall: 1977.115 ms total_mem: 0.000 ms total_hard: 0.000 ms total_other: 1977.115 ms
total_time: 0.015 ms, total_hard_time: 0.000 ms
ImageMake: 0.000 ms
SegPost: 0.000 ms
当前仿真系统的cpu配置情况(不同配置运行速度不同,只作参考)
D:\dtpw\Desktop\icraft_workplace\BiSeNet\2_compile>wmic cpu get name,numberofcores,numberoflogicalprocessors,maxclockspeed
MaxClockSpeed Name NumberOfCores NumberOfLogicalProcessors
1800 AMD Ryzen 7 4800U with Radeon Graphics 8 16
分析
- 核心推理耗时在
HardOp,即模型主干部分(19446.349ms),这是真正的前向计算阶段; - 前处理部分(Resize + ImageMake)耗时约 131.286ms;
- 类型转换和输出阶段使用SegPost耗时1977.115 ms;
6. ICRAFT编译
编译通过
1. 框架动态图推理
-
输入图片
-
-
框架推理
-
python 0_infer.py -
输出结果:
-
2. 导出静态模型推理
-
输入图片:同上
-
导出静态模型推理
python 2_save_infer.py
3. 解析组件仿真(icraft run命令)
-
命令:
-
# BiSeNet_8.toml文件中 [run] log_time = true log_io = true dump_format = "SFB" json = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_parsed.json" raw = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_parsed.raw" input = "./qtset/cityscapes/frankfurt_000000_000294_leftImg8bit.png" backends = "Host"
-
命令行运行
\BiSeNet\2_compile>icraft run ./config/BiSeNet_8.toml
-
-
输出截图(使用仿真命令icraft run)
-
4. 量化组件仿真(icraft run命令)
-
命令:
-
# BiSeNet_8.toml文件中 [run] log_time = true log_io = true dump_format = "SFB" json = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_quantized.json" raw = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_quantized.raw" input = "./qtset/cityscapes/frankfurt_000000_000294_leftImg8bit.png" backends = "Host"
-
命令行运行
\BiSeNet\2_compile>icraft run ./config/BiSeNet_8.toml
-
-
输出截图
-
5. 指令组件仿真(icraft run命令)
-
命令:
-
# BiSeNet_8.toml文件中 [run] log_time = true log_io = true dump_format = "SFB" json = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_BY.json" raw = "../3_deploy/modelzoo/BiSeNet/imodel/8/BiSeNet_BY.raw" input = "./qtset/cityscapes/frankfurt_000000_000294_leftImg8bit.png" backends = "Host"
-
命令行运行
\Bisenet\2_compile>icraft run ./config/BiSeNet_8.toml
-
-
输出截图
-
6. 完整的c++工程仿真输出(c++ runtime)
-
在
3_deploy/modelzoo/BiSeNet/build_win目录下运行:Release\BiSeNet_psin.exe ..\cfg\BiSeNet_8.yaml #注意,windows中一般用\,而/可以用但有些时假会报错,仿真时分割任务时间长,请等待。 Release\BiSeNet_psin.exe ..\cfg\BiSeNet_16.yaml Release\BiSeNet_psin.exe ..\cfg\BiSeNet_mix.yaml
-
输出截图
-
7. 8bit,16bit,混合精度的精度测试结果
-
分别运行以下命令,得到8bit,16bit,混合精度模型在CITYSCAPES验证集图像上的分割结果。
Release\BiSeNet_psin.exe ..\cfg\BiSeNet_test_8.yaml # 注意,windows中一般用\,而/可以用但有些时假会报错 Release\BiSeNet_psin.exe ..\cfg\BiSeNet_test_16.yaml Release\BiSeNet_psin.exe ..\cfg\BiSeNet_test_mix.yaml
结果分别保存在
3_deploy\modelzoo\BiSeNet\io\output\8\val_5000 3_deploy\modelzoo\BiSeNet\io\output\16\val_5000 3_deploy\modelzoo\BiSeNet\io\output\mix\val_5000
-
运行精度测试命令
\Release\BiSeNet_psin.exe ..\cfg\BiSeNet_test_8.yaml后,使用metrics测试脚本,根据保存的测试结果计算出精度指标。metrics测试脚本使用与配置说明:
- 精度测试脚本存放于:https://gitee.com/mxh-spiger/benchmark
- 因为该模型是在512x1024的输入和输出下训练的,CITYSCAPES验证集的GT大小都为1024x2028,所以可使用我提供的bisenet\3_deploy\modelzoo\BiSeNet\io\output\8\leftImg8bit\val\myresizeGT.py的这个脚本resize到1024x2028大小,再进行精度测试。
-
精度结果
-
BiSeNet input shape 精度 int8 [1, 3, 512,1024] allAcc/mAcc/mIoU 93.42%/70.63%/63.33% int16 [1, 3, 512,1024] allAcc/mAcc/mIoU 93.78%/73.44%/64.99% mix(混合精度) [1, 3, 512,1024] allAcc/mAcc/mIoU 93.43%/70.61%/63.32%
-
8. 编译过程的一些特殊说明
-
注意,windows中的命令行中,一般用左,右/可以用但有些时假会报错
-
编译命令注意事项
-
cd 3_deploy\modelzoo\BiSeNet mkdir -p build_win cd build_win cmake -G "Visual Studio 17 2022" -A x64 .. # 不要直接cmake .. cmake --build . --config Release
-
不能直接使用
cmake ..命令,因为如果系统上多种编译器,且默认cmake使用的编译器不是visualstudio2022会出错,所以使用-G "Visual Studio 17 2022" -A x64命令,让cmake使用visualstudio2022编译器编译。
-
7. 上板测试与优化
1)耗时分析与优化
(1)耗时测试
获取op算子的 time profile,查看npu算子性能瓶颈
耗时瓶颈在于Conv2d、Batchnorm、Relu等算子
(2)优化一:并合算子
使用算子合并成HardOP算子
-
使用官方提供的modelzoo utils/pyrtutils/中的analyze_time_utils对网络进行耗时瓶颈分析,得到性能表格:
-
整体耗时
-
-
各类硬算子耗时统计结果
-
-
内部数据
-
-
-
结论:
- 耗时的Conv2d、Batchnorm、Relu等算子合并成HardOP算子,时间大大减少。
- 加速比:4.2641
- 主要耗时:
- PruneAxis:将通道对齐(去除无效通道),
- Cast将数据搬移(PL->PS)、排布转换,
(3)优化二:Segpost硬算子加速
分析:
- 使用官方提供的modelzoo utils/pyrtutils/中的analyze_time_utils对网络进行耗时瓶颈分析、从而制定针对性优化部署方案
-
- [docs/Part 4 ](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 4 性能分析及优化.md)[性能分析及优化](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 4 性能分析及优化.md)[.md · ](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 4 性能分析及优化.md)[spiger](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 4 性能分析及优化.md)[/TUTORIAL - Gitee.com](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 4 性能分析及优化.md)
-
结论:
- 耗时瓶颈在于数据输出和后处理,即npu的io耗时长导致延时长。
- 耗时瓶颈在于Icraft-cpu算子操作耗时。
方法:
-
参考相关文档[docs/Part 5 fpga-op.md · spiger/TUTORIAL - Gitee.com](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 5 fpga-op.md#412-设置segpost参数)
-
-
可以使用通过Segpost硬算子从而实现缩短数据输出和后处理时间、Icraft-cpu耗时:
- 去通道:代替PruneAxis将通道对齐(去除无效通道)
- 排布转换:代替Cast将数据搬移(PL->PS)、排布转换
- 分割网络后处理:将分割网络的特征图进行Argmax处理和上采样处理,得到内容为类别索引的分割网络结果
困难:
- 分析parsed后的模型最后一层,可以看到最后一层是Upsample算子,因为要使用segpostFPGA 硬算子加速,要求最后一层是普通卷积,所以无法使用segpost加速。
trick:
- 最后一层是Upsample算子,在Upsample后面再加一个恒等卷积,最后一层是一个恒等卷积(单位矩阵形式,不需训练)。
- 图中黑框即为加上的恒等卷积。
- 该卷积算子不起任何作用,不需训练,但要初始化为单位矩阵形式。
结果:
-
整体耗时
-
-
各类硬算子耗时统计结果
-
-
内部数据
-
-
对比
-
- 当使用SegPost加速后,cpu算子耗时(后处理)时间为0
- 当使用SegPost加速后,数据传出时间差不多减半。
- 加速比:2.9037
-
2)精度分析与优化
(1)精度测试
| BiSeNet | input shape | 精度 |
|---|---|---|
| int8 | [1, 3, 512,1024] | allAcc/mAcc/mIoU 93.42%/70.63%/63.33% |
| int16 | [1, 3, 512,1024] | allAcc/mAcc/mIoU 93.78%/73.44%/64.99% |
| mix(混合精度) | [1, 3, 512,1024] | allAcc/mAcc/mIoU 93.43%/70.61%/63.32% |
- Int16精度精度最高。
- 相比混合精度,int8还要更高一点。
- 显然混合精度还有提升的空间。
(2)精度对时间的影响分析
- Int16是进间上限,int8是时间下限,mix的时间优化到最优也只是下限时间。
- Mix中还要有Cast算子时间(用于精度转换)。
- 上限与下限在该模型参数下相差不大,因此在在该模型下mix精度优化过程不考虑对时间的影响。
(3)混合精度优化
-
多个算子处有明显的余弦相似度下降,说明精度有损失。
-
配置参数mix_precision = "auto",生成相应的csv配置文件。
-
尝试对尽可能多的精度损失处进行:量化位宽、饱和点、量化方式来提升精度。
-
原csv文件中的某个算子
-
-
修改后的配置
-
* 量化位数:该算子提高到16位量化。
* 饱和方法:使用ems饱和量化的方式。
* 量化粒度:使用per_channel量化
-
-
-
该算子优化后
-
-
在该算子处的余弦相似度增加了。
-
余弦相似度从0.5提升到0.75。
-
-
多个算子优化后
-
BiSeNet input shape 精度 int8 [1, 3, 512,1024] allAcc/mAcc/mIoU 93.42%/70.63%/63.33% int16 [1, 3, 512,1024] allAcc/mAcc/mIoU 93.78%/73.44%/64.99% mix(混合精度) [1, 3, 512,1024] allAcc/mAcc/mIoU 93.53%/73.11%/64.35% -
混合精度模型在CITYSCAPES验证集上的精度提升了。
-