Skip to main content

执行固定Shape算子示例代码

调用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>

父主题: 单算子模型执行

执行动态Shape算子示例代码

基本原理

对于支持动态Shape的算子:

示例代码

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

<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>cpp<br>// ......<br>const char *opType;<br>int numInputs;<br>aclTensorDesc *inputDesc[2];<br>aclDataBuffer *inputs[2];<br>int numOutputs;<br>aclTensorDesc *outputDesc[1];<br>aclopAttr *attr;<br>aclError ret = aclopInferShape(opType, numInputs, inputDesc, inputs,numOutputs, outputDesc, attr);<br>std::vector<std::vector<int64_t>> tensorDims; // inferShape之后的输出tensor的Shape<br>// 循环算子的每一个输出,推导或预估Shape值:<br>for (int index = 0; index < numOutputs; ++index) &#123;<br> std::vector<int64_t> dimSize; // 表示执行算子时,输出的shape<br> size_t dimNums = aclGetTensorDescNumDims(outputDesc[index]);<br> // 表示动态Shape场景下维度个数未知,该场景预留<br> if (dimNums == ACL_UNKNOWN_RANK) &#123;<br> // 由用户预估最大Shape值max shape<br> dimSize.push_back(max_shape);<br> &#125; else &#123;<br> for (size_t i = 0; i < dimNums; ++i) &#123;<br> int64_t dim;<br> ret = aclGetTensorDescDimV2(outputDesc[index], i, &dim);<br> // 表示动态Shape场景下维度值是动态的<br> if(dim == -1) &#123;<br> int64_t dimRange[2];<br> // 获取Shape范围,使用该范围中的Shape最大值来构造输出tensorDesc,作为aclopExecuteV2的输入<br> ret = aclGetTensorDescDimRange(outputDesc[index], i, 2, dimRange);<br> dim = dimRange[1];<br> &#125;<br> dimSize.push_back(dim);<br> &#125;<br> &#125;<br> tensorDims.push_back(dimSize);<br>&#125;<br>// 构造算子输入tensorDesc和输入tensor,作为aclOpExecuteV2的输入<br>aclTensorDesc *inputDescNew[2];<br>aclDataBuffer *inputsNew[2];<br>aclDataBuffer *outputsNew[1];<br>// 以上给出了执行算子时输出的shape, 根据tensorDims中的dims构造输出tensorDesc(即outputDescNew参数值), 用于调用aclopExecuteV2<br>ret = aclopExecuteV2(opType, numInputs, inputDescNew, inputsNew, numOutputs, outputDescNew, outputsNew, attr, stream);<br>// 针对上面用户预估Shape值以及使用Shape范围中的最大Shape的场景,在算子执行结束后,需增加下面的调用,获取准确的shape:<br>// for 循环每一个输出的tensorDesc<br>std::vector<std::vector<int64_t>> outTensorDims; // 准确的输出tensorShape<br>for (int index = 0; index < numOutputs; ++index) &#123;<br> std::vector<int64_t> dimSize;<br> int dimNums = aclGetTensorDescNumDims(outputDescNew[index]);<br> for (int i = 0; i < dimNums; i++)&#123;<br> int64_t dim;<br> ret = aclGetTensorDescDimV2(outputDescNew[index], i, &dim);<br> dimSize.push_back(dim);<br> &#125;<br> outTensorDims.push_back(dimSize);<br>&#125;<br>// ......<br>

父主题: 单算子模型执行

内存二次分配管理

用户内存管理有两种管理方式:

  • 独立内存管理,根据需要单独申请所需的内存,内存不做拆分或者二次分配。
  • 内存池管理内存,用户一次性申请一块较大内存,并在使用时从这块较大内存中二次分配所需内存。

在内存二次分配时,使用如下接口从内存池申请对应内存,由于接口对申请的内存地址、大小有约束,在内存池管理时,需要关注,否则容易出现内存越界。

内存管理的总体说明请参见总体说明

接口用途输入内存/输出内存
aclrtMemcpyAsync实现异步内存复制。- 调用本接口进行内存复制时,源地址和目的地址都必须64字节对齐。
aclrtMalloc在Device上申请size大小的线性内存,该接口对用户申请的size向上对齐成32字节整数倍后再多加32字节,并通过*devPtr返回已分配内存的指针。同步接口。- 若用户使用本接口申请大块内存并自行划分、管理内存时,建议使用aclrtMallocAlign32接口,该接口相比aclrtMalloc接口,只会对用户申请的size向上对齐成32字节整数倍,不会再多加32字节。 不管是aclrtMalloc接口,还是aclrtMallocAlign32接口,每段内存需同时满足以下需求:
- 内存大小向上对齐成32整数倍+32字节(m=ALIGN_UP[len,32]+32字节);
- 内存起始地址需满足64字节对齐(ALIGN_UP[m,64])。(说明:len表示某段内存的大小,ALIGN_UP[len,k]表示向上按k字节对齐:((len-1)/k+1)*k。)
acldvppMalloc该接口主要用于分配内存给Device侧媒体数据处理时使用,申请的大页内存满足数据处理的要求(例如,内存首地址128字节对齐),同步接口。 媒体数据处理各功能的详细介绍请参见媒体数据处理V1媒体数据处理的输出作为模型推理的输入时,若用户使用本接口申请大块内存并自行划分、管理内存时,每段内存需同时满足以下要求:
- 内存大小向上对齐成32整数倍+32字节(m=ALIGN_UP[len,32]+32字节);
- 内存起始地址需满足128字节对齐(ALIGN_UP[m,128])。
(说明:len表示某段内存的大小,ALIGN_UP[len,k]表示向上按k字节对齐:((len-1)/k+1)*k。)
hi_mpi_dvpp_malloc申请Device上的内存,申请的内存满足媒体数据处理的要求(例如,内存首地址128字节对齐)。 媒体数据处理各功能的详细介绍请参见媒体数据处理V2媒体数据处理的输出作为模型推理的输入时,若用户使用本接口申请大块内存并自行划分、管理时,每段内存需同时满足以下要求:
- 内存大小向上对齐成32整数倍+32字节(m=ALIGN_UP[len,32]+32字节);
- 内存起始地址需满足128字节对齐(ALIGN_UP[m,128])。
(说明:len表示某段内存的大小,ALIGN_UP[len,k]表示向上按k字节对齐:((len-1)/k+1)*k。)
aclrtMallocHost应用在Host上运行时,调用该接口申请的是Host内存,由系统保证内存首地址64字节对齐。应用在Device上运行时,调用该接口申请的是Device内存,且Device上的内存按普通页申请,如需首地址64字节对齐,需要用户自行处理对齐。同步接口。- 若用户使用本接口申请大块内存并自行划分、管理内存时,每段内存需同时满足以下需求:
- 内存大小向上对齐成32整数倍+32字节(m=ALIGN_UP[len,32]+32字节);
- 内存起始地址需满足64字节对齐(ALIGN_UP[m,64])。(说明:len表示某段内存的大小,ALIGN_UP[len,k]表示向上按k字节对齐:((len-1)/k+1)*k。)
aclrtMallocCached在Device上申请size大小的线性内存,通过*devPtr返回已分配内存的指针,该接口在任何场景下申请的内存都是支持cache缓存。同步接口。其它约束与aclrtMalloc接口相同。

在计算机视觉领域,一般涉及使用媒体数据处理功能,因此会涉及以上多种内存申请接口,内存首地址涉及64字节或128字节对齐,为方便统一管理,内存首地址对齐值建议选取较大的,比如内存首地址128字节对齐。

关于媒体数据处理时自行管理内存时的典型场景如下,媒体数据处理的功能点介绍请参见媒体数据处理V1

图1 VDEC场景

图2 JPEGD场景

父主题: 扩展更多特性

在线提单