在加载与执行动态Shape算子前,您需要参见《TBE&AI CPU算子开发指南》中的“专题 > TIK自定义算子动态Shape专题”中的说明开发自定义算子以及生成对应的二进制文件。
动态Shape、注册算子选择器场景下,算子加载与执行的流程如下:
算子选择器需由用户提前定义并实现:
typedef aclError (*aclopCompileFunc)(int numInputs, const aclTensorDesc *const inputDesc[], int numOutputs, const aclTensorDesc *const outputDesc[], const aclopAttr *opAttr, aclopKernelDesc *aclopKernelDesc);
用户自行编写代码逻辑实现Tiling策略选择、Tiling参数生成,并调用aclopSetKernelArgs接口,设置算子Tiling参数、执行并发数等。
若没有显式创建Stream,则使用默认Stream,默认Stream是在调用aclrtSetDevice接口时隐式创建的,默认Stream作为接口入参时,直接传NULL。
调用aclopUpdateParams接口编译指定算子,触发算子选择器的调用逻辑。
调用aclopExecuteV2接口加载并执行算子。
不涉及显式创建Stream,使用默认Stream时,无需调用aclrtDestroyStream接口释放Stream。
不涉及显式创建Context,使用默认Context时,无需调用aclrtDestroyContext接口释放Context。
在样例代码中,调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
动态Shape算子(注册算子选择器)样例的获取、编译运行请参见《TBE&AI CPU算子开发指南》中的“专题 > TIK自定义算子动态Shape专题>样例使用”。
#include "acl/acl.h" // ...... // 1.资源初始化 // 此处是相对路径,相对可执行文件所在的目录 aclError ret = aclInit(NULL); aclopRegisterCompileFunc("BatchNorm", SelectAclopBatchNorm); // 将算子Kernel的*.o文件需要用户提前编译好,并调用用户自定义函数该文件加载到内存buffer中,length表示内存大小,如果有多个算子Kernel的*.o文件,需要多次调用该接口 aclopCreateKernel("BatchNorm", "tiling_mode_1__kernel0", "tiling_mode_1__kernel0", buffer, length, ACL_ENGINE_AICORE, Deallocator); // -----自定义函数BatchNormTest(n, c, h, w),执行以下操作----- // 2.构造BatchNorm算子的输入输出Tensor、输入输出Tensor描述,并申请存放算子输入数据、输出数据的内存 aclTensorDesc *input_desc[3]; aclTensorDesc *output_desc[1]; input_desc[0] = aclCreateTensorDesc(ACL_FLOAT16, 4, shape_input, ACL_FORMAT_NCHW); input_desc[1] = aclCreateTensorDesc(ACL_FLOAT16, 1, shape_gamma, ACL_FORMAT_ND); input_desc[2] = aclCreateTensorDesc(ACL_FLOAT16, 1, shape_beta, ACL_FORMAT_ND); output_desc[0] = aclCreateTensorDesc(ACL_FLOAT16, 4, shape_out, ACL_FORMAT_NCHW); for (int i = 0; i < n * c * h * w; ++i) { input[i] = aclFloatToFloat16(1.0f); } for (int i = 0; i < c; ++i) { gamma[i] = aclFloatToFloat16(0.5f); beta[i] = aclFloatToFloat16(0.1f); } aclrtMalloc(&devInput, size_input, ACL_MEM_MALLOC_NORMAL_ONLY); aclrtMalloc(&devInput_gamma, size_gamma, ACL_MEM_MALLOC_NORMAL_ONLY); aclrtMalloc(&devInput_beta, size_beta, ACL_MEM_MALLOC_NORMAL_ONLY); aclrtMalloc(&devOutput, size_output, ACL_MEM_MALLOC_NORMAL_ONLY); // 3.将算子输入数据复制到Device上 aclrtMemcpy(devInput, size_input, input, size_input, ACL_MEMCPY_HOST_TO_DEVICE); aclrtMemcpy(devInput_gamma, size_gamma, gamma, size_gamma, ACL_MEMCPY_HOST_TO_DEVICE); aclrtMemcpy(devInput_beta, size_beta, beta, size_beta, ACL_MEMCPY_HOST_TO_DEVICE); // 4.调用aclopUpdateParams接口编译算子 aclopUpdateParams("BatchNorm", 3, input_desc, 1, output_desc, nullptr, ACL_ENGINE_AICORE, ACL_COMPILE_UNREGISTERED, nullptr)); // 5.调用aclopExecuteV2接口加载并执行算子 aclopExecuteV2("BatchNorm", 3, input_desc, inputs, 1, output_desc, outputs, nullptr, stream); // -----自定义函数BatchNormTest(n, c, h, w),执行以上操作----- // 6.获取算子运算的输出数据 aclrtMemcpy(output, size_output, devOutput, size_output, ACL_MEMCPY_DEVICE_TO_HOST); // 7.按顺序释放资源 // 7.1 释放算子的输入输出Tensor描述 for (auto desc : input_desc) { aclDestroyTensorDesc(desc); } for (auto desc : output_desc) { aclDestroyTensorDesc(desc); } // 7.2 及时释放不使用的内存 delete[]input; delete[]gamma; delete[]beta; delete[]output; // 7.3 释放Device上的内存 aclrtFree(devInput); aclrtFree(devInput_gamma); aclrtFree(devInput_beta)); aclrtFree(devOutput); // 7.4 依次释放Stream、Context、Device资源,如果未显式创建Stream、Context,则无需释放 aclrtDestroyStream(stream); aclrtDestroyContext(context); aclrtResetDevice(deviceId); aclFinalize();