CANN opbase 算子属性封装宏 OP_ATTR:用法详解与源码级原理剖析
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
导读
OP_ATTR是 CANN opbase 框架库(include/nnopbase/opdev/op_arg_def.h)中用于封装算子属性参数的核心宏,与OP_INPUT、OP_OUTPUT、OP_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对象。OpAttr由DEFINE_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 个属性参数dstType和sqrtMode:
// 封装算子的2个属性参数,dstType和sqrtMode OP_ATTR(dstType, sqrtMode);在实际代码中,OP_ATTR通常与OP_INPUT、OP_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 | 说明 |
|---|---|---|
bool | OPARG_BOOL | 布尔开关类属性 |
int32_t/int64_t等整型 | OPARG_INT | 整数属性 |
uint32_t/uint64_t | OPARG_UINT | 无符号整数属性 |
float | OPARG_FLOAT | 浮点属性 |
double | OPARG_DOUBLE | 双精度浮点属性 |
op::DataType | OPARG_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),其核心流程如下:
- 统计参数个数:
OpArgContextSize递归累加各参数组(如OpInput、OpAttr)内部 tuple 的元素个数,据此分配内存:sizeof(OpArgContext) + (OpArgContextSize(ts...) + 1) * sizeof(OpArg),多出的 1 个OpArg预留给后续追加的 workspace 参数。 - 逐组写入:
OpArgContextInit遍历传入的每个参数对象,取出其value(即OP_ATTR_ARG等类型标识);若该值落在OP_INPUT_ARG到OP_EXEC_MODE_ARG区间内,则通过VisitByNoReturn对 tuple 内每个属性元素调用AppendOpArg,把OpArg{type, value}连续写入缓冲区(op_arg_def.h)。 - 归类挂载:
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)判断是否存在属性参数。 - 释放:使用完毕后调用
DestroyOpArgContext(ctx)回收。
整个机制对同一类型的多组参数做了合并处理:OpArgContextInit中ctx.argLists[opArgType].count += ...会把多次OP_ATTR声明的属性累积到同一个属性分桶中。
测试用例验证
仓库的单元测试 test_op_arg_def.cpp 对OP_ATTR进行了充分验证,涵盖多种属性类型组合:
- 标量与字符串:
OP_ATTR(&t6, name)后,attr1.count == 2,且attr1.args[0].type == OPARG_ACLTENSOR、attr1.args[1].type == OPARG_STRING(L170-L200); - 算术类型混合:
OP_ATTR(&t4, name, dim, dim1, weight, weight1)中name为bool、dim为int32_t、dim1为uint32_t、weight为float、weight1为double,组装后attr1.count == 6,各元素类型分别被正确推导为OPARG_BOOL、OPARG_INT、OPARG_UINT、OPARG_FLOAT、OPARG_DOUBLE(L202-L240); - DataType 与数组:
OP_ATTR(outDdataType, &dim, &weight, &name)中dim、weight、name分别为aclIntArray*、aclFloatArray*、aclBoolArray*,对应类型为OPARG_DATATYPE、OPARG_INT_LIST、OPARG_FLOAT_LIST、OPARG_BOOL_LIST(L242-L279); - 全类型覆盖:
TestAllType用例一次声明 13 个属性,覆盖bool、DataType、aclScalar*、aclIntArray*、字符串(std::string、std::string*、char*)、double、float、int32_t、uint32_t、int64_t、uint64_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_INPUT、OP_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),仅供参考