一、Sophon服务器及芯片简介
1.AI芯片
-
SOPHON BM1684
- 算力:17.6 TOPS INT8;2.2 TFLOPS FP32【不支持FP16】
- Video Decode:32x 1080p30 (H.264/H.265)
- Video Encode:2x 1080p30 (H.264/H.265)
- Memory:12GB LPDDR4x
-
SOPHON BM1684X
- CPU:8核ARM A53 2.3GHz
- 算力:32 TOPS INT8;16 TFLOPS FP16/BF16;2 TFLOPS FP32
- Memory:12GB LPDDR4x 128bit 68.3GB/s
- Video Decode:32x 1080p30 (H.264/H.265)
- Video Encode:12x 1080p30 (H.264/H.265)
2.AI服务器
-
AI微服务器SE6-192
-
搭载国产 TPU 芯片 BM1684,可选配两块算力板(简称 SE6-192)或一块算力板(简称 SE6-96)
- SE6-192:12张BM1684
- SE6-96:6张BM1684
-
CPU:
- 主控:8核ARM A53 2.3GHz
- 算力节点:12/6 * 8核ARM A53 2.3GHz
-
Memory:
- 主控:12GB LPDDR4x
- 算力节点:12/6 *12GB LPDDR4x
- 每个计算模组中,4GB专用于TPU计算加速;4GB专用于视频图像编解码加速;4GB可用于系统内存。
-
磁盘:
- 主控:32GB EMMC
- 算力节点:12/6 * 32GB EMMC
-
网络拓扑
- eth0 的 ip 为 172.16.140.200 。 算 力 板 0 的 6 块 核 心 板 ip 为 172.16.140.11 ~
172.16.140.16。- ssh端口映射:10021~10026,用户名密码均为linaro。
- eth1 的 ip 为 172.16.150.200 。 算 力 板 1 的 6 块 核 心 板 ip 为 172.16.150.11 ~
172.16.150.16。- ssh端口映射:20021~20026,用户名密码均为linaro。
- eth0 的 ip 为 172.16.140.200 。 算 力 板 0 的 6 块 核 心 板 ip 为 172.16.140.11 ~
-
二、SophonSDK简介
https://github.com/sophon-ai-algo
SophonSDK是算能科技基于其自主研发的 AI 芯片所定制的深度学习 SDK,涵盖了神经网络推理阶段所需的模型优化、高效运行支持等能力,为深度学习应用开发和部署提供易用、高效的全栈式解决方案。
SophonSDK既兼容第三代BM1684芯片,也支持第四代BM1684X芯片,目前仅支持Linux版本。
1.主要功能模块
硬件驱动及运行时库 LIBSOPHON : 包含BMCV、BMRuntime、BMLib等库,用来驱动VPP、TPU等硬件,完成图像处理、张量运算、模型推理等操作。
-
多媒体库 SOPHON-MW : 支持Sophon设备硬件加速的BM-OpenCV和BM-FFmpeg,支持RTSP流、GB28181流的解析,视频及图片的编解码。
算法步骤 支持硬件加速 BM-OpenCV BM-FFmpeg Native接口 视频/图片编解码 支持 Y Y BMCV(图片)/SAIL 输入预处理 支持 Y N BMCV/SAIL 模型推理 支持 N N BMRuntime/SAIL 输出后处理 部分支持 N N BMCV 模型编译量化工具链 TPU-NNTC : 支持Caffe、Tensorflow、Pytorch、MXNet、 Darknet、Paddle Paddle、ONNX等框架模型的模型转换;支持模型量化:原始模型 -> FP32 UModel -> INT8 UModel -> INT8 BModel, 同时提供 auto-cali 自动量化工具。
张量运算及图像处理库 BMCV : 色彩空间转换、尺度变换、仿射变换、投射变换、线性变换、画框、JPEG编码、BASE64编码、NMS、排序、特征匹配。
设备管理 BMLib : 基础接口:设备Handle的管理,内存管理、数据搬运、API的发送和同步、A53使能等
AI加速库 SAIL : 支持Python/C++的高级接口,是对BMRuntime、BMCV、BMDecoder等底层库接口的封装。
自定义算子高级编程库 BMLang:基于C++的面向Sophon TPU的高级编程库,与硬件信息解耦,无需了解硬件架构,使用张量数据(bmlang::Tensor)和计算操作(bmlang::Operator)编写代码,最后使用bmlang::compile或bmlang::compile_with_check来生成TPU可以运行的BModel;此外也支持使用BM168X中的arm cpu来实现TPU尚不支持的算子。
算法并行加速编程库 TPUKernel:基于Sophon芯片底层原子操作接口的底层编程接口,需要熟悉硬件架构和指令集。
模型性能和精度验证工具 TPUPerf : 可对模型进行性能分析和精度验证。
2.sdk工程目录说明
sudo apt-get install p7zip
sudo apt-get install p7zip-full
$ wget https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/06/15/16/Release_230501-public.zip
$ 7z x Release_230501-public.zip
$ tree -L 2 Release_230501-public
├── bmmonkey_20230605_064500
│ ├── bmmonkey.MD5
│ ├── pcie_amd
│ ├── pcie_arm
│ ├── release_version.txt
│ └── soc
├── bmpanda_20230605_065400
│ ├── amd_pcie
│ ├── arm_pcie
│ ├── arm_soc
│ ├── bm_panda_3.1.0_pcie_amd64.deb
│ ├── bm_panda_3.1.0_pcie_amd64.rpm
│ ├── bm_panda_3.1.0_pcie_arm64.deb
│ ├── bm_panda_3.1.0_soc_arm64.deb
│ ├── bmpanda.MD5
│ ├── bm_panda使用说明.pdf
│ ├── centos
│ ├── loongarch64_pcie
│ └── release_version.txt
├── libsophon_20230605_025400
│ ├── BMCV_Technical_Reference_Manual.pdf
│ ├── BMCV开发参考手册.pdf
│ ├── BMLib_Technical_Reference_Manual.pdf
│ ├── BMLIB开发参考手册.pdf
│ ├── BMRuntime Technical Reference Manual.pdf
│ ├── BMRUNTIME开发参考手册.pdf
│ ├── centos
│ ├── libsophon_0.4.8_aarch64.tar.gz
│ ├── libsophon_0.4.8_loongarch64.tar.gz
│ ├── libsophon_0.4.8_x86_64.tar.gz #x86 其他LINUX系统对应的libsophon安装包
│ ├── libsophon_dockerfile
│ ├── libsophon.MD5
│ ├── LIBSOPHON_User_Guide.pdf
│ ├── LIBSOPHON使用手册.pdf
│ ├── release_version.txt
│ ├── sophon-driver_0.4.8_amd64.deb #x86_64,Debian/Ubuntu系统对应PCIe卡驱动安装文件
│ ├── sophon-driver_0.4.8_arm64.deb
│ ├── sophon-libsophon_0.4.8_amd64.deb #x86_64,Debian/Ubuntu系统对应libsophon运行时环境安装文件
│ ├── sophon-libsophon_0.4.8_arm64.deb
│ ├── sophon-libsophon-dev_0.4.8_amd64.deb #x86_64,Debian/Ubuntu系统对应libsophon开发环境安装文件
│ └── sophon-libsophon-dev_0.4.8_arm64.deb
├── sophon-demo_20230605_085900
│ ├── release_version.txt
│ ├── sophon-demo.MD5
│ └── sophon-demo_v0.1.6_f4d1abc_20230605.tar.gz #针对单模型或场景的综合例程
├── sophon-img_20230605_054900
│ ├── bsp-debs
│ ├── bsp_update.tgz # SoC OTA升级包【板卡升级】
│ ├── libsophon_soc_0.4.8_aarch64.tar.gz #交叉编译所需文件,包含aarch64 libsophon的库和头文件
│ ├── release_version.txt
│ ├── sdcard.tgz #SoC SD卡刷机包
│ ├── SOPHON_BSP_Technical_Reference_Manual.pdf
│ ├── SOPHON BSP开发参考手册.pdf
│ ├── sophon-img.MD5
│ ├── system.tgz # SoC OTA升级包【系统升级】
│ └── tftp.tgz
├── sophon-mw_20230605_032400
│ ├── Multimedia FAQ.pdf
│ ├── Multimedia Technical Reference Manual.pdf
│ ├── Multimedia User Guide.pdf
│ ├── MULTIMEDIA使用手册.pdf
│ ├── MULTIMEDIA常见问题手册.pdf
│ ├── MULTIMEDIA开发参考手册.pdf
│ ├── release_version.txt
│ ├── sophon-mw_0.6.3_aarch64.tar.gz
│ ├── sophon-mw_0.6.3_loongarch64.tar.gz
│ ├── sophon-mw_0.6.3_x86_64.tar.gz #x86_64,其他LINUX系统对应的sophon-mw安装包
│ ├── sophon-mw.MD5
│ ├── sophon-mw-soc_0.6.3_aarch64.tar.gz #交叉编译所需文件,包含aarch64 sophon-mw的库和头文件
│ ├── sophon-mw-soc-sophon-ffmpeg_0.6.3_arm64.deb #SoC平台,ffmpeg运行时环境安装文件
│ ├── sophon-mw-soc-sophon-ffmpeg-dev_0.6.3_arm64.deb
│ ├── sophon-mw-soc-sophon-opencv_0.6.3_arm64.deb #SoC平台,opencv运行时环境安装文件
│ ├── sophon-mw-soc-sophon-opencv-dev_0.6.3_arm64.deb
│ ├── sophon-mw-soc-sophon-sample_0.6.3_arm64.deb #SoC平台,多媒体程序示例文件
│ ├── sophon-mw-sophon-ffmpeg_0.6.3_amd64.deb
│ ├── sophon-mw-sophon-ffmpeg_0.6.3_amd64.rpm
│ ├── sophon-mw-sophon-ffmpeg_0.6.3_arm64.deb #arm64,Debian/Ubuntu系统对应的ffmpeg运行时环境安装文件
│ ├── sophon-mw-sophon-ffmpeg_0.6.3_arm64.rpm
│ ├── sophon-mw-sophon-ffmpeg_0.6.3_loongarch64.deb
│ ├── sophon-mw-sophon-ffmpeg-dev_0.6.3_amd64.deb
│ ├── sophon-mw-sophon-ffmpeg-dev_0.6.3_amd64.rpm
│ ├── sophon-mw-sophon-ffmpeg-dev_0.6.3_arm64.deb
│ ├── sophon-mw-sophon-ffmpeg-dev_0.6.3_arm64.rpm
│ ├── sophon-mw-sophon-ffmpeg-dev_0.6.3_loongarch64.deb
│ ├── sophon-mw-sophon-opencv_0.6.3_amd64.deb
│ ├── sophon-mw-sophon-opencv_0.6.3_arm64.deb #arm64,Debian/Ubuntu系统对应的opencv运行时环境安装文件
│ ├── sophon-mw-sophon-opencv_0.6.3_loongarch64.deb
│ ├── sophon-mw-sophon-opencv-abi0_0.6.3_amd64.rpm
│ ├── sophon-mw-sophon-opencv-abi0_0.6.3_arm64.rpm
│ ├── sophon-mw-sophon-opencv-abi0-dev_0.6.3_amd64.rpm
│ ├── sophon-mw-sophon-opencv-abi0-dev_0.6.3_arm64.rpm
│ ├── sophon-mw-sophon-opencv-dev_0.6.3_amd64.deb
│ ├── sophon-mw-sophon-opencv-dev_0.6.3_arm64.deb
│ ├── sophon-mw-sophon-opencv-dev_0.6.3_loongarch64.deb
│ ├── sophon-mw-sophon-sample_0.6.3_amd64.deb
│ ├── sophon-mw-sophon-sample_0.6.3_amd64.rpm
│ ├── sophon-mw-sophon-sample_0.6.3_arm64.deb #arm64,多媒体程序示例文件
│ ├── sophon-mw-sophon-sample_0.6.3_arm64.rpm
│ └── sophon-mw-sophon-sample_0.6.3_loongarch64.deb
├── sophon-pipeline_20230605_080800
│ ├── release_version.txt
│ ├── sophon-pipeline.MD5
│ └── sophon-pipeline_v0.3.7_c9b37f7_20230605.tar.gz #基于pipeline的高性能推理框架
├── sophon-rpc_20230608_060600
│ ├── release_version.txt
│ ├── sophon-rpc-3.0.0-1.amd64.rpm
│ ├── sophon-rpc-3.0.0-1.arm64.rpm
│ ├── sophon-rpc_3.0.0_amd64.deb #x86_64,Debian/Ubuntu系统对应的sophon-rpc安装文件
│ ├── sophon-rpc_3.0.0_arm64.deb
│ ├── sophon-rpc_3.0.0.tar.gz
│ ├── sophon-rpc.MD5
│ └── sophon-rpc使用指南.pdf
├── sophon-sail_20230605_085400
│ ├── release_version.txt
│ ├── sophon-sail_3.5.0.tar.gz #封装了BMLib、BMDecoder、BMCV、BMRuntime的高级接口库
│ ├── sophon-sail_en.pdf
│ ├── sophon-sail.MD5
│ └── sophon-sail_zh.pdf
├── sophon-testhub_20221013_211859
│ ├── release_version.txt
│ ├── sophon-testhub_0.1.0.tar.gz
│ └── sophon-testhub.MD5
├── tpu-kernel_20230605_055600
│ ├── release_version.txt
│ ├── tpu-kernel-1684_v3.1.7-d286b63c-230605.tar.gz #BM1684 自定义算子开发工具
│ ├── tpu-kernel-1684x_v3.1.7-4a3e13bf-230605.tar.gz
│ └── tpu-kernel.MD5
├── tpu-mlir_20230605_054500
│ ├── release_version.txt
│ ├── tpu-mlir.MD5
│ └── tpu-mlir_v1.1.7-g30df2e3c-20230605.tar.gz #编译器工具链
├── tpu-nntc_20230605_054100
│ ├── release_version.txt
│ ├── tpu-nntc.MD5
│ ├── tpu-nntc-test_v3.1.9-73e3f629-230605.tar.gz
│ └── tpu-nntc_v3.1.9-73e3f629-230605.tar.gz #模型编译量化工具链
└── tpu-perf_v1.2.13
├── tpu_perf-1.2.13-py3-none-manylinux2014_aarch64.whl
├── tpu_perf-1.2.13-py3-none-manylinux2014_x86_64.whl
└── tpu-perf-1.2.13.tar.gz #模型的性能分析和精度验证工具
三、开发环境搭建
- X86 PCIe模式:一种产品形态,SDK运行于X86平台,BM1684、BM1684X存在于PCIe接口的深度学习计算加速卡上
- 可以在x86主机上基于tpu-nntc和libsophon完成模型的编译量化与应用的开发部署。
- Arm PCIe模式:一种产品形态,SDK运行于aarch64平台(如银河麒麟),BM1684、BM1684X存在于PCIe接口的深度学习计算加速卡上。
- Soc模式:一种产品形态,SDK运行于A53 AARCH64平台,TPU作为平台总线设备,如:SE微服务器/SM模组
- 可以在x86主机上基于tpu-nntc和libsophon完成模型的编译量化与程序的交叉编译,部署时将编译好的程序拷贝至SoC平台中执行。
1.x86 PCIe模式
1)安装libsophon及sophon-mw
# 安装依赖库
sudo apt install dkms libncurses5
# 安装驱动、libsophon开发包及运行时包
cd Release_230501-public/libsophon_20230605_025400
ls *amd64.deb
sophon-driver_0.4.8_amd64.deb
sophon-libsophon_0.4.8_amd64.deb
sophon-libsophon-dev_0.4.8_amd64.deb
cd -
sudo dpkg -i sophon-*amd64.deb
source /etc/profile
# 查看设备文件
$ ls /dev/bm*
/dev/bmdev-ctl /dev/bm-sophon0
# 查看驱动
$ bm-smi
$ ls /sys/class/bm-sophon/bm-sophon0/device
# 安装sophon-mw
cd Release_230501-public/sophon-mw_20230605_032400
ls *amd64.deb
sophon-mw-sophon-ffmpeg_0.6.3_amd64.deb
sophon-mw-sophon-opencv_0.6.3_amd64.deb
sophon-mw-sophon-sample_0.6.3_amd64.deb
sophon-mw-sophon-ffmpeg-dev_0.6.3_amd64.deb
sophon-mw-sophon-opencv-dev_0.6.3_amd64.deb
cd -
source /etc/profile
2.arm PCIe模式
银河麒麟v10为例
3.SoC模式
1)x86上交叉编译准备
# 安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
cd Release_230501-public
mkdir -p soc-sdk
# 解压sophon-img包,拷贝已编译的aarch64 sophon lib及include文件
cd sophon-img_20230605_054900
tar -zxvf libsophon_soc_0.4.8_aarch64.tar.gz
$ ls libsophon_soc_0.4.8_aarch64/opt/sophon/
driver-0.4.8 libsophon-0.4.8
cp -rf libsophon_soc_0.4.8_aarch64/opt/sophon/libsophon-0.4.8/lib/ ../soc-sdk
cp -rf libsophon_soc_0.4.8_aarch64/opt/sophon/libsophon-0.4.8/include/ ../soc-sdk
cd -
# 解压sophon-mw包,拷贝已编译的aarch64 ffmpeg和opencv的lib及include文件
cd sophon-mw_20230605_032400
tar -zxvf sophon-mw-soc_0.6.3_aarch64.tar.gz
$ ls sophon-mw-soc_0.6.3_aarch64/opt/sophon/
sophon-ffmpeg_0.6.3 sophon-opencv_0.6.3 sophon-sample_0.6.3
# 将ffmpeg和opencv的库目录和头文件目录拷贝到依赖文件根目录下
cp -rf sophon-mw-soc_0.6.3_aarch64/opt/sophon/sophon-ffmpeg_0.6.3/lib/ ../soc-sdk
cp -rf sophon-mw-soc_0.6.3_aarch64/opt/sophon/sophon-ffmpeg_0.6.3/include/ ../soc-sdk
cp -rf sophon-mw-soc_0.6.3_aarch64/opt/sophon/sophon-opencv_0.6.3/lib/ ../soc-sdk
cp -rf sophon-mw-soc_0.6.3_aarch64/opt/sophon/sophon-opencv_0.6.3/include/ ../soc-sdk
2)SoC上运行环境
对于SoC平台,一般内部已经集成了驱动和相应的libsophon、sophon-opencv和sophon-ffmpeg运行库包,位于 /opt/sophon/目录下,如需更新,推荐直接更新固件。
# 查看设备文件
$ ls /dev/bm*
/dev/bm-tach-0 /dev/bm-tach-1 /dev/bm-top /dev/bm-tpu0 /dev/bm-vpp /dev/bm-wdt-0 /dev/bm_efuse /dev/bmdev-ctl
$ ls /sys/class/bm-tpu/bm-tpu0/device
# 查看驱动
$ bm-smi
+--------------------------------------------------------------------------------------------------+
| SDK Version: 0.4.8 Driver Version: 0.4.8 |
+---------------------------------------+----------------------------------------------------------+
......
$ bm_version
sophon-soc-libsophon : 0.4.8
sophon-soc-libsophon-dev : 0.4.8
sophon-mw-soc-sophon-ffmpeg : 0.6.3
sophon-mw-soc-sophon-opencv : 0.6.3
BL2 v2.7(release):3612e34 Built : 05:50:40, Jun 5 2023
BL31 v2.7(release):3612e34 Built : 05:50:40, Jun 5 2023
U-Boot 2022.10 3612e34 (Jun 05 2023 - 05:50:17 +0800) Sophon BM1684
KernelVersion : Linux bm1684 5.4.217-bm1684-g5223559170c0 #1 SMP Mon Jun 5 05:51:01 CST 2023 aarch64 aarch64 aarch64 GNU/Linux
HWVersion: 0x11
MCUVersion: 0x35
# 查看sophon库文件
$ $ tree /opt/sophon/ -L 2
/opt/sophon/
├── libsophon-0.4.8
│ ├── bin
│ │ ├── bm-smi
│ │ ├── bm1684x
│ │ ├── bm_firmware_update
│ │ ├── bm_test
│ │ ├── bmjpegdec
│ │ ├── bmjpegenc
│ │ ├── bmjpegmulti
│ │ ├── bmrt_test
│ │ ├── bmvpuenc
│ │ ├── fbcmulti
│ │ ├── i420tonv12
│ │ ├── ...
│ ├── data
│ ├── include
│ └── lib
├── libsophon-current -> /opt/sophon/libsophon-0.4.8
├── sophon-ffmpeg-latest -> /opt/sophon/sophon-ffmpeg_0.6.3
├── sophon-ffmpeg_0.6.3
│ ├── bin
│ ├── data
│ └── lib
├── sophon-opencv-latest -> /opt/sophon/sophon-opencv_0.6.3
└── sophon-opencv_0.6.3
├── bin
├── data
├── lib
├── opencv-python
└── test
四、模型转移及量化
有两种方式支持用户构建自己的深度学习Graph模型,并离线编译成bmodel,部署在Sophon TPU中。
两种方式为:
- 通过BMCompiler的算子IR(中间表示)接口来构建。
- 通过BMLang来构建。
1.TPU-NNTC简介
NNToolchain是算能基于其自主研发的AI芯片,所定制的深度学习工具链,涵盖了神经网络推理阶段所需的模型优化、高效运行时支持等能力,为深度学习应用开发和部署提供易用、高效的全栈式解决方案。
NNToolchain整体架构如下图所示,由Compiler和Runtime(图中左右)两部分组成。
- Compiler负责对各种主流深度神经网络模型(如Caffe model、Tensorflow model等)进行编译和优化。
- 离线运行在X86平台,该平台不用包含BM168x产品。
- Runtime向下屏蔽底层硬件实现细节,驱动TPU芯片,向上为应用程序提供统一的可编程接口,既提供神经网络推理功能,又提供对DNN和CV算法的加速。
- PCIE模式:SDK运行于X86平台,BM168x作为PCIE接口的深度学习计算加速卡存在;
- SOC模式:SDK独立运行于BM168x平台,支持通过千兆以太网与其他设备连接;
- CMODEL模式:SDK运行在X86平台,并脱离BM168x运行,用于在没有芯片的模式下进行软件仿真。
1)框架支持及转换过程
深度学习框架 | 版本要求 | 使用的bmnetx模型编译器 |
---|---|---|
Caffe | 官方版本 | bmnetc |
Darknet | 官方版本 | bmnetd |
MXNet | mxnet >= 1.3.0 | bmnetm |
ONNX | onnx == 1.7.0 (Opset version == 12) onnxruntime == 1.3.0 protobuf >= 3.8.0 |
bmneto |
PyTorch | pytorch >= 1.3.0, 建议1.8.0 | bmnetp |
TensorFlow | tensorflow >= 1.10.0 | bmnett |
PaddlePaddle | paddlepaddle >= 2.1.1 | bmpaddle |
转换过程
- TPU-NNTC工具链提供了bmnetc、bmnetd、bmnetm、bmneto、bmnetp、bmnett、bmnetu等工具,分别用来转换Caffe、Darknet、MXNet、ONNX、Pytorch、Tensorflow、UFramework(算能科技自定义的模型中间格式框架)等框架下的模型。
- 经前端工具解析后,模型编译器BMNet Compiler会对各种框架的模型进行离线转换,生成 TPU 能够执行的指令流并序列化保存为BModel文件;
- 当执行在线推理时, 由BMRuntime负责BModel模型的读取、数据的拷贝传输、TPU推理的执行以及计算结果的读取等。
2)BModel简介
bmodel是面向算能TPU处理器的深度神经网络模型文件格式,基于BMLang编程语言的编译所得,包含一个至多个目标网络的权重(weight)、TPU指令流等,在runtime阶段作为模型文件被加载和使用。
-
静态bmodel
保存TPU可直接使用的固定参数原子操作指令,模型输入大小固定。
- 由于静态接口简单稳定,在新的sdk下编译出来的模型通常能在旧机器上运行,不用更新firmware刷机。
- 有些模型虽然指定的是静态编译,但有些算子必需有TPU内部mcu参与或host cpu参与,如排序、nms、where、detect_out这类逻辑运算比较多的算子,该部分会被切分成子网,用动态方式实现。
-
多stage bmodel(适用于输入的shape只有固定离散的几种情况)
使用tpu_model将同一个网络的不同batch size的模型combine为一个BModel;同一个网络的不同batch size的输入对应着不同的stage,推理时BMRuntime会根据输入shape的大小自动选择相应stage的模型。
-
动态bmodel(适用于可变shape过多的情况)
保存每个算子的参数信息,并不能直接在TPU上运行。需要TPU内部的mcu逐层解析参数,进行shape及dtype推理,调用原子操作来实现具体的功能,故运行效率比静态bmodel稍差。
- 动态bmodel为了保证参数可扩展以及兼容,会保证新sdk的runtime能运行旧版本sdk编译出的动态bmodel。通常建议换新sdk后刷机,保证两者版本一致。
2.容器部署tpu-nntc量化环境【X86平台】
https://hub.docker.com/r/sophgo/tpuc_dev
或者官网下载nntc镜像
# pull docker image
sudo docker pull sophgo/tpuc_dev:latest
# 或者官网下载镜像解压
7z x sophgo-tpuc_dev-v2.1-82d75f5c633d.tar.zip
bunzip2 sophgo-tpuc_dev-v2.1-82d75f5c633d.tar.bz2
sudo docker load -i sophgo-tpuc_dev-v2.1-82d75f5c633d.tar
# 解压tpu-nntc
cd Release_230501-public/tpu-nntc_20230605_054100
mkdir tpu-nntc
tar zxvf tpu-nntc_v3.1.9-73e3f629-230605.tar.gz --strip-components=1 -C tpu-nntc
# 创建docker容器并进入Docker
sudo docker run -v $PWD:/workspace -p 8001:8001 -it sophgo/tpuc_dev:v2.1
# 初始化软件环境【容器中】
cd /workspace/tpu-nntc
source scripts/envsetup.sh
$ python --version
Python 3.7.5
$ pip list
bmlang 3.1.9
bmnetc 3.1.9
bmnetd 3.1.9
bmnetm 3.1.9
bmneto 3.1.9
bmnetp 3.1.9
bmnett 3.1.9
bmnetu 3.1.9
bmpaddle 3.1.9
bmprofile 3.1.9
bmtflite 3.1.9
...
onnx 1.12.0
onnxruntime 1.12.0
onnxsim 0.4.17
opencv-contrib-python 4.7.0.68
opencv-python 4.7.0.68
opencv-python-headless 4.7.0.68
...
numpy 1.21.6
protobuf 3.19.6
...
paddle-bfloat 0.1.7
paddle2onnx 1.0.5
paddlepaddle 2.4.2
...
torch 1.13.0+cpu
torchaudio 0.13.0
torchvision 0.14.0+cpu
...
3.PyTorch模型转bmodel
BMNETP是针对pytorch的模型编译器,可以把pytorch的model直接编译成BMRuntime所需的执行指令。pytorch的模型在编译前要经过torch.jit.trace,trace后的模型才能用于编译。在编译模型的同时,可选择将每一个操作的NPU模型计算结果和CPU的计算结果进行对比,保证正确性。
1)注意事项
什么是JIT(torch.jit)
JIT(Just-In-Time)是一组编译工具,用于弥合PyTorch研究与生产之间的差距。它允许创建可以在不依赖Python解释器的情况下运行的模型,并且可以更积极地进行优化。
JIT与BMNETP的关系
BMNETP只接受PyTorch的JIT模型,且须为CPU模型。
-
如何得到JIT模型
-
通过torch.jit.trace将PyTorch的Python模型(基类为torch.nn.Module)转换得到JIT模型。
torch.jit.trace(python_model, torch.rand(input_shape)).save('jit_model')
-
为什么不能使用torch.jit.script得到JIT模型
- BMNETP暂时不支持带有控制流操作(如if语句或循环)、inplace的操作(如copy_函数等)的JIT模型,但torch.jit.script可以产生这类模型,而torch.jit.trace却不可以,仅跟踪和记录张量上的操作,不会记录任何控制流操作。
-
pytorch模型需包含权重信息和完整网络信息,可以通过torch.jit.load(pt_file_path, map_location="cpu")是否报错来测试。
2)模型转换
方法1:命令行方式
python -m bmnetp [-h]
[--mode {compile,GenUmodel,dump,check,verify}]
[--model MODEL] # Necessary. Traced PyTorch model (.pt) path
[--shapes SHAPES] # Necessary. Shapes of all inputs, format [x,x,x,x],[x,x]…
[--net_name NET_NAME] # Optional. Name of the network, default “network”
[--outdir OUTDIR] # Optional. Output directory, default “compilation”
[--target {BM1684,BM1684X,BM1686}]
[--opt {1,2}] # Optional. Optimization level. Option: 0, 1, 2, default 1.
[--cmp {True,False}] # Optional. Check result during compilation. Default: True
[--dyn {True,False}] # Optional. Use dynamic compilation, default False.
[--seed SEED]
[--descs DESCS]
[--desc DESC]
[--enable_profile {True,False}] # Optional. Enable profile log. Default: False
[--log_dir LOG_DIR]
[--v V]
[--no_log_prefix]
[--list_ops] # Optional. List supported ops.
[--input_structure INPUT_STRUCTURE]
[--output_names OUTPUT_NAMES]
## List all supported ops
python -m bmnetp --list_ops
- cmp=True模式,会在指定的文件夹中生成一个input_ref_data.dat和一个output_ref_data.dat, 分别是Pytorch产生的网络输入参考数据和网络输出参考数据,可用于bmrt_test验证生成的bmodel在芯片运行时结果是否正确。
方法2:python接口
import bmnetp
## compile fp32 model
bmnetp.compile(
model = "/path/to/.pth", ## Necessary
outdir = "xxx", ## Necessary
target = "BM1684", ## Necessary
shapes = [[x,x,x,x], [x,x,x]], ## Necessary
net_name = "name", ## Necessary
opt = 2, ## optional, if not set, default equal to 1
dyn = False, ## optional, if not set, default equal to False
cmp = True, ## optional, if not set, default equal to True
enable_profile = True ## optional, if not set, default equal to False
)
4.ONNX模型转bmodel
https://github.com/sophon-ai-algo/examples/tree/3.0.0/nntc/bmneto
BMNETO是针对ONNX的模型编译器,可以把ONNX格式的model经过图编译优化后,转换成BMRuntime所需的文件。在编译模型的同时,可选择将每一个操作的NPU模型计算结果和CPU的计算结果进行对比,保证正确性。
环境要求
- python 3.x
- onnx == 1.7.0 (Opset version == 12)
- onnxruntime == 1.3.0
- protobuf >= 3.8.0
1)方法1:命令行转换
python3 -m bmneto [-h]
[--mode {compile,GenUmodel,check}]
[--model MODEL] # Necessary. ONNX model (.onnx) path
[--input_names INPUT_NAMES]
[--output_names OUTPUT_NAMES]
[--shapes SHAPES] # Necessary. Shapes of all inputs, format [x,x,x,x],[x,x]…
[--net_name NET_NAME] # Optional. Name of the network, default “network”
[--dyn {True,False}] # Optional. Use dynamic compilation, default False.
[--opt OPT] # Optional. Optimization level. Option: 0, 1, 2, default 1.
[--outdir OUTDIR] # Optional. Output directory, default "compilation"
[--input_data INPUT_DATA]
[--target {BM1684,BM1684X}]
[--cmp [CMP]] # Compare compile results with reference DL software tool. (default: None)
[--debug DEBUG]
[--enable_profile {True,False}] # Optional. Enable profile log. Default: False
[--log_dir LOG_DIR]
[--v {0,1,2,3,4}]
[--list_ops]
[--log_prefix]
[--seed SEED] # Random seed (default: 42)
[--descs DESCS]
python3 -m bmneto \
--mode compile \
--model mobilenetv2.onnx \
--outdir compilation \
--target BM1684 \
--shapes [1,3,224,224] \
--net_name mobilenetv2 \
--opt 2 \
--dyn False \
--cmp True \
--enable_profile True
2)方法2:python转换
import bmneto
## compile fp32 model
bmneto.compile(
model = "xxx.onnx", ## Necessary
outdir = "compilation", ## Necessary
target = "BM1684", ## Necessary
shapes = [[1,3,224,224]], ## Necessary
net_name = "mobilenetv2", ## optional
opt = 2, ## optional, if not set, default equal to 1
dyn = False, ## optional, if not set, default equal to False
cmp = True, ## optional, if not set, default equal to True
enable_profile = True ## optional, if not set, default equal to False
)
输出内容
$ ls compilation/
compilation.bmodel compiler_profile_0.dat input_ref_data.dat io_info.dat net_0.profile output_ref_data.dat
5.bmodel验证
1)bmrt_test
基于bmruntime接口实现的对bmodel的正确性和实际运行性能的测试工具。包含以下功能:
- 直接用随机数据bmodel进行推理,验证bmodel的完整性及可运行性
- 通过固定输入数据直接用bmodel进行推理,会对输出与参考数据进行比对,验证证数据正确性
- 测试bmodel的实际运行时间
- 通过bmprofile机制进行bmodel的profile
$ bmrt_test -h
Usage:
--context_dir : 模型编译出的结果文件夹,比对数据也是编译时生成的, 默认开启比对.
--append_dir : The dir of another context after compiltaion.
--bmodel : 与context_dir二选一, 直接指定.bmodel文件,默认不开启比对
--bmodel_list : File that include one or more context dirs.
--devid : The number of device.
--compare : 可选, 0表示不开比对, 1表示开启比对
--accuracy_f : 可选, 指定浮点数据比对误差门限,默认是0.01
--accuracy_i : 可选, 指定整数数据比对误差门限,默认是0
--loopnum : 可选, 指定连续运行次数,默认是1
--thread_num : 可选, 指定运行线程数目,默认是1,测试多线程正确性
--prealloc : Set alloc ion size (hex format) before load bmodel.
--export_neuron : Export neuron mem to file.
--export_output : Export output data to file.
--profile : Open profile function.
--subnet_time : 可选, 是否显示bmodel的subnet时间
--net_idx : 可选, 在包含多个网络的bmodel通过序号选择运行用的net
--stage_idx : 可选, 在包含多个stage的bmodel通过序号选择运行用的stage
--debug_output : Dump output data and reference data for debug.
--shapes : Set shapes of the input shapes
$ bmrt_test --context_dir compilation --compare 1 --loopnum 1
[BMRT][deal_with_options:1412] INFO:Loop num: 1
bmcpu init: skip cpu_user_defined
open usercpu.so, init user_cpu_init
[BMRT][load_bmodel:1018] INFO:Loading bmodel from [compilation/compilation.bmodel]. Thanks for your patience...
[BMRT][load_bmodel:982] INFO:pre net num: 0, load net num: 1
[BMRT][show_net_info:1336] INFO: ########################
[BMRT][show_net_info:1337] INFO: NetName: mobilenetv2, Index=0
[BMRT][show_net_info:1339] INFO: ---- stage 0 ----
[BMRT][show_net_info:1347] INFO: Input 0) 'input' shape=[ 1 3 224 224 ] dtype=FLOAT32 scale=1
[BMRT][show_net_info:1356] INFO: Output 0) 'output' shape=[ 1 4 ] dtype=FLOAT32 scale=1
[BMRT][show_net_info:1359] INFO: ########################
[BMRT][bmrt_test:770] INFO:==> running network #0, name: mobilenetv2, loop: 0
[BMRT][bmrt_test:834] INFO:reading input #0, bytesize=602112
[BMRT][print_array:702] INFO: --> input_data: < 0.37454 0.950714 0.731994 0.598659 0.156019 0.155995 0.0580836 0.866176 0.601115 0.708073 0.0205845 0.96991 0.832443 0.212339 0.181825 0.183405 ... > len=150528
[BMRT][bmrt_test:987] INFO:reading output #0, bytesize=16
[BMRT][print_array:702] INFO: --> output ref_data: < -2.20592 1.20597 -12.6197 -10.1316 >
[BMRT][bmrt_test:1019] INFO:net[mobilenetv2] stage[0], launch total time is 1033 us (npu 942 us, cpu 91 us)
[BMRT][bmrt_test:1022] INFO:+++ The network[mobilenetv2] stage[0] output_data +++
[BMRT][print_array:702] INFO:output data #0 shape: [1 4 ] < -2.20592 1.20597 -12.6197 -10.1316 >
[BMRT][bmrt_test:1038] INFO:==>comparing #0 output ...
[BMRT][bmrt_test:1043] INFO:+++ The network[mobilenetv2] stage[0] cmp success +++
[BMRT][bmrt_test:1063] INFO:load input time(s): 0.001336
[BMRT][bmrt_test:1064] INFO:calculate time(s): 0.001036
[BMRT][bmrt_test:1065] INFO:get output time(s): 0.000046
[BMRT][bmrt_test:1066] INFO:compare time(s): 0.000113
6.FP32 BModel精度对齐
https://github.com/sophgo/sophon-demo/blob/release/docs/FP32BModel_Precise_Alignment.md
- 原始模型转FP32 BModel存在一定的精度误差,如果开启精度对比(cmp=True),且精度对比通过,在前后处理与原算法对齐的前提下,FP32 BModel的最大误差通常在0.001以下,不会对最终的预测结果造成影响。
- Sophon OpenCV使用芯片中的硬件加速单元进行解码,相比原生OpenCV采用了不同的upsample算法。解码和前后处理的方式与原生的OpenCV存在一定差异,可能影响最终的预测结果,但通常不会对鲁棒性好的模型造成明显影响。
- 多输出模型的输出是以字典形式保存的,如果将输出字典values转list,可能出现输出顺序错误的问题,建议使用输出名称获取对应的输出数据。
五、离线模型推理
分析任务与接口库
任务流程 | 是否支持硬件加速 | SAIL高级接口库 | OPENCV接口库 | FFMPEG接口库 | Native接口库 |
---|---|---|---|---|---|
视频/图片解码 | 支持 | sail::Decoder | Y | Y | BMCV(图片) |
输入预处理 | 支持 | sail::Bmcv | Y | N | BMCV |
模型推理 | 支持 | sail::Engine | N | N | BMruntime |
输出后处理 | 部分支持 | sail::Bmcv | N | N | BMCV |
视频/图片编码 | 支持 | sail::Bmcv | Y | Y | BMCV(图片) |
- BMRuntime模块支持C/C++接口编程,用于读取BMCompiler的编译输出(.bmodel),驱动其在Sophon TPU芯片中执行。
- BMCV、BMLib支持C接口编程
- SAIL库(Sophon Artificial Intelligent Library)
1.BMRuntime及BMLib库使用
软件架构
1)BMLib
实现C语言接口,负责设备Handle的管理、内存管理、数据搬运、API的发送和同步、A53使能、设置TPU工作频率等。
- 头文件bmlib_runtime.h,lib库为libbmlib.so。
/*******************handle releated functions *********************************/
// 用于请求一个设备,得到设备句柄handle。
bm_status_t bm_dev_request(bm_handle_t *handle, int devid);
// get device index for the given handle
int bm_get_devid(bm_handle_t handle);
// 用于释放一个设备。
void bm_dev_free(bm_handle_t handle);
/*******************memory help functions ************************************/
// 申请指定大小的device mem,size为device mem的字节大小。
bm_status_t bm_malloc_device_byte(bm_handle_t handle, bm_device_mem_t *pmem,
unsigned int size);
// 释放device mem
void bm_free_device(bm_handle_t handle, bm_device_mem_t mem);
// 将在系统内存上的数据拷贝到device mem。
// 拷贝的大小是device mem的大小,从src开始拷贝
bm_status_t bm_memcpy_s2d(bm_handle_t handle, bm_device_mem_t dst, void *src);
// size指定拷贝的字节大小,从src的offset偏移开始拷贝
bm_status_t bm_memcpy_s2d_partial_offset(bm_handle_t handle, bm_device_mem_t dst,
void *src, unsigned int size,
unsigned int offset);
// size指定拷贝的字节大小,从src开始拷贝
bm_status_t bm_memcpy_s2d_partial(bm_handle_t handle, bm_device_mem_t dst,
void *src, unsigned int size);
// 将device mem中的数据拷贝到系统内存
bm_status_t bm_memcpy_d2s(bm_handle_t handle, void *dst, bm_device_mem_t src);
bm_status_t bm_memcpy_d2s_partial_offset(bm_handle_t handle, void *dst,
bm_device_mem_t src, unsigned int size,
unsigned int offset);
bm_status_t bm_memcpy_d2s_partial(bm_handle_t handle, void *dst,
bm_device_mem_t src, unsigned int size);
// 将数据从一个device mem拷贝到另一个device mem。len是以dword为单位(4bytes)
bm_status_t bm_memcpy_d2d(bm_handle_t handle, bm_device_mem_t dst, int dst_offset,
bm_device_mem_t src, int src_offset, int len);
/*******************memory map functions *************************************/
// 在SoC上,系统内存和Device Memory虽然是隔开的,但其实都是DDR上的内存。
// 通过mmap,得到Device Memory的虚拟地址,从而可以被应用程序直接访问。
// 1.将device mem映射出来,得到虚拟地址。
bm_status_t bm_mem_mmap_device_mem(bm_handle_t handle,
bm_device_mem_t *dmem,
unsigned long long *vmem);
bm_status_t bm_mem_unmap_device_mem(bm_handle_t handle,
void* vmem, int size);
// 2.使cache失效,确保DDR数据同步到了cache【应用程序获取数据前需操作】
bm_status_t bm_mem_invalidate_device_mem(bm_handle_t handle, bm_device_mem_t *dmem);
bm_status_t bm_mem_invalidate_partial_device_mem(bm_handle_t handle, bm_device_mem_t *dmem,
unsigned int offset, unsigned int len);
// 3.刷新cache数据,确保cache数据同步到了DDR【应用程序修改数据后需操作】
bm_status_t bm_mem_flush_device_mem(bm_handle_t handle, bm_device_mem_t *dmem);
bm_status_t bm_mem_flush_partial_device_mem(bm_handle_t handle, bm_device_mem_t *dmem,
unsigned int offset, unsigned int len);
/*******************api(kernel) functions *************************************/
// To synchronize APIs of the current thread.
void bm_flush(bm_handle_t handle);
// To synchronize APIs of the device.
bm_status_t bm_device_sync(bm_handle_t handle);
// To synchronize APIs of the handle.
bm_status_t bm_handle_sync(bm_handle_t handle);
// To synchronize APIs of the current thread.
bm_status_t bm_thread_sync(bm_handle_t handle);
2)BMRuntime
实现了C/C++接口,用于读取BMCompiler的编译输出(.bmodel),驱动其在Sophon TPU芯片中执行。
C Interface
- 头文件为bmruntime_interface.h,lib库为libbmrt.so。
- 线程安全
// 通过bm_handle创建bmruntime,返回runtime指针。
void* bmrt_create(bm_handle_t bm_handle);
// 销毁bmruntime,释放资源。
void bmrt_destroy(void* p_bmrt);
// 从runtime指针中得到设备句柄bm_handle
void * bmrt_get_bm_handle(void* p_bmrt);
// To load the bmodel which is created by BM compiler
bool bmrt_load_bmodel(void* p_bmrt, const char *bmodel_path);
// 加载bmodel,不同于bmrt_load_bmodel,它的bmodel数据存在内存中
bool bmrt_load_bmodel_data(void* p_bmrt, const void * bmodel_data, size_t size);
// 打印bmruntime中存在的网络的名称
void bmrt_show_neuron_network(void* p_bmrt);
// 获得bmruntime中存在的网络的数量
int bmrt_get_network_number(void* p_bmrt);
// 得到runtime中存在的所有网络的名称
void bmrt_get_network_names(void* p_bmrt, const char*** network_names);
// To get network info by net name
typedef struct {
bm_shape_t* input_shapes; /* input_shapes[0] / [1] / ... / [input_num-1] */
bm_shape_t* output_shapes; /* output_shapes[0] / [1] / ... / [output_num-1] */
} bm_stage_info_t;
typedef struct {
const char* name; /* net name */
bool is_dynamic; /* dynamic or static */
int input_num; /* number of inputs */
char const** input_names; /* input_names[0] / [1] / .../ [input_num-1] */
bm_data_type_t* input_dtypes; /* input_dtypes[0] / [1] / .../ [input_num-1] */
float* input_scales; /* input_scales[0] / [1] / .../ [input_num-1] */
int output_num; /* number of outputs */
char const** output_names; /* output_names[0] / [1] / .../ [output_num-1] */
bm_data_type_t* output_dtypes; /* output_dtypes[0] / [1] / .../ [output_num-1] */
float* output_scales; /* output_scales[0] / [1] / .../ [output_num-1] */
int stage_num; // 网络支持的stages数量
bm_stage_info_t* stages; // 表示该网络支持的不同的shape情况
} bm_net_info_t; // 表示一个网络的全部信息
const bm_net_info_t* bmrt_get_network_info(void* p_bmrt, const char* net_name);
// 打印网络的信息
void bmrt_print_network_info(const bm_net_info_t* net_info);
// 对指定的网络,进行npu推理
bool bmrt_launch_tensor(void* p_bmrt, const char * net_name,
const bm_tensor_t input_tensors[], int input_num,
bm_tensor_t output_tensors[], int output_num);
// 会为output_tensors申请device mem,用于存储结果数据。
// 输出数据是以BM_STROE_1N存储;
// 为异步接口,需要调用bm_thread_sync确保推理完成。
bool bmrt_launch_tensor_ex(void* p_bmrt, const char * net_name,
const bm_tensor_t input_tensors[], int input_num,
bm_tensor_t output_tensors[], int output_num,
bool user_mem, bool user_stmode);
// 可以在output_tensors中指定输出的device mem,以及输出的store mode。
// 为异步接口,需要调用bm_thread_sync确保推理完成。
bool bmrt_launch_data(void* p_bmrt, const char* net_name, void* const input_datas[],
const bm_shape_t input_shapes[], int input_num, void * output_datas[],
bm_shape_t output_shapes[], int output_num, bool user_mem);
// 输入和输出都存储在系统内存
// 为同步接口,接口返回的时候推理已经完成。
// 校验runtime的数据,打印runtime的一些信息,方便调试。
void bmrt_trace(void* p_bmrt);
C++ Interface
- 头文件为bmruntime_cpp.h,lib库为libbmrt.so
- 线程安全
namespace bmruntime {
class Context; // 用于网络管理,比如加载模型,可以加载1个到多个模型;获取网络信息,可以得到已经加载了的所有网络的名称,以及通过网络名获得某个具体网络的信息。
class Network; // 用于对某个具体网络进行推理,会自动为该网络申请输入输出的device memory。
class Tensor; // 用于对网络的input tensors和output tensors进行管理。
......
};
explicit Context(int devid = 0);
explicit Context(bm_handle_t bm_handle);
Context ctx;
delete ctx;
// 加载模型
bm_status_t load_bmodel(const void *bmodel_data, size_t size);
bm_status_t load_bmodel(const char *bmodel_file);
// 获得已加载的网络的数量。
int get_network_number() const;
// 获得已加载的网络的名称,保存到names中。
void get_network_names(std::vector<const char *> *names) const;
// 通过网络名,获得某个具体网络的信息。
const bm_net_info_t *get_network_info(const char *net_name) const;
// 得到context的设备句柄
bm_handle_t handle() const;
Network(const Context &ctx, const char *net_name, int stage_id = -1);
// 得到所有input tensors
const std::vector<Tensor *> &Inputs();
// 通过input name得到input tensor
Tensor *Input(const char *tensor_name);
// 网络推理
bm_status_t Forward(bool sync = true) const;
// sync为false时,需要调用bm_thread_sync接口确保它推理结束。
// 得到output tensors
const std::vector<Tensor *> &Outputs();
// 通过output name得到output tensor
Tensor *Output(const char *tensor_name);
// 得到该网络的信息
const bm_net_info_t *info() const;
// 用户不能自己创建Tensor,Tensor在Network类生成时自动创建
// 将tensor的device mem上的数据拷贝到系统内存。
bm_status_t CopyTo(void *data) const;
bm_status_t CopyTo(void *data, size_t size, uint64_t offset = 0) const;
// 将系统内存的数据拷贝到tensor中的device mem。
bm_status_t CopyFrom(const void *data);
bm_status_t CopyFrom(const void *data, size_t size, uint64_t offset = 0);
// 设置tensor的shape。
bm_status_t Reshape(const bm_shape_t &shape);
// 获得tensor的数据的大小,以字节为单位。
size_t ByteSize() const;
// 获得tensor的元素数量。
uint64_t num_elements() const;
// 获得tensor的bm_tensor_t结构
const bm_tensor_t *tensor() const;
// 设置tensor的store mode。
void set_store_mode(bm_store_mode_t mode) const;
// 设置tensor的device mem。
bm_status_t set_device_mem(const bm_device_mem_t &device_mem);
3)bm_wrapper.hpp
需要在include/bmruntime/bm_wrapper.hpp头文件中开启宏定义USE_OPENCV和USE_FFMPEG
// Convert opencv Mat object to BMCV bm_image object
static inline bool bm_image_from_mat (cv::Mat &in, bm_image &out);
static inline bool bm_image_from_mat (std::vector<cv::Mat> &in, std::vector<bm_image> &out);
// Convert ffmpeg a avframe object to a BMCV bm_image object
static inline bool bm_image_from_frame (bm_handle_t &bm_handle,
AVFrame &in,
bm_image &out);
static inline bool bm_image_from_frame (bm_handle_t &bm_handle,
std::vector<AVFrame> &in,
std::vector<bm_image> &out);
// Copy a malloc() buffer to BMCV bm_image object
bm_status_t bm_image_copy_buffer (void *in, int size, bm_image &out)
bm_status_t bm_image_copy_buffer (const std::vector<void *> &in,
const std::vector<int> &size,
std::vector<bm_image> &out);
// create bm images with continuous device memory
bm_status_t bm_image_create_batch (bm_handle_t handle,
int img_h,
int img_w,
bm_image_format_ext img_format,
bm_image_data_format_ext data_type,
bm_image *image,
int batch_num,
int *stride = NULL,
int heap_mask = -1);
bm_status_t bm_image_destroy_batch (bm_image *image, int batch_num);
// Convert BMCV bm_image memory to bin
bm_status_t bm_image_dumpdata (bm_image &in, const char *output_name);
// 需要在include/bmruntime/bm_wrapper.hpp头文件中开启宏定义USE_OPENCV
// TPU推理模型(CPU程序会被阻塞)
static inline bool bm_inference (void *p_bmrt,
bm_image *input,
void *output,
bm_shape_t input_shape,
const char *net_name);
// * This API support single input && multi output, and multi thread safety
static inline bool bm_inference (void *p_bmrt,
bm_image *input,
std::vector<void*> outputs,
bm_shape_t input_shape,
const char *net_name);
// * This API support multiple inputs && multiple outputs, and multi thread safety
static inline bool bm_inference (void *p_bmrt,
std::vector<bm_image*> inputs,
std::vector<void*> outputs,
std::vector<bm_shape_t> input_shapes,
const char *net_name);
2.BMCV库使用
BMCV 提供了一套基于 Sophon AI 芯片优化的机器视觉库,通过利用芯片的 TPU 和 VPP 模块,可以完成色彩空间转换、尺度变换、仿射变换、透射变换、线性变换、画框、JPEG编解码、BASE64编解码、NMS、排序、特征匹配等操作。
- 头文件为bmcv_api.h和bmcv_api_etx.h,lib库为libbmcv.so
// ====== bmcv_api.h ======
typedef struct bm_image_t{
bmcv_color_space color_space;
bmcv_data_format data_format;
bm_image_format image_format;
int image_width;
int image_height;
bm_device_mem_t data[MAX_bm_image_CHANNEL];
int stride[MAX_bm_image_CHANNEL];
}bmcv_image;
bm_status_t bm_img_yuv2bgr(bm_handle_t handle, bmcv_image input, bmcv_image output);
bm_status_t bmcv_img_crop(bm_handle_t handle, bmcv_image input,
int channels,
int top,
int left,
bmcv_image output);
bm_status_t bmcv_img_bgrsplit(bm_handle_t handle, bmcv_image input, bmcv_image output);
bm_status_t bmcv_img_transpose(bm_handle_t handle, bmcv_image input, bmcv_image output);
bm_status_t bmcv_img_scale(bm_handle_t handle, bmcv_image input,
int n,
int do_crop, int top, int left, int height, int width,
unsigned char stretch, unsigned char padding_b, unsigned char padding_g, unsigned char padding_r,
int pixel_weight_bias,
float weight_b, float bias_b,
float weight_g, float bias_g,
float weight_r, float bias_r,
bmcv_image output);
bm_status_t bmcv_gemm(bm_handle_t handle,
bool is_A_trans, bool is_B_trans,
int M, int N, int K,
float alpha,
bm_device_mem_t A, int lda,
bm_device_mem_t B, int ldb,
float beta,
bm_device_mem_t C, int ldc);
bm_status_t bm_image_to_bmcv_image(bm_image *src, bmcv_image *dst);
// ====== bmcv_api_etx.h ======
struct bm_image {
int width;
int height;
bm_image_format_ext image_format;
bm_image_data_format_ext data_type;
bm_image_private * image_private = NULL;
};
// Create and fill bm_image structure
bm_status_t bm_image_create(bm_handle_t handle,
int img_h, int img_w,
bm_image_format_ext image_format,
bm_image_data_format_ext data_type,
bm_image * image,
int * stride = NULL);
bm_status_t bm_image_destroy(bm_image image);
bm_status_t bm_image_copy_host_to_device(bm_image image, void * buffers[]);
bm_status_t bm_image_copy_device_to_host(bm_image image, void * buffers[]);
bm_status_t bm_image_alloc_contiguous_mem(int image_num, bm_image *images, int heap_id = BMCV_HEAP_ANY);
bm_status_t bm_image_free_contiguous_mem(int image_num, bm_image *images);
bm_status_t bmcv_image_copy_to(bm_handle_t handle,
bmcv_copy_to_atrr_t copy_to_attr,
bm_image input,
bm_image output);
bm_status_t bmcv_image_crop(bm_handle_t handle,
int crop_num,
bmcv_rect_t * rects,
bm_image input,
bm_image * output);
bm_status_t bmcv_image_split(bm_handle_t handle,
bm_image input,
bm_image * output);
bm_status_t bmcv_image_jpeg_enc(bm_handle_t handle, int image_num, bm_image * src,
void ** p_jpeg_data,
size_t * out_size,
int quality_factor = 85);
bm_status_t bmcv_image_jpeg_dec(bm_handle_t handle, void ** p_jpeg_data, size_t * in_size,
int image_num,
bm_image * dst);
......
3.sophon-mw使用
4.SAIL库使用
SophonSDK通过SAIL库(Sophon Artificial Intelligent Library)向用户提供Python编程接口。其支持Python/C++编程接口编程。对BMRuntime、 BMCV、 BMLib、BMDecoder库的高级封装,将 SophonSDK中原有的“加载 BModel 并驱动 TPU 推理”、“驱动 TPU+VPP 做图像处理”、“驱动 VPU 做图像和视频解码”等功能抽象成更为简单的 C++ 接口对外提供;并且使用 pybind11 再次封装,提供了简洁易用的 Python 接口。
SAIL 模块中所有的类、枚举、函数都在“sail”命名空间下,核心的类包括:
- Handle:SDK中BMLib的bm_handle_t的包装类,设备句柄,上下文信息,用来和内核驱动交互信息。
- Tensor:SDK中BMLib的包装类,封装了对device memory的管理以及与system memory的同步。
- Engine:SDK中BMRuntime的包装类,可以加载bmodel并驱动TPU进行推理。一个Engine实例可以加载一个任意的bmodel,自动地管理输入张量与输出张量对应的内存。
- Decoder:使用VPU解码视频,JPU解码图像,均为硬件解码。
- Bmcv:SDK中BMCV的包装类,封装了一系列的图像处理函数,可以驱动 TPU 进行图像处理。
1)SAIL的编译及安装
cd sophon-sail_20230605_085400
tar -zxvf sophon-sail_3.5.0.tar.gz
cd sophon-sail
$ tree -L 2
├── 3rdparty
│ ├── catch.hpp
│ ├── inireader.hpp
│ ├── json
│ ├── jsoncpp.cpp
│ ├── prebuild
│ ├── pybind11
│ ├── spdlog
│ └── tabulate.hpp
├── build_unix.cmake
├── cmake
│ ├── BM168x_ARM_PCIE
│ ├── BM168x_LoongArch64
│ ├── BM168x_SOC
│ └── SAILConfig.cmake.in
├── CMakeLists.txt
├── docs
├── git_version
├── include
│ ├── algokit.h
│ ├── base64.h
│ ├── cvwrapper.h
│ ├── decoder_multi.h
│ ├── encoder.h
│ ├── engine.h
│ ├── engine_multi.h
│ ├── graph.h
│ ├── message_queue.hpp
│ ├── tensor.h
│ ├── tools.h
│ └── tpu_kernel_api.h
├── python
│ ├── arm_pcie
│ ├── pcie
│ └── soc
├── python_wheels
│ ├── arm_pcie
│ └── soc
├── README.md
├── sample
│ ├── cpp
│ └── python
└── src
├── algokit.cpp
├── base64.cpp
├── bind.cpp
├── CMakeLists.txt
├── cvwrapper.cpp
├── decoder_multi.cpp
├── encoder.cpp
├── engine.cpp
├── engine_multi.cpp
├── graph.cpp
├── internal.h
├── sail.pyi
├── tensor.cpp
├── tools.cpp
└── tpu_kernel_api.cpp
-
编译可被C++接口调用的动态库及头文件
mkdir build && cd build # PCIE MODE 需预先安装libsophon,sophon-ffmpeg,sophon-opencv cmake -DBUILD_PYSAIL=OFF .. # SOC MODE 需要指定依赖库 cmake -DBUILD_TYPE=soc -DBUILD_PYSAIL=OFF \ -DCMAKE_TOOLCHAIN_FILE=../cmake/BM168x_SOC/ToolChain_aarch64_linux.cmake \ -DLIBSOPHON_BASIC_PATH=../../../soc-sdk \ -DFFMPEG_BASIC_PATH=../../../soc-sdk \ -DOPENCV_BASIC_PATH=../../../soc-sdk .. # ARM PCIE MODE 需要指定依赖库 cmake -DBUILD_TYPE=arm_pcie -DBUILD_PYSAIL=OFF \ -DCMAKE_TOOLCHAIN_FILE=../cmake/BM168x_ARM_PCIE/ToolChain_aarch64_linux.cmake \ -DLIBSOPHON_BASIC_PATH=../../../soc-sdk \ -DFFMPEG_BASIC_PATH=../../../soc-sdk \ -DOPENCV_BASIC_PATH=../../../soc-sdk .. make sail sudo make install # 默认安装到/opt/sophon
-
编译可被Python3接口调用的Wheel文件
mkdir build && cd build # PCIE MODE 需预先安装libsophon,sophon-ffmpeg,sophon-opencv cmake .. make pysail # 打包生成python wheel cd ../python/pcie chmod +x sophon_pcie_whl.sh ./sophon_pcie_whl.sh # .whl文件输出到dist路径 # SOC MODE 需要指定依赖库 cmake -DBUILD_TYPE=soc \ -DCMAKE_TOOLCHAIN_FILE=../cmake/BM168x_SOC/ToolChain_aarch64_linux.cmake \ -DPYTHON_EXECUTABLE=python_3.8.2/bin/python3 \ # 可以指定运行环境的python版本 -DCUSTOM_PY_LIBDIR=python_3.8.2/lib \ -DLIBSOPHON_BASIC_PATH=../../../soc-sdk \ -DFFMPEG_BASIC_PATH=../../../soc-sdk \ -DOPENCV_BASIC_PATH=../../../soc-sdk .. make pysail # 打包生成python wheel cd ../python/soc chmod +x sophon_soc_whl.sh ./sophon_soc_whl.sh # .whl文件输出到dist路径 # ARM PCIE MODE 需要指定依赖库 cmake -DBUILD_TYPE=arm_pcie -DBUILD_PYSAIL=OFF \ -DCMAKE_TOOLCHAIN_FILE=../cmake/BM168x_ARM_PCIE/ToolChain_aarch64_linux.cmake \ -DPYTHON_EXECUTABLE=python_3.8.2/bin/python3 \ # 可以指定运行环境的python版本 -DCUSTOM_PY_LIBDIR=python_3.8.2/lib \ -DLIBSOPHON_BASIC_PATH=../../../soc-sdk \ -DFFMPEG_BASIC_PATH=../../../soc-sdk \ -DOPENCV_BASIC_PATH=../../../soc-sdk .. make pysail # 打包生成python wheel cd ../python/arm_pcie chmod +x sophon_arm_pcie_whl.sh ./sophon_arm_pcie_whl.sh # .whl文件输出到dist路径
2)使用SAIL的Python接口
示例
#define USE_FFMPEG 1
#define USE_OPENCV 1
#define USE_BMCV 1
#include <stdio.h>
#include <sail/cvwrapper.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int device_id = 0;
std::string video_path = "test.avi";
sail::Decoder decoder(video_path,true,device_id);
if(!decoder.is_opened()){
printf("Video[%s] read failed!\n",video_path.c_str());
exit(1) ;
}
sail::Handle handle(device_id);
sail::Bmcv bmcv(handle);
while(true){
sail::BMImage ost_image = decoder.read(handle);
bmcv.imwrite("test.jpg", ost_image);
break;
}
return 0;
}
3)使用SAIL的C++接口
5.sophon-demo
https://github.com/sophgo/sophon-demo
cd sophon-demo_20230605_085900
tar -zxvf sophon-demo_v0.1.6_f4d1abc_20230605.tar.gz
tree sophon-demo_v0.1.6_f4d1abc_20230605
├── cmake
│ └── common.cmake
├── CONTRIBUTING_CN.md
├── CONTRIBUTING_EN.md
├── docs
│ ├── Calibration_Guide_EN.md
│ ├── Calibration_Guide.md
│ ├── Environment_Install_Guide_EN.md
│ ├── Environment_Install_Guide.md
│ ├── FAQ_EN.md
│ ├── FAQ.md
│ ├── FP32BModel_Precise_Alignment_EN.md
│ ├── FP32BModel_Precise_Alignment.md
│ ├── torch.jit.trace_Guide_EN.md
│ └── torch.jit.trace_Guide.md
├── git_version
├── include
│ ├── bmnn_utils.h
│ ├── bm_wrapper.hpp
│ ├── ff_decode.hpp
│ ├── json.hpp
│ └── utils.hpp
├── LICENSE
├── README_EN.md
├── README.md
├── sample
│ ├── ByteTrack
│ ├── C3D
│ ├── CenterNet
│ ├── DeepSORT
│ ├── LPRNet
│ ├── OpenPose
│ ├── PP-OCR
│ ├── ResNet
│ ├── RetinaFace
│ ├── SSD
│ ├── WeNet
│ ├── yolact
│ ├── YOLOv34
│ ├── YOLOv5
│ ├── YOLOv5_opt
│ ├── YOLOv8
│ └── YOLOX
├── scripts
│ ├── auto_test_regression.sh
│ └── release.sh
└── src
└── ff_decode.cpp
1)YOLOv8模型推理
https://github.com/sophgo/sophon-demo/tree/release/sample/YOLOv8
交叉编译sample
# 安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
cd sophon-demo_20230605_085900/sophon-demo_v0.1.6_f4d1abc_20230605/sample/YOLOv8/cpp/yolov8_bmcv
mkdir build && cd build
#请根据实际情况修改-DSDK的路径,需使用绝对路径。
cmake -DTARGET_ARCH=soc -DSDK=/home/share/thirdparty/sophon/Release_230501-public/soc-sdk/ ..
make -j12; cd -
$ file yolov8_bmcv.soc
yolov8_bmcv.soc: ELF 64-bit LSB shared object, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c27677bdeec30625bc78ecb20e742881b3693705, not stripped
模型推理
# 将yolov8_bmcv.soc和soc-sdk拷贝到BM1684设备上。
library="soc-sdk"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${library}/lib/
./yolov8_bmcv.soc --input=datasets/test --bmodel=models/BM1684X/yolov8s_fp32_1b.bmodel --dev_id=0 --conf_thresh=0.5 --nms_thresh=0.7 --classnames=datasets/coco.names
c++ yolov8_bmcv 关键代码
#include <iostream>
#include <vector>
#include "opencv2/opencv.hpp"
#include "bmnn_utils.h"
#include "utils.hpp"
#include "bm_wrapper.hpp"
// Define USE_OPENCV for enabling OPENCV related funtions in bm_wrapper.hpp
#define USE_OPENCV 1
#define DEBUG 0
YoloV8::YoloV8(std::shared_ptr<BMNNContext> context) : m_bmContext(context) {
std::cout << "YoloV8 ctor .." << std::endl;
}
int YoloV8::Init(float confThresh, float nmsThresh, const std::string& coco_names_file) {
m_confThreshold = confThresh;
m_nmsThreshold = nmsThresh;
std::ifstream ifs(coco_names_file);
if (ifs.is_open()) {
std::string line;
while (std::getline(ifs, line)) {
line = line.substr(0, line.length() - 1);
m_class_names.push_back(line); // vector <string>
}
}
// 1. get network
m_bmNetwork = m_bmContext->network(0);
// 2. get input
max_batch = m_bmNetwork->maxBatch();
auto tensor = m_bmNetwork->inputTensor(0);
m_net_h = tensor->get_shape()->dims[2];
m_net_w = tensor->get_shape()->dims[3];
// 3. get output
output_num = m_bmNetwork->outputTensorNum();
assert(output_num > 0);
min_dim = m_bmNetwork->outputTensor(0)->get_shape()->num_dims;
// 4. initialize bmimages
m_resized_imgs.resize(max_batch);
m_converto_imgs.resize(max_batch);
// some API only accept bm_image whose stride is aligned to 64
int aligned_net_w = FFALIGN(m_net_w, 64);
int strides[3] = {aligned_net_w, aligned_net_w, aligned_net_w};
for (int i = 0; i < max_batch; i++) {
auto ret = bm_image_create(m_bmContext->handle(), m_net_h, m_net_w, FORMAT_RGB_PLANAR, DATA_TYPE_EXT_1N_BYTE,
&m_resized_imgs[i], strides);
assert(BM_SUCCESS == ret);
}
bm_image_alloc_contiguous_mem(max_batch, m_resized_imgs.data());
bm_image_data_format_ext img_dtype = DATA_TYPE_EXT_FLOAT32;
if (tensor->get_dtype() == BM_INT8) {
img_dtype = DATA_TYPE_EXT_1N_BYTE_SIGNED;
}
auto ret = bm_image_create_batch(m_bmContext->handle(), m_net_h, m_net_w, FORMAT_RGB_PLANAR, img_dtype,
m_converto_imgs.data(), max_batch);
assert(BM_SUCCESS == ret);
// 5.converto
float input_scale = tensor->get_scale();
input_scale = input_scale * 1.0 / 255.f;
converto_attr.alpha_0 = input_scale;
converto_attr.beta_0 = 0;
converto_attr.alpha_1 = input_scale;
converto_attr.beta_1 = 0;
converto_attr.alpha_2 = input_scale;
converto_attr.beta_2 = 0;
return 0;
}
int YoloV8::pre_process(const std::vector<bm_image>& images) {
std::shared_ptr<BMNNTensor> input_tensor = m_bmNetwork->inputTensor(0);
int image_n = images.size();
// 1. resize image letterbox
int ret = 0;
for (int i = 0; i < image_n; ++i) {
bm_image image1 = images[i];
bm_image image_aligned;
bool need_copy = image1.width & (64 - 1);
if (need_copy) {
int stride1[3], stride2[3];
bm_image_get_stride(image1, stride1);
stride2[0] = FFALIGN(stride1[0], 64);
stride2[1] = FFALIGN(stride1[1], 64);
stride2[2] = FFALIGN(stride1[2], 64);
bm_image_create(m_bmContext->handle(), image1.height, image1.width, image1.image_format, image1.data_type,
&image_aligned, stride2);
bm_image_alloc_dev_mem(image_aligned, BMCV_IMAGE_FOR_IN);
bmcv_copy_to_atrr_t copyToAttr;
memset(©ToAttr, 0, sizeof(copyToAttr));
copyToAttr.start_x = 0;
copyToAttr.start_y = 0;
copyToAttr.if_padding = 1;
bmcv_image_copy_to(m_bmContext->handle(), copyToAttr, image1, image_aligned);
} else {
image_aligned = image1;
}
#if USE_ASPECT_RATIO
bool isAlignWidth = false;
float ratio = get_aspect_scaled_ratio(images[i].width, images[i].height, m_net_w, m_net_h, &isAlignWidth);
bmcv_padding_atrr_t padding_attr;
memset(&padding_attr, 0, sizeof(padding_attr));
padding_attr.dst_crop_sty = 0;
padding_attr.dst_crop_stx = 0;
padding_attr.padding_b = 114;
padding_attr.padding_g = 114;
padding_attr.padding_r = 114;
padding_attr.if_memset = 1;
if (isAlignWidth) {
padding_attr.dst_crop_h = images[i].height * ratio;
padding_attr.dst_crop_w = m_net_w;
int ty1 = (int)((m_net_h - padding_attr.dst_crop_h) / 2); // padding 大小
padding_attr.dst_crop_sty = ty1;
padding_attr.dst_crop_stx = 0;
} else {
padding_attr.dst_crop_h = m_net_h;
padding_attr.dst_crop_w = images[i].width * ratio;
int tx1 = (int)((m_net_w - padding_attr.dst_crop_w) / 2);
padding_attr.dst_crop_sty = 0;
padding_attr.dst_crop_stx = tx1;
}
bmcv_rect_t crop_rect{0, 0, image1.width, image1.height};
auto ret = bmcv_image_vpp_convert_padding(m_bmContext->handle(), 1, image_aligned, &m_resized_imgs[i],
&padding_attr, &crop_rect);
#else
auto ret = bmcv_image_vpp_convert(m_bmContext->handle(), 1, images[i], &m_resized_imgs[i]);
#endif
assert(BM_SUCCESS == ret);
if (need_copy)
bm_image_destroy(image_aligned);
}
// 2. converto img /= 255
ret = bmcv_image_convert_to(m_bmContext->handle(), image_n, converto_attr, m_resized_imgs.data(),
m_converto_imgs.data());
CV_Assert(ret == 0);
// 3. attach to tensor
if (image_n != max_batch)
image_n = m_bmNetwork->get_nearest_batch(image_n);
bm_device_mem_t input_dev_mem;
bm_image_get_contiguous_device_mem(image_n, m_converto_imgs.data(), &input_dev_mem);
input_tensor->set_device_mem(&input_dev_mem);
input_tensor->set_shape_by_dim(0, image_n); // set real batch number
return 0;
}
float YoloV8::get_aspect_scaled_ratio(int src_w, int src_h, int dst_w, int dst_h, bool* pIsAligWidth) {
float ratio;
float r_w = (float)dst_w / src_w;
float r_h = (float)dst_h / src_h;
if (r_h > r_w) {
*pIsAligWidth = true;
ratio = r_w;
} else {
*pIsAligWidth = false;
ratio = r_h;
}
return ratio;
}
int YoloV8::post_process(const std::vector<bm_image>& images, std::vector<YoloV8BoxVec>& detected_boxes) {
YoloV8BoxVec yolobox_vec;
std::vector<cv::Rect> bbox_vec;
std::vector<std::shared_ptr<BMNNTensor>> outputTensors(output_num);
for (int i = 0; i < output_num; i++) {
outputTensors[i] = m_bmNetwork->outputTensor(i);
}
for (int batch_idx = 0; batch_idx < images.size(); ++batch_idx) {
yolobox_vec.clear();
auto& frame = images[batch_idx];
int frame_width = frame.width;
int frame_height = frame.height;
int min_idx = 0;
int box_num = 0;
// Single output
auto out_tensor = outputTensors[min_idx];
m_class_num = out_tensor->get_shape()->dims[1] - mask_num - 4;
int feat_num = out_tensor->get_shape()->dims[2];
int nout = m_class_num + mask_num + 4;
float* output_data = nullptr;
std::vector<float> decoded_data;
LOG_TS(m_ts, "post 1: get output");
assert(box_num == 0 || box_num == out_tensor->get_shape()->dims[1]);
box_num = out_tensor->get_shape()->dims[1];
output_data = (float*)out_tensor->get_cpu_data() + batch_idx * feat_num * (m_class_num + mask_num + 4);
LOG_TS(m_ts, "post 1: get output");
}
return 0;
}
int YoloV8::Detect(const std::vector<bm_image>& input_images, std::vector<YoloV8BoxVec>& boxes) {
int ret = 0;
// 3. preprocess
ret = pre_process(input_images);
// 4. forward
LOG_TS(m_ts, "yolov8 inference");
ret = m_bmNetwork->forward();
// 5. post process
ret = post_process(input_images, boxes);
return ret;
}
int main(int argc, char *argv[]) {
// creat handle
BMNNHandlePtr handle = make_shared<BMNNHandle>(dev_id);
cout << "set device id: " << dev_id << endl;
bm_handle_t h = handle->handle();
// load bmodel
shared_ptr<BMNNContext> bm_ctx = make_shared<BMNNContext>(handle, bmodel_file.c_str());
// initialize net
YoloV8 yolov8(bm_ctx);
}