ARTICLE DETAIL

资讯详情

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

CANN opbase 动态地址更新 API aclSetDynamicTensorAddr 深度解析:复用 aclOpExecutor 场景下的输入输出张量地址刷新指南

CANN opbase 动态地址更新 API aclSetDynamicTensorAddr 深度解析:复用 aclOpExecutor 场景下的输入输出张量地址刷新指南 CANN opbase 动态地址更新 API aclSetDynamicTensorAddr 深度解析复用 aclOpExecutor 场景下的输入输出张量地址刷新指南【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase导读aclSetDynamicTensorAddr是 CANN opbase 算子库为单算子调用场景提供的一体化动态地址刷新接口。当开发者通过aclSetAclOpExecutorRepeatable将aclOpExecutor置为可复用状态后若算子输入或输出张量的设备内存地址发生变化即可调用本接口同步更新aclTensorList中记录的设备地址从而避免重复走GetWorkspaceSize阶段、以更小的开销循环执行算子。读完本文你将掌握该接口的原型、参数语义、错误码对照、源码级执行路径含输入输出分流与原地算子联动逻辑以及与其他地址更新接口aclSetDynamicInputTensorAddr、aclSetDynamicOutputTensorAddr、aclSetTensorAddr等的选型差异并能在实战代码中正确组织建张量 → 获取 workspace → 置为可复用 → 动态刷新地址 → 循环执行 → 销毁 executor的完整调用链。背景为什么要动态更新张量地址在 CANN opbase 的常规单算子调用流程中算子执行分为两个阶段第一阶段调用形如aclnnAddCustomGetWorkspaceSize的接口完成算子原型校验、shape 推导、workspace 规划并创建aclOpExecutor第二阶段调用aclnnAddCustom实际下发算子执行。对于推理、训练或推理引擎中频繁以同样算子、同样 shape、不同设备地址循环执行的场景每次循环都重新创建并销毁 executor 会带来可观的额外开销。为此opbase 提供了 executor 复用机制在完成第一阶段后调用 aclSetAclOpExecutorRepeatable 将 executor 置为可复用状态后续可以多次调用第二阶段接口执行算子。复用机制的代价是executor 内部记录的张量设备地址是缓存下来的。一旦某次循环中输入/输出张量的设备内存地址发生变化就必须显式通知 executor 更新地址否则算子会继续读写旧地址产生未定义行为。aclSetDynamicTensorAddr正是为这个场景设计的一体化刷新入口——它不区分输入还是输出由接口内部自动识别并分派同时支持aclTensorList动态张量列表内部的按位更新。接口原型与参数语义接口在头文件 include/nnopbase/aclnn/acl_meta.h 中声明同时提供大小写两种导出名aclSetDynamicTensorAddr与AclSetDynamicTensorAddr二者行为等价。aclnnStatus aclSetDynamicTensorAddr(aclOpExecutor *executor, size_t irIndex, const size_t relativeIndex, aclTensorList *tensors, void *addr)参数说明如下表参数输入/输出说明executor输入已被置为可复用状态的 aclOpExecutor即已调用过aclSetAclOpExecutorRepeatable。irIndex输入待更新的 aclTensorList 在算子原型定义中的参数序号从 0 开始计数。注意该序号是算子原型参数序号而非扁平化后的张量序号具体换算规则见下文源码分析。relativeIndex输入待更新的 aclTensor 在 aclTensorList 中的相对下标。若 aclTensorList 含 N 个张量取值区间为 [0, N – 1]。tensors输入待更新的 aclTensorList 指针输入列表或输出列表均可。addr输入要更新到指定 aclTensor 上的设备存储地址。该地址必须 32 字节对齐否则可能出现未定义错误。与 aclSetDynamicInputTensorAddr仅处理输入和 aclSetDynamicOutputTensorAddr仅处理输出不同aclSetDynamicTensorAddr是两者的统一入口传入同一个tensors参数接口内部会根据irIndex落在算子原型的输入区还是输出区自动决定走输入分支还是输出分支。这使得调用方无需在业务代码里单独维护输入/输出语义尤其适合原型中同时含多个 aclTensorList 形参的算子。返回值与错误码对照接口约定返回 0 表示成功否则失败。具体错误码含义可参考 Common API Return Codes与本接口强相关的错误码如下错误码常量名含义触发场景0ACLNN_SUCCESS成功正常完成地址更新。561103ACLNN_ERR_INNER_NULLPTR内部空指针executor或tensors为 nullptr。161002ACLNN_ERR_PARAM_INVALID参数校验失败relativeIndex≥tensors中张量个数或irIndex≥ 算子原型输入/输出参数个数。其中 561103 的校验在接口入口即完成见下文源码第 476、483 行的空指针断言161002 则分两层触发relativeIndex的越界在公共层acl_op_api.cpp第 477 行对tensors-Size()的比较和内核层individual_op_api.cpp中对instances[irIndex].num的比较都会被拦截irIndex的越界则在内核层依据输入/输出paramDescs.count判定。对照返回的错误码可以快速定位是传参为空、相对下标越界还是原型参数序号写错。源码级实现剖析aclSetDynamicTensorAddr的公共入口实现在 src/nnopbase/common/api/acl_op_api.cpp其执行路径可拆解为以下步骤日志与空指针校验先通过OP_LOGI记录 executor 地址与两个索引随后对tensors、executor、目标aclTensor依次做空指针断言任一为空即返回ACLNN_ERR_INNER_NULLPTR相对下标校验CHECK_COND((relativeIndex tensors-Size()), ...)确保相对下标不越界PCIe 地址刷新检查调用aclCheckPcieAddrRefresh(tensor, addr, addr)校验待写入地址的合法性失败返回ACLNN_ERR_PARAM_INVALID更新张量记录tensor-SetStorageAddr(addr)将新地址写入用户侧aclTensor的存储地址字段魔数鉴别与分派读取 executor 首地址处的魔数若为NNOPBASE_EXECUTOR_MAGIC_NUMBER即本仓库实现的 NnopbaseExecutor则调用NnopbaseDynamicIsInput判定irIndex属于输入还是输出并分别转入NnopbaseSetDynamicInputTensorAddr或NnopbaseSetDynamicOutputTensorAddr。内核层实现位于 src/nnopbase/individual_op/api/individual_op_api.cpp这里隐藏了两个值得注意的细节扁平索引换算算子原型参数列表中每个 aclTensorList 形参在 executor 内部被展开为多个扁平张量槽位。内核层通过instances[irIndex].startIndex relativeIndex将原型参数序号 列表内相对下标换算为扁平索引再对extTensors[index]做写入。这就是为什么文档强调irIndex是算子原型定义中的 aclTensorList 序号。原地算子的输出联动在NnopbaseSetDynamicInputTensorAddr中若该输入形参的refIdx ! -1表示该输入与某个输出共享存储即原地/inplace 语义接口会自动调用NnopbaseSetDynamicOutputTensorAddr将新地址同步到对应的输出槽位第 836-838 行。这意味着对原地算子只需刷新输入侧地址输出侧会被自动级联更新无需调用方重复刷新。空张量保护若目标槽位isNull为 true张量为空指针接口会记录一条带有算子类型与位置信息的执行错误日志并返回ACLNN_ERR_PARAM_INVALID防止向空张量写入地址。完整使用流程与代码示例下面示例取自 aclSetDynamicTensorAddr 英文文档代码仅供理解流程不可直接照抄运行。示例中的 AddCustom 算子原型包含两个输入aclTensorList、aclTensor和一个输出aclTensorList// 1. 创建输入输出张量与张量列表。 std::vectorint64_t shape {1, 2, 3}; aclTensor tensor1 aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor2 aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor3 aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor4 aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor5 aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor *list1[] {tensor1, tensor2}; auto tensorList aclCreateTensorList(list1, 2); aclTensor *list2[] {tensor4, tensor5}; auto output aclCreateTensorList(list2, 2); uint64_t workspaceSize 0; aclOpExecutor *executor; // 2. 第一阶段 API创建 executor 并获取 workspace 大小。 aclnnAddCustomGetWorkspaceSize(tensorList, tensor3, output, workspaceSize, executor); // 3. 将 executor 置为可复用状态必须在第一阶段之后立即调用。 aclSetAclOpExecutorRepeatable(executor); // 4. 循环体中每次更换设备地址后调用动态刷新接口。 void *addr; // irIndex0 对应原型的第 1 个参数输入 tensorList // relativeIndex 依次刷新其内部的第 0、1 个张量。 aclSetDynamicTensorAddr(executor, 0, 0, tensorList, addr); aclSetDynamicTensorAddr(executor, 0, 1, tensorList, addr); // 输出同样是 tensorList原型输出参数序号为 0同样按相对下标刷新。 aclSetDynamicTensorAddr(executor, 0, 0, output, addr); aclSetDynamicTensorAddr(executor, 0, 1, output, addr); ... // 5. 第二阶段 API执行算子可多次循环执行。 aclnnAddCustom(workspace, workspaceSize, executor, stream); // 6. 销毁 executor释放复用状态下的资源。 aclDestroyAclOpExecutor(executor);流程要点归纳时序强约束aclSetAclOpExecutorRepeatable必须紧跟第一阶段 API 调用aclSetDynamicTensorAddr则必须在该调用之后使用否则 executor 未进入可复用状态地址刷新语义不成立地址对齐传入的addr必须 32 字节对齐建议通过 CANN Runtime 的设备内存分配接口申请以保证对齐要求资源释放可复用状态的 executor 不会在第二阶段执行后自动清理内部资源必须显式调用 aclDestroyAclOpExecutor 完成销毁对应实现见 acl_op_api.cpp其中对 NnopbaseExecutor 走NnopbaseResetExecutor复位对旧版 executor 走delete释放。与相关地址接口的选型对比opbase 的aclnn元接口族还提供了多个地址更新接口适合不同场景接口面向对象适用场景aclSetTensorAddr单个 aclTensor原型参数本身就是单个张量形参传入全局扁平参数序号index内部通过NnopbaseIsInput自动判定输入/输出见 acl_op_api.cpp。aclSetInputTensorAddr单个 aclTensor明确指定刷新的是输入侧单个张量。aclSetOutputTensorAddr单个 aclTensor明确指定刷新的是输出侧单个张量。aclSetDynamicInputTensorAddraclTensorList算子原型含 aclTensorList 形参且只需刷新输入列表内的张量。aclSetDynamicOutputTensorAddraclTensorList算子原型含 aclTensorList 形参且只需刷新输出列表内的张量。aclSetDynamicTensorAddraclTensorList输入输出通用由接口自动按irIndex分派适合调用方不关心输入/输出语义、或同一循环中两者都要刷新的场景。选型建议若算子的输入输出都是固定个数的单个aclTensor用aclSetTensorAddr最简洁若涉及动态列表aclTensorList例如 shape 数量可变、批量大小动态变化则应使用*Dynamic*系列接口其中aclSetDynamicTensorAddr是覆盖输入和输出的统一选择。限制说明与注意事项文档明确指出该接口无额外限制Restrictions: None但结合 aclSetAclOpExecutorRepeatable 与仓库实现使用时仍需注意以下前提executor 复用能力取决于算子使用的计算单元目前仅使用 AI CPU 与 AI Core 计算单元的算子支持 executor 复用特定 L0 接口会破坏复用能力单算子 API 中若使用了与 host 到 device、device 到 device 拷贝相关的 L0 接口如 CopyToNpu、CopyNpuToNpu、CopyToNpuSync或使用了 ViewCopy 且源、目的地址相同则无法复用 executor单算子 API 内不能创建设备张量复用场景下只能使用外部传入的张量算子 API 内部创建的设备张量不参与地址动态刷新地址必须 32 字节对齐非对齐地址可能触发未定义错误这是文档明确给出的硬性约束可复用 executor 不自动回收需要与aclDestroyAclOpExecutor配套使用避免资源泄漏。测试与验证依据仓库的单元测试 tests/nnopbase/ut/individual_op/api_utest.cppNnopBaseTensorAddrSet用例对地址刷新内核接口做了完整验证可直接对照理解接口行为对合法索引(irIndex, relativeIndex)组合如输入(0,0)、(1,0)输出(0,0)断言返回OK对相对下标越界如(0,1)而该列表只有 1 个张量断言返回ACLNN_ERR_PARAM_INVALID对原型参数序号越界如输入(3,0)、输出(1,0)同样断言返回ACLNN_ERR_PARAM_INVALID。这组用例与文档中relativeIndex越界返回 161002、irIndex越界返回 161002的错误说明一一对应可作为功能正确性的行为基准读者在自己的工程中集成该接口后也可仿照该用例的索引矩阵做边界回归测试。总结aclSetDynamicTensorAddr是 CANN opbase executor 复用机制中面向aclTensorList的一体化地址刷新入口它用一个接口同时覆盖输入与输出内部通过原型参数序号 列表内相对下标完成定位并自动处理原地算子的输出级联。配合aclSetAclOpExecutorRepeatable与aclDestroyAclOpExecutor开发者可以构建一次构图、多次换址执行的高效单算子调用循环显著降低高频小算子场景下的调度开销。使用时请务必牢记 32 字节对齐约束与可复用 executor 的显式销毁要求并依据 Common API Return Codes 对返回值做防御性处理。【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表