【昇腾CANN】metadef元数据:为什么你写的算子加载报格式错误 写自定义算子的时候加载阶段报错是最让人崩溃的——代码逻辑明明没问题但 ACL 就是说format error或者schema mismatch。这类问题大多数出在 metadef元数据定义上。metadef 描述了算子的接口规范包括输入输出的 shape、dtype、format 等信息。如果自定义算子的实现和 metadef 描述对不上ACL 在加载阶段就会拒绝。元数据到底在描述什么昇腾的算子加载流程里metadef 是一个桥梁一边是算子的实际实现核函数另一边是 ACL 的调用接口Graph 或者单算子调用。# 用户代码里调用一个自定义算子importacl# 定义输入 tensorinput_xacl.create_tensor(acl_dtype,shape,format,data_ptr)input_yacl.create_tensor(acl_dtype,shape,format,data_ptr)# 调用算子假设叫 my_custom_opop_descacl.create_op_desc(my_custom_op,# 算子名字acl.rt.op_type(),# 算子类型input_x,input_y,)# 执行acl.rt.execute_op(op_desc)这段代码里算子名字my_custom_op对应的实现必须在 metadef 里注册过。ACL 会去 metadef 定义的 schema 里校验传入的 tensor 参数是否匹配。常见的 schema 校验失败原因dtype 不匹配最常见的问题代码里传的 dtype 和 metadef 定义的不一样。# 用户代码里传的是 float32atorch.randn(4,4).npu().half()# 这里用了 .half() 变成 FP16# 但 metadef 里定义的是 float32# 加载的时候 ACL 会报 dtype mismatch排查方式打印出实际 tensor 的 dtype和 metadef 里的定义对照。# 查 tensor dtypeprint(finput dtype:{tensor.dtype})# torch.float16print(fmetadef expects:{metadef.input_dtype})# torch.float32# 如果不匹配要么改代码强制转 dtype要么更新 metadef 定义format 不匹配昇腾 NPU 支持多种 tensor formatNCHW、NHWC、ND 等等。自定义算子如果对 format 有要求但调用的时候传的 format 不对也会报错。# metadef 里定义卷积算子要求 NCHW 格式# 但某段代码把 tensor 转成了 NHWCaa.permute(0,3,1,2)# NCHW - NHWC# 调用算子报 format mismatch昇腾的图编译器Graph Compiler一般会自动做 format 转换但有些特殊算子不支持某些 format 之间的转换这时候就要手动处理。shape 不匹配shape 校验有时候会被忽略。比如 metadef 里定义了输入要求是 4 维张量但某段代码传了一个 3 维的 tensorbatch size 1 的时候被 squeeze 掉了。# 代码里传了 3 维 tensorxtorch.randn(1,64,64)# 少了 batch 维度# metadef 定义的是 [batch, channel, height, width]要求 4 维# 报错shape rank mismatch写 metadef 的规范metadef 文件通常是一个 JSON 或者 proto 格式的定义描述算子的签名。{op_name:my_custom_gemm,op_type:Gemm,input_desc:[{name:x,dtype:[float32,float16],format:[ND,NCHW],shape:[-1,-1,-1,-1]},{name:w,dtype:[float32,float16],format:[ND],shape:[-1,-1]}],output_desc:[{name:y,dtype:[float32,float16],format:[ND],shape:[-1,-1,-1,-1]}],attr_desc:[{name:transpose_a,dtype:bool,default:false},{name:transpose_b,dtype:bool,default:false}]}-1在 shape 里表示动态维度运行时才确定具体值。如果你的算子只支持固定维度这里要写具体数字。动态 shape 的坑动态 shape 是 metadef 里最容易出问题的部分。比如写一个变长序列处理的算子输入是[batch, seq_len, hidden]seq_len 每次不一样。如果 metadef 里把 seq_len 写成固定值推理的时候一旦实际长度和定义不符就会报 shape 不匹配。# metadef 定义shape:[8,512,768]# 固定长度# 但实际推理的时候 seq_len 可能是 256也可能是 1024# 报 shape mismatch正确的写法是用 -1 表示动态维度shape:[-1,-1,768]# batch 和 seq_len 动态hidden 固定但用 -1 之后昇腾的图编译器在优化阶段可能没法做一些 shape-specific 的优化。动态 shape 是个双刃剑用的时候要想清楚。属性attr校验metadef 里除了描述输入输出还能定义算子的属性。属性是一种静态参数在创建算子的时候就固定了不参与计算图的数据流。# 定义算子属性op_descacl.create_op_desc(my_op)acl.set_attr_bool(op_desc,use_relu,True)acl.set_attr_int(op_desc,threshold,128)这些属性在 metadef 里也要声明。如果代码里设置了一个属性但 metadef 里没有定义ACL 会报unknown attribute错误。调试 metadef 问题的小技巧当报错信息不够明确的时候可以用昇腾提供的工具校验 metadef 文件# 校验 metadef 文件的合法性python-mmetadef.validator my_op_schema.json# 检查 dtype、format、shape 定义是否完整# 输出可能的问题列表另外很多 metadef 相关的报错其实是加载顺序问题算子的实现库.so没有先加载metadef 里定义的算子找不到对应的实现。# 确保先加载算子库再注册 metadefimportacl# 加载自定义算子的实现acl.rt.load_addon(/path/to/libmy_custom_op.so)# 然后才能通过名字找到算子acl.op.set_addon_op_type(my_custom_op,ACL_ENGINE_OP_TYPE_USER_DEF)加载顺序搞反的话报错往往也是 schema 相关的问题很容易误判。仓库在 https://atomgit.com/cann/metadef仓库里有一些标准算子的 metadef 定义可以参考。