手把手带你用香橙派AIpro开发AI推理应用

瑞星  金牌会员 | 2024-5-15 15:57:04 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 637|帖子 637|积分 1911

本文分享自华为云社区《如何基于香橙派AIpro开发AI推理应用》,作者:昇腾CANN。
01 简介

香橙派AIpro开发板采用昇腾AI技术路线,接口丰富且具有强大的可扩展性,提供8/20TOPS澎湃算力,可广泛利用于AI边缘计算、深度视觉学习及视频流AI分析、视频图像分析、天然语言处理等AI领域。通过昇腾CANN软件栈的AI编程接口,可满足大多数AI算法原型验证、推理应用开发的需求。
AscendCL(Ascend Computing Language,昇腾计算语言)是昇腾计算开放编程框架,是对底层昇腾计算服务接口的封装,提供Device管理、Context管理、Stream管理、内存管理、模子加载与执行、算子加载与执行、媒体数据处理等API,支持C&C++、Python编程语言,能够实现深度学习推理计算、图形图像预处理、单算子加速计算等能力。
掌握了AscendCL的编程方法,就意味着可以在香橙派AIpro开发板上充实利用昇腾的算力资源,能够基于深度学习算法开发图片分类、目标检测等一系列深度学习推理计算步伐。

02 开发流程

利用AscendCL开发推理应用时,开发流程大抵分为以下几步:

  • AscendCL初始化:初始化AscendCL内部资源,为运行做准备
  • 运行管理资源申请:申请运行时相关资源,比方计算装备
  • 媒体数据处理:可实现抠图、缩放、视频或图片的编解码等
  • 模子推理:包括模子加载、执行、卸载
  • 运行管理资源开释:资源利用后及时开释
  • AscendCL去初始化:与初始化配对利用
首先,我们得先了解下,利用AscendCL时,经常会提到的“数据类型的操作接口” ,这是什么呢?为啥会存在?
在C/C++中,对用户开放的数据类型通常以Struct结构体方式定义、以声明变量的方式利用,但这种方式一旦结构体要增加成员参数,用户的代码就涉及兼容性问题,未便于维护,因此AscendCL对用户开放的数据类型,均以接口的方式操作该数据类型,比方,调用某个数据类型的Create接口创建该数据类型、调用Get接口获取数据类型内参数值、调用Set接口设置数据类型内的参数值、调用Destroy接口销毁该数据类型,用户无需关注定义数据类型的结构体长什么样,这样即使后续数据类型需扩展,只需增加该数据类型的操作接口即可,也不会引起兼容性问题。
所以,总结下,“数据类型的操作接口”就是创建数据类型、Get/Set数据类型中的参数值、销毁数据类型的一系列接口,存在的最大利益就是减少兼容性问题
接下来,进入我们今天的主题,怎么用AscendCL的接口开发网络模子推理场景下的应用。看完本文介绍的关键知识点,也可以到 “昇腾文档中央[2]”查阅详细的文档介绍。
03 AscendCL初始化与去初始化

利用AscendCL接口开发应用时,必须先初始化AscendCL ,否则可能会导致后续系统内部资源初始化出错,进而导致别的业务异常。在初始化时,还支持以下跟推理相关的配置项(比方,性能相关的收罗信息配置),以json格式的配置文件传入AscendCL初始化接口。如果当前的默认配置已满足需求(比方,默认不开启性能相关的收罗信息配置),无需修改,可向AscendCL初始化接口中传入NULL,或者可将配置文件配置为空json串(即配置文件中只有{})。
有初始化就有去初始化,在确定完成了AscendCL的所有调用之后,或者进程退出之前,需调用AscendCL接口实现AscendCL去初始化。
  1. // 此处以伪代码的形式展示接口的调用流程
  2. // 初始化
  3. // 此处的..表示相对路径,相对可执行文件所在的目录,例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录
  4. const char *aclConfigPath = "../src/acl.json";
  5. aclError ret = aclInit(aclConfigPath);
  6. // ......
  7. // 去初始化
  8. ret = aclFinalize();
复制代码
04 运行管理资源申请与开释

运行管理资源包括Device、Context、Stream、Event等,此处重点介绍Device、Context、Stream,其基本概念如下图所示 。

您需要按顺序依次申请如下运行管理资源:Device、Context、Stream,确保可以利用这些资源执行运算、管理任务。所有数据处理都结束后,需要按顺序依次开释运行管理资源:Stream、Context、Device
在申请运行管理资源时,Context、Stream支持隐式创建和显式创建两种申请方式。
  1. // 此处以伪代码的形式展示接口的调用流程,以显式创建Context和Stream为例
  2. // 运行管理资源申请
  3. // 1、指定运算的Device
  4. aclError ret = aclrtSetDevice(deviceId);
  5. // 2、显式创建一个Context,用于管理Stream对象
  6. ret = aclrtCreateContext(context, deviceId);
  7. // 3、显式创建一个Stream,用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序执行任务
  8. ret = aclrtCreateStream(stream);
  9. //......
  10. // 运行管理资源释放
  11. // 1、销毁Stream
  12. ret = aclrtDestroyStream(stream);
  13. // 2、销毁Context
  14. ret = aclrtDestroyContext(context);
  15. // 3、释放Device资源
  16. ret = aclrtResetDevice(deviceId);
  17. //......
复制代码
05 媒体数据处理

如果模子对输入图片的宽高要求与用户提供的源图不同等,AscendCL提供了媒体数据处理的接口,可实现抠图、缩放、格式转换、视频或图片的编解码等,将源图裁剪成符合模子的要求。后续期刊中会展开说明这个功能,本期偏重介绍模子推理的部分,以输入图片满足模子的要求为例。
06 模子加载

模子推理场景下,必须要有适配昇腾AI处理器的离线模子(*.om文件),我们可以利用ATC(Ascend Tensor Compiler)来构建模子。如果模子推理涉及动态Batch、动态分辨率等特性,需在构建模子增加相关配置。关于如何利用ATC来构建模子,请参见“昇腾文档中央[2]”。
有了模子,就可以开始加载了,当前AscendCL支持以下几种方式加载模子:

  • 从*.om文件中加载模子数据,由AscendCL管理内存
  • 从*.om文件中加载模子数据,由用户自行管理内存
  • 从内存中加载模子数据,由AscendCL管理内存
  • 从内存中加载模子数据,由用户自行管理内存
由用户自行管理内存时,需关注工作内存、权值内存。工作内存用于存放模子执行过程中的临时数据,权值内存用于存放权值数据。这个时候,是不是有疑问了,我怎么知道工作内存、权值内存需要多大?不消担心,AscendCL不光提供了加载模子的接口,同时也提供了“根据模子文件获取模子执行时所需的工作内存和权值内存大小”的接口,方便用户利用 。
  1. // 此处以伪代码的形式展示接口的调用流程,以“由用户管理内存”为例
  2. // 1.根据om模型文件获取模型执行时所需的权值内存大小、工作内存大小。
  3. aclError ret = aclmdlQuerySize(omModelPath, &modelWorkSize,
  4.                       &modelWeightSize);
  5. // 2.根据工作内存大小,申请Device上模型执行的工作内存。
  6. ret = aclrtMalloc(&modelWorkPtr, modelWorkSize,
  7.           ACL_MEM_MALLOC_HUGE_FIRST);
  8. // 3.根据权值内存的大小,申请Device上模型执行的权值内存。
  9. ret = aclrtMalloc(&modelWeightPtr, modelWeightSize,
  10.           ACL_MEM_MALLOC_HUGE_FIRST);
  11. // 4.以从om模型文件加载模型、由用户管理工作内存和权值内存为例
  12. // 模型加载成功,返回标识模型的ID。
  13. ret = aclmdlLoadFromFileWithMem(modelPath, &modelId, modelWorkPtr,  
  14.                              modelWorkSize, modelWeightPtr,
  15. modelWeightSize);
复制代码
07 模子执行

在调用AscendCL接口进行模子推理时,模子推理有输入、输出数据,输入、输出数据需要按照AscendCL规定的数据类型存放。相关数据类型如下:

  • 利用aclmdlDesc类型的数据描述模子基本信息(比方输入/输出的个数、名称、数据类型、Format、维度信息等)。
模子加载成功后,用户可根据模子的ID,调用该数据类型下的操作接口获取该模子的描述信息,进而从模子的描述信息中获取模子输入/输出的个数、内存大小、维度信息、Format、数据类型等信息。

  • 利用aclDataBuffer类型的数据来描述每个输入/输出的内存地址、内存大小。
调用aclDataBuffer类型下的操作接口获取内存地址、内存大小等,便于向内存中存放输入数据、获取输出数据。

  • 利用aclmdlDataset类型的数据描述模子的输入/输出数据。
模子可能存在多个输入、多个输出,调用aclmdlDataset类型的操作接口添加多个aclDataBuffer类型的数据。
  1. // 此处以伪代码的形式展示如何准备模型的输入、输出数据结构
  2. // 1.根据加载成功的模型的ID,获取该模型的描述信息
  3. aclmdlDesc *modelDesc = aclmdlCreateDesc();
  4. aclError ret = aclmdlGetDesc(modelDesc, modelId);
  5. // 2.准备模型推理的输入数据结构
  6. // (1)申请输入内存
  7. // 当前示例代码中的模型只有一个输入,所以index为0,如果模型有多个输入,则需要先调用aclmdlGetNumInputs接口获取模型输入的数量
  8. void *modelInputBuffer = nullptr;
  9. size_t modelInputSize = aclmdlGetInputSizeByIndex(modelDesc, 0);
  10. ret = aclrtMalloc(&modelInputBuffer, modelInputSize,                                              ACL_MEM_MALLOC_NORMAL_ONLY);
  11. // (2)准备模型的输入数据结构
  12. // 创建aclmdlDataset类型的数据,描述模型推理的输入
  13. aclmdlDataset *input = aclmdlCreateDataset();
  14. aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize);
  15. ret = aclmdlAddDatasetBuffer(input, inputData);
  16. // 3.准备模型推理的输出数据结构
  17. // (1)创建aclmdlDataset类型的数据output,描述模型推理的输出
  18. aclmdlDataset *output = aclmdlCreateDataset();
  19. // (2)获取模型的输出个数.
  20. size_t outputSize = aclmdlGetNumOutputs(modelDesc);
  21. // (3)循环为每个输出申请内存,并将每个输出添加到aclmdlDataset类型的数据中
  22. for (size_t i = 0; i < outputSize; ++i) {
  23. size_t buffer_size = aclmdlGetOutputSizeByIndex(modelDesc, i);
  24. void *outputBuffer = nullptr;
  25. ret = aclrtMalloc(&outputBuffer, buffer_size,
  26.               ACL_MEM_MALLOC_NORMAL_ONLY);
  27. aclDataBuffer *outputData = aclCreateDataBuffer(outputBuffer, buffer_size);   
  28. ret = aclmdlAddDatasetBuffer(output, outputData);
  29. }
复制代码
准备好模子执行所需的输入和输出数据类型、且存放好模子执行的输入数据后,可以执行模子推理了,如果模子的输入涉及动态Batch、动态分辨率等特性,则在模子执行前,还需要调用AscendCL接口告诉模子本次执行时需要用的Batch数、分辨率等。
当前AscendCL支持同步模子执行、异步模子执行两种方式,这里说的同步、异步是站在调用者和执行者的角度。

  • 若调用模子执行的接口后需等待推理完成再返回,则表示模子执行是同步的。当用户调用同步模子执行接口后,可直接从该接口的输出参数中获取模子执行的结果数据,如果需要推理的输入数据量很大,同步模子执行时,需要等所有数据都处理完成后,才气获取推理的结果数据。
  • 若调用模子执行的接口后不等待推理完成完成再返回,则表示模子执行是异步的。当用户调用异步模子执行接口时,需指定Stream(Stream用于维护一些异步操作的执行顺序,确保按照应用步伐中的代码调用顺序在Device上执行),另外,还需调用aclrtSynchronizeStream接口阻塞步伐运行,直到指定Stream中的所有任务都完成,才可以获取推理的结果数据。如果需要推理的输入数据量很大,异步模子执行时,AscendCL提供了Callback机制,触发回调函数,在指定时间内一旦有推理的结果数据,就获取出来,达到分批获取推理结果数据的目的,提高服从。
  1. // 此处以伪代码的形式展示同步模型执行的过程
  2. // 1. 由用户自行编码,将模型所需的输入数据读入内存
  3. // 如果模型推理之前先进行媒体数据处理,则此处可以将媒体数据处理后的输出内容作为模型推理的输入内存,
  4. // ......
  5. // 2. 执行模型推理
  6. // modelId表示模型ID,在模型加载成功后,会返回标识模型的ID
  7. // input、output分别表示模型推理的输入、输出数据,在准备模型推理的输入、输出数据结构时已定义
  8. aclError ret = aclmdlExecute(modelId, input, output)
  9.         
  10. // 3. 处理模型推理的输出数据
  11. for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output); ++i) {
  12. //获取每个输出的内存地址和内存大小
  13. aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output, i);
  14. void* data = aclGetDataBufferAddr(dataBuffer);
  15. size_t len = aclGetDataBufferSizeV2(dataBuffer);
  16. //获取到输出数据后,由用户自行编码,处理输出数据
  17. //......
  18. }
  19. // 4.销毁模型输入、输出数据结构
  20. // 释放输入资源,包括数据结构和内存
  21. (void)aclDestroyDataBuffer(dataBuffer);
  22. (void)aclmdlDestroyDataset(mdlDataset);
  23. // 5.释放内存资源,防止内存泄露
  24. // ......
复制代码
推理结束后,如果需要获取并进一步处理推理结果数据,则由用户自行编码实现。最后,别忘了,我们还要销毁aclmdlDataset、aclDataBuffer等数据类型,开释相关内存,防止内存泄露。
08 模子卸载

在模子推理结束后,还需要通过aclmdlUnload接口卸载模子,并销毁aclmdlDesc类型的模子描述信息、开释模子运行的工作内存和权值内存。
  1. // 此处以伪代码的形式展示模型卸载的过程
  2. // 1. 卸载模型
  3. aclError ret = aclmdlUnload(modelId);
  4. // 2. 释放模型描述信息
  5. (void)aclmdlDestroyDesc(modelDesc);
  6. // 3. 释放模型运行的工作内存和权值内存
  7. (void)aclrtFree(modelWorkPtr);
  8. (void)aclrtFree(modelWeightPtr);
复制代码
09 更多介绍 

[1]香橙派AIpro开源样例代码:https://gitee.com/ascend/EdgeAndRobotics
[2]昇腾文档中央:https://www.hiascend.com/zh/document
[3]香橙派AIpro学习资源一站式导航:https://www.hiascend.com/forum/thread-0285140173361311056-1-1.html
点击关注,第一时间了解华为云新鲜技术~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表