跳到主要内容

单算子调用基础知识

内存被提前释放,导致编码数据花屏

现象描述

编码出来的数据花屏,其他无异常日志信息。

可能原因

VENC输入内存是YUV图片数据,输入内存被踩或者被提前释放。

处理步骤

在DVPP内存释放接口处、以及hi_mpi_venc_get_stream/aclvencCallback/acldvppJpegEncodeAsync接口处,增加内存大小及地址的打印日志,确认内存释放时序,是否存在输入内存在编码完成前被提前释放的情况。

父主题: JPEGE图片编码/VENC视频编码

使用正确的内存申请接口,但内存大小传值错误

现象描述

日志报错如下:

device:0 chan 0, output terminal address is invalid, maybe outBufSize:3110400 is invalid.

可能原因

传入的buffer size太大,超出了实际申请的buffer范围,导致内部结束地址校验出错。

处理步骤

如果内存申请接口使用正常,业务流程中dvpp内存申请接口增加地址及长度日志,检查接口hi_mpi_venc_send_frame/hi_mpi_venc_send_jpege_frame/aclvencSendFrame/acldvppJpegEncodeAsync传入buffer长度是否一致。

父主题: JPEGE图片编码/VENC视频编码

单算子调用视频课程

通过在线视频课程学习该功能,请参见AscendCL加载与执行算子

父主题: 单算子调用

单算子调用基础知识

单算子调用的使用场景

如果AI应用中不仅仅包括模型推理,还有数学运算(例如BLAS基础线性代数运算)、数据类型转换等功能,也想使用昇腾的算力,昇腾CANN还能支持吗?

答案是肯定的,昇腾CANN提供了单算子调用的方式,直接通过AscendCL接口加载并执行单个算子,省去模型构建、训练的过程,相对轻量级,又可以使用昇腾的算力。

另外,自定义的算子,也可以通过单算子调用的方式来验证算子的功能。

单算子调用与模型推理的差别

在解释单算子调用与模型推理的差别前,我们先观察下面这个开发流程图,先找出基本的共同点、不同点。

  • 共同点
    • 不管是模型推理,还是单算子调用,都需要AscendCL初始化和去初始化、运行管理资源申请和释放。
    • 不管是模型推理,还是单算子调用,都涉及加载、执行的步骤,但是要注意,两者的加载、执行是调用不同的AscendCL接口。
  • 不同点
    • 模型推理涉及模型卸载的步骤,单算子调用不涉及。

图1 单算子调用与模型推理的流程对比

单算子调用功能开发流程

图2 开发流程

  1. 准备环境

    请参见。

  2. 创建代码目录

    在开发应用前,您需要先创建目录,存放代码文件、编译脚本、测试图片数据、模型文件等。

    如下仅是示例,供参考:

    ├App名称
    ├── op_model // 该目录下存放编译算子的算子描述文件
    │ ├── xxx.json

    ├── data
    │ ├── xxxxxx // 测试数据

    ├── inc // 该目录下存放声明函数的头文件
    │ ├── xxx.h

    ├── out // 该目录下存放输出结果

    ├── src
    │ ├── xxx.json // 系统初始化的配置文件
    │ ├── CMakeLists.txt // 编译脚本
    │ ├── xxx.cpp // 实现文件
  3. 编译算子

    若基于“单算子模型执行”方式调用算子,则需编译算子,编译算子有以下两种方式:

    • 使用ATC工具编译算子生成om模型文件

      该种方式,需要先构造*.json格式单算子描述文件(描述算子的输入、输出及属性等信息),借助ATC工具,将单算子描述文件编译成om模型文件;再分别调用AscendCL接口加载om模型文件、执行算子。

      关于ATC工具的使用说明,请参见《ATC工具使用指南》。

    • 也可以调用AscendCL提供的编译算子接口

      该种方式,直接调用AscendCL接口编译、执行算子。

    :::note 说明 关于单算子模型执行详细的接口调用流程请参见单算子调用流程。 :::

  4. 开发应用

    依赖的头文件和库文件的说明请参见调用接口依赖的头文件和库文件说明

    单算子调用的流程请参见单算子调用流程及相关的示例代码。

  5. 编译运行应用,请参见应用调试

父主题: 单算子调用

单算子调用流程

开发应用时,如果涉及执行单个算子,请先参见AscendCL接口调用流程了解整体流程,再查看本节中的流程说明。

系统支持的算子请参见《算子清单》。

对于系统不支持的算子,用户需先参见《TBE&AI CPU算子开发指南》完成自定义算子开发。

单算子模型执行接口调用流程

图1 单算子模型执行接口调用流程

关键接口的说明如下:

  1. 编译算子

    根据算子编译的方式,可分为以下两种:

    • 编译算子后,算子相关数据保存在*.om模型文件中

      该种方式下编译算子,需使用ATC工具,详细描述请参见《ATC工具使用指南》,将单算子定义文件(*.json)编译成适配昇腾AI处理器的离线模型(*.om文件)。

      编译算子后,依次进行234567

    • 编译算子后,算子相关数据保存在内存中

      该种方式下编译算子,需调用AscendCL提供的接口,根据不同场景调用不同的接口:

      • 对于同一个算子,编译一次,多次执行的场景,建议调用aclopCompile接口编译算子。编译算子后,依次进行34567
      • 对于编译算子、执行算子次数相同的场景,建议先执行3,再调用aclopCompileAndExecute接口编译算子。编译算子后,再依次进行67
  2. 加载算子模型文件

    支持以下2种方式中的一种加载单算子模型文件:

    • 调用aclopSetModelDir接口,设置加载模型文件的目录,目录下存放单算子模型文件(*.om文件)。
    • 调用aclopLoad接口,从内存中加载单算子模型数据,由用户管理内存。单算子模型数据是指“单算子编译成*.om文件后,再将om文件读取到内存中”的数据。
  3. 调用aclrtMalloc接口申请Device上的内存,存放执行算子的输入、输出数据。

  4. 动态Shape场景,如果无法明确算子的输出Shape时,在执行算子前,还需推导或预估算子的输出Shape

    需用户调用aclopInferShape接口、aclGetTensorDescNumDims接口、aclGetTensorDescDimV2接口、aclGetTensorDescDimRange等接口,推导或预估算子的输出Shape,作为算子执行接口aclopExecuteV2的输入。

  5. 执行算子

    • 对于被封装成AscendCL接口的算子(参见CBLAS接口),包括GEMM算子、Cast算子,目前支持以下两种执行方式:
    • 对于未被封装成AscendCL接口的算子,目前支持以下两种执行方式:

    :::note 说明 不以handle方式执行算子时,每次执行算子时,系统内部都会根据算子描述信息匹配内存中的模型。

    以handle方式执行算子时,系统内部将算子描述信息匹配到内存中的模型,并缓存在Handle中,每次执行算子时,无需重复匹配算子与模型,因此在涉及多次执行同一个算子时,效率更高,但该方式不支持动态Shape算子,且Handle使用结束后,需调用aclopDestroyHandle接口释放。 :::

  6. 调用aclrtSynchronizeStream接口阻塞应用运行,直到指定Stream中的所有任务都完成。

  7. 调用aclrtFree接口释放内存

父主题: 单算子调用

调用CBLAS接口执行算子示例代码

基本原理

接口调用流程,请参见单算子调用流程

目前,AscendCL已将GEMM算子(用于矩阵-向量乘、矩阵-矩阵乘)、Cast算子(用于转换数据类型)封装成AscendCL的CBLAS接口,可参见CBLAS接口,目前支持以下两种执行方式:

:::note 说明 不以handle方式执行算子时,每次执行算子时,系统内部都会根据算子描述信息匹配内存中的模型。

以handle方式执行算子时,系统内部将算子描述信息匹配到内存中的模型,并缓存在Handle中,每次执行算子时,无需重复匹配算子与模型,因此在涉及多次执行同一个算子时,效率更高,但该方式不支持动态Shape算子,且Handle使用结束后,需调用aclopDestroyHandle接口释放。 :::

示例代码

本章以aclblasGemmEx接口为例,该接口封装的是GEMM算子,该接口中矩阵乘的计算公式为:C = αAB + βC。

调用CBLAS接口(封装GEMM算子)分为以下几步:

  1. 准备GEMM算子的模型文件。

    1. 构造GEMM算子的描述文件(*.json文件,描述输入输出Tensor描述、算子属性等)。

      GEMM算子的描述文件示例如下:

      [
      {
      "op": "GEMM",
      "input_desc": [
      {
      "format": "ND",
      "shape": [16, 16],
      "type": "float16"
      },
      {
      "format": "ND",
      "shape": [16, 16],
      "type": "float16"
      },
      {
      "format": "ND",
      "shape": [16, 16],
      "type": "float16"
      },
      {
      "format": "ND",
      "shape": [],
      "type": "float16"
      },
      {
      "format": "ND",
      "shape": [],
      "type": "float16"
      }
      ],
      "output_desc": [
      {
      "format": "ND",
      "shape": [16, 16],
      "type": "float16"
      }
      ],
      "attr": [
      {
      "name": "transpose_a",
      "type": "bool",
      "value": false
      },
      {
      "name": "transpose_b",
      "type": "bool",
      "value": false
      }
      ]
      }
      ]
    2. 借助ATC工具,将该算子描述文件编译成单算子模型文件(*.om文件),再分别调用AscendCL接口加载om模型文件、执行算子。

      ATC工具的命令示例如下:

      atc --singleop=$HOME/singleop/gemm.json --output=$HOME/singleop/out/op_model --soc_version=<soc_version>

      关键参数解释如下(详细参数说明,请参见《ATC工具使用指南》。):

      • --singleop:单算子描述文件(json格式)的路径。

      • --output:存放单算子模型文件的目录。

      • --soc_version:昇腾AI处理器的版本。

        进入“CANN软件安装目录/compiler/data/platform_config”目录,".ini"文件的文件名即为昇腾AI处理器的版本,请根据实际情况选择。

  2. 编写调用CBLAS的代码逻辑。

    以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考,调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。

    <br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>47<br>48<br>49<br>50<br>51<br>52<br>53<br>54<br>55<br>56<br>57<br>58<br>59<br>60<br>61<br>62<br>63<br>64<br>65<br>66<br>67<br>68<br>69<br>70<br>71<br>72<br>73<br>74<br>75<br>76<br>77<br>78<br>79<br><br>// 1.AscendCL初始化<br>aclRet = aclInit(nullptr);<br>// 2.运行管理资源申请(使用默认Context、默认Stream,默认Stream在作为其它接口入参时,可传空指针)<br>aclRet = aclrtSetDevice(0);<br>获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)<br>aclrtRunMode runMode;<br>bool g_isDevice = false;<br>aclError aclRet = aclrtGetRunMode(&runMode);<br>g_isDevice = (runMode == ACL_DEVICE);<br>// 3. 设置单算子模型文件所在的目录<br>// 该目录相对可执行文件所在的目录,例如,编译出来的可执行文件存放在run/out目录下,此处就表示run/out/op_models目录<br>aclopSetModelDir("op_models");<br>// 4. 申请内存<br>// 申请Device上的内存存放执行算子的输入数据<br>// 对于该矩阵乘示例,依次申请存放矩阵A数据、矩阵B数据、矩阵C数据、标量α数据、标量β数据的内存<br>aclrtMalloc((void **) &devMatrixA_, sizeA_, ACL_MEM_MALLOC_NORMAL_ONLY);<br>aclrtMalloc((void **) &devMatrixB_, sizeB_, ACL_MEM_MALLOC_NORMAL_ONLY);<br>aclrtMalloc((void **) &devMatrixC_, sizeC_, ACL_MEM_MALLOC_NORMAL_ONLY);<br>aclrtMalloc((void **) &devAlpha_, sizeAlphaBeta_, ACL_MEM_MALLOC_NORMAL_ONLY);<br>aclrtMalloc((void **) &devBeta_, sizeAlphaBeta_, ACL_MEM_MALLOC_NORMAL_ONLY);<br>// 申请Host上的内存,此处根据软件栈的运行模式判断是否需要申请Host上的内存<br>// 如果运行模式为ACL_DEVICE,则g_isDevice参数值为true,表示软件栈运行在Device侧,无需申请Host内存,无需传输图片数据或在Device内传输数据 <br>// 如果运行模式为ACL_HOST,则g_isDevice参数值为false,表示软件栈运行在Host侧,需要申请Host内存,涉及Host和Device之间的数据传输<br>if (g_isDevice) &#123;<br> hostMatrixA_ = devMatrixA_;<br> hostMatrixB_ = devMatrixB_;<br> hostMatrixC_ = devMatrixC_;<br> &#125; else &#123;<br> aclrtMallocHost((void **) &hostMatrixA_, sizeA_);<br> aclrtMallocHost((void **) &hostMatrixB_, sizeB_);<br> aclrtMallocHost((void **) &hostMatrixC_, sizeC_);<br> &#125;<br>// 5. 准备输入数据,ReadFile为自定义函数,由用户自行管理,从文件中读入数据到内存中<br>size_t fileSize;<br>// Read matrix A<br>char *fileData = ReadFile("test_data/data/matrix_a.bin", fileSize, hostMatrixA_, sizeA_);<br>// Read matrix B<br>fileData = ReadFile("test_data/data/matrix_b.bin", fileSize, hostMatrixB_, sizeB_);<br>// Read matrix C<br>fileData = ReadFile("test_data/data/matrix_c.bin", fileSize, hostMatrixC_, sizeC_);<br>// 根据软件栈的运行模式判断是否涉及Host与Device之间的数据传输<br>if (!g_isDevice) &#123;<br> aclError ret = aclrtMemcpy(devMatrixA_, sizeA_, hostMatrixA_, sizeA_, ACL_MEMCPY_HOST_TO_DEVICE);<br> ret = aclrtMemcpy(devMatrixB_, sizeB_, hostMatrixB_, sizeB_, ACL_MEMCPY_HOST_TO_DEVICE);<br> ret = aclrtMemcpy(devMatrixC_, sizeC_, hostMatrixC_, sizeC_, ACL_MEMCPY_HOST_TO_DEVICE);<br>&#125;<br>aclrtMemcpyKind kind = g_isDevice ? ACL_MEMCPY_DEVICE_TO_DEVICE : ACL_MEMCPY_HOST_TO_DEVICE;<br>ret = aclrtMemcpy(devAlpha_, sizeAlphaBeta_, hostAlpha_, sizeAlphaBeta_, kind);<br>ret = aclrtMemcpy(devBeta_, sizeAlphaBeta_, hostBeta_, sizeAlphaBeta_, kind);<br>// 6. 执行单算子<br>// 对于该示例,调用aclblasGemmEx接口(异步接口)实现矩阵-矩阵的乘法<br>aclblasGemmEx(ACL_TRANS_N, ACL_TRANS_N, ACL_TRANS_N, m_, n_, k_,<br> devAlpha_, devMatrixA_, k_, inputType_, devMatrixB_, n_, inputType_,<br> devBeta_, devMatrixC_, n_, outputType_, ACL_COMPUTE_HIGH_PRECISION,<br> stream);<br>// 调用aclrtSynchronizeStream接口阻塞Host运行,直到指定Stream中的所有任务都完成<br>aclrtSynchronizeStream(nullptr);<br>// 7. 传输算子执行结果,根据软件栈的运行模式判断是否涉及Host与Device之间的数据传输<br>if (!g_isDevice) &#123;<br> auto ret = aclrtMemcpy(hostMatrixC_, sizeC_, devMatrixC_, sizeC_, ACL_MEMCPY_DEVICE_TO_HOST);<br>&#125;<br>// 8. 是否直接在终端屏幕上显示算子执行结果,由用户自行管理代码逻辑<br>// 9. 释放运行管理资源(默认Context、Stream无需用户释放,调用aclrtResetDevice接口后自动释放)<br>aclRet = aclrtResetDevice(0);<br>// 10.AscendCL去初始化<br>aclRet = aclFinalize();<br>// ......<br>

相关资源

通过在线视频课程学习该功能,请参见CANN应用开发初级

父主题: 单算子模型执行

执行固定Shape算子示例代码

前提条件

在调用AscendCL接口执行固定Shape算子前,需提前编译算子。此处借助ATC工具编译Add算子的模型文件为例:

  1. 先构造该算子的描述文件(*.json文件,描述输入输出Tensor描述、算子属性等)。

    Add算子的描述文件示例如下:

    [
    &#123;
    "op": "Add",
    "input_desc": [
    &#123;
    "format": "ND",
    "shape": [8, 16],
    "type": "int32"
    &#125;,
    &#123;
    "format": "ND",
    "shape": [8, 16],
    "type": "int32"
    &#125;
    ],
    "output_desc": [
    &#123;
    "format": "ND",
    "shape": [8, 16],
    "type": "int32"
    &#125;
    ]
    &#125;
    ]
  2. 借助ATC工具,将该算子描述文件编译成单算子模型文件(*.om文件),再分别调用AscendCL接口加载om模型文件、执行算子。

    ATC工具的命令示例如下:

    atc --singleop=$HOME/singleop/add.json --output=$HOME/singleop/out/op_model --soc_version=<soc_version>

    关键参数解释如下(详细参数说明,请参见《ATC工具使用指南》。):

    • --singleop:单算子描述文件(json格式)的路径。

    • --output:存放单算子模型文件的目录。

    • --soc_version:昇腾AI处理器的版本。

      进入“CANN软件安装目录/compiler/data/platform_config”目录,".ini"文件的文件名即为昇腾AI处理器的版本,请根据实际情况选择。

示例代码

以下是单算子加载、执行关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考,调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。完整代码,您可以从acl_execute_add样例中查看。

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br><br>// 1.AscendCL初始化<br>aclRet = aclInit(nullptr);<br>// 2.运行管理资源申请(使用默认Context、默认Stream,默认Stream在作为其它接口入参时,可传空指针)<br>aclRet = aclrtSetDevice(0);<br>获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)<br>aclrtRunMode runMode;<br>bool g_isDevice = false;<br>aclError aclRet = aclrtGetRunMode(&runMode);<br>g_isDevice = (runMode == ACL_DEVICE);<br>// 3.加载单算子模型文件(*.om文件)<br>// 该目录相对可执行文件所在的目录,例如,编译出来的可执行文件存放在out目录下,此处就表示out/op_models目录<br>aclRet = aclopSetModelDir("op_models");<br>// 4.执行算子<br>// opType表示算子类型名称,例如Add<br>// numInputs表示算子输入个数,例如Add算子是2个输入<br>// inputDesc表示算子输入tensor描述的数组,描述每个输入的format、shape、数据类型<br>// inputs表示算子输入tensor数据<br>// numOutputs表示算子输出个数,例如Add算子是1个输出<br>// outputDesc表示算子输出tensor描述的数组,描述每个输出的format、shape、数据类型<br>// outputs表示算子输出tensor数据<br>// attr表示算子属性,如果算子没有属性,也需要调用aclopCreateAttr接口创建aclopAttr类型的数据<br>// stream用于维护一些异步操作的执行顺序<br>aclopExecuteV2(opType, numInputs, inputDesc, inputs, <br> numOutputs, outputDesc, outputs, attr, nullptr);<br>// 处理执行算子后的输出数据,例如在屏幕上显示、写入文件等,由用户根据实际情况自行实现<br>// ......<br>// 阻塞应用运行,直到指定Stream中的所有任务都完成<br>aclrtSynchronizeStream(nullptr);<br>// 5. 释放运行管理资源(默认Context、Stream无需用户释放,调用aclrtResetDevice接口后自动释放)<br>aclRet = aclrtResetDevice(0);<br>// 6.AscendCL去初始化<br>aclRet = aclFinalize();<br>// ....<br>

父主题: 单算子模型执行

在线提单