文章目录
【2024第一期CANN训练营】1、AscendCL应用开发快速入门
本教程将介绍如何使用AscendCL接口(C语言接口)开发一个简单的ResNet-50图片分类应用。并通过昇腾AI处理器和CANN平台来完成这个任务。
前提条件
已在环境上部署昇腾AI软件栈。
安装环境,请参见准备开发和运行环境。
1. 创建代码目录
首先,下载并解压样例包MyFirstApp_ONNX.zip
,样例包中包含以下文件和目录结构:
MyFirstApp_ONNX
├── data
│ ├── dog1_1024_683.jpg // 测试图片
├── model // 用于存放ResNet-50模型文件
├── script
│ ├── transferPic.py // 预处理脚本
├── src
│ ├── CMakeLists.txt // cmake编译脚本
│ ├── main.cpp // 主函数实现文件
├── sample_build.sh // 编译脚本
├── sample_run.sh // 运行脚本
2. 准备模型
使用ATC工具将ONNX模型转换为昇腾AI处理器可识别的.om格式,这里以resnet50.onnx
模型为例:
各参数的解释如下,详细约束说明请参见《ATC工具使用指南》。
- –model:ResNet-50网络的模型文件的路径。
- –framework:原始框架类型。5表示ONNX。
- –output:resnet50.om模型文件的路径。请注意,记录保存该om模型文件的路径,后续开发应用时需要使用。
- –input_shape:模型输入数据的shape。
- –soc_version:昇腾AI处理器的版本,执行npu-smi info命令进行查询,在查询到的
Name
前增加Ascend,如Ascend310
# 下载resnet50.onnx
wget https://cann-info.obs.cn-east-2.myhuaweicloud.com/samples/MyfirstAPP/MyFirstApp_ONNX.zip
unzip MyFirstApp_ONNX.zip
cd MyFirstApp_ONNX/model
wget https://obs-9be7.obs.cn-east-2.myhuaweicloud.com/003_Atc_Models/resnet50/resnet50.onnx
# 使用ATC工具将ONNX模型转换为昇腾AI处理器可识别的.om格式
# 看到ATC run success, welcome to the next use.则转换成功
atc --model=resnet50.onnx --framework=5 --output=resnet50 --input_shape="actual_input_1:1,3,224,224" --soc_version=Ascend310
3. 开发应用
在MyFirstApp_ONNX/src/main.cpp
文件中,添加以下代码:
#include "acl/acl.h"
#include <iostream>
#include <fstream>
#include <cstring>
#include <map>
#include <cmath>
using namespace std;
const char *modelPath = "../model/resnet50.om";
const char *picturePath = "../data/dog1_1024_683.bin";
// 主函数
int main()
{
// 初始化资源并加载模型
InitResource();
LoadModel(modelPath);
// 加载图片
LoadPicture(picturePath);
// 执行推理
Inference();
// 打印结果
PrintResult();
// 卸载模型并释放资源
UnloadModel();
DestroyResource();
return 0;
}
接下来,实现每个函数的具体操作。
3.1 初始化资源和加载模型
- 调用aclInit接口初始化AscendCL
- 调用aclrtSetDevice接口指定计算设备
- 完成了AscendCL的所有调用之后,或者进程退出之前,需执行资源去初始化
// 资源初始化
int32_t deviceId = 0;
void InitResource()
{
aclError ret = aclInit(nullptr);
ret = aclrtSetDevice(deviceId);
}
// 模型加载
uint32_t modelId;
void LoadModel(const char* modelPath)
{
aclError ret = aclmdlLoadFromFile(modelPath, &modelId);
}
3.2 加载图片
size_t pictureDataSize = 0;
void *pictureHostData;
void *pictureDeviceData;
void ReadPictureTotHost(const char *picturePath);
void CopyDataFromHostToDevice();
// 图片加载到Host内存并传输到Device
void LoadPicture(const char* picturePath)
{
ReadPictureTotHost(picturePath); // 读取图片到Host内存
CopyDataFromHostToDevice(); // 传输到Device
}
//申请内存,使用C/C++标准库的函数将测试图片读入内存
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();
}
//申请Device侧的内存,再以内存复制的方式将内存中的图片数据传输到Device
void CopyDataFromHostToDevice()
{
aclError ret = aclrtMalloc(&pictureDeviceData, pictureDataSize, ACL_MEM_MALLOC_HUGE_FIRST);
ret = aclrtMemcpy(pictureDeviceData, pictureDataSize, pictureHostData, pictureDataSize, ACL_MEMCPY_HOST_TO_DEVICE);
}
3.3 执行推理
- 使用
aclmdlDesc
类型的数据描述模型基本信息 - 使用
aclDataBuffer
类型的数据来描述每个输入/输出的内存地址、内存大小。 - 使用
aclmdlDataset
类型的数据描述模型的输入/输出数据。 aclmdlDataset
与aclDataBuffer
的关系类似于列表与元素的集合关系:aclmdlDataset
[aclDataBuffer1
,aclDataBuffer2
,…]
aclmdlDataset *inputDataSet;
aclDataBuffer *inputDataBuffer;
aclmdlDataset *outputDataSet;
aclDataBuffer *outputDataBuffer;
aclmdlDesc *modelDesc;
size_t outputDataSize = 0;
void *outputDeviceData;
void CreateModelInput();
void CreateModelOutput();
// 执行推理
void Inference()
{
// 创建输入输出数据结构
CreateModelInput();
CreateModelOutput();
// 执行模型推理
aclError ret = aclmdlExecute(modelId, inputDataSet, outputDataSet);
}
// 准备模型推理的输入数据结构
void CreateModelInput()
{
// 创建aclmdlDataset类型的数据,描述模型推理的输入
inputDataSet = aclmdlCreateDataset();
inputDataBuffer = aclCreateDataBuffer(pictureDeviceData, pictureDataSize);
aclError ret = aclmdlAddDatasetBuffer(inputDataSet, inputDataBuffer);
}
// 准备模型推理的输出数据结构
void CreateModelOutput()
{
// 创建模型描述信息
modelDesc = aclmdlCreateDesc();
aclError ret = aclmdlGetDesc(modelDesc, modelId);
// 创建aclmdlDataset类型的数据,描述模型推理的输出
outputDataSet = aclmdlCreateDataset();
// 获取模型输出数据需占用的内存大小,单位为Byte
outputDataSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
// 申请输出内存
ret = aclrtMalloc(&outputDeviceData, outputDataSize, ACL_MEM_MALLOC_HUGE_FIRST);
outputDataBuffer = aclCreateDataBuffer(outputDeviceData, outputDataSize);
ret = aclmdlAddDatasetBuffer(outputDataSet, outputDataBuffer);
}
3.4 打印结果
- 在屏幕上显示图片的top5置信度的类别编号。
void *outputHostData;
// 打印结果
void PrintResult()
{
// 获取推理结果
aclError ret = aclrtMallocHost(&outputHostData, outputDataSize);
ret = aclrtMemcpy(outputHostData, outputDataSize, outputDeviceData, outputDataSize, ACL_MEMCPY_DEVICE_TO_HOST);
// 将内存中的数据转换为float类型
float* outFloatData = reinterpret_cast<float *>(outputHostData);
map<float, unsigned int, greater<float>> resultMap;
for (unsigned int j = 0; j < outputDataSize / sizeof(float);++j)
{
resultMap[*outFloatData] = j;
outFloatData++;
}
// 使用softmax进行数据处理并打印前5类
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, exp(it->first) /totalValue);
}
}
3.5 卸载模型并释放资源
// 卸载模型
void UnloadModel()
{
// 释放模型描述信息
aclmdlDestroyDesc(modelDesc);
// 卸载模型
aclmdlUnload(modelId);
}
// 销毁推理数据类型
void UnloadPicture()
{
aclError 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;
}
// 资源释放
void DestroyResource()
{
aclError ret = aclrtResetDevice(deviceId);
aclFinalize();
}
4. 编译及运行应用
编译代码:
<SAMPLE_DIR>
表示样例所在的目录$HOME/Ascend/ascend-toolkit
表示CANN软件包的安装路径,<arch-os>
表示操作系统架构(Arch64或者X86_64)- 如果执行脚本报错“ModuleNotFoundError: No module named ‘PIL’”,是由于开发环境中缺少Pillow库,需使用pip3 install Pillow --user命令安装Pillow库后,再执行脚本。
- 如果执行脚本报错“bash: ./sample_build.sh: /bin/bash^M: bad interpreter: No such file or directory”,是由于开发环境中没有安装dos2unix包,需使用sudo apt-get install dos2unix命令安装dos2unix包后,执行dos2unix sample_build.sh,然后再执行脚本。
修改sample_build.sh
内的内容如下
model_name="MyFirstApp_build"
cd ${APP_SOURCE_PATH}/data
python3 ${APP_SOURCE_PATH}/script/transferPic.py
if [ -d ${APP_SOURCE_PATH}/build/intermediates/host ];then
rm -rf ${APP_SOURCE_PATH}/build/intermediates/host
fi
mkdir -p ${APP_SOURCE_PATH}/build/intermediates/host
cd ${APP_SOURCE_PATH}/build/intermediates/host
cmake ${APP_SOURCE_PATH}/src -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUE
make
if [ $? -eq 0 ];then
echo "make for app ${model_name} Successfully"
exit 0
else
echo "make for app ${model_name} failed"
exit 1
fi
export APP_SOURCE_PATH=<SAMPLE_DIR>/MyFirstApp_ONNX
export DDK_PATH=$HOME/Ascend/ascend-toolkit/latest
export NPU_HOST_LIB=$HOME/Ascend/ascend-toolkit/latest/<arch-os>/devlib
# 在MyFirstApp_ONNX文件夹下执行
pip3 install Pillow
chmod +x sample_build.sh
./sample_build.sh
运行应用:
# 运行应用后,终端将显示测试图片的Top 5类别编号及其置信度
chmod +x sample_run.sh
./sample_run.sh