ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CANN ops-nn 两段式接口(aclnn API)深度解析:从 GetWorkspaceSize 到算子执行

CANN ops-nn 两段式接口(aclnn API)深度解析:从 GetWorkspaceSize 到算子执行 CANN ops-nn 两段式接口aclnn API深度解析从 GetWorkspaceSize 到算子执行【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn导读两段式接口是 CANN 神经网络算子库ops-nn中基于单算子 aclnn API 调用算子的统一编程范式先通过aclxxXxxGetWorkspaceSize计算本次调用所需的 workspace 临时内存大小再调用aclxxXxx真正下发计算。本文以仓库中的 两段式接口说明 为骨架结合 AddExample 完整调用样例 与 aclnnAddRelu 接口实现源码完整讲解两段式接口的调用规范、workspace 内存管理、底层实现原理与常见错误排查方法帮助开发者正确、高效地在业务工程中集成任意 aclnn 算子。一、什么是两段式接口在 CANN 生态中Host 侧为算子提供以aclnn为前缀的 C 语言 API无需提供算子 IRIntermediate Representation定义即可直接调用算子。这类单算子 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 与 Xxx两段式接口的命名遵循固定规律aclxx表示算子接口前缀最常见的取值是aclnn代表基于 AscendCL 的神经网络算子 API。仓库中所有内置算子与示例算子均遵循该前缀例如aclnnAddExample、aclnnAddRelu、aclnnInplaceAddRelu。Xxx表示对应的算子类型如Add表示加法算子Relu表示线性整流算子。以 Add 为例两段式接口即为aclnnAddGetWorkspaceSize(...)与aclnnAdd(...)。以仓库中的 AddExample 示例算子为例即为aclnnAddExampleGetWorkspaceSize(...)与aclnnAddExample(...)。三、第一段接口aclxxXxxGetWorkspaceSize3.1 接口职责第一段接口aclxxXxxGetWorkspaceSize用于计算本次 API 调用过程中需要多少 workspace 内存。其典型参数构成如下参数方向说明src、out等输入/输出算子的输入与输出张量aclTensor*具体取决于算子定义workspaceSize输出返回本次计算所需的临时内存大小单位字节executor输出返回创建的aclOpExecutor执行器对象指针3.2 workspace 是什么根据 两段式接口 的说明workspace 是指除输入/输出外算子在 NPU 上完成计算所需要的临时内存workspaceSize表示临时内存的大小。也就是说workspace 是算子内部计算过程中用于中转数据的额外缓冲区既不属于用户传入的输入张量也不属于最终写回的输出张量而是算子框架为完成计算临时申请的中间存储。并非所有算子都需要 workspace——例如 AddExample 这样的逐元素算子其 workspace 大小在 tiling 阶段被设置为 0见下文源码分析。3.3 第一段接口内部做了什么从源码看第一段接口并不仅仅是算一个数字而是完成了算子执行的绝大部分准备工作。以 aclnnAddReluGetWorkspaceSize 为例其内部流程为创建 OpExecutor调用框架的CREATE_EXECUTOR()固定写法创建执行器参数检查通过CheckParams校验输入输出张量的合法性如空指针、类型是否满足推导关系等输入规整调用l0op::Contiguous将非连续的输入张量转换为连续张量构图通过l0op系列底层算子接口完成中间计算图如 Add → Cast → Relu → ViewCopy的搭建获取 workspace 大小通过uniqueExecutor-GetWorkspaceSize()汇总计算过程中所有中间算子所需的临时内存写入*workspaceSize移交执行器通过uniqueExecutor.ReleaseTo(executor)将内部持有的 executor 所有权转移给调用方。可见第一段接口实际上完成了参数校验 计算图构建 workspace 规划是第二段接口能够安全执行的前提。四、第二段接口aclxxXxx 执行计算获取到workspaceSize后必须先按照 workspaceSize 在 NPU 侧申请内存再调用第二段接口执行计算aclnnStatus aclxxXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);第二段接口各参数含义参数说明workspace调用方按workspaceSize申请的 device 侧内存指针workspaceSize与第一段接口返回的大小保持一致executor第一段接口返回的aclOpExecutor执行器stream异步任务所在的aclrtStream流从 aclnnAddRelu 的实现可以看到第二段接口本身非常简单——通过框架的CommonOpExecutorRun(workspace, workspaceSize, executor, stream)固定写法将之前构建好的计算图与 workspace 一起提交到指定 stream 上完成 NPU 计算。关于执行器对象的生命周期数据结构 中有明确说明通常调用算子一阶段接口aclxxXxxGetWorkspaceSize时框架会自动创建aclOpExecutor调用二阶段接口aclxxXxx后会自动释放该对象。因此调用方不需要也不应该手动释放 executor。五、完整调用流程实战以 AddExample 为例仓库中的 test_aclnn_add_example.cpp 是两段式接口的标准落地样例完整展示了初始化 → 构造张量 → 两段式调用 → 校验结果的全过程。5.1 环境初始化// 固定写法acl 初始化、设置 device、创建 stream auto ret aclInit(nullptr); ret aclrtSetDevice(deviceId); ret aclrtCreateStream(stream);5.2 构造输入输出 aclTensor使用aclrtMalloc申请 device 侧内存、aclrtMemcpy将 Host 数据拷贝到 device再通过aclCreateTensor创建aclTensor见样例中的CreateAclTensor辅助函数test_aclnn_add_example.cpp。样例中 AddExample 的输入输出 shape 均为{32, 4, 4, 4}数据类型为ACL_FLOAT。5.3 两段式接口调用核心uint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段接口计算 workspace 大小并获取 executor ret aclnnAddExampleGetWorkspaceSize(selfX, selfY, out, workspaceSize, executor); // 根据第一段接口计算出的 workspaceSize 申请 device 内存 void* workspaceAddr nullptr; if (workspaceSize static_castuint64_t(0)) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // 第二段接口执行计算 ret aclnnAddExample(workspaceAddr, workspaceSize, executor, stream);这段代码test_aclnn_add_example.cpp是全仓库算子样例的通用模板需要注意两点样例中使用智能指针std::unique_ptrvoid, aclError (*)(void*) workspaceAddrPtr(nullptr, aclrtFree)管理 workspace 内存配合workspaceAddrPtr.reset(workspaceAddr)实现自动释放当workspaceSize 0时不申请内存workspace直接传nullptr即可这是所有 aclnn 样例的统一约定。5.4 同步等待与结果校验// 固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); // 将 device 侧结果拷贝回 Host 并打印 PrintOutResult(outShape, outDeviceAddr, selfXHostData, selfYHostData);最后在main中完成资源释放aclrtDestroyStream、aclrtResetDevice、aclFinalize。六、从算子侧看 workspace 来源tiling 阶段两段式接口中的workspaceSize并非凭空而来它由算子在 Host 侧的 tiling分块调度阶段计算并上报。以 AddExample 的 tiling 实现 add_example_tiling.cpp 为例ge::graphStatus GetWorkspaceSize(gert::TilingContext* context) { size_t* currentWorkspace context-GetWorkspaceSizes(1); currentWorkspace[0] WS_SYS_SIZE; // WS_SYS_SIZE 0 return ge::GRAPH_SUCCESS; }AddExample 是逐元素加法算子不需要额外的临时缓冲因此 tiling 阶段将 workspace 大小上报为 0add_example_tiling.cpp。而复杂算子如涉及转置、归约、量化等中间结果的算子则会在 tiling 阶段根据 UB统一缓冲区大小、AI Core 数量、数据形状等计算真实的 workspace 需求。从源码结构可以推断workspace 的大小 算子在 NPU 上执行时所有中间临时缓冲的总和它由 tiling 阶段规划、由第一段接口透传、由调用方按值申请、由第二段接口消费。理解了这条链路就理解了两段式接口为何要先算大小、再申请、再执行。七、必须遵守的调用规范7.1 顺序与单次执行约束两段式接口的调用顺序不可颠倒且第二段接口aclxxXxx(...)不能重复调用。如下调用方式会出现异常aclxxXxxGetWorkspaceSize(...) aclxxXxx(...) aclxxXxx(...) // 错误executor 已被第二段接口释放重复调用行为未定义原因是第一段接口返回的aclOpExecutor是一次性的执行器对象第二段接口执行完毕后会自动释放该对象再次调用时 executor 已失效。7.2 内存申请与释放workspace 内存必须使用 device 侧内存如aclrtMalloc申请不能使用 Host 内存申请大小必须与第一段接口返回的workspaceSize完全一致当workspaceSize为 0 时workspace传入nullptrworkspace 内存由调用方负责释放与输入输出张量的 device 内存一样建议用智能指针管理以避免泄漏。7.3 返回码检查两段式接口的每一步都应检查返回状态码。常见返回码及含义参见 aclnn 返回码状态码名称状态码值说明ACLNN_SUCCESS0成功ACLNN_ERR_PARAM_NULLPTR161001参数中存在非法的 nullptrACLNN_ERR_PARAM_INVALID161002参数校验错误如输入数据类型不满足推导关系ACLNN_ERR_RUNTIME_ERROR361001API 内部调用 NPU runtime 接口异常ACLNN_ERR_INNER_XXX561xxxAPI 内部异常如 tiling 错误、找不到 kernel 二进制等当返回码异常时可通过aclGetRecentErrMsg接口获取详细异常信息依据报错提示排查问题。八、两段式接口的完整调用链路两段式接口在整体调用流程中的位置可参见 aclnn 调用原理图从整体链路看两段式接口之上还需要前置的 ACL 环境初始化aclInit/aclrtSetDevice/aclrtCreateStream与张量构造aclCreateTensor之后才是第一段接口算 workspace → 申请 workspace 内存 → 第二段接口执行 → 同步流 → 回收结果的两段式主体最后以aclrtDestroyStream/aclrtResetDevice/aclFinalize收尾。九、编译与运行编写完两段式调用代码后有两种方式可以编译运行方式一快速验证推荐。直接使用项目build.sh执行已有算子的样例无需搭建工程bash build.sh --run_example ${op} eager # 以 AddExample 为例bash build.sh --run_example add_example eager其中${mode}为eager时即对应 aclnn 两段式调用方式graph则对应图模式。详细参数说明参见 算子调用。方式二业务工程集成。自行创建调用工程新建${test_aclnn_op_name}.cpp、CMakeLists.txt与run.sh通过 CMake 编译并链接算子库。链接内置算子时需依赖libopapi_nn.so内置算子库、libascendcl.so、libnnopbase.so头文件路径需包含${ASCEND_PATH}/include与${ASCEND_PATH}/include/aclnnop调用自定义算子时则改为链接自定义算子包中的libcust_opapi.soCMake 完整示例同样参见 算子调用。运行成功后AddExample 样例会打印计算结果例如add_example first input[0] is: 1.000000, second input[0] is: 1.000000, result[0] is: 2.000000十、常见问题与排查建议问题现象可能原因排查方向第一段接口返回ACLNN_ERR_PARAM_NULLPTR/ACLNN_ERR_PARAM_INVALID张量指针为空、数据类型或 shape 不满足算子约束检查aclTensor构造与算子参数要求返回ACLNN_ERR_INNER_TILING_ERRORtiling 阶段异常核对输入 shape、dtype 是否在算子支持范围内返回ACLNN_ERR_INNER_FIND_KERNEL_ERROR未找到算子 kernel 二进制确认算子包内置libopapi_nn.so或自定义算子包已正确安装并配置ASCEND_OPP_PATH等环境变量第二段接口执行异常或崩溃workspace 未按workspaceSize申请、executor 被重复使用、stream 无效核对两段式调用顺序与内存申请逻辑结语两段式接口是 CANN ops-nn 算子库 aclnn API 的统一调用范式第一段aclxxXxxGetWorkspaceSize完成参数校验、构图与 workspace 规划第二段aclxxXxx在调用方提供的 workspace 上真正执行计算。掌握命名规则 → 接口职责 → workspace 生命周期 → 调用顺序约束这条主线即可举一反三地调用仓库中任何 aclnn 算子。建议结合 AddExample 样例 与 aclnnAddRelu 源码 动手实践并参考 算子调用指南 完成编译验证。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表