快速入门
在本节中,您可以通过一个简单的图片分类应用了解使用AscendCL接口(C语言接口)开发应用的基本过程以及开发过程中涉及的关键概念。
什么是图片分类应用?
“图片分类应用”,从名称上,我们也能直观地看出它的作用:标识图片所属的分类。
但“图片分类应用”是怎么做到这一点的呢?当然得先有一个能做到图片分类的模型,我们可以直接使用一些训练好的开源模型,也可以基于开源模型的源码进行修改、重新训练,还可以自己基于算法、框架构建适合自己的模型。
鉴于当前我们是入门内容,此处我们直接获取已训练好的开源模型,毕竟这种最简单、最快。此处我们选择的是Caffe框架的ResNet-50模型。
ResNet-50模型的基本介绍如下:
- 输入数据:RGB格式、224*224分辨率的输入图片
- 输出数据:图片的类别标签及其对应置信度
- 置信度是指图片所属某个类别可能性。
- 类别标签和类别的对应关系与训练模型时使用的数据集有关,需要查阅对应数据集的标签及类别的对应关系。
了解基本概念
- Host
Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。
- Device
- 开发环境、运行环境
开发环境指编译开发代码的环境,运行环境指运行算子、推理或训练等程序的环境,运行环境上必须带昇腾AI处理器。
开发环境和运行环境可以合设在同一台服务器上,也可以分设:
- 合设场景下,登录到合设的服务器上,执行以下步骤,不用切换开发环境、运行环境。
- 分设场景下,若开发环境和运行环境上的操作系统架构不同,在开发环境中需使用交叉编译,这样编译出来的可执行文件,才可以在运行环境中执行。
您可以登录对应的环境,执行“uname -a”命令查询其操作系统的架构。
- 运行用户:
了解开发过程
AscendCL(Ascend Computing Language)是一套用于在CANN(Compute Architecture for Neural Networks)上开发深度神经网络推理应用的C语言API库,提供模型加载与执行、媒体数据处理、算子加载与执行等API,能够实现在昇腾CANN平台上进行深度学习推理计算、图形图像预处理、单算子加速计算等能力。
了解了这些大步骤后,下面我们再展开来说明开发应用具体涉及哪些关键功能?各功能又使用哪些AscendCL接口,这些AscendCL接口怎么串联?
虽然此时您可能不理解所有细节,但这也不影响,通过快速入门旨先了解整体的代码逻辑,后续再深入学习,了解其它细节。
创建代码目录
您可以单击Link,获取基础的样例包并解压,其中包含测试图片、ResNet-50原始模型、测试图片预处理脚本、编译脚本等,如下所示。
MyFirstApp ├── data │ ├── dog1_1024_683.jpg // 测试图片 ├── model │ ├── resnet50.caffemodel // ResNet-50网络的权重文件(*.caffemodel) │ ├── resnet50.prototxt // ResNet-50网络的模型文件(*.prototxt) ├── script │ ├── transferPic.py // 将测试图片预处理为符合模型要求的图片 // 包括将*.jpg转换为*.bin,同时将图片从1024*683的分辨率缩放为224*224 ├── src │ ├── CMakeLists.txt // cmake编译脚本 │ ├── main.cpp // 主函数,图片分类功能的实现文件 ├── sample_build.sh // 编译代码的脚本 ├── sample_run.sh // 运行应用的脚本
准备模型
- 以运行用户将MyFirstApp目录上传至开发环境。
- 以运行用户登录开发环境。
- 执行模型转换。
执行以下命令(以昇腾310 AI处理器为例),将原始模型转换为昇腾AI处理器能识别的*.om模型文件。请注意,执行命令的用户需具有命令中相关路径的可读、可写权限。
atc --model=model/resnet50.prototxt --weight=model/resnet50.caffemodel --framework=0 --output=model/resnet50 --soc_version=Ascend310
- --model:ResNet-50网络的模型文件(*.prototxt)的路径。
- --weight:ResNet-50网络的权重文件(*.caffemodel)的路径。
- --framework:原始框架类型。0表示Caffe。
- --output:resnet50.om模型文件的路径。请注意,记录保存该om模型文件的路径,后续开发应用时需要使用。
- --soc_version:昇腾AI处理器的版本。进入“CANN软件安装目录/compiler/data/platform_config”目录,".ini"文件的文件名即为昇腾AI处理器的版本,请根据实际情况选择。
开发应用
在开发环境中,打开“MyFirstApp/src/main.cpp“文件,在该cpp中添加如下步骤中灰色底纹的代码。
- 定义一个main函数,按照了解开发过程中的开发应用的过程详解,串联整个应用的代码逻辑。
int main() { // 1.定义一个资源初始化的函数,用于AscendCL初始化、运行管理资源申请(指定计算设备) InitResource(); // 2.定义一个模型加载的函数,加载图片分类的模型,用于后续推理使用 const char *modelPath = "../model/resnet50.om"; LoadModel(modelPath); // 3.定义一个读图片数据的函数,将测试图片数据读入内存,并传输到Device侧,用于后续推理使用 const char *picturePath = "../data/dog1_1024_683.bin"; LoadPicture(picturePath); // 4.定义一个推理的函数,用于执行推理 Inference(); // 5.定义一个推理结果数据处理的函数,用于在终端上屏显测试图片的top5置信度的类别编号 PrintResult(); // 6.定义一个模型卸载的函数,卸载图片分类的模型 UnloadModel(); // 7.定义一个函数,用于释放内存、销毁推理相关的数据类型,防止内存泄露 UnloadPicture(); // 8.定义一个资源去初始化的函数,用于AscendCL去初始化、运行管理资源释放(释放计算设备) DestroyResource(); }
了解总体的代码逻辑后,接下来开始写各自定义函数的实现,以下函数的实现请按顺序添加在main函数之前。
- include依赖的头文件,包括AscendCL的、C或C++标准库的头文件。在“MyFirstApp/src/main.cpp”文件的开头增加如下代码。
#include "acl/acl.h" #include <iostream> #include <fstream> #include <cstring> #include <map> using namespace std;
- 资源初始化。
使用AscendCL接口开发应用时,必须先初始化AscendCL ,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。
在初始化时,还支持跟推理相关的可配置项(例如,性能相关的采集信息配置),以json格式的配置文件传入AscendCL初始化接口。如果当前的默认配置已满足需求(例如,默认不开启性能相关的采集信息配置),无需修改,可向AscendCL初始化接口中传入nullptr。int32_t deviceId = 0; void InitResource() { aclError ret = aclInit(nullptr); ret = aclrtSetDevice(deviceId); }
有初始化就有去初始化,在确定完成了AscendCL的所有调用之后,或者进程退出之前,需调用接口实现AscendCL去初始化,请参见10。
- 模型加载。此处通过加载om模型文件来实现模型加载,如何获取om模型文件请参见准备模型。
uint32_t modelId; void LoadModel(const char* modelPath) { aclError ret = aclmdlLoadFromFile(modelPath, &modelId); }
有加载就有卸载,模型推理结束后,需要卸载模型,请参见8。
- 将测试图片读入内存,再传输到Device侧,供推理使用。
size_t pictureDataSize = 0; void *pictureHostData; void *pictureDeviceData; //申请内存,使用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); } void LoadPicture(const char* picturePath) { ReadPictureTotHost(picturePath); CopyDataFromHostToDevice(); }
- 执行推理。
在调用AscendCL接口进行模型推理时,模型推理有输入、输出数据,输入、输出数据需要按照AscendCL规定的数据类型存放。相关数据类型如下:
- 使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。
模型加载成功后,用户可根据模型的ID,调用该数据类型下的操作接口获取该模型的描述信息,进而从模型的描述信息中获取模型输入/输出的个数、内存大小、维度信息、Format、数据类型等信息。
- 使用aclmdlDataset类型的数据描述模型的输入/输出数据。
模型可能存在多个输入、多个输出,调用aclmdlDataset类型的操作接口添加多个aclDataBuffer类型的数据。
图3 aclmdlDataset与aclDataBuffer的关系
aclmdlDataset *inputDataSet; aclDataBuffer *inputDataBuffer; aclmdlDataset *outputDataSet; aclDataBuffer *outputDataBuffer; aclmdlDesc *modelDesc; size_t outputDataSize = 0; void *outputDeviceData; // 准备模型推理的输入数据结构 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); } // 执行模型 void Inference() { CreateModelInput(); CreateModelOutput(); aclError ret = aclmdlExecute(modelId, inputDataSet, outputDataSet); }
- 使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。
- 处理模型推理的结果数据,在屏幕上显示图片的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); // 屏显测试图片的top5置信度的类别编号 map<float, unsigned int, greater<float>> resultMap; for (unsigned int j = 0; j < outputDataSize / sizeof(float);++j) { resultMap[*outFloatData] = j; outFloatData++; } 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); } }
- 卸载模型,并释放模型描述信息。推理结束,需及时释放模型描述信息、卸载模型。
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; }
- 资源释放。在确定完成了AscendCL的所有调用之后,或者进程退出之前,需调用如下接口实现计算设备释放,AscendCL去初始化。
void DestroyResource() { aclError ret = aclrtResetDevice(deviceId); aclFinalize(); }
编译及运行应用
- 编译代码。
以运行用户登录开发环境,切换到MyFirstApp目录下,执行以下命令。
如下为设置环境变量的示例,<SAMPLE_DIR>表示样例所在的目录,$HOME/Ascend/ascend-toolkit表示CANN软件包的安装路径,<arch-os>中arch表示操作系统架构(需根据运行环境的架构选择),os表示操作系统(需根据运行环境的操作系统选择)。
export APP_SOURCE_PATH=<SAMPLE_DIR>/MyFirstApp export DDK_PATH=$HOME/Ascend/ascend-toolkit/latest export NPU_HOST_LIB=$HOME/Ascend/ascend-toolkit/latest/<arch-os>/devlib chmod +x sample_build.sh ./sample_build.sh
- 当开发环境与运行环境的操作系统架构不同时,例如,当开发环境为X86架构、运行环境为AArch64架构时,则涉及交叉编译,需在开发环境上安装AArch64架构的软件包,将DDK_PATH、NPU_HOST_LIB环境变量的路径指向AArch64架构的软件包安装目录(例如,将DDK_PATH配置为$HOME/Ascend/ascend-toolkit/latest/aarch64-linux,将NPU_HOST_LIB配置为$HOME/Ascend/ascend-toolkit/latest/aarch64-linux/devlib),便于使用与运行环境架构相同的软件包中的头文件和库文件来编译代码。
- 如果执行脚本报错“ModuleNotFoundError: No module named 'PIL'”,则表示缺少Pillow库,请使用pip3 install Pillow --user命令安装Pillow库。
- 运行应用。
以运行用户将MyFirstApp目录上传至运行环境,以运行用户登录运行环境,切换到MyFirstApp目录下,执行以下命令。
chmod +x sample_run.sh ./sample_run.sh
终端上屏显的结果如下,index表示类别标识、value表示该分类的最大置信度:
top 1: index[161] value[0.764648] top 2: index[162] value[0.156616] top 3: index[167] value[0.038971] top 4: index[163] value[0.021698] top 5: index[166] value[0.011887]
类别标签和类别的对应关系与训练模型时使用的数据集有关,本样例使用的模型是基于imagenet数据集进行训练的,您可以在互联网上查阅对应数据集的标签及类别的对应关系。
当前屏显信息中的类别标识与类别的对应关系如下:
"161": ["basset", "basset hound"]
"162": ["beagle"]
"163": ["bloodhound", "sleuthhound"]
"166": ["Walker hound", "Walker foxhound"]
"167": ["English foxhound"]