应用班第三课
图片分类
这节课得目标是掌握基本的开发流程,以及相关的应用日志查看。
主要步骤
准备模型、准备数据、编译程序、运行程序
编译
编译的主要步骤有准备环境,ACL初始化、运行管理资源申请、模型加载、模型推理、模型卸载、运行资源管理资源释放、ACL去初始化、编译代码生成可执行文件。
运行ACL时需要先初始化,初始化时需要有配置文件,json格式的。如果不关注配置文件,则该文件可以为空json文件,即只有“{}”。
运行
准备运行环境、执行可执行文件、运行运用。
程序整体框架
首先时头文件,需要有acl的头文件
#include "acl/acl.h"
ACL初始化
// 此处的..表示相对路径,相对可执行文件所在的目录
//例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录
const char *aclConfigPath = "../src/acl.json";
aclError ret = aclInit(aclConfigPath);
运行资源管理申请
需要申请的资源原有:device、context、stream等。
aclrtSetDevice这个接口,调用完毕后,除了指定了计算设备之外,还会同时创建1个默认的Context;而这个默认的Context还附赠了2个Stream,1个默认Stream和1个用于执行内部同步的Stream。这也意味着如果是编写非常简单的单线程同步推理应用,在运行资源这里我们只需要调用aclrtSetDevice就够了。
// 1 指定运算的Device
ret = aclrtSetDevice(deviceId_);
// 2 显式创建一个Context,用于管理Stream对象
ret = aclrtCreateContext(&context_, deviceId_);
// 3 显式创建一个Stream
// 用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序执行任务
ret = aclrtCreateStream(&stream_);
// 4 获取当前昇腾AI软件栈的运行模式,根据不同的运行模式,后续的接口调用方式不同
aclrtRunMode runMode;
extern bool g_isDevice;
ret = aclrtGetRunMode(&runMode);
g_isDevice = (runMode == ACL_DEVICE);
模型加载获取模型新型
AscendCL只认识适配昇腾AI处理器的离线模型(*.om文件),因此,模型加载前,需要将第三方网络(例如,Caffe ResNet-50网络)转换为适配昇腾AI处理器的离线模型(*.om文件)
// 1 初始化变量
// 此处的..表示相对路径,相对可执行文件所在的目录
// 例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录
const char* omModelPath = "../model/resnet50.om"
// 2 根据模型文件获取模型执行时所需的权值内存大小、工作内存大小。
aclError ret = aclmdlQuerySize(omModelPath, &modelMemSize_, &modelWeightSize_);
// 3 根据工作内存大小,申请Device上模型执行的工作内存。
ret = aclrtMalloc(&modelMemPtr_, modelMemSize_, ACL_MEM_MALLOC_NORMAL_ONLY);
// 4 根据权值内存的大小,申请Device上模型执行的权值内存。
ret = aclrtMalloc(&modelWeightPtr_, modelWeightSize_, ACL_MEM_MALLOC_NORMAL_ONLY);
// 5 加载离线模型文件(适配昇腾AI处理器的离线模型),由用户自行管理模型运行的内存(包括权值内存、工作内存)。
// 模型加载成功,返回标识模型的ID。
ret = aclmdlLoadFromFileWithMem(modelPath, &modelId_, modelMemPtr_, modelMemSize_, modelWeightPtr_, modelWeightSize_);
// 6 根据加载成功的模型的ID,获取该模型的描述信息。
// modelDesc_为aclmdlDesc类型。
modelDesc_ = aclmdlCreateDesc();
ret = aclmdlGetDesc(modelDesc_, modelId_);
执行模型推理,并且处理推理结果
准备模型推理的输入、输出数据结构,用于描述模型的输入、输出数据。
// 1 准备模型推理的输入数据结构
// 1.1申请输入内存
size_t modelInputSize;
void *modelInputBuffer = nullptr;
// 当前示例代码中的模型只有一个输入,所以index为0
// 如果模型有多个输入,则需要先调用aclmdlGetNumInputs接口获取模型输入的数量
modelInputSize = aclmdlGetInputSizeByIndex(modelDesc_, 0);
aclRet = aclrtMalloc(&modelInputBuffer, modelInputSize, ACL_MEM_MALLOC_NORMAL_ONLY);
// 1.2准备模型的输入数据结构
// 创建aclmdlDataset类型的数据,描述模型推理的输入,input_为aclmdlDataset类型
input_ = aclmdlCreateDataset();
aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize);
ret = aclmdlAddDatasetBuffer(input_, inputData);
// 2 准备模型推理的输出数据结构
// 2.1创建aclmdlDataset类型的数据,描述模型推理的输出,output_为aclmdlDataset类型
output_ = aclmdlCreateDataset();
// 2.2获取模型的输出个数
size_t outputSize = aclmdlGetNumOutputs(modelDesc_);
// 2.3循环为每个输出申请内存,并将每个输出添加到aclmdlDataset类型的数据中.
for (size_t i = 0; i < outputSize; ++i) {
size_t buffer_size = aclmdlGetOutputSizeByIndex(modelDesc_, i);
void *outputBuffer = nullptr;
aclError ret = aclrtMalloc(&outputBuffer, buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY);
aclDataBuffer* outputData = aclCreateDataBuffer(outputBuffer, buffer_size);
ret = aclmdlAddDatasetBuffer(output_, outputData);
}
准备模型推理的输入数据,进行推理,推理结束后,处理推理结果。
string picturePath = "../data/dog1_1024_683.bin";
// 1 自定义函数ReadBinFile,调用C++标准库std::ifstream中的函数读取图片文件
// 输出图片文件占用的内存大小inputBuffSize以及图片文件存放在内存中的地址inputBuff
void *inputBuff = nullptr;
uint32_t inputBuffSize = 0;
auto ret = Utils::ReadBinFile(picturePath, inputBuff, inputBuffSize);
// 2 准备模型推理的输入数据
// 在"申请运行管理资源"时调用aclrtGetRunMode接口获取软件栈的运行模式
// 如果运行模式为ACL_DEVICE,则g_isDevice参数值为true,无需传输图片数据或在Device内传输数据
// 如果运行模式为ACL_HOST,则g_isDevice参数值为false,需要将图片数据从Host传输到Device
if (!g_isDevice) {
// 内存在"输入/输出数据结构准备"时申请该内存
aclError aclRet = aclrtMemcpy(modelInputBuffer, modelInputSize, inputBuff, inputBuffSize, ACL_MEMCPY_HOST_TO_DEVICE);
(void)aclrtFreeHost(inputBuff);
}
else {
aclError aclRet = aclrtMemcpy(modelInputBuffer, modelInputSize, inputBuff, inputBuffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
(void)aclrtFree(inputBuff);
}
// 3. 执行模型推理
// modelId_表示模型ID,在"模型加载"成功后,会返回标识模型的ID
// input_、output_分别表示模型推理的输入、输出数据,在"准备模型推理的输入、输出数据结构"时已定义
aclError ret = aclmdlExecute(modelId_, input_, output_)
// 4. 处理模型推理的输出数据,输出top5置信度的类别编号
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output_); ++i) {
// 获取每个输出的内存地址和内存大小
aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, i);
void* data = aclGetDataBufferAddr(dataBuffer);
size_t len = aclGetDataBufferSizeV2(dataBuffer);
// 将内存中的数据转换为float类型
float *outData = NULL;
outData = reinterpret_cast<float*>(data);
// 屏显每张图片的top5置信度的类别编号
map<float, int, greater<float> > resultMap;
for (int j = 0; j < len / sizeof(float); ++j) {
resultMap[*outData] = j;
outData++;
}
int cnt = 0;
for (auto it = resultMap.begin(); it != resultMap.end(); ++it) {
// print top 5
if (++cnt > 5) {
break;
}
INFO_LOG("top %d: index[%d] value[%lf]", cnt, it->second, it->first);
}
// 5 释放模型推理的输入、输出资源
// 释放输入资源,包括数据结构和内存
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(input_); ++i) {
aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(input_, i);
(void)aclDestroyDataBuffer(dataBuffer);
}
(void)aclmdlDestroyDataset(input_);
input_ = nullptr;
aclrtFree(modelInputBuffer);
// 释放输出资源,包括数据结构和内存
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output_); ++i) {
aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, i);
void* data = aclGetDataBufferAddr(dataBuffer);
(void)aclrtFree(data);
(void)aclDestroyDataBuffer(dataBuffer);
}
(void)aclmdlDestroyDataset(output_);
output_ = nullptr;
卸载模型
// 1. 卸载模型
aclError ret = aclmdlUnload(modelId_);
// 2. 释放模型描述信息
if (modelDesc_ != nullptr) {
(void)aclmdlDestroyDesc(modelDesc_);
modelDesc_ = nullptr;
}
// 3. 释放模型运行的工作内存
if (modelWorkPtr_ != nullptr) {
(void)aclrtFree(modelWorkPtr_);
modelWorkPtr_ = nullptr;
modelWorkSize_ = 0;
}
// 4. 释放模型运行的权值内存
if (modelWeightPtr_ != nullptr) {
(void)aclrtFree(modelWeightPtr_);
modelWeightPtr_ = nullptr;
modelWeightSize_ = 0;
}
运行管理资源释放
要注意,只能销毁由aclrtCreateContext接口显式创建的Context、销毁由aclrtCreateStream接口显式创建的Stream,不能销毁默认Context、默认Stream。默认Context、默认Stream会由系统自行销毁,无需调用者关注。
// 1 释放Stream
aclError ret = aclrtDestroyStream(stream_);
// 2 释放Context
ret = aclrtDestroyContext(context_);
// 3 释放Device
ret = aclrtResetDevice(deviceId_);
ACL去初始化
aclError ret = aclFinalize();
模型
使用Caffe框架的开源ResNet-50模型作为其输入输出。
模型转换
由于昇腾无法使用tensorflow、caffe等模型,所以需要使用ATC工具转换为昇腾能够使用的模型,只需要准备好开发环境,并不需要运行环境。
模型转换约束条件
支持原始框架类型为Caffe和TensorFlow,
日志
日志目录默认为:
日志中关键信息