马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
由于官方SDK比力冗余且经常跨文档讲解且SDK整理的乱七八糟,对于新手来说全部看完上手成本较高,本文旨在以简短的方式先容 CAFFE / ONNX 模型转 om 模型,并进行推理的全流程。盼望能够资助到第一次接触华为海思框架的道友们。大佬们就没必要看这种底子文章啦!
注:本文全部操纵均在 WSL (Windows 虚拟子系统)上操纵的,默认 root 环境,默认开发板系统为LINUX,全部环境变量均写入 bashrc ,非虚拟机用户谨慎写入。全部用到的工具以及示例代码,均在文末。
1. 简介
ATC 是海思芯片的模型转换工具,用于将开源框架网络模型(caffe,onnx,tensorflow)转化为 .om 格式并使用 AscendCL进行推理(由于笔者比力熟悉瑞芯微系列,分别雷同于雷同瑞芯微系列的RKNNToolkit工具, .rknn 模型,RKNNToolkitLite板端推理工具)。 起首在进行下面这篇文章之前,先来相识一下 SS928V100(Hi3403V100) 这款芯片,该芯片有两颗 NPU,分别是 NNN 和SVP_NNN,其中 NNN 支持4.8Tops算力,支持 INT4/INT8/FP16;SVP_NNN 支持5.6Tops算力,支持 INT8/INT16。 简单阐明:
- NNN和SVP_NNN 进行模型转换时,用到的 ATC 工具链以及环境变量不一样
- NNN和SVP_NNN 进行推理时,推理接口以及依赖的环境变量也不一样
- NNN和SVP_NNN是能够并利用用的,但是测试过程中发现,如果并利用用,两颗NPU的平均算力利用率会下降10%-15%
因此本文的安装内容也会分成两部分,分别是 NNN 和 SVP_NNN 1.1 通用交织编译工具链安装
- chmod +x Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run
- # 校验安装包是否完整, 校验成功会显示 Verifying archive integrity... 100%
- ./Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run --check
-
- # 安装,安装过程中有协议确认,按 Y 即可,安装成功会显示 <...> install success
- # 安装路径:root用户:“/usr/local/Ascend”,非root用户:“${HOME}/Ascend”
- ./Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run --install
- # 配置环境变量,以 root 用户为例(WSL默认sudo权限)
- source /usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/x86_64-linux/bin/setenv.bash
- vi ~/.bahsrc
- export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/x86_64-linux/devlib/:$LD_LIBRARY_PATH
- source ~/.bashrc
复制代码 1.2 安装openssh(有的话可跳过,没有也可以通过 NFS 共享目录办理文件传输问题)
说实话,挺烦这种精简 linux 系统的,要啥没啥,不知道精减的意义是啥,每次都要重复这种安装编译环境的过程。
参考,注意本文使用的交织编译工具链的名字和文中的不一样:【MindStudio】【OpenSSH】海思SOC环境准备(linux + android)-云社区-华为云
真是令人抓狂,海思SS928这款开发板,连adb都莫得,交织编译好的文件甚至都没办法传到开发板上,太恶心啦!!!
NFS 共享目录方法如下:
- # 1. 以 root 用户登录 Linux 服务器(Ubuntu 操作系统),安装 NFS 服务并配置共享目录。
- apt-get install nfs-kernel-server
-
- # 2. 在“/etc/exports”文件中配置共享目录
- vi /etc/exports
- # 在末尾添加 <服务器共享目录绝对路径> *(rw,sync,root_squash), 如
- /userdata/share *(rw,sync,root_squash)
-
- # 3. 重启 nfs 服务
- /etc/init.d/nfs-kernel-server restart
-
- # 4. 开发板板端挂载,mount -t nfs -o nolock,tcp NFS <服务器IP地址:服务器绝对路径path>,如
-
- mount -t nfs -o nolock,tcp NFS 192.168.1.2:/userdata/share
复制代码 2. NNN 引擎下的模型转换与推理
2.1 安装CANN ToolKit(以python3.9为例)
坑:各 python 必须严格按照下述版本,否则实行模型转换时会报错 :Python3.7.x(3.7.5~3.7.11)、Python3.8.x(3.8.0~3.8.11)、Python3.9.x(3.9.0~3.9.2)保举使用Python3.9.2
- # 安装python3.9
- sudo apt update
- sudo apt install -y build-essential libssl-dev zlib1g-dev \
- libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
- libncurses5-dev libncursesw5-dev xz-utils tk-dev \
- libffi-dev libxslt1-dev liblzma-dev python3-openssl cmake Pillow git
- wget https://www.python.org/ftp/python/3.9.2/Python-3.9.2.tgz
- tar -zxvf Python-3.9.2.tgz
- cd Python-3.9.2
- ./configure --prefix=/usr/local/python3.9.2 --with-ssl-default-suites=openssl --enable-shared CFLAGS=-fPIC
- make -j8
- make install
- vi ~/.bashrc
- export LD_LIBRARY_PATH=/usr/local/python3.9.2/lib:$LD_LIBRARY_PATH
- export PATH=/usr/local/python3.9.2/bin:$PATH
- source ~/.bashrc
- # 安装 cann 所需 python 环境
- # 注意,WSL默认sudo权限,此处如果用sudo,后续执行 ./Ascend-cann-toolkit_8.1.RC1_linux-x86_64.run --install 也要加sudo
- pip3 install attrs cython numpy==1.24.0 decorator sympy cffi pyyaml pathlib2 psutil protobuf==3.20 scipy requests absl-py -i https://pypi.tuna.tsinghua.edu.cn/simple
复制代码
- 在昇腾官网下载 cann 安装包, 并传到WSL中:社区版资源下载-资源下载中心-昇腾社区
注:我用的是SDK中提供的 Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run
- 实行如下命令安装
- chmod +x Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run
-
- # 校验安装包是否完整, 校验成功会显示 Verifying archive integrity... 100%
- ./Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run --check
-
- # 安装,安装过程中有协议确认,按 Y 即可,安装成功会显示 <...> install success
- # 安装路径:root用户:“/usr/local/Ascend”,非root用户:“${HOME}/Ascend”
- ./Ascend-cann-toolkit_5.20.t6.2.b060_linux-x86_64.run --install
-
- # 配置环境变量,以 root 用户为例(WSL默认sudo权限)
- source /usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/x86_64-linux/bin/setenv.bash
- vi ~/.bahsrc
- export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/x86_64-linux/devlib/:$LD_LIBRARY_PATH
- source ~/.bashrc
复制代码 安装完成后,模型转换工具 atc 在 " ${HOME}Ascend/ascend-toolkit/latest/bin " 目录下。
2.2 caffe/onnx 转 om(以resnet50为例)
- # 模型转换(类似RKNNToolKit),注意每次重启后都需要再次 source /usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/x86_64-linux/bin/setenv.bash
- # caffe 转 om
- atc --model=./caffe_model/resnet50.prototxt --weight=./caffe_model/resnet50.caffemodel --framework=0 --output=./model/caffe_resnet50 --soc_version=OPTG --input_fp16_nodes="data" --enable_single_stream=true
- # onnx 转 om
- atc --model=./caffe_model/resnet50_official.onnx --framework=5 --output=./model/resnet50_bs64 --input_format=NCHW --soc_version=OPTG --input_fp16_nodes="data" --enable_single_stream=true
- # sample 编译
- vi ~/.bashrc
- export DDK_PATH=/usr/local/Ascend/ascend-toolkit/latest/arm64-lmixlinux200
- export NPU_HOST_LIB=/usr/local/Ascend/ascend-toolkit/latest/arm64-lmixlinux200/aarch64-linux/devlib
- source ~/.bashrc
- mkdir -p build/intermediates/soc
- cd build/intermediates/soc
- cmake ../../../src -DCMAKE_CXX_COMPILER=aarch64-mix210-linux-g++ -DCMAKE_SKIP_RPATH=TRUE
- make
复制代码
| 参数 | 阐明 | --weight
| 仅caffe模型必要,指定 .caffemodel 路径
| --framewowrk
| 0:Caffe,1:MindSpore,2:TensorFlow ,5:ONNX (opset v11) | | --model | 模型文件
| --input_format
| 输入数据格式 (Caffe、ONNX 默认为 NCHW;TensorFlow 默认为 NHWC)
| --enable_single_stream
| 是否使能一个模型推理时只能使用一条 Stream (由于 SoC 的 Stream 资源有限,在 SoC 场景下必要用户打开此开关,SOC 广义上讲其实就是我们常用的开发板啦)
| | --input_fp16_nodes | 模型输入范例: FP32、FP16通过指定该参数设置,UINT8 必要结合 AIPP 预处置处罚文件设置
| 2.3 om 模型推理流程(雷同RKNNToolKitLite)
Device 表示板端环境上的 NNN(Neural-Network Processing Unit),提供 NN 计算本领。
Context 作为一个容器,管理了全部对象(包括 Stream、Event、设备内存等)的生命周期。
Stream 用于维护一些异步操纵的实行次序,确保按照应用程序中的代码调用次序在 Device 上实行。
坑1:
如果开发板没有相干的 NPU 驱动 "dev/xiaoyaozi0", 切换到 " /smp/a55_linux/mpp/out/ko " 目录下实行 " ./load_ss928v100 -a "。等等,先不要惊讶,这个 NNN 的 NPU 驱动就叫 xiaoyaozi0,哈哈哈哈,看得出来,编这个驱动的程序猿很清闲......
坑2:
板端实行需设置环境变量 , {PATH} 替换成实际目录地点:
export LD_LIBRARY_PATH={PATH}/smp/a55_linux/mpp/out/lib/npu LD_LIBRARY_PATH
- #include "acl/acl.h"
- #include <iostream>
- #include <fstream>
- #include <cstring>
- #include <map>
- #include <math.h>
- using namespace std;
- int32_t deviceId = 0;
- uint32_t modelId;
- size_t pictureDataSize = 0;
- void *pictureHostData;
- void *pictureDeviceData;
- aclmdlDataset *inputDataSet;
- aclDataBuffer *inputDataBuffer;
- aclmdlDataset *outputDataSet;
- aclDataBuffer *outputDataBuffer;
- aclmdlDesc *modelDesc;
- size_t outputDataSize = 0;
- void *outputDeviceData;
- void *outputHostData;
- void ReadPictureTotHost(const char *picturePath)
- {
- string fileName = picturePath;
- ifstream binFile(fileName, ifstream::binary);
- binFile.seekg(0, binFile.end);
- pictureDataSize = binFile.tellg();
- binFile.seekg(0, binFile.beg);
- aclError ret = aclrtMallocHost(&pictureHostData, pictureDataSize);
- binFile.read((char*)pictureHostData, pictureDataSize);
- binFile.close();
- }
- int main()
- {
- printf("==== 这是测试代码123 ==== \n");
- // ===== step1 : 初始化 acl =====
- aclError ret = aclInit(nullptr);
- if (ret != ACL_SUCCESS) cerr << "aclInit failed with ret: " << ret << "\n" << endl;
- ret = aclrtSetDevice(deviceId); // 默认创建了 context 和 stream
- if (ret != ACL_SUCCESS) cerr << "aclrtSetDevice failed with ret: " << ret << "\n" << endl;
-
- // ===== step2 : 加载模型 =====
- const char *modelPath = "../model/resnet50.om";
- ret = aclmdlLoadFromFile(modelPath, &modelId);
- if (ret != ACL_SUCCESS) cerr << "aclmdlLoadFromFile failed with ret: " << ret << "\n" << endl;
- // ===== setp3 : 加载 bin 类型的图片 =====
- const char *picturePath = "../data/dog1_1024_683.bin";
- ReadPictureTotHost(picturePath);
- ret = aclrtMalloc(&pictureDeviceData, pictureDataSize, ACL_MEM_MALLOC_HUGE_FIRST);
- if (ret != ACL_SUCCESS) cerr << "aclrtMalloc failed with ret: " << ret << "\n" << endl;
- ret = aclrtMemcpy(pictureDeviceData, pictureDataSize, pictureHostData, pictureDataSize, ACL_MEMCPY_HOST_TO_DEVICE);
- if (ret != ACL_SUCCESS) cerr << "aclrtInputMemcpy failed with ret: " << ret << "\n" << endl;
- // ===== step4 :执行推理 =====
- // === step4.1 : 创建aclmdlDataset类型的数据,描述模型推理的输入 ===
- inputDataSet = aclmdlCreateDataset();
- inputDataBuffer = aclCreateDataBuffer(pictureDeviceData, pictureDataSize);
- ret = aclmdlAddDatasetBuffer(inputDataSet, inputDataBuffer);
- if (ret != ACL_SUCCESS) cerr << "aclmdlAddDatasetBuffer failed with ret: " << ret << "\n" << endl;
- // === step4.2 : 创建aclmdlDataset类型的数据,描述模型推理的输出 ===
- // 创建模型描述信息
- modelDesc = aclmdlCreateDesc();
- ret = aclmdlGetDesc(modelDesc, modelId);
- if (ret != ACL_SUCCESS) cerr << "aclmdlGetDesc failed with ret: " << ret << "\n" << endl;
- outputDataSet = aclmdlCreateDataset();
- outputDataSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
- // 申请输出内存
- ret = aclrtMalloc(&outputDeviceData, outputDataSize, ACL_MEM_MALLOC_HUGE_FIRST);
- if (ret != ACL_SUCCESS) cerr << "aclrtMalloc failed with ret: " << ret << "\n" << endl;
- outputDataBuffer = aclCreateDataBuffer(outputDeviceData, outputDataSize);
- ret = aclmdlAddDatasetBuffer(outputDataSet, outputDataBuffer);
- if (ret != ACL_SUCCESS) cerr << "aclmdlAddDatasetBuffer failed with ret: " << ret << "\n" << endl;
- // === step 4.3 : 推理 ===
- ret = aclmdlExecute(modelId, inputDataSet, outputDataSet);
- if (ret != ACL_SUCCESS) cerr << "aclmdlExecute failed with ret: " << ret << "\n" << endl;
- // ===== step5:获取推理结果数据 =====
- // === step5.1 : copy 推理结果到 outputHostData
- ret = aclrtMallocHost(&outputHostData, outputDataSize);
- if (ret != ACL_SUCCESS) cerr << "aclrtMallocHost failed with ret: " << ret << "\n" << endl;
- ret = aclrtMemcpy(outputHostData, outputDataSize, outputDeviceData, outputDataSize, ACL_MEMCPY_DEVICE_TO_HOST);
- if (ret != ACL_SUCCESS) cerr << "aclrtOutputMemcpy failed with ret: " << ret << "\n" << endl;
-
- // === step5.2 :后处理部分
- float* outFloatData = reinterpret_cast<float *>(outputHostData);
- // 屏显测试图片的top5置信度的类别编号
- map<float, unsigned int, greater<float>> resultMap;
- for (unsigned int j = 0; j < outputDataSize / sizeof(float); ++j)
- {
- resultMap[*outFloatData] = j;
- outFloatData++;
- }
- double totalValue=0.0;
- for (auto it = resultMap.begin(); it != resultMap.end(); ++it)
- {
- totalValue += exp(it->first);
- }
- int cnt = 0;
- for (auto it = resultMap.begin();it != resultMap.end();++it)
- {
- if(++cnt > 5) break;
- printf("top %d: index[%d] value[%lf] \n", cnt, it->second, it->first);
- }
- // ===== step6 : 释放资源 =====
- // === 6.1 释放模型描述信息并卸载模型 ===
- aclmdlDestroyDesc(modelDesc);
- aclmdlUnload(modelId);
- // === 6.2 释放加载的测试图片 ===
- ret = aclrtFreeHost(pictureHostData);
- pictureHostData = nullptr;
- ret = aclrtFree(pictureDeviceData);
- pictureDeviceData = nullptr;
- aclDestroyDataBuffer(inputDataBuffer);
- inputDataBuffer = nullptr;
- aclmdlDestroyDataset(inputDataSet);
- inputDataSet = nullptr;
- ret = aclrtFreeHost(outputHostData);
- outputHostData = nullptr;
- ret = aclrtFree(outputDeviceData);
- outputDeviceData = nullptr;
- aclDestroyDataBuffer(outputDataBuffer);
- outputDataBuffer = nullptr;
- aclmdlDestroyDataset(outputDataSet);
- outputDataSet = nullptr;
- // === 6.3 释放计算资源 ===
- ret = aclrtResetDevice(deviceId);
- aclFinalize();
- }
复制代码 3. SVP_NNN 引擎下的模型转换与推理
3.1 安装 CANN ToolKit(以python3.9为例)
注意,此处用的SDK是"Ascend-cann-toolkit_6.10.t01spc030b270_linux.x86_64.run",写到这里,不得不吐槽一下,我不知道是我拿到手的SDK有问题,还是整理这个SDK的人有问题,喵的,整个SDK没有一篇文档有说过 SVP_NNN 和 NNN 用的ATC工具不是同一个。也没有一篇文档讲了 SVP_NNN 的 ATC 工具进行转换时参数应该怎么写......
不外幸亏,安装过程和 NNN 的 ATC 工具差别不大,只必要注意环境变量即可
- chmod +x Ascend-cann-toolkit_6.10.t01spc030b270_linux.x86_64.run
- ./Ascend-cann-toolkit_6.10.t01spc030b270_linux.x86_64.run --install
- # install 成功后,终端会打印如下 export 环境变量信息
- vi ~/.bashrc
- export DDK_PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/x86_64-linux
- export NPU_HOST_LIB=/usr/local/Ascend/ascend-toolkit/svp_latest/x86_64-linux/acllib/lib64/stub
- export PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/toolkit/bin:$PATH
- export PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/atc/bin:$PATH
- export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/toolkit/tools/sim/lib:$LD_LIBRARY_PATH
- export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/atc/lib64:$LD_LIBRARY_PATH
- export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/svp_latest/atc/third_party_lib:$LD_LIBRARY_PATH
- source ~/.bashrc
复制代码 3.2 caffe 转 om
我个人觉得 SVP_NNN 的 ATC 比力好用,可能由于 SVP_NNN 只支持 INT8/INT16 ,所以就量化而言,要比 NNN 下使用 AMCT 工具来的轻松友好的多,转换示例:
- atc \
- --weight="./caffe_model/resnet50.caffemodel" \
- --model="./caffe_model/resnet50.prototxt" \
- --framework=0 \
- --log_level=0 \
- --dump_data=0 \
- --online_model_type=0 \
- --batch_num=256 \
- --input_shape="data:1,3,224,224" \
- --input_type="data:UINT8" \
- --input_format=NCHW \
- --output="./model/resnet50" \
- --soc_version=SS928V100 \
- --insert_op_conf=./insert_op.cfg \
- --compile_mode=0 \
- --save_original_model=false \
- --image_list="data:./data/image_ref_list.txt"
复制代码 具体参数信息,安装完 ATC 环境后,可通过 atc --hlep 查看,以下仅对常用底子参数进行阐明:
| 参数 | 阐明 | | --batch_num
| 批处置处罚层的批次数,[0, 256],默认值为 256 | --compile_mode
| 量化数据范例,0:低精度(8位量化),1:高精度(16位量化)
| --framework
| 输入模型的框架,0:caffe,5:onnx,默认 0
| --image_list
| 量化校准使用的图像列表文件
| --input_shape
| 输入数据的形状
| --input_format
| 输入数据的格式,{NCHW},默认:NCHW
| --input_type
| 支持的输入数据范例:{FP16, FP32, INT16, INT8, S16, S8, U16, U8, UINT16, UINT8},默认 FP32
| --insert_op_conf
| AIPP 配置文件路径
| --model
| 输入模型文件路径
| --weight
| 权重文件路径
| --output
| 输出 om 路径及文件名
| --output_type
| 支持的输出数据范例:{FP16, FP32, INT16, INT8, S16, S8, U16, U8, UINT16, UINT8},默认 FP32
| --soc_version
| SoC 版本,{SS626V100, SS928V100},默认 SS626V100(注意,次数和 NNN 下的 ATC 有一定区别,不支持 OPTG)
| --save_original_model
| 是否生存原始模型,默认 false
| 3.3 om 推理流程
坑1:
如果开发板没有相干的 NPU 驱动 "dev/svp_npu",切换到 " /smp/a55_linux/mpp/out/ko " 目录下,修改 " ./load_ss928v100" 这个可实行程序,搜索加载 " pqp.ko "的代码,然后将其解释掉,然后关闭解释或添加如下内容:"insmod svp_npu/ot_svp_npu.ko svp_npu_save_power=1 svp_npu_max_task_node_num=512", 由于 "ot_svp_npu.ko" 和 "pqp.ko" 不能同时加载
坑2:
板端实行需设置环境变量,{PATH} 替换成实际目录地点 :
export LD_LIBRARY_PATH={PATH}/smp/a55_linux/mpp/out/lib/svp_npu LD_LIBRARY_PATH
SVP_NNN 下的推理流程和 NNN 雷同,区别在于引用的头文件名称以及接口都添加了 svp 的前缀,且接口风格由驼峰变成了下划线分割,下列代码仅贴出和 NNN 不同的代码段以供理解,完备代码见文末:
- #include "acl/svp_acl.h"
- # 1. 初始化 svp_acl
- svp_acl_error ret = svp_acl_init(aclConfigPath);
- svp_acl_error ret = svp_acl_rt_set_device(deviceId_);
- # 2. 加载模型
- svp_acl_error ret = svp_acl_mdl_load_from_mem(static_cast<uint8_t* >(modelMemPtr_), modelMemSize_, &modelId_);
- # 3. 创建模型描述信息
- modelDesc_ = svp_acl_mdl_create_desc();
- svp_acl_error ret = svp_acl_mdl_get_desc(modelDesc_, modelId_);
- # 4. 创建输入
- Result ret = GetInputStrideParam(0, devSize, stride, inputDims);
- size_t dataSize = GetInputDataSize(0);
- void *picDevBuffer = Utils::GetDeviceBufferOfFile(filePath, inputDims, stride, dataSize);
- Result ret = InitInput();
- Result ret = CreateInput(picDevBuffer, devSize, stride);
- # 4. 创建输出
- output_ = svp_acl_mdl_create_dataset();
- size_t outputSize = svp_acl_mdl_get_num_outputs(modelDesc_);
- # 5. 执行推理
- svp_acl_error ret = svp_acl_mdl_execute(modelId_, input_, output_);
复制代码 4. 其他
4.1 om 模型信息查看
通过 netron 可以直接查看NNN ATC 工具转换的模型信息(SVP_NNN ATC 工具转换的 om 模型无法直接使用 netron 查看),或者实行如下命令,将 .om 模型转化为 json 文件查看模型信息:
- atc --mode=1 --om={oem_model_path.oem} --json={oem_json_path.json}
复制代码 4.2 查看 NPU 利用率
看团体的NPU资源占用,分两个核看
- # SVP_NNN, 执行命令后查看 hw_utilization
- watch -n 1 cat /proc/umap/svp_npu
- # NNN, 执行命令后查看 utilizaiton rate
- watch -n 1 cat /proc/umap/tsfw的utilizaiton rate
复制代码 4.3 源码传送门
SS928V100_NNN&SVP_NNN_TOOLS 提取码: 2jdc
|