CANN ops-transformer 两段式接口(aclnn API)调用机制与实践指南
【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer
两段式接口是 CANN ops-transformer 中基于单算子 API 执行方式调用算子时的标准范式:先调用aclxxXxxGetWorkspaceSize获取本次调用所需的 workspace 临时内存大小,再按需申请 NPU 内存,最后调用aclxxXxx完成实际计算。本文以仓库中真实算子接口(如 AttentionUpdate、AddExample)及其头文件、示例代码为证据,系统讲解两段式接口的调用流程、参数含义、内存管理、异常排查与编译运行方法,帮助开发者写出正确、可复用的 aclnn API 调用代码。
两段式接口概述
在 CANN ops-transformer 中,基于单算子 API 执行方式调用算子 API 时,通常分为"两段式",样式形如:
aclnnStatus aclxxXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, ..., uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclxxXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);其中:
aclxx表示算子接口前缀,例如aclnn;Xxx表示对应的算子类型,例如 Add 算子对应aclnnAdd、AttentionUpdate 算子对应aclnnAttentionUpdate。
两段接口的协作关系为:必须先调用第一段接口aclxxXxxGetWorkspaceSize,用于计算本次 API 调用过程中需要多少 workspace 内存,获取到计算所需的workspaceSize后,按照workspaceSize申请 NPU 内存,然后调用第二段接口aclxxXxx执行计算。
说明:
workspace 是指除输入/输出外,算子在 NPU 上完成计算所需要的临时内存,
workspaceSize表示临时内存的大小。第二段接口
aclxxXxx(...)不能重复调用,如下调用方式会出现异常:aclxxXxxGetWorkspaceSize(...) aclxxXxx(...) aclxxXxx(...) // 重复调用会异常
第一段接口:aclxxXxxGetWorkspaceSize
第一段接口负责"规划"。它基于输入/输出张量、算子属性以及设备信息,完成参数校验、输出 shape 推导、tiling 计算,并给出执行本次算子计算所需的最小 workspace 大小,同时创建算子执行器aclOpExecutor。
以仓库中 AttentionUpdate 算子为例,其第一段接口声明位于 attention/attention_update/op_host/op_api/aclnn_attention_update.h:
ACLNN_API aclnnStatus aclnnAttentionUpdateGetWorkspaceSize( const aclTensorList *lse, const aclTensorList *localOut, int64_t updateType, aclTensor *out, aclTensor *lseOut, uint64_t *workspaceSize, aclOpExecutor **executor);其参数语义(与仓库头文件注释一致):
| 参数 | 方向 | 说明 |
|---|---|---|
lse | in | NPU device 侧的aclTensorList,数据类型支持 FLOAT32,数据格式支持 ND |
localOut | in | NPU device 侧的aclTensorList,数据类型支持 FLOAT32、FLOAT16、BFLOAT16,数据格式支持 ND |
updateType | in | int64_t,控制lseOut是否输出,支持 0、1,分别表示不输出 / 输出 lseOut |
out | out | NPU device 侧的aclTensor,数据类型支持 FLOAT32、FLOAT16、BFLOAT16,数据格式支持 ND |
lseOut | out | NPU device 侧的aclTensor,作为 lse_m 可选输出,数据类型支持 FLOAT32 |
workspaceSize | out | 返回用户需要在 NPU device 侧申请的 workspace 大小 |
executor | out | 返回 op 执行器,包含算子计算流程 |
从 attention/attention_update/op_host/op_api/aclnn_attention_update.cpp 的源码实现可以看到,第一段接口内部会做一系列校验与推导,例如CheckNotNull逐项检查lse、localOut、out及列表内每个张量不为空指针,CheckDtypeValid依据ATTENTION_UPDATE_DTYPE_SUPPORT_LIST等支持列表校验输入数据类型是否合法,这些正是两段式接口"提前暴露参数问题"的价值所在——参数错误会在 GetWorkspaceSize 阶段即被拦截并返回错误码,而不是等到真正执行时才失败。
第二段接口:aclxxXxx 执行计算
第二段接口负责"执行"。它接收第一段接口返回的workspace指针、workspaceSize、executor以及用户指定的aclrtStream流,将算子计算任务下发到 NPU。
ACLNN_API aclnnStatus aclnnAttentionUpdate(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);其中executor是框架定义的一种执行器数据结构,用来执行算子计算的容器。通常调用第一段接口aclxxXxxGetWorkspaceSize时,框架会自动创建aclOpExecutor;调用第二段接口aclxxXxx后会自动释放该对象(参见 docs/zh/context/data_structure.md 中对aclOpExecutor的说明)。
这里需要特别强调的是原文档中的警告:第二段接口aclxxXxx不能重复调用。因为executor在一次调用生命周期内只能消费一次,重复调用会触发ACLNN_ERR_INNER_NOT_TRANS_EXECUTOR(561102,API 内部未调用 uniqueExecutor ReleaseTo)等异常。正确的做法是:一次GetWorkspaceSize+ 一次Xxx严格配对使用。
workspace 内存的申请与释放
workspaceSize返回的是字节大小,用户需要自行在 NPU device 侧通过aclrtMalloc申请内存,并在计算结束后通过aclrtFree释放。仓库示例 attention/attention_update/examples/test_aclnn_attention_update.cpp 中的标准写法为:
uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; ret = aclnnAttentionUpdateGetWorkspaceSize(lseList, localOutList, update_type, out, nullptr, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAttentionUpdateGetWorkspaceSize 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); } // 调用第二段接口执行计算 ret = aclnnAttentionUpdate(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAttentionUpdate failed. ERROR: %d\n", ret); return ret);要点:
- 必须判空:
workspaceSize > 0时才需要申请内存,若为 0 则传nullptr即可; - 内存属性:使用
ACL_MEM_MALLOC_HUGE_FIRST标志申请 NPU 侧大页内存; - 释放时机:必须在流同步(
aclrtSynchronizeStream)之后释放,避免任务尚未完成时内存被回收; - 使用智能指针(如
std::unique_ptr<void, aclError (*)(void *)>)管理workspaceAddr可以在异常分支自动释放,示例工程中即采用了该模式。
完整的 aclnn API 调用流程
两段式接口并非孤立的两个函数,而是嵌入在一整套 aclnn API 调用流程中。仓库文档 docs/zh/invocation/quick_op_invocation.md 给出了完整的调用流程图:
完整调用步骤可归纳为以下七步(以仓库示例 examples/add_example/examples/test_aclnn_add_example.cpp 和 AttentionUpdate 示例中的通用骨架为参考):
- 初始化 AscendCL:调用
aclInit、aclrtSetDevice、aclrtCreateStream完成 device/stream 初始化; - 构造输入与输出:使用
aclrtMalloc申请张量内存、aclrtMemcpy搬运数据,再调用aclCreateTensor创建aclTensor(多个张量可组合为aclTensorList); - 调用第一段接口
aclxxXxxGetWorkspaceSize(...),得到workspaceSize与executor; - 申请 workspace 内存:按
workspaceSize调用aclrtMalloc申请 device 内存; - 调用第二段接口
aclxxXxx(workspaceAddr, workspaceSize, executor, stream); - 同步等待:调用
aclrtSynchronizeStream(stream)等待异步任务执行结束(固定写法); - 获取输出并释放资源:将 device 侧结果
aclrtMemcpy拷回 host 侧打印,依次aclDestroyTensor、aclrtFree、aclrtDestroyStream、aclrtResetDevice、aclFinalize。
其中第 3~5 步即"两段式接口"的核心部分,其余步骤为 aclnn API 调用的通用外壳。仓库中的算子示例(如 attention/attention_update/examples/test_aclnn_attention_update.cpp)均按此骨架组织,可作为新算子调用的模板。
返回码与异常排查
两段式接口的每一段都会返回aclnnStatus状态码。常见返回码如下(详见 docs/zh/context/aclnn_return_code.md):
| 状态码名称 | 状态码值 | 状态码说明 |
|---|---|---|
| ACLNN_SUCCESS | 0 | 成功 |
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 参数校验错误,参数中存在非法的 nullptr |
| ACLNN_ERR_PARAM_INVALID | 161002 | 参数校验错误,如输入的两个数据类型不满足输入类型推导关系 |
| ACLNN_ERR_RUNTIME_ERROR | 361001 | API 内部调用 npu runtime 的接口异常 |
| ACLNN_ERR_INNER_XXX | 561xxx | API 内部发生异常 |
其中与两段式接口生命周期强相关的内部异常码包括:
ACLNN_ERR_INNER_INFERSHAPE_ERROR(561001):API 内部进行输出 shape 推导发生错误;ACLNN_ERR_INNER_TILING_ERROR(561002):API 内部做 npu kernel 的 tiling 时发生异常;ACLNN_ERR_INNER_CREATE_EXECUTOR(561101):API 内部创建aclOpExecutor失败(可能因为操作系统异常);ACLNN_ERR_INNER_NOT_TRANS_EXECUTOR(561102):API 内部未调用 uniqueExecutor ReleaseTo(典型场景即重复调用第二段接口);ACLNN_ERR_INNER_OPP_KERNEL_PKG_NOT_FOUND(561112):没有加载到算子的二进制 kernel 库。
排查方法:对于异常状态码值,可通过aclGetRecentErrMsg接口获取具体的错误信息。仓库文档 docs/zh/context/compile_and_run_sample.md 给出了实际排查示例——构造空指针q调用aclnnFlashAttentionScoreGetWorkspaceSize后:
aclnnFlashAttentionScoreGetWorkspaceSize failed. ERROR: 161001 [ERROR msg][PID:xxxx] xxx(timestamp) AclNN_Parameter_Error(EZ1001): The query cannot be nullptr.可以看到,返回码 161001(ACLNN_ERR_PARAM_NULLPTR)配合aclGetRecentErrMsg能精确定位到具体是哪个参数为空,这正是两段式接口第一段"参数校验前置"优势的体现。
编译与运行两段式接口示例
编写好包含两段式接口调用的.cpp文件后,需要创建 CMakeLists.txt 并链接算子库。对于本项目标准内置算子(依赖 ops-transformer 整包),CMake 关键配置如下(完整示例见 docs/zh/context/compile_and_run_sample.md):
cmake_minimum_required(VERSION 3.18.4) project(ACLNN_EXAMPLE) add_compile_options(-std=c++11) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./bin") if(NOT "$ENV{ASCEND_HOME_PATH}" STREQUAL "") set(ASCEND_PATH $ENV{ASCEND_HOME_PATH}) else() set(ASCEND_PATH "/usr/local/Ascend/cann") endif() set(INCLUDE_BASE_DIR "${ASCEND_PATH}/include") include_directories( ${INCLUDE_BASE_DIR} ${ASCEND_PATH}/include/aclnnop ) add_executable(opapi_test test_aclnn_xxx.cpp) target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_math.so ${ASCEND_PATH}/lib64/libopapi_transformer.so ${ASCEND_PATH}/lib64/libc_sec.so)注意:本项目编译生成算子样例时,需额外链接libopapi_math.so动态库,因为算子在实现过程中调用了部分 L0 接口,这些接口封装在libopapi_math.so中,因此在编译链接阶段需显式声明此项依赖。若调用自定义算子(如 experimental 贡献目录下算子),则需将libopapi_transformer.so替换为自定义算子包中的libcust_opapi.so,并增加${ASCEND_PATH}/opp/vendors/${vendor_name}_transformer/op_api/include头文件路径(${vendor_name}默认为custom)。
编译运行流程:
# 1. 生效 CANN 环境变量(${INSTALL_DIR} 为 CANN 软件安装路径) source ${INSTALL_DIR}/set_env.sh # 2. 新建 build 目录并编译 mkdir -p build && cd build cmake ../ -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUE make # 3. 运行生成的可执行文件 cd bin ./opapi_test若执行结果报错、未出现预期结果,可使用aclGetRecentErrMsg接口获取报错具体信息(调用方式见上文"返回码与异常排查"一节)。
小结
两段式接口是 CANN ops-transformer 中所有 aclnn API 的统一调用范式:GetWorkspaceSize负责参数校验、shape 推导与 workspace 规划,Xxx负责执行计算。正确使用两段式接口的关键在于:严格先调第一段、按返回的workspaceSize申请内存、第二段只调用一次、计算完成后再释放内存,并结合aclGetRecentErrMsg与返回码表进行问题定位。读者可进一步阅读仓库中的 两段式接口(本文依据)、数据结构、算子调用总览 与 编译运行样例,并结合各算子目录下的examples/test_aclnn_*.cpp示例文件加深理解。
【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考