CANN opbase 算子属性封装宏 OP_ATTR:用法详解与源码级原理剖析
2026/9/18 6:28:52 网站建设 项目流程

CANN opbase 算子属性封装宏 OP_ATTR:用法详解与源码级原理剖析

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

导读

OP_ATTR是 CANN opbase 框架库(include/nnopbase/opdev/op_arg_def.h)中用于封装算子属性参数的核心宏,与OP_INPUTOP_OUTPUTOP_WORKSPACE等同属算子参数定义(OpArgDef)体系。本文围绕该宏的功能、原型、参数与约束展开,结合仓库源码说明其底层如何把属性值转换为带类型的OpArg并装入OpArgContext,同时给出可复制的调用示例与单元测试验证依据,帮助算子开发者在编写算子原型参数描述时正确使用OP_ATTR

宏功能:在算子原型中声明"属性参数"

在 CANN 算子的原型(OpDef)描述中,一个算子的入参分为输入(Tensor)、输出(Tensor)、属性(Attr)等几类。OP_ATTR宏专门用于封装算子的属性参数(即算子原型中的属性参数),将一组属性值打包成一个op::OpAttr参数对象,供后续构建OpArgContext使用。

属性参数与输入/输出的本质区别在于:输入输出承载的是张量数据,而属性是描述算子行为方式的标量或数组配置,例如数据类型选择、模式开关、维度参数、权重系数等。在 opbase 的参数体系里,属性通过枚举OP_ATTR_ARG(值为 2)进行标识,见 op_arg_def.h 中的enum OpArgDef

enum OpArgDef { OP_INPUT_ARG = 0, OP_OUTPUT_ARG = 1, OP_ATTR_ARG = 2, OP_WORKSPACE_ARG = 3, OP_OUTSHAPE_ARG = 4, OP_OPTION_ARG = 5, OP_EXEC_MODE_ARG = 6, OP_MEMSET_ARG = 7, OP_EMPTY_ARG = 8, OP_ARG_DEF_BUTT = 9, };

宏原型与参数说明

宏原型:

OP_ATTR(x...)

参数说明:

参数输入/输出说明
x...输入算子的属性参数(即算子原型中属性参数),可传一个或多个,用逗号分隔。

约束说明:无。

从源码实现看,该宏是一个非常轻量的展开:

#define OP_ATTR(x...) op::OpAttr(std::make_tuple(x))

它等价于op::OpAttr(std::make_tuple(x)),即把可变参数列表打包进std::tuple,再构造op::OpAttr对象。OpAttrDEFINE_OP_ARG宏模板批量生成,定义在 op_arg_def.h:

#define DEFINE_OP_ARG(op_arg, op_type) \ template <typename... T> \ struct op_arg : public OpArgBase<T...> { \ static constexpr int value = op_type; \ using type = op_arg; \ op_arg(std::tuple<T...>&& arg) : OpArgBase<T...>(std::forward<std::tuple<T...>>(arg)) {} \ op_arg(const std::tuple<T...>& arg) : OpArgBase<T...>(arg) {} \ op_arg() {} \ constexpr int Type() const { return value; } \ }; DEFINE_OP_ARG(OpAttr, OP_ATTR_ARG)

因此OP_ATTR生成的对象:

  • 继承自OpArgBase<T...>,内部持有一个std::tuple<T...> arg_,通过Size()可获取属性个数;
  • 携带静态常量value = OP_ATTR_ARG,用于在OpArgContext组装时识别"这是属性分组";
  • 提供VisitBy/VisitByNoReturn/VisitAt三个遍历接口,分别支持带返回码回调、无返回码回调、按下标访问单个元素(见 op_arg_def.h)。

调用示例

官方文档给出的最简用法如下,封装算子的 2 个属性参数dstTypesqrtMode

// 封装算子的2个属性参数,dstType和sqrtMode OP_ATTR(dstType, sqrtMode);

在实际代码中,OP_ATTR通常与OP_INPUTOP_OUTPUT等宏一起,作为参数传给GetOpArgContext/MakeOpArgContext,组装出完整的算子参数上下文。以仓库中的MemsetV2ArgContext实现为例(memset_op.cpp):

memsetV2OpArgCtx_ = GetOpArgContext(OP_INPUT(memsetTensors_), OP_OUTPUT(memsetTensors_), OP_WORKSPACE(memsetV2WsTensor), OP_ATTR(intAttrArray_, floatAttrArray_));

其中intAttrArray_aclIntArray*)与floatAttrArray_aclFloatArray*)就是该算子的两组属性,分别承载整数数组属性与浮点数组属性,通过OP_ATTR一并声明。

属性值的类型映射

OP_ATTR能封装的属性类型非常丰富。当属性值写入OpArgContext时,AppendOpArg重载会根据 C++ 静态类型自动推导出对应的OpArgType(见 op_arg_def.h 与 op_arg_def.h),映射关系如下:

属性 C++ 类型推导出的 OpArgType说明
boolOPARG_BOOL布尔开关类属性
int32_t/int64_t等整型OPARG_INT整数属性
uint32_t/uint64_tOPARG_UINT无符号整数属性
floatOPARG_FLOAT浮点属性
doubleOPARG_DOUBLE双精度浮点属性
op::DataTypeOPARG_DATATYPE数据类型属性
std::string/std::string*/const char*/char*OPARG_STRING字符串属性(统一转为char*
aclScalar*OPARG_ACLSCALAR标量属性
aclIntArray*OPARG_INT_LIST整数数组属性
aclFloatArray*OPARG_FLOAT_LIST浮点数组属性
aclBoolArray*OPARG_BOOL_LIST布尔数组属性
aclTensor*/aclTensorList*OPARG_ACLTENSOR/OPARG_ACLTENSOR_LIST也可承载张量型属性

属性值本身通过OpArgValue统一存储,其内部是一个联合体ValueData,可容纳整型、浮点、指针等不同表示(op_arg_def.h);而OpArg则把OpArgType(类型标识)与OpArgValue(值)组合在一起,构成参数上下文中的最小单元:

struct OpArg { OpArgType type; OpArgValue value; ... };

底层工作流程:OP_ATTR 如何进入 OpArgContext

OP_ATTR产生的OpAttr对象最终会进入MakeOpArgContext(op_arg_def.h),其核心流程如下:

  1. 统计参数个数OpArgContextSize递归累加各参数组(如OpInputOpAttr)内部 tuple 的元素个数,据此分配内存:sizeof(OpArgContext) + (OpArgContextSize(ts...) + 1) * sizeof(OpArg),多出的 1 个OpArg预留给后续追加的 workspace 参数。
  2. 逐组写入OpArgContextInit遍历传入的每个参数对象,取出其value(即OP_ATTR_ARG等类型标识);若该值落在OP_INPUT_ARGOP_EXEC_MODE_ARG区间内,则通过VisitByNoReturn对 tuple 内每个属性元素调用AppendOpArg,把OpArg{type, value}连续写入缓冲区(op_arg_def.h)。
  3. 归类挂载OpArgContext内部用std::array<OpArgList, OP_ARG_TYPE_NUM> argLists按参数类型分桶保存(op_arg_def.h)。对于OP_ATTR声明的属性,argLists[OP_ATTR_ARG]会被填充起始指针与计数,后续可通过GetOpArg(op::OpArgDef::OP_ATTR_ARG)取回整个属性列表,或用ContainsOpArgType(OP_ATTR_ARG)判断是否存在属性参数。
  4. 释放:使用完毕后调用DestroyOpArgContext(ctx)回收。

整个机制对同一类型的多组参数做了合并处理:OpArgContextInitctx.argLists[opArgType].count += ...会把多次OP_ATTR声明的属性累积到同一个属性分桶中。

测试用例验证

仓库的单元测试 test_op_arg_def.cpp 对OP_ATTR进行了充分验证,涵盖多种属性类型组合:

  • 标量与字符串OP_ATTR(&t6, name)后,attr1.count == 2,且attr1.args[0].type == OPARG_ACLTENSORattr1.args[1].type == OPARG_STRING(L170-L200);
  • 算术类型混合OP_ATTR(&t4, name, dim, dim1, weight, weight1)namebooldimint32_tdim1uint32_tweightfloatweight1double,组装后attr1.count == 6,各元素类型分别被正确推导为OPARG_BOOLOPARG_INTOPARG_UINTOPARG_FLOATOPARG_DOUBLE(L202-L240);
  • DataType 与数组OP_ATTR(outDdataType, &dim, &weight, &name)dimweightname分别为aclIntArray*aclFloatArray*aclBoolArray*,对应类型为OPARG_DATATYPEOPARG_INT_LISTOPARG_FLOAT_LISTOPARG_BOOL_LIST(L242-L279);
  • 全类型覆盖TestAllType用例一次声明 13 个属性,覆盖boolDataTypeaclScalar*aclIntArray*、字符串(std::stringstd::string*char*)、doublefloatint32_tuint32_tint64_tuint64_t,并逐一断言其类型标识与存储值(L334-L434)。

上述测试同样出现在 ST 测试 test_op_arg_def.cpp 中,可作为复现与回归验证的参考。

与同族宏的配套使用

OP_ATTR属于算子参数定义宏家族,同族还包括(详见常用宏和类):

功能对应 OpArgDef
OP_INPUT封装输入张量OP_INPUT_ARG
OP_OUTPUT封装输出张量OP_OUTPUT_ARG
OP_ATTR封装属性参数OP_ATTR_ARG
OP_WORKSPACE封装工作区张量OP_WORKSPACE_ARG
OP_OUTSHAPE封装输出形状OP_OUTSHAPE_ARG
OP_OPTION封装算子选项(如实现模式)OP_OPTION_ARG
OP_MODE封装执行模式OP_EXEC_MODE_ARG
OP_EMPTY_ARG空参数占位OP_EMPTY_ARG

GetOpArgContext中,上述宏可任意组合传入,OpArgContextInit会按value值自动分流到不同分桶,这也是OP_ATTR能与OP_INPUTOP_OUTPUT等并存于一次调用中的原因。若算子需要按名称引用属性(例如 AI CPU 算子场景),可配合 OP_ATTR_NAMES 使用,它用于封装属性的名字列表。

总结

  • OP_ATTR(x...)用于封装算子的一个或多个属性参数,无使用约束,支持标量、字符串、数组、张量等多种属性类型;
  • 其实现本质是op::OpAttr(std::make_tuple(x)),类型标识为OP_ATTR_ARG
  • 属性值经AppendOpArg按静态类型自动映射为对应的OpArgType,最终归类到OpArgContext.argLists[OP_ATTR_ARG],供算子执行引擎统一取用;
  • 完整调用可参照 memset_op.cpp 的生产示例与 test_op_arg_def.cpp 的测试覆盖。

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询