CANN内置算子调用【免费下载链接】docs该仓库用于维护cann公共文档项目地址: https://gitcode.com/cann/docs基本介绍CANN算子库提供了一系列丰富且经过深度优化、硬件亲和的高性能内置算子可直接应用于AI业务。算子包括Device侧Kernel实现和Host侧调用两部分为方便调用每个算子都提供了相应的C语言API方便用户在Host侧调用下文简称单算子API或者算子Host API。本章主要阐述单算子API的调用其采用“两段式接口”形式以aclnn作为接口前缀命名风格为驼峰格式具体样式如下aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, ..., uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)其中_“Xxx”_表示对应的算子类型如Add算子。两段式接口具体作用如下第一段接口aclnn_Xxx_GetWorkspaceSize完成入参校验、动态Shape场景下推导输出Shape、数据切块Tiling以及计算执行过程中算子所需的workspace内存大小等任务。第二段接口aclnn_Xxx_executor执行器执行计算框架自动调用DFX例如Dump、溢出检测等、Runtime提供的LaunchKernel接口等_。_算子分类CANN算子主要包含如下几类Math类算子数值计算类算子提供不同维度的数值处理与计算算子例如Add、Abs等覆盖张量形态变换、基础数学运算、随机数生成等场景。NN类算子神经网络类算子Neural Network提供深度学习模型中常见的计算算子例如卷积、矩阵乘、激活函数、归一化等。CV类算子计算机视觉类算子Computer Vision提供图像处理和目标检测算子例如GridSample等。Transformer类算子大模型计算类算子提供Transformer核心算子例如Attention类、LayerNorm类、通算融合类简称MC2等。对于集合通信和MatMul计算融合、并行的算子统称为通算融合算子简称MC2算子如AllGatherMatmul、AlltoAllAllGatherBatchMatMul、BatchMatMulReduceScatterAlltoAll、MatMulAllReduce、MatMulAllReduceAddRmsNorm、MatMulReduceScatter等。关于算子IRIntermediate Representation规格介绍请参见《算子库》中“Ascend IR算子规格说明”关于算子对应的API文档介绍请参见《算子库》中“算子接口aclnn”。调用算子API时请include依赖必要的头文件和库文件其中${INSTALL_DIR}请替换为CANN软件安装后文件存储路径。以root用户安装为例安装后文件默认存储路径为/usr/local/Ascend/cann。头文件路径一般默认在“${INSTALL_DIR}/include/”目录可引用每类算子总头文件aclnnop/aclnn_ops_math.h、aclnnop/aclnn_ops_nn.h、aclnnop/aclnn_ops_cv.h、aclnnop/aclnn_ops_transformer.h也可以引用单个算子头文件aclnnop/aclnn_*.h*表示具体算子名。库文件路径一般默认在“${INSTALL_DIR}/lib64/”目录可引用每类算子总库文件libopapi_math.so、libopapi_nn.so、libopapi_cv.so、libopapi_transformer.so。注意从CANN 8.5.0版本开始全量算子总库文件libopapi.so要废弃请勿使用。如需进一步了解CANN算子库的实现请访问CANN开源项目学习算子源码的实现过程。单算子API调用流程图 1单算子API调用流程关键接口说明如下过程中使用的acl前缀接口详细介绍可参考《Runtime运行时 API》。**初始化**调用aclInit接口实现初始化。**运行时资源申请**调用aclrtSetDevice、aclrtCreateStream等接口依次申请运行时资源即Device、Context、Stream。数据内存申请和传输。调用aclrtMalloc接口申请Device上的内存存放待执行算子的输入、输出数据。调用aclCreateTensor、aclCreateIntArray等构造算子输入、输出参数相关接口详见《算子库》中“公共接口”。如果需要将Host上数据传输到Device则需要调用aclrtMemcpy接口同步接口或aclrtMemcpyAsync接口异步接口通过内存复制的方式实现数据传输。计算workspace并执行算子。调用acl_xx__Xxx_GetWorkspaceSize接口获取算子入参并计算该算子执行时需要的workspace大小。调用aclrtMalloc接口根据workspace大小申请Device侧内存。调用acl_xx__Xxx_接口执行计算并得到结果。调用aclrtSynchronizeStream接口阻塞应用运行直到指定Stream中的所有任务都完成。调用aclrtFree接口释放内存。如果需要将Device上的算子执行结果数据传输到Host则需要调用aclrtMemcpy接口同步接口或aclrtMemcpyAsync接口异步接口通过内存复制的方式实现数据传输然后再释放内存。运行时资源释放。调用aclDestroyTensor、aclDestroyIntArray等接口释放算子输入、输出参数相关接口详见《算子库》中“公共接口”。所有数据释放后调用aclrtDestroyStream、aclrtResetDevice等接口依次释放运行时资源即Stream、Context、Device。**去初始化**调用aclFinalize接口实现去初始化。示例代码以Add算子API调用为例介绍算子两段式接口调用的基本逻辑其他算子调用过程类似请根据实际情况自行修改。已知Add算子实现了张量加法运算计算公式为yx1αxx2。您可以获取如下示例代码并将文件命名为“test_add.cpp”代码如下#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_add.h #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vectorint64_t shape) { int64_t shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法初始化 auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclInit failed. ERROR: %d\n, ret); return ret); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSetDevice failed. ERROR: %d\n, ret); return ret); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); return ret); return 0; } template typename T int CreateAclTensor(const std::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMalloc failed. ERROR: %d\n, ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMemcpy failed. ERROR: %d\n, ret); return ret); // 计算连续tensor的strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. 固定写法device/stream初始化 // 根据自己的实际device填写deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); // check根据自己的需要处理 CHECK_RET(ret 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t selfShape {4, 2}; std::vectorint64_t otherShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* otherDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* other nullptr; aclScalar* alpha nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat otherHostData {1, 1, 1, 2, 2, 2, 3, 3}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; float alphaValue 1.2f; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建other aclTensor ret CreateAclTensor(otherHostData, otherShape, otherDeviceAddr, aclDataType::ACL_FLOAT, other); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建alpha aclScalar alpha aclCreateScalar(alphaValue, aclDataType::ACL_FLOAT); CHECK_RET(alpha ! nullptr, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的算子接口 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnAdd第一段接口 ret aclnnAddGetWorkspaceSize(self, other, alpha, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAddGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret;); } // 调用aclnnAdd第二段接口 ret aclnnAdd(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAdd failed. ERROR: %d\n, ret); return ret); // 4. 固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 5. 获取输出的值将device侧内存上的结果拷贝至host侧需要根据具体API的接口定义修改 auto size GetShapeSize(outShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy result from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i size; i) { LOG_PRINT(result[%ld] is: %f\n, i, resultData[i]); } // 6. 释放aclTensor和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyScalar(alpha); aclDestroyTensor(out); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }CMakeLists文件以Add算子编译为例CMake文件定义如下请根据实际情况自行修改。# CMake lowest version requirement cmake_minimum_required(VERSION 3.14) # 设置工程名 project(ACLNN_EXAMPLE) # Compile options add_compile_options(-stdc11) # 设置编译选项 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin) set(CMAKE_CXX_FLAGS_DEBUG -fPIC -O0 -g -Wall) set(CMAKE_CXX_FLAGS_RELEASE -fPIC -O2 -Wall) # 设置可执行文件名如opapi_test并指定待运行算子文件*.cpp所在目录 add_executable(opapi_test test_add.cpp) # 设置ASCEND_PATHCANN软件包目录请根据实际路径修改和INCLUDE_BASE_DIR头文件目录 if(NOT $ENV{ASCEND_CUSTOM_PATH} STREQUAL ) set(ASCEND_PATH $ENV{ASCEND_CUSTOM_PATH}) else() set(ASCEND_PATH /usr/local/Ascend/cann) endif() set(INCLUDE_BASE_DIR ${ASCEND_PATH}/include) include_directories( ${INCLUDE_BASE_DIR} ${INCLUDE_BASE_DIR}/aclnn ) # 设置链接的库文件路径 target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_${ops_project}.so) # 可执行文件在CMakeLists文件所在目录的bin目录下 install(TARGETS opapi_test DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})其中${ops_project}表示不同类型算子库请按需引用Math类算子引用对应libopapi_math.so。NN类算子引用libopapi_math.so和libopapi_nn.so。CV类算子引用libopapi_math.so和libopapi_cv.so。Transformer类算子引用libopapi_math.so和libopapi_transformer.so。调用MC2算子API时一般会涉及多线程和HCCLHuawei Collective Communication Library集合通信库因此CMake文件需要额外导入如下内容否则无法成功编译。# 设置链接的库文件路径 find_package(Threads REQUIRED) target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_${ops_project}.so ${ASCEND_PATH}/lib64/libhccl.so # 集合通信库文件 ${CMAKE_THREAD_LIBS_INIT}) # 多线程依赖的库文件“find_package(Threads REQUIRED)”是CMake用于查找线程库的命令可自动链接线程库依赖的头文件和间接依赖的其它库文件。编译与运行本节以开发和运行环境合设场景为例即带AI处理器的机器既作为开发环境又作为运行环境。该场景下代码开发与代码运行在同一台机器上。根据前文提供的示例代码、CMakeLists文件提前准备好算子的调用代码*.cpp和编译脚本CMakeLists.txt。配置环境变量。安装CANN软件后使用CANN运行用户登录环境执行如下命令生效环境变量。source /usr/local/Ascend/cann/set_env.sh编译并运行。进入CMakeLists.txt所在目录执行如下命令新建build目录存放生成的编译文件。mkdir -p build进入build所在目录执行cmake命令编译再执行make命令生成可执行文件。cd build cmake ../ -DCMAKE_CXX_COMPILERg -DCMAKE_SKIP_RPATHTRUE make编译成功后会在build目录的bin文件夹下生成opapi_test可执行文件。进入bin目录运行可执行文件opapi_test。cd bin ./opapi_test以Add算子的运行结果为例运行后的结果如下【免费下载链接】docs该仓库用于维护cann公共文档项目地址: https://gitcode.com/cann/docs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考