在本节中,您可以通过一个简单的图片分类应用了解使用AscendCL接口开发应用的基本过程以及开发过程中涉及的关键概念。
AscendCL(Ascend Computing Language)是一套用于在昇腾平台上开发深度神经网络推理应用的C语言API库,提供Device管理、Context管理、Stream管理、内存管理、模型加载与执行、算子加载与执行、媒体数据处理等API,能够实现在昇腾CANN平台上进行深度学习推理计算、图形图像预处理、单算子加速计算等能力。
Host、Device、Context、Stream的概念如下:
Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。
Context作为一个容器,管理了所有对象(包括Stream、Event、设备内存等)的生命周期。不同Context的Stream、不同Context的Event是完全隔离的,无法建立同步等待关系。
多线程编程场景下,每切换一个线程,都要为该线程指定当前Context,否则无法获取任何其他运行资源。
Stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。
基于Stream的kernel执行和数据传输能够实现Host运算操作、Host与Device间的数据传输、Device内的运算并行。
同步接口在调用的时候,当前线程会阻塞在调用点等待接口执行结束返回;而异步接口在调用之后会立即返回,此类接口在调用的时候要指定一个Stream作为执行队列,以保证多个异步调用按照调用顺序执行。
通过AscendCL提供的接口可以开发出图片分类的应用,如下:
若想直接体验该样例,请参见基于Caffe ResNet-50网络实现图片分类(同步推理)。
了解了入门的应用场景后,接下来可以了解下如果要开发出这个应用,主要的步骤有哪些?
了解了这些大步骤后,下面我们再展开来说明开发应用具体涉及哪些关键功能?各功能又使用哪些AscendCL接口,这些AscendCL接口怎么串联?
当前此时您可能不理解所有细节,但这也不影响,快速入门旨在帮助您快速了解整体的代码逻辑。
此处仅仅是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考 。
1
|
#include "acl/acl.h" |
使用AscendCL接口开发应用时,必须先初始化,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。
1 2 3 4 |
//此处的..表示相对路径,相对可执行文件所在的目录 //例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录 const char *aclConfigPath = "../src/acl.json"; aclError ret = aclInit(aclConfigPath); |
有初始化就有去初始化,在确定完成了AscendCL的所有调用之后,或者进程退出之前,需调用接口实现AscendCL去初始化,请参见8。
您需要按顺序依次申请如下资源:Device、Context、Stream,确保可以使用这些资源执行运算、管理任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//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); |
有运行管理资源的申请,自然也有对应的释放接口,所有数据处理都结束后,需要按顺序释放运行管理资源:Stream、Context、Device,请参见7。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//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_); |
有加载就有卸载,模型推理结束后,需要卸载模型,请参见6。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//1 准备模型推理的输入数据结构 //(1)申请输入内存 size_t modelInputSize; void *modelInputBuffer = nullptr; //当前示例代码中的模型只有一个输入,所以index为0,如果模型有多个输入,则需要先调用aclmdlGetNumInputs接口获取模型输入的数量 modelInputSize = aclmdlGetInputSizeByIndex(modelDesc_, 0); aclRet = aclrtMalloc(&modelInputBuffer, modelInputSize, ACL_MEM_MALLOC_NORMAL_ONLY); //(2)准备模型的输入数据结构 //创建aclmdlDataset类型的数据,描述模型推理的输入,input_为aclmdlDataset类型 input_ = aclmdlCreateDataset(); aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize); ret = aclmdlAddDatasetBuffer(input_, inputData); //2 准备模型推理的输出数据结构 //(1)创建aclmdlDataset类型的数据,描述模型推理的输出,output_为aclmdlDataset类型 output_ = aclmdlCreateDataset(); //(2)获取模型的输出个数. size_t outputSize = aclmdlGetNumOutputs(modelDesc_); //(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); } |
推理结束后,处理推理结果,打印出推理结果中置信度前五的标签及其对应置信度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
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接口获取软件栈的运行模式 //g_isDevice参数值为true,表示软件栈运行在Device侧,无需传输图片数据或在Device内传输数据;否则,需要调用内存复制接口将数据传输到Device侧 if (!g_isDevice) { // if app is running in host, need copy data from host to device //modelInputBuffer、modelInputSize分别表示模型推理输入数据的内存地址、内存大小,在输入/输出数据结构准备时申请该内存 aclError aclRet = aclrtMemcpy(modelInputBuffer, modelInputSize, inputBuff, inputBuffSize, ACL_MEMCPY_HOST_TO_DEVICE); (void)aclrtFreeHost(inputBuff); } else { // app is running in device 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//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; } |
1 2 3 4 5 6 |
//1 释放Stream aclError ret = aclrtDestroyStream(stream_); //2 释放Context ret = aclrtDestroyContext(context_); //3 释放Device ret = aclrtResetDevice(deviceId_); |
1
|
aclError ret = aclFinalize(); |