CANN opbase 算子输出声明宏 OP_OUTPUT:从宏原型到 OpArgContext 底层实现解析 CANN opbase 算子输出声明宏 OP_OUTPUT从宏原型到 OpArgContext 底层实现解析【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbaseOP_OUTPUT 是 CANN opbase算子库基础框架中用于声明算子输出参数的编译期宏它与 OP_INPUT 对称共同构成了算子aclTensor/aclTensorList参数描述体系。本文以 OP_OUTPUT 官方文档为主体结合 op_arg_def.h 与 make_op_executor.h 源码完整讲解宏原型、参数语义、约束规则、调用示例及其底层封装机制帮助算子开发者正确声明输出参数并理解其在算子执行链路中的流转过程。一、宏功能输出参数的统一声明入口OP_OUTPUT 用于封装算子的输出aclTensor和aclTensorList。在 CANN opbase 的算子开发模型中算子的输入输出参数需要通过 OP_INPUT、OP_OUTPUT 等宏统一包装才能被aclOpExecutor正确识别、组织并下发给形状推导InferShape与内核启动Launch阶段。输出为单个张量时传入aclTensor*输出为动态数量的张量集合时传入aclTensorList*。两者可以混用、可以出现多个宏会以“按位置依次记录”的方式把它们整理到算子输出参数列表中最终通过算子类型 IDOpTypeId与内核绑定完成一次算子执行。二、宏原型OP_OUTPUT(x...)其中x...为变长参数用于依次列出算子所有的输出参数。三、参数说明参数输入/输出说明x...输入算子输出的 aclTensor 和 aclTensorList。补充说明变长参数内各元素按书写顺序排列该顺序即输出参数的逻辑顺序与算子定义op proto 或 KernelLauncher 登记中的输出顺序保持一致单个元素必须是aclTensor*或aclTensorList*含其 const 版本不支持裸指针数组等其他形态宏要求所有参数以“立即数表达式”形式给出即直接书写变量名或可求值的表达式不能传入运行时动态拼装的参数容器。四、调用示例4.1 单个输出// 封装算子的1个输出参数addOut OP_OUTPUT(addOut);4.2 多个输出// 封装算子的2个输出参数y 和 indices OP_OUTPUT(y, indices);4.3 输出为张量列表动态数量输出// 封装算子的1个输出参数outputsaclTensorList OP_OUTPUT(outputs);将上述 OP_OUTPUT 与 OP_INPUT 组合使用时即可构成一个完整的算子执行参数声明例如OP_INPUT(self, other); OP_OUTPUT(addOut);五、约束说明非 Tensor 输出的转换要求原文档明确了一条关键约束如果算子包含非 aclTensor 且非 aclTensorList 的输出参数需要先调用aclOpExecutor::ConvertToTensor将参数转换为 aclTensor。也就是说OP_OUTPUT 只接受张量形态的输出。若算子的某个输出在用户侧以标量aclScalar、数组aclIntArray/aclFloatArray/aclBoolArray等形式表达必须先转换为aclTensor后再传入 OP_OUTPUT。从 op_executor.h 的源码可以看到aclOpExecutor为这类转换提供了一组重载ConvertToTensor(const aclIntArray* value, op::DataType dataType)ConvertToTensor(const aclBoolArray* value, op::DataType dataType)ConvertToTensor(const aclFloatArray* value, op::DataType dataType)ConvertToTensor(const aclFp16Array* value, op::DataType dataType)ConvertToTensor(const aclBf16Array* value, op::DataType dataType)ConvertToTensor(const aclScalar* value, op::DataType dataType)模板版本ConvertToTensor(const T* value, uint64_t size, op::DataType dataType)直接按给定元素个数与数据类型构造aclTensor构造成功后会把张量登记到 executor 内部的对象管理列表allocatedObjList_/allocatedTensorList_中统一释放构造失败时记录OP_LOGE错误日志并返回空指针。实践中的典型用法是先通过 executor 的AllocScalar/AllocTensorList等分配接口得到非张量对象再调用对应的ConvertToTensor得到const aclTensor*最后将结果放入 OP_OUTPUT。六、源码级原理OP_OUTPUT 的宏展开与参数流转6.1 宏定义与 OpOutput 结构OP_OUTPUT 的宏定义位于 op_arg_def.h#define OP_INPUT(x...) op::OpInput(std::make_tuple(x)) #define OP_OUTPUT(x...) op::OpOutput(std::make_tuple(x))展开后OP_OUTPUT(y, indices)等价于op::OpOutput(std::make_tuple(y, indices))即把全部输出参数打包进一个std::tuple。而OpOutput类型由同文件中的DEFINE_OP_ARG宏生成DEFINE_OP_ARG(OpInput, OP_INPUT_ARG) DEFINE_OP_ARG(OpOutput, OP_OUTPUT_ARG)其中OP_OUTPUT_ARG 1见 OpArgDef 枚举。从源码结构看DEFINE_OP_ARG生成的OpOutput继承自OpArgBaseT...该基类持有元组arg_并提供三个访问方法Size()返回参数个数编译期sizeof...(T)VisitBy(func)/VisitByNoReturn(func)按序遍历元组元素并回调VisitAt(idx, func)仅访问指定下标元素。每个OpOutput对象还通过static constexpr int value携带OP_OUTPUT_ARG类型标识这是后续参数分类的依据。6.2 OpArgContext参数按类型归档算子执行时GetOpArgContext(op_args...)op_arg_def.h会把这些带类型的参数对象初始化进OpArgContextOpArgContextSize在编译期统计所有元组元素总数从而确定需要分配多少个OpArg槽位OpArgContextInit逐对象遍历对每个元素调用AppendOpArg生成对应的OpArg{type, value}并按OP_INPUT_ARG/OP_OUTPUT_ARG/OP_ATTR_ARG等类型把结果累计到ctx.argLists[opArgType]对应的OpArgList中。OpArgContext::GetOpArg(OpArgDef type)op_arg_def.h随后可按类型取回输出参数列表例如GetOpArg(op::OP_OUTPUT_ARG)即拿到全部输出参数。6.3 输出参数的类型标记当 OP_OUTPUT 中的元素被AppendOpArg处理时依据元素的实际 C 类型选择重载并打上OpArgType标记op_arg_def.haclTensor*/const aclTensor*→OPARG_ACLTENSOR值装载为张量指针aclTensorList*/const aclTensorList*→OPARG_ACLTENSOR_LIST值装载为列表指针。这意味着 OP_OUTPUT 不仅能区分“单张量”与“张量列表”还能在后续执行阶段如地址绑定、Workspace 偏移更新按OPARG_ACLTENSOR/OPARG_ACLTENSOR_LIST分别处理从而保证动态列表输出的正确下发。6.4 在 InferShape 中的使用OP_OUTPUT 声明的输出参数会被传入形状推导流程。以 INFER_SHAPE 宏为例inferShapeRet InferShape(KERNEL_NAME##OpTypeId(), *opArgCtx-GetOpArg(op::OP_INPUT_ARG), *opArgCtx-GetOpArg(op::OP_OUTPUT_ARG), *opArgCtx-GetOpArg(op::OP_ATTR_ARG));可见输出参数列表正是通过GetOpArg(op::OP_OUTPUT_ARG)从 OpArgContext 中取出后传给InferShape的。InferShape会结合算子类型 ID 与输入、属性信息推导输出张量的形状与数据类型并写回这些输出张量对象而经由 OP_OUTPUT 正确登记的输出参数正是该环节读取和回填的对象。可以推断在后续的ADD_TO_LAUNCHER_LIST_AICORE/ADD_TO_LAUNCHER_LIST_DSA等启动阶段同一份 OpArgContext 中的输出列表也会被用于构建内核启动参数完成从声明到执行的完整闭环。七、与同系列宏的协同OP_OUTPUT 是 常用宏和类 系列中的一员该系列还包括输入声明OP_INPUT属性声明OP_ATTR、OP_ATTR_NAMES额外资源OP_WORKSPACE、OP_OUTSHAPE、OP_EMPTY_ARG执行模式与调度OP_MODE、OP_OPTION、CREATE_EXECUTOR、ADD_TO_LAUNCHER_LIST_AICORE、ADD_TO_LAUNCHER_LIST_AICPU形状推导与 DFXINFER_SHAPE、OP_TYPE_REGISTER 以及各类 DFX 宏在典型算子实现中通常按照OP_INPUT(...) OP_OUTPUT(...) OP_ATTR(...)的顺序组织参数声明再交由CREATE_EXECUTOR创建的aclOpExecutor统一执行形成一套完整、类型安全的算子参数描述与执行方案。八、总结OP_OUTPUT 是 CANN opbase 中声明算子输出参数的基础宏它接收任意数量的aclTensor与aclTensorList通过op::OpOutput(std::make_tuple(x))展开为带OP_OUTPUT_ARG类型标记的参数对象经GetOpArgContext归档进OpArgContext的输出参数列表最终服务于 InferShape 形状推导与内核启动。使用时应牢记两条准则输出参数必须是张量形态否则先经aclOpExecutor::ConvertToTensor转换参数书写顺序即输出逻辑顺序。理解其宏展开与OpArgContext底层机制有助于定位算子执行链路中的参数问题并为编写自定义算子打下基础。【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考