CANN ops-math 除法算子 Div 深度指南:从 aclnnDiv/aclnnInplaceDiv 两段式调用到源码实现
2026/9/20 16:44:51 网站建设 项目流程

CANN ops-math 除法算子 Div 深度指南:从 aclnnDiv/aclnnInplaceDiv 两段式调用到源码实现

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

本篇技术指南围绕 CANN 开源数学算子库 ops-math 中的Div(除法)算子展开,系统讲解其计算语义、产品支持范围、x1/x2/y 参数规格,以及 aclnnDiv/aclnnInplaceDiv 两段式接口和图模式(GE IR)两种调用方式,并深入到op_apiop_kernel_aicpu等目录的源码实现,帮助你理解类型提升、Broadcast、AICore/AICpu 分发与整数除零/溢出防护等底层细节,可直接用于算子选型、编码调用与问题定位。

Div 算子功能与计算语义

Div 算子在 CANN ops-math 仓库中的位置为 math/div,功能定义见 math/div/README.md。其接口功能非常简单明确:完成两个输入张量的逐元素除法计算

计算公式如下:

$$ out_i = \frac{input_i}{other_i} $$

即对两个 ND 格式的张量逐元素相除,input是被除数(除数算子中的 x1),other是除数(x2),out是商(y)。两个输入支持隐式 Broadcast 广播运算,即 shape 满足 NumPy 风格广播规则时,低维张量会自动扩展对齐。

例外说明(重要):当涉及 Complex(复数)运算且分母的模为 0 时,Div 算子仍然采用通用的复数计算公式,不做特殊处理。这意味着在此场景下,NPU 上的计算结果与 CPU/GPU 平台存在差异。该行为在 math/div/README.md 与 math/div/docs/aclnnDiv&aclnnInplaceDiv.md 中均有明确说明,属于设计预期而非缺陷,涉及复数除法的场景需要特别留意。

从 GE IR 算子的注册原型 math/div/op_graph/div_proto.h 可以确认,该算子兼容 TensorFlow 的 Div 算子语义(Compatible with the TensorFlow operator Div.),支持广播运算,输入 x1、x2 与输出 y 均为 ND Tensor,且 x2 与 x1 需保持相同的数据类型与格式。

产品支持情况

根据 math/div/README.md 的说明,Div 算子在不同产品上的支持情况如下:

产品是否支持
Ascend 950PR/Ascend 950DT
Atlas A3 训练系列产品/Atlas A3 推理系列产品
Atlas A2 训练系列产品/Atlas A2 推理系列产品
Atlas 200I/500 A2 推理产品
Atlas 推理系列产品×
Atlas 训练系列产品

可以看到,除Atlas 推理系列产品(310P 系列)不支持外,其余主流训练/推理产品均支持 Div 算子。该信息与 math/div/docs/aclnnDiv&aclnnInplaceDiv.md 中的逐产品声明一致。

参数说明

Div 算子的核心参数定义如下(源自 math/div/README.md 的参数表,并与算子注册 math/div/op_host/div_def.cpp 中的数据类型保持一致):

参数名输入/输出/属性描述数据类型数据格式
x1输入公式中的 input(被除数)BFLOAT16、FLOAT16、FLOAT、INT32、UINT8、INT8、COMPLEX32、COMPLEX64ND
x2输入公式中的 other(除数)BFLOAT16、FLOAT16、FLOAT、INT32、UINT8、INT8、COMPLEX32、COMPLEX64-
y输出公式中的 out(商)BFLOAT16、FLOAT16、FLOAT、INT32、UINT8、INT8、COMPLEX32、COMPLEX64ND

约束说明:无(该算子没有额外的使用约束)。

需要注意的是,上表是算子(GE IR / 图模式)层面的类型范围。而 aclnn 单算子 API(aclnnDiv)层面的支持范围更广,详见下文接口章节。

两种调用方式总览

math/div/README.md 的调用说明给出了两种官方支持的方式:

调用方式样例代码说明
aclnn 接口test_aclnn_div通过 AclnnDiv 接口方式调用 Div 算子
图模式调用test_geir_div通过 算子IR 构图方式调用 Div 算子
  • aclnn 接口:适合在 Host 侧直接发起单算子计算,走 CANN 的两段式接口(GetWorkspaceSize + Execute)流程,是算子开发、调试和上层框架适配时最常用的方式。
  • 图模式调用(GE IR):通过op::Div("div_graph")构建计算图节点,再经由 GE 图引擎下发执行,适用于需要将 Div 算子嵌入整体计算图的场景,底层 IR 定义在 math/div/op_graph/div_proto.h。

aclnnDiv 与 aclnnInplaceDiv 两段式接口详解

接口选择:普通版 vs Inplace 版

aclnnDiv 与 aclnnInplaceDiv 实现完全相同的除法功能,区别仅在于结果的存放方式:

  • aclnnDiv:需要新建一个输出张量对象(out)存储计算结果;
  • aclnnInplaceDiv:无需新建输出张量对象,直接在输入张量 self 的内存中就地存储计算结果,可以省去一次输出张量分配。

两段式调用模型

每个算子都遵循 CANN 的两段式接口(two_phase_api)设计:

  1. 第一段:调用aclnnDivGetWorkspaceSize(或aclnnInplaceDivGetWorkspaceSize)获取计算所需的 workspace 大小,同时返回一个包含了算子计算流程的执行器 executor;
  2. 第二段:调用aclnnDiv(或aclnnInplaceDiv)真正执行计算,将第一段获取的 workspace 与 executor 传入。

四个接口的原型如下:

aclnnStatus aclnnDivGetWorkspaceSize( const aclTensor *self, const aclTensor *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnDiv( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)
aclnnStatus aclnnInplaceDivGetWorkspaceSize( aclTensor *selfRef, const aclTensor *other, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnInplaceDiv( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)

aclnnDivGetWorkspaceSize 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
self输入公式中的输入 input数据类型与 other 需满足互推导关系;shape 需与 other 满足 broadcast 关系FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL、COMPLEX128、COMPLEX64、BFLOAT16ND-
other输入公式中的输入 other数据类型与 self 需满足互推导关系;shape 需与 self 满足 broadcast 关系同上ND-
out输出公式中的 out数据类型需是 self 与 other 推导之后可转换的数据类型,shape 需是 broadcast 之后的 shape同上ND-
workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----
executor输出返回 op 执行器,包含了算子计算流程-----

平台相关的数据类型差异

  • Atlas 200I/500 A2 推理产品、Atlas 训练系列产品:数据类型不支持 BFLOAT16;
  • Ascend 950PR/Ascend 950DT
    • self 与 other 推导之后的数据类型为整数类型或布尔类型时,推导结果会转换为 FLOAT;
    • out 不支持 INT32、INT64、INT16、INT8、UINT8、BOOL 数据类型。

aclnnDiv 参数说明(第二段接口)

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnDivGetWorkspaceSize 获取
executor输入op 执行器,包含了算子计算流程
stream输入指定执行任务的 Stream

返回码与错误场景

两个接口均返回aclnnStatus状态码,具体参见 aclnn 返回码。第一段接口完成入参校验,出现以下场景时报错:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 self、other 或 out 是空指针
ACLNN_ERR_PARAM_INVALID161002self 和 other 的数据类型和数据格式不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002self 和 other 不满足数据类型推导规则
ACLNN_ERR_PARAM_INVALID161002推导出的数据类型无法转换为指定输出 out 的类型
ACLNN_ERR_PARAM_INVALID161002self 和 other 的 shape 无法做 broadcast
ACLNN_ERR_PARAM_INVALID161002self 和 other 的维度大于 8

对于aclnnInplaceDivGetWorkspaceSize,空指针场景为 selfRef、other 是空指针;错误码 161002 对应的场景为:selfRef 和 other 数据类型/格式不在支持范围、不满足数据类型推导规则、shape 无法 broadcast、维度大于 8(Inplace 版本无需校验 out 类型转换,因为结果就地写回 selfRef)。此外,Ascend 950PR/950DT 上 selfRef 不支持 INT32、INT64、INT16、INT8、UINT8、BOOL。

约束与确定性计算

aclnnDiv 与 aclnnInplaceDiv 均为默认确定性实现(见 math/div/docs/aclnnDiv&aclnnInplaceDiv.md 的约束说明),即相同输入在多次执行下结果可复现,对精度敏感的推理/训练场景友好。

完整调用示例:C 语言风格 aclnn 接口调用

以下示例代码来自仓库的 examples/test_aclnn_div.cpp(保留了核心流程,去掉版权头),它同时演示了 aclnnDiv 与 aclnnInplaceDiv 的完整调用。具体编译与执行过程可参考编译与运行样例。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_div.h" #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); aclFinalize(); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); aclrtResetDevice(deviceId); aclFinalize(); return ret); return 0; } template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1.(固定写法)device/stream初始化,参考acl API手册,根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape = {4, 2}; std::vector<int64_t> otherShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* otherDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* other = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<float> otherHostData = {1, 1, 1, 2, 2, 2, 3, 3}; std::vector<float> outHostData(8, 0); ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(otherHostData, otherShape, &otherDeviceAddr, aclDataType::ACL_FLOAT, &other); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); uint64_t workspaceSize = 0; aclOpExecutor* executor; // ---- aclnnDiv 接口调用示例 ---- LOG_PRINT("test aclnnDiv\n"); // 3. 调用aclnnDiv第一段接口 ret = aclnnDivGetWorkspaceSize(self, other, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDivGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } // 调用aclnnDiv第二段接口 ret = aclnnDiv(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDiv failed. ERROR: %d\n", ret); return ret); // 4.(固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 将device侧内存上的结果拷贝至host侧 auto size = GetShapeSize(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]); } // ---- aclnnInplaceDiv 接口调用示例 ---- LOG_PRINT("\ntest aclnnInplaceDiv\n"); // 调用aclnnInplaceDiv第一段接口:不需要out,结果写回self ret = aclnnInplaceDivGetWorkspaceSize(self, other, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceDivGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } ret = aclnnInplaceDiv(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceDiv failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 结果在self的内存中,从selfDeviceAddr取回 ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), selfDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]); } // 6. 释放aclTensor与Device资源 aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

示例中使用 shape 均为{4, 2}的 FLOAT 张量,self = {0,1,2,3,4,5,6,7}other = {1,1,1,2,2,2,3,3},可预期 aclnnDiv 输出为{0/1, 1/1, 2/1, 3/2, 4/2, 5/2, 6/3, 7/3}。仓库中还提供了一个使用std::unique_ptr做资源管理的 C++ RAII 版本示例,位于 math/div/examples/test_aclnn_div.cpp,更适合直接嵌入工程代码。

调用流程中的关键点:

  1. aclnnDivGetWorkspaceSize第一段接口完成全部入参校验并构图,workspaceSize 可能为 0,此时无需申请 workspace 内存(代码中if (workspaceSize > 0)判断即为此意);
  2. workspace 内存在 Device 侧申请,生命周期需覆盖第二段接口的执行;
  3. 第二段aclnnDiv是异步提交,务必通过aclrtSynchronizeStream(stream)同步后再读取结果。

图模式(GE IR)调用 Div

对于图模式调用,仓库提供了 examples/test_geir_div.cpp。核心构图思路如下(摘自该示例):

#include "array_ops.h" #include "ge_api.h" #include "ge_api_types.h" #include "ge_ir_build.h" #include "graph.h" #include "tensor.h" #include "types.h" #include "../op_graph/div_proto.h" // 创建图节点 auto div_op = op::Div("div_graph"); std::vector<int64_t> input_shape = {4, 2}; auto x1 = op::Data("x1").set_attr_index(0); TensorDesc x1_desc(Shape(input_shape), FORMAT_ND, DT_DOUBLE); x1_desc.SetPlacement(ge::kPlacementHost); // ... 构造 x1/x2 输入 Tensor 与 Data 节点、设置 div_op 输入输出、构建 Graph 并完成图编译与执行

GE IR 的算子原型注册于 math/div/op_graph/div_proto.h:

REG_OP(Div) .INPUT(x1, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT8, DT_UINT8, DT_INT32, DT_DOUBLE, DT_INT64, DT_UINT16, DT_INT16, DT_COMPLEX64, DT_COMPLEX128, DT_BF16, DT_COMPLEX32})) .INPUT(x2, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT8, DT_UINT8, DT_INT32, DT_DOUBLE, DT_INT64, DT_UINT16, DT_INT16, DT_COMPLEX64, DT_COMPLEX128, DT_BF16, DT_COMPLEX32})) .OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT8, DT_UINT8, DT_INT32, DT_DOUBLE, DT_INT64, DT_UINT16, DT_INT16, DT_COMPLEX64, DT_COMPLEX128, DT_BF16, DT_COMPLEX32})) .OP_END_FACTORY_REG(Div)

IR 层面的支持类型比 aclnn 接口更宽(包含 DOUBLE、INT64、UINT16、INT16、COMPLEX128、COMPLEX32),并允许 NCHW、NHWC、ND 等格式(proto 注释中声明),而 aclnn 接口按产品能力做了更细的收敛。图模式下 Div 的 shape 推导使用通用的 Broadcast 推导实现,见 math/div/op_host/div_infershape.cpp 中的IMPL_OP_INFERSHAPE(Div).InferShape(Ops::Base::InferShape4Broadcast)

源码级实现原理

1. aclnn 接口层的类型提升与校验(op_api/aclnn_div.cpp)

math/div/op_api/aclnn_div.cpp 中实现了aclnnDivGetWorkspaceSize/aclnnDiv以及配套的 Divs、DivMod 系列接口。aclnnDivGetWorkspaceSize的执行流程可以归纳为:

  1. 创建 Executor并调用CheckParams完成五步校验:空指针检查 → shape 与 broadcast 检查(维度上限MAX_SUPPORT_DIMS_NUMS,即不超过 8 维)→ 数据类型支持范围检查 → 类型推导(promote)及输出可转换性检查 → 数据格式(禁止私有格式)检查,分别对应返回码 161001/161002 的各条错误描述;
  2. 空 Tensor 特判:如果 self 或 other 为空 Tensor(IsEmpty()),kernel 侧天然支持,直接返回workspaceSize = 0且不构图;
  3. 类型提升:根据芯片架构(IsRegBase/ 非 RegBase)调用不同的推导逻辑。核心策略(见CompatibleInferDivDtypeInferDivModeDtype)是:浮点/复数类型按op::PromoteType提升,整数(除 int32 在 910B/910C 上 L0 算子支持外)与布尔一律提升为FLOAT参与计算,以对齐 CUDA 的精度行为;fp16/bf16 也会被提升到 float 计算(PromoteLowPrecToFloat);
  4. 混合 dtype 特判:对于 910B/910C 平台上的部分混合类型组合(如 fp16+fp32、bf16+fp32 等,定义在AllowedMixDtypePairs),跳过 Cast 直接走 RealDiv,避免不必要的类型转换开销;
  5. 构图与落盘:按需对输入做Contiguous(转连续)与Cast(类型转换),调用 L0 算子l0op::RealDiv完成除法,再用l0op::Cast将结果转回 out 的目标类型,最后用l0op::ViewCopy写回非连续输出;
  6. 返回executor->GetWorkspaceSize()作为第一段接口的输出。

2. L0 算子层的 AICore/AICpu 分发(op_api/div.cpp)

math/div/op_api/div.cpp 定义了 L0 算子l0op::Div。它先通过BroadcastInferShape推导广播后的输出 shape,然后按数据类型决定执行后端:

  • 若 self 的 dtype 在 AICore 支持列表内(910 系列:FLOAT、FLOAT16、INT32、INT8、UINT8;910B/RegBase 系列额外支持 BF16,见GetAiCoreDtypeSupportListBySocVersion),走AICore kernelDivAiCore,通过ADD_TO_LAUNCHER_LIST_AICORE入队);
  • 否则走AICpu kernelDivAiCpu,通过ADD_TO_LAUNCHER_LIST_AICPU入队),例如 DOUBLE、INT64、COMPLEX 等类型在部分平台会落到 CPU 侧计算。

在 Ascend 950(ascend950)产品上,Div 算子注册了专用 AICore 配置,见 math/div/op_host/div_def.cpp 中的OpAICoreConfig:支持动态 shape(DynamicShapeSupportFlag(true))、动态 rank(DynamicRankSupportFlag(true)),并开启精度降低豁免(PrecisionReduceFlag(true)),kernel 文件指向div_apt。对应的静态二进制注册表 math/div/op_host/config/ascend950/div_binary.json 列出了 FLOAT16/FLOAT32/INT32/INT8/UINT8/BFLOAT16/COMPLEX32/COMPLEX64 等 ND 格式的 bin 组合,shape 使用-2表示动态维度。

3. AICpu Kernel 的数值安全处理(op_kernel_aicpu/div_aicpu.cpp)

math/div/op_kernel_aicpu/div_aicpu.cpp 中的DivCpuKernel是 AICpu 后端的核心实现,除了常规的逐元素除法*(output + i) = lhs_getter(i) / rhs_getter(i)之外,还包含三类值得注意的数值防护逻辑:

  • 整数除零检查DivParamCheckZeroComputeIntDivValue会在除数为 0 时打印Division by zero.错误并返回KERNEL_STATUS_INNER_ERROR
  • 有符号整数溢出检查IsDivOverflow检测INT_MIN / -1这一唯一会触发整数除法溢出的组合,CheckNoBcastDivOverflow/CheckBcastDivOverflow会在广播场景(SAME_SHAPE / X_ONE_ELEMENT / Y_ONE_ELEMENT)下逐元素预检;
  • 向零取整修正NeedFloorAdjust在整数除法余数非零且被除数与除数异号时执行lhs / rhs - 1修正,保证整数除法结果与浮点语义一致地向零舍入。

此外 kernel 内部对大数据量场景使用aicpu::CpuKernelUtils::ParallelFor并行切分(阈值常量kParallelDataNum = 2 * 1024kParallelDataNumSameShape = 7 * 1024等,并对中小规模数据限制最大并行核数为 4),说明 Div 的 AICpu 实现具备多核并行能力。

测试与验证

仓库为 Div 算子提供了多层次的测试资产:

  • ST 场景测试:math/div/tests/st/aclnnDiv/atk_aclnnDiv.json 是标准的 ATK 测试描述文件,全部用例以torch.div为对标基准("name": "torch.div", "aclnn_name": "Div"),精度标准为high_performance,覆盖 int8/int16/int32/int64、fp16/fp32/fp64、bf16、bool、complex64/complex128 等多种 dtype,以及多维 shape、0 维标量、1x1x1x1极简 shape、大 shape(如[1,1,20,131073])与边界值(inf/nan/-inf)等用例;
  • UT 单测:math/div/tests/ut/op_api/test_aclnn_div.cpp、math/div/tests/ut/op_host/test_div_infershape.cpp、math/div/tests/ut/op_kernel_aicpu/test_div.cpp 分别覆盖 op_api、infershape 与 aicpu kernel 三个层面;
  • AICore kernel CSV:math/div/tests/st/arch35/ttk_kernel_div_st.csv 用于 arch35(950)平台的 kernel 级测试。

小结

Div 算子是 CANN ops-math 中最基础的逐元素数学算子之一,语义上等价于 PyTorch 的torch.div/ TensorFlow 的Div。本文从 math/div/README.md 出发,梳理了其计算语义、产品支持矩阵、x1/x2/y 参数规格,完整给出了 aclnnDiv/aclnnInplaceDiv 两段式接口的原型、参数、返回码与可直接运行的示例代码,并沿调用链向下展开到 op_api 类型提升、L0 算子 AICore/AICpu 分发、AICpu kernel 的整数除零/溢出防护,以及 ATK/UT 测试资产,可作为你在 NPU 上使用或排查 Div 除法计算的直接参考。

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

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

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

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

立即咨询