资讯详情

资讯详情

CANN ops-math NotEqual 算子全解析:NPU 上的逐元素不等于比较实现与调用指南

CANN ops-math NotEqual 算子全解析NPU 上的逐元素不等于比较实现与调用指南【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math导读NotEqual 是 CANN ops-math 数学算子库中负责逐元素比较两个张量是否不相等的二元逻辑算子对两个输入张量逐元素执行x1 ! x2输出布尔BOOL张量。本文以 math/not_equal/README.md 为核心结合该目录下的算子定义、构图原型、tiling/kernel 实现与测试用例系统讲解 NotEqual 的产品支持范围、计算公式、参数约束、aclnn 单算子 API 与图模式两种调用方式并深入剖析其底层计算链路帮助开发者在 Ascend NPU 上正确、高效地使用该算子。产品支持情况NotEqual 算子对不同硬件产品的支持情况如下表所示与 math/not_equal/README.md 保持一致产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品√Atlas 训练系列产品√从源码配置可以印证这一支持矩阵op_host/not_equal_def.cpp 中为算子注册了ascend950与ascend350两个 AICore 配置并且存在 op_host/config/ascend950/not_equal_binary.json 与 op_host/config/ascend350/not_equal_binary.json 两份不同昇腾平台的二进制配置清单说明算子主要面向 950 系列与 A3arch35系列产品交付。同时目录中还提供 op_kernel_aicpu/not_equal_aicpu.cpp 的 AICPU 实现为不支持场景或其他执行路径提供兜底能力。功能说明与计算公式NotEqual 的算子功能为逐元素比较两个输入张量是否不相等。对于两个输入张量 x1、x2输出 y 的计算公式为$$ \text{y}i \begin{cases} True,\text{if }x{i, 1} \ne x_{i,2} \ False,\text{otherwise} \end{cases} $$即对应位置元素不相等时输出True相等时输出False。输出张量 y 的每个元素为布尔值BOOL 类型因此该算子天然适合作为掩码mask生成、条件过滤、梯度保护等场景的基础构件。在 op_graph/not_equal_proto.h 的算子原型注释中明确将其描述为 Returns the truth value of (x1 ! x2) element-wise. Support broadcasting operations.并注明与 TensorFlow 框架的NotEqual算子兼容。参数说明NotEqual 算子的核心参数如下表所示信息来自 math/not_equal/README.md 参数说明参数名输入/输出/属性描述数据类型数据格式x1输入公式中的输入 x1DOUBLE、FLOAT16、FLOAT、BFLOAT16、INT64、INT32、INT8、UINT8、BOOL、INT16、COMPLEX64、COMPLEX128、UINT64NDx2输入公式中的输入 x2DOUBLE、FLOAT16、FLOAT、BFLOAT16、INT64、INT32、INT8、UINT8、BOOL、INT16、COMPLEX64、COMPLEX128、UINT64NDy输出公式中的 yBOOLND参数使用要点数据类型推导x1 与 x2 的数据类型必须满足 数据类型推导规则两者类型需要一致或可推导。这一约束在 tiling 实现中有硬性校验见 op_host/arch35/not_equal_tiling_arch35.cpp 的DoOpTiling当x1与x2的DataType不一致时会直接返回GRAPH_FAILED并打印 The dtypes of x1 and x2 must be the same。shape 广播x1 与 x2 的 shape 需要满足 broadcast 关系。输出 y 的 shape 为 x1 与 x2 广播后的 shape形状推导复用通用广播逻辑见 op_host/not_equal_infershape.cpp 中InferShape4NotEqual调用Ops::Base::InferShape4Broadcast(context)。数据格式输入输出均为 ND 格式支持 0-8 维张量支持非连续 Tensor见下文 aclnn 接口参数表。确定性计算aclnnNeTensor / aclnnInplaceNeTensor 默认是确定性实现见 math/not_equal/docs/aclnnNeTensoraclnnInplaceNeTensor.md 的约束说明。调用说明NotEqual 算子支持两种调用方式对应目录下的两个调用样例调用方式调用样例说明aclnn 调用test_aclnn_ne_tensor.cpp通过 aclnnNeTensor / aclnnInplaceNeTensor 接口调用 NotEqual 算子图模式调用test_geir_not_equal.cpp通过 算子 IR 构图方式调用 NotEqual 算子此外同一算子目录下还提供标量比较变体 aclnnNeScalar / aclnnInplaceNeScalar 以及逻辑异或接口aclnnLogicalXor对应 op_api/aclnn_logical_xor.h说明该模块围绕比较语义构建了一组完整 API 家族。方式一aclnn 单算子 API 调用两段式接口aclnn 方式遵循 CANN 通用的 两段式接口 范式即每个算子拆分为两个接口第一段接口aclnnNeTensorGetWorkspaceSize完成入参校验并根据计算流程计算出本次调用所需的 workspace 大小第二段接口aclnnNeTensor使用第一段接口返回的 workspace 与 executor 真正执行计算。函数原型定义见 op_api/aclnn_ne_tensor.haclnnStatus aclnnNeTensorGetWorkspaceSize( const aclTensor *self, const aclTensor *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnNeTensor( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);其中self输入NPU Device 侧的 aclTensor即公式中的 x1other输入NPU Device 侧的 aclTensor即公式中的 x2out输出NPU Device 侧的 aclTensor输出 BOOL 类型结果workspaceSize输出需要在 Device 侧申请的 workspace 临时内存大小executor输出op 执行器封装了算子计算流程由第一段接口生成供第二段接口使用workspace输入在 Device 侧申请的 workspace 内存起始地址stream输入指定执行任务的 aclrtStream 流。注意第二段接口aclnnNeTensor(...)不可重复调用重复调用同一 executor 会出现异常。同一场景需重新走一遍第一段 → 申请 workspace → 第二段流程。入参校验与错误码第一段接口完成入参校验出现以下场景时返回对应错误码错误码详细说明参见 aclnn 返回码返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、other、out 是空指针时ACLNN_ERR_PARAM_INVALID161002self、other 或 out 的数据类型不在支持范围之内时ACLNN_ERR_PARAM_INVALID161002self、other 或 out 的维度大于 8 时ACLNN_ERR_PARAM_INVALID161002self 和 other 的数据类型无法进行推导时ACLNN_ERR_PARAM_INVALID161002self 和 other 的 shape 无法进行 broadcast 时ACLNN_ERR_PARAM_INVALID161002out 的 shape 与 broadcast 后的 shape 不一致时接口参数详表aclnnNeTensorGetWorkspaceSize完整参数说明摘自 math/not_equal/docs/aclnnNeTensoraclnnInplaceNeTensor.md参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入公式中的 self数据类型需要与 other 满足数据类型推导规则shape 需要与 other 满足 broadcast 关系DOUBLE、FLOAT16、FLOAT、BFLOAT16、INT64、INT32、INT8、UINT8、BOOL、INT16、COMPLEX64、COMPLEX128、UINT64ND0-8√otheraclTensor*输入公式中的 other数据类型需要与 self 满足数据类型推导规则shape 需要与 self 满足 broadcast 关系DOUBLE、FLOAT16、FLOAT、BFLOAT16、INT64、INT32、INT8、UINT8、BOOL、INT16、COMPLEX64、COMPLEX128、UINT64ND0-8√outaclTensor*输出公式中的 out数据类型需要是 BOOL 可转换的数据类型shape 为 self 与 other 广播后的 shapeDOUBLE、FLOAT16、FLOAT、BFLOAT16、INT64、INT32、INT8、UINT8、BOOL、INT16、COMPLEX64、COMPLEX128、UINT64、UINT32、UINT16ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----Inplace 变体aclnnInplaceNeTensoraclnnNeTensor与aclnnInplaceNeTensor实现相同功能区别在于aclnnNeTensor需新建一个输出张量对象存储计算结果aclnnInplaceNeTensor无需新建输出张量对象直接在输入张量 selfRef 的内存中存储计算结果原地更新。aclnnStatus aclnnInplaceNeTensorGetWorkspaceSize( aclTensor *selfRef, const aclTensor *other, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnInplaceNeTensor( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);Inplace 接口的计算公式为selfRef_i (selfRef_i ! other_i) ? 1 : 0。需要注意inplace 场景下selfRef同时是输入与输出因此要求 broadcast 后的 shape 必须等于 selfRef 自身的 shape否则第一段接口会返回ACLNN_ERR_PARAM_INVALID161002。完整 aclnn 调用示例仓库提供了可直接编译运行的完整示例 examples/test_aclnn_ne_tensor.cpp核心流程拆解如下第一步初始化 ACL 环境固定写法int Init(int32_t deviceId, aclrtStream* stream) { auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, ...); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, ...); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, ...); return 0; }第二步构造输入与输出 aclTensor通过aclrtMalloc申请 Device 侧内存aclrtMemcpy将 Host 数据拷贝到 Device再调用aclCreateTensor创建 aclTensorND 格式、连续 stridestemplate typename T int CreateAclTensor(const std::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, ...); ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret ACL_SUCCESS, ...); // 计算连续 tensor 的 strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; }第三步两段式调用 aclnnNeTensoruint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段计算 workspace 大小 ret aclnnNeTensorGetWorkspaceSize(self, other, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, ...); // 按需申请 workspace 内存 void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, ...); } // 第二段执行计算 ret aclnnNeTensor(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, ...); // 同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, ...);第四步结果回拷与资源释放将 Device 侧输出通过aclrtMemcpyACL_MEMCPY_DEVICE_TO_HOST拷贝回 Host 并打印随后依次aclDestroyTensor、aclrtFree、aclrtDestroyStream、aclrtResetDevice、aclFinalize完成资源清理。示例中使用的输入数据为self {0,1,2,3,4,5,6,7}、other {1,1,1,2,2,2,3,3}shape 均为{4,2}读者可自行推导输出验证逻辑正确性。若需同时体验 inplace 变体可参考同一文档 aclnnNeTensoraclnnInplaceNeTensor.md 中test aclnnInplaceNeTensor部分将self直接作为selfRef传入注意重新申请 workspace 前需先释放上一轮申请的 workspace避免内存泄漏。示例编译与运行的整体流程可参考 编译与运行样例。方式二图模式GEIR调用图模式通过算子 IR 构图方式调用 NotEqual对应示例 examples/test_geir_not_equal.cpp 与算子原型 op_graph/not_equal_proto.h。算子原型使用REG_OP宏注册输入为两个RealNumberType类型 Tensorx1、x2输出为DT_BOOL类型 TensorREG_OP(NotEqual) .INPUT(x1, TensorType::RealNumberType()) .INPUT(x2, TensorType::RealNumberType()) .OUTPUT(y, TensorType({DT_BOOL})) .OP_END_FACTORY_REG(NotEqual)图模式示例的核心构图逻辑见CreateOppInGraph// 自定义代码添加单算子定义到图中 auto notequalop op::NotEqual(notequalop); std::vectorint64_t xShape {4, 4}; ADD_INPUT(1, x1, inDtype, xShape); // 通过 op::Data 创建占位输入并挂到算子 x1 输入 ADD_INPUT(2, x2, inDtype, xShape); // 通过 op::Data 创建占位输入并挂到算子 x2 输入 ADD_OUTPUT(1, y, DT_BOOL, xShape); // 声明输出 desc outputs.push_back(notequalop);随后通过graph.SetInputs(inputs).SetOutputs(outputs)绑定图输入输出创建ge::Sessionsession-AddGraph(graph_id, graph, graph_options)添加计算图session-RunGraph(graph_id, input, output)执行最后调用aclgrphDumpGraph可将构图结果 dump 成文本便于调试。全局初始化使用ge::GEInitialize示例传入ge.exec.deviceId0、ge.graphRunMode1收尾调用ge::GEFinalize。另外framework/not_equal_tf_plugin.cpp 展示了框架层适配方式通过REGISTER_CUSTOM_OP(NotEqual).FrameworkType(TENSORFLOW).OriginOpType(NotEqual)将 TensorFlow 的NotEqual算子映射到本实现这也是其与 TF 框架兼容见 proto 注释的底层机制。源码级原理从算子定义到 NPU 计算算子定义层OpDefop_host/not_equal_def.cpp 中完成了 NotEqual 的完整定义注册输入 x1、x2 支持DT_BF16 / DT_FLOAT16 / DT_FLOAT / DT_INT8 / DT_UINT8 / DT_INT32 / DT_INT64 / DT_UINT64 / DT_BOOL九种数据类型注意实际 AICore 二进制支持范围以 op_host/config/ascend950/not_equal_binary.json 等配置文件为准COMPLEX/UINT32 等扩展类型主要由 aclnn 接口层覆盖输出 y 恒为DT_BOOL输入输出格式均为FORMAT_NDAICore 配置开启DynamicCompileStaticFlag(true)、DynamicRankSupportFlag(true)、DynamicShapeSupportFlag(true)、PrecisionReduceFlag(true)并注册ascend950、ascend350两个平台的核函数入口not_equal_apt。形状推导层op_host/not_equal_infershape.cpp 将 shape 推导委托给通用广播工具Ops::Base::InferShape4Broadcast即输出 shape broadcast(x1.shape, x2.shape)与 README 及 aclnn 文档中关于 broadcast 关系的描述完全一致。Tiling 层op_host/arch35/not_equal_tiling_arch35.cpp 的DoOpTiling按输入数据类型分派到对应的模板实例DT_INT64→NotEqualComputeint64_tDT_INT32→NotEqualComputeint32_tDT_FLOAT16 / DT_BF16→NotEqualComputehalfDT_FLOAT→NotEqualComputefloatDT_UINT8→NotEqualComputeuint8_tDT_INT8 / DT_BOOL→NotEqualComputeint8_tDT_UINT64→NotEqualComputeuint64_t所有分支统一调用BroadcastBaseTiling...::DoTiling()完成基于广播语义的切分tiling并校验 x1 与 x2 数据类型必须一致。Kernel 计算层核函数入口在 op_kernel/not_equal_apt.cpp通过BroadcastSchschMode, OpDag sch(tiling); sch.Process(x1, x2, y);驱动计算。实际计算图DAG定义在 op_kernel/arch35/not_equal_dag.h// 通过 Compute 构造计算图 using ConstOne MAKE_CONST(uint8_t, 1); using ConstZero MAKE_CONST(uint8_t, 0); using InputX1 BindVec::CopyInBrcT, Placeholder::In0T; using InputX2 BindVec::CopyInBrcT, Placeholder::In1T; using DataOne BindVec::Duplicateuint8_t, ConstOne; using DataZero BindVec::Duplicateuint8_t, ConstZero; using CompareMask BindVec::Compareuint8_t, T, CMP_MODE, InputX1, InputX2; using SelectRes BindVec::Selectuint8_t, uint8_t, SELECT_MODE, CompareMask, DataOne, DataZero; using OpCopyOut BindVec::CopyOutuint8_t, Placeholder::Out0uint8_t, SelectRes; using Outputs ElemsOpCopyOut; using MemCfg MemOptCfgMemLevel::LEVEL_2; using OpDag DAGSchOutputs, void, MemCfg;从 DAG 结构可以清晰还原 NotEqual 的计算语义两个输入经CopyInBrc带广播的搬运读入 →Vec::Compare比较模式CMP_MODE5即不等于生成比较掩码 →Vec::Select选择模式SELECT_MODE2依据掩码在常量1True与0False之间选择 →CopyOut以uint8_tBOOL 存储形态写出。对于 BOOL 类型输入kernel 会先将其转换为int8_t参与比较见not_equal_apt.cpp中的特判分支。MemCfg MemOptCfgMemLevel::LEVEL_2表明该计算图针对 L2 内存层级做了访存优化。测试与质量保障单元测试tests/ut/覆盖 aclnn 接口层test_aclnn_neTensor.cpp、test_aclnn_neScalar.cpp、test_aclnn_inplace_neTensor.cpp、test_logical_xor.cpp等、host 层 infershapetest_not_equal_infershape.cpp、arch35 tilingtest_not_equal_tiling_arch35.cpp以及 AICPU kerneltest_not_equal.cpp。以test_aclnn_neTensor.cpp为例分别构造 INT8、UINT8、INT32 等多种数据类型输入并断言GetWorkspaceSize返回ACL_SUCCESS验证了接口层对不同 dtype 的支持。系统测试tests/st/atk_aclnnNeTensor.json以torch.ne为对标接口aclnn_name: NeTensor配置 bool 类型输入、high_precision精度标准说明该算子与 PyTorchtorch.ne语义对齐arch35/ttk_kernel_not_equal_st.csv为 arch35 平台的 kernel 级系统测试用例。典型使用场景与注意事项掩码生成NotEqual 常用于生成哪些位置需要处理的布尔掩码例如判断label ! 0、input ! padding_value等输出可直接用于后续MaskedFill、条件选择等算子。broadcast 语义利用 x1 与 x2 之间的广播关系可以用标量扩展方式实现张量与常量比较例如[4,2]张量与[2]向量比较输出 shape 自动广播为[4,2]。数据类型一致性x1 与 x2 类型必须一致或满足推导规则tiling 层会硬校验不同类型请先用 Cast 算子对齐。inplace 语义限制aclnnInplaceNeTensor要求广播后的 shape 等于 selfRef 的 shape否则报错 161002。两段式接口规范第二段接口不可重复调用每次调用前先通过第一段接口获取新的 workspaceSize 与 executor。平台差异Atlas 200I/500 A2 推理产品不支持该算子开发前请先确认目标硬件的支持情况参见产品支持情况表。参考资料NotEqual 算子 READMEaclnnNeTensor aclnnInplaceNeTensor 接口文档aclnnNeScalar aclnnInplaceNeScalar 接口文档aclnn 调用示例图模式调用示例算子 IR 原型两段式接口说明数据类型推导规则broadcast 关系aclnn 返回码【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →