CANN ops-math FloorDiv 算子深度解析:向下取整除法的 NPU 实现与 aclnn 调用指南
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
FloorDiv(向下取整除法)是 CANN ops-math 数学算子库中的基础二元算子,完成out = floor(self / other)计算。本文以 math/floor_div/README.md 为骨架,结合算子源码、Tiling 实现、配置与测试用例,系统讲解 FloorDiv 的功能语义、产品支持矩阵、aclnnFloorDivide 系列接口的参数规范与错误码、两段式调用流程,并深入到 op_api、op_host、op_kernel 三层源码剖析其实现原理。读完本文,你将能够在 Ascend NPU 上正确、高效地通过 aclnn 接口调用 FloorDiv,并理解其内部数据类型推导、Broadcast 与 Kernel 计算细节。
一、功能说明与数学语义
FloorDiv 算子的功能是完成除法计算,对结果向下取整,计算公式为:
$$ out_i = floor(\frac{self_i}{other_i}) $$
其中self、other为两个输入 Tensor,out为输出 Tensor。需要特别注意的是"向下取整"与"向零取整"的差异:对于负数结果(例如-7 / 2 = -3.5),向下取整得到-4,而 C 语言中整数除法向零取整得到-3。从 floor_div_dag.h 中可以看到,整数 Kernel 通过判断两操作数符号是否相异(signs_differ)来修正商:当符号相异且余数不为 0 时,商减 1,从而严格保证 floor 语义。
从算子定义看,FloorDiv 在 Graph 层注册的算子名称为FloorDiv,输入为x1、x2,输出为y(见 floor_div_def.cpp),aclnn 层的self/other/out与之一一对应。
二、产品支持情况
根据 math/floor_div/README.md,FloorDiv 算子在不同产品的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | √ |
| Atlas 训练系列产品 | √ |
其中Atlas 训练系列产品不支持 BFLOAT16 数据类型。对于aclnnFloorDivides(标量版本),Atlas 推理系列产品同样不支持(详见 aclnnFloorDivides&aclnnInplaceFloorDivides.md)。
三、算子接口全景:四种 aclnn 接口
FloorDiv 在 aclnn 层提供四组接口,其头文件声明位于 aclnn_floor_divide.h:
| 接口 | 特点 | 适用场景 |
|---|---|---|
aclnnFloorDivide | Tensor ÷ Tensor,需新建输出张量 | 两个输入均为 Tensor 的常规计算 |
aclnnInplaceFloorDivide | Tensor ÷ Tensor,结果直接写回 selfRef | 无需保留原输入、希望省内存 |
aclnnFloorDivides | Tensor ÷ Scalar,需新建输出张量 | 除数是一个标量(如除以 2.0) |
aclnnInplaceFloorDivides | Tensor ÷ Scalar,结果直接写回 selfRef | 标量除数 + 原地更新 |
非 Inplace 接口与 Inplace 接口实现相同功能,区别仅在于是否新建输出张量:aclnnFloorDivide/aclnnFloorDivides需要调用方新建out张量存放结果;aclnnInplaceFloorDivide/aclnnInplaceFloorDivides无需输出张量,直接在输入selfRef张量的内存中覆盖写入计算结果。
每个算子均采用 CANN 通用的两段式接口模式:先调用GetWorkspaceSize接口完成入参校验、计算所需 workspace 大小并生成包含算子计算流程的执行器executor;再调用执行接口真正下发计算任务。
3.1 函数原型(Tensor-Tensor 版本)
aclnnStatus aclnnFloorDivideGetWorkspaceSize( const aclTensor* self, const aclTensor* other, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnFloorDivide( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) aclnnStatus aclnnInplaceFloorDivideGetWorkspaceSize( aclTensor* selfRef, const aclTensor* other, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnInplaceFloorDivide( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)3.2 函数原型(Tensor-Scalar 版本)
aclnnStatus aclnnFloorDividesGetWorkspaceSize( const aclTensor* self, const aclScalar* other, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnFloorDivides( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) aclnnStatus aclnnInplaceFloorDividesGetWorkspaceSize( aclTensor* selfRef, const aclScalar* other, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnInplaceFloorDivides( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)四、第一段接口参数详解(aclnnFloorDivideGetWorkspaceSize)
以 Tensor 版本为例,第一段接口参数说明如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续 Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 公式中的输入 self | 数据类型需与 other 满足互推导关系,shape 需与 other 满足 broadcast 关系 | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL | ND | 不超过 8 维 | √ |
| other(aclTensor*) | 输入 | 公式中的输入 other | 数据类型需与 self 满足互推导关系,shape 需与 self 满足 broadcast 关系 | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL | ND | 不超过 8 维 | √ |
| out(aclTensor*) | 输出 | 公式中的输出 out | 数据类型需是 self 与 other 推导之后可转换的类型,shape 需是 broadcast 之后的 shape | FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、COMPLEX64、COMPLEX128 | ND | 不超过 8 维 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含算子计算流程 | - | - | - | - | - |
补充说明(与 README 中算子级参数表一致):
- BFLOAT16 扩展:Atlas A2 训练系列产品/Atlas A2 推理系列产品、Atlas A3 训练系列产品/Atlas A3 推理系列产品、Ascend 950PR/Ascend 950DT 上,
self、other、out额外支持 BFLOAT16;aclnnFloorDivides在 Ascend 950PR/Ascend 950DT 上还要求数据类型满足 Tensor-Scalar 互推导关系。 - 数据格式:仅支持 ND 格式,且 self、other、out 的格式需要一致(参见数据格式说明)。
- 非连续 Tensor:三个 Tensor 参数均支持非连续输入(参见非连续 Tensor 说明),op_api 内部会先做
Contiguous归一化处理。 - 维度限制:shape 不超过 8 维,超过则报参数非法错误。
对于aclnnFloorDivides的标量版本,other为aclScalar*,无 shape/format/非连续概念,仅要求与self满足数据类型推导规则;self的维度同样不超过 8 维。
Inplace 版本的第一段接口(aclnnInplaceFloorDivideGetWorkspaceSize)参数为selfRef(输入/输出)、other、workspaceSize、executor,selfRef同时充当输入与输出,数据类型、shape、format 约束与self相同。
五、返回值与错误码
所有第一段接口均返回aclnnStatus状态码,具体语义参见 aclnn 返回码。第一段接口在入参校验阶段发现非法输入时报错,Tensor-Tensor 版本的典型错误场景如下:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 self、other 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 的数据类型和数据格式不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 不满足数据类型推导规则 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 推导出的数据类型无法转换为指定输出 out 的类型 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 的 shape 无法做 broadcast |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 的维度大于 8 |
Inplace 版本(aclnnInplaceFloorDivideGetWorkspaceSize)的错误场景为:selfRef或other为空指针(161001);数据类型/格式不在支持范围、不满足推导规则、shape 无法 broadcast、维度大于 8(161002)。Scalar 版本(aclnnFloorDividesGetWorkspaceSize)则额外包含"推导出的数据类型无法转换为指定输出 out 的类型"这一错误场景,且维度检查只针对self。
第二段执行接口(如aclnnFloorDivide)的入参为:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
六、完整调用示例(aclnnFloorDivide 与 aclnnInplaceFloorDivide)
以下示例代码来自 test_aclnn_floor_divide.cpp(仓库中另有 test_aclnn_floor_divides.cpp 演示 Scalar 版本),完整展示了从资源初始化、Tensor 构造、两段式调用到结果回拷与资源释放的整个流程。编译与执行环境搭建请参考编译与运行样例。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_floor_divide.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); // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建other aclTensor ret = CreateAclTensor(otherHostData, otherShape, &otherDeviceAddr, aclDataType::ACL_FLOAT, &other); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnFloorDivide第一段接口 ret = aclnnFloorDivideGetWorkspaceSize(self, other, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFloorDivideGetWorkspaceSize 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); } // 调用aclnnFloorDivide第二段接口 ret = aclnnFloorDivide(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFloorDivide failed. ERROR: %d\n", ret); return ret); uint64_t inplaceWorkspaceSize = 0; aclOpExecutor* inplaceExecutor; // 调用aclnnInplaceFloorDivide第一段接口 ret = aclnnInplaceFloorDivideGetWorkspaceSize(self, other, &inplaceWorkspaceSize, &inplaceExecutor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceFloorDivideGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* inplaceWorkspaceAddr = nullptr; if (inplaceWorkspaceSize > 0) { ret = aclrtMalloc(&inplaceWorkspaceAddr, inplaceWorkspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } // 调用aclnnInplaceFloorDivide第二段接口 ret = aclnnInplaceFloorDivide(inplaceWorkspaceAddr, inplaceWorkspaceSize, inplaceExecutor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceFloorDivide 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侧,需要根据具体API的接口定义修改 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]); } auto inplaceSize = GetShapeSize(selfShape); std::vector<float> inplaceResultData(inplaceSize, 0); ret = aclrtMemcpy(inplaceResultData.data(), inplaceResultData.size() * sizeof(inplaceResultData[0]), selfDeviceAddr, inplaceSize * sizeof(float), 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 < inplaceSize; i++) { LOG_PRINT("inplaceResult[%ld] is: %f\n", i, inplaceResultData[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }上述样例中self = {0,1,2,3,4,5,6,7}、other = {1,1,1,2,2,2,3,3},计算结果为out = {0,1,2,1,2,2,2,2};Inplace 版本执行后self内存中的值会被原地覆盖为同样的结果。Scalar 版本(aclnnFloorDivides)的核心差异在于:other使用aclCreateScalar(&otherValue, aclDataType::ACL_FLOAT)创建aclScalar*,且输出 shape 必须与self完全一致(标量除不产生 broadcast 扩展),释放资源时使用aclDestroyScalar。
七、源码级实现原理
7.1 op_api 层:统一的数据类型推导与执行流
FloorDiv 的 aclnn 实现位于 aclnn_floor_divide.cpp,核心执行函数CalcFloorDivide的调用链如下:
- 入参校验(
CheckParams):依次执行空指针检查、dtype 支持列表检查、类型推导检查(CheckPromoteType)、shape 与 broadcast 检查(CheckShape); - 空 Tensor 短路:
self->IsEmpty() || other->IsEmpty()时直接返回成功,不执行计算; - 执行图构建:
Contiguous(非连续 Tensor 归一化)→Cast(输入提升到 promoteType)→l0op::FloorDiv(核心除算子)→Cast(结果转换到 out 的数据类型)→ViewCopy(写入输出张量)。
其中 dtype 支持列表按 NPU 架构区分(aclnn_floor_divide.cpp):ASCEND910_DTYPE_SUPPORT_LIST包含 FLOAT16、FLOAT、INT64、INT32、INT16、INT8、UINT8、DOUBLE、BOOL;ASCEND910B_DTYPE_SUPPORT_LIST在此基础上额外包含 BF16,与 README 中"A2/A3/950 额外支持 BFLOAT16"的说明一致。从该实现可以推断:aclnn 层先通过PromoteType推导出统一的中间类型,再对输入做 Cast 提升,因此 README 中列出的 DOUBLE、INT16、BOOL 等类型(如与另一输入推导后落入支持列表)也可以正常计算。Scalar 版本还实现了InferFloorDivTensorScalarDtype对 Tensor-Scalar 组合做专门推导,并在 950 等架构上将 FLOAT16/BFLOAT16 提升为 FLOAT 参与数学运算以提高精度。
7.2 op_host 层:算子定义、shape 推导与 Tiling
- 算子定义:floor_div_def.cpp 通过
OpDef注册FloorDiv,输入x1/x2与输出y的数据类型支持 BF16、FLOAT16、FLOAT、INT32、UINT8、INT8、INT64,格式均为 ND,并为ascend950、ascend350两个架构添加 AICore 配置(支持动态编译、动态 shape、动态 rank,关闭格式动态)。 - shape 推导:floor_div_infershape.cpp 直接复用
Ops::Base::InferShape4Broadcast,即输出 shape 由两个输入按广播规则推导得出。 - Tiling:floor_div_tiling_arch35.cpp 首先要求
x1、x2数据类型一致,然后按 dtype 分发到不同的BroadcastBaseTiling模板:INT64/INT32 走整数版、FLOAT16/BF16 走"先 Cast 到 float 再计算"的浮点版、FLOAT 直接走 float 版、UINT8/INT8 走对应整数版,其余 dtype 报错。Tiling 过程还通过GetCoreMemSize(CoreMemType::UB)获取片上 UB 大小,并在PostTiling中预留DCACHE_SIZE(32KB)后将剩余空间设置为算子可用 Local Memory。
7.3 op_kernel 层:DAG 化 Kernel 与 floor 语义实现
Kernel 采用 DAG(有向无环图)方式描述计算流水,定义见 floor_div_dag.h,按数据类型拆分为多套 OpDag:
- 浮点无 Cast(FLOAT):
CopyInBrc → DivHighPrecision → Truncate(floor) → CopyOut; - 浮点有 Cast(FLOAT16/BF16):先
Cast到 FLOAT 做高精度除法,floor 后再Cast回原类型(DIV_CAST_MODE_RINT),避免低精度浮点直接除法放大误差; - INT32/INT64:使用 SIMT 向量核函数
FloorDivInt_1,通过signs_differ判断符号差异并按余数修正商,严格实现向下取整; - INT8/UINT8:先拓宽到 INT16/UINT16 做除法(避免 8 位整型中间溢出),再做饱和转换回原类型(
SAT_POS = 60控制饱和模式)。
Kernel 入口 floor_div_apt.cpp 根据模板参数DTYPE_X1在编译期选择对应 OpDag,并通过BroadcastSch调度执行。各 dtype 对应的二进制产物映射关系由 floor_div_binary.json 描述(如FloorDiv_FLOAT32、FloorDiv_INT64等,shape: [-2]表示支持动态 shape)。
八、约束与精度说明
- 确定性计算:
aclnnFloorDivide、aclnnInplaceFloorDivide、aclnnFloorDivides、aclnnInplaceFloorDivides默认均为确定性实现,多次调用结果一致。 - 低精度误差:在 Atlas A2 训练系列产品/Atlas A2 推理系列产品、Atlas A3 训练系列产品/Atlas A3 推理系列产品上,因 FLOAT16/BFLOAT16 精度有限、无法表示所有小数,向下取整时存在一定误差。若对精度敏感,可以选择更高精度的数据类型(如 FLOAT32)参与计算——这与 op_api 层"FLOAT16/BF16 先提升到 FLOAT 再计算"的实现相呼应,而 950 架构上的标量版本甚至直接采用该提升策略保证精度。
九、测试与验证
仓库为 FloorDiv 提供了多层次的测试用例,可用于验证实现正确性:
- test_aclnn_floor_divide.cpp:aclnn API 层单元测试,覆盖 Tensor 与 Scalar 两套接口;
- test_floor_div_infershape.cpp:shape 推导(广播)单测;
- test_floor_div_tiling_arch35.cpp:Tiling 单测;
- atk_aclnnFloorDivide.json 与 ttk_kernel_floor_div_st.csv:系统级(ST)测试用例描述,覆盖各 dtype 组合与典型 shape。
十、总结
FloorDiv 是 ops-math 中实现简单但细节严谨的算子:数学语义上严格遵循"向下取整"(而非向零取整),实现上通过 op_api 层的数据类型推导 + op_host 层的动态 Tiling + op_kernel 层的 DAG 流水三层协作,覆盖了从 FLOAT/BFLOAT16 到 INT8/INT64 的广泛数据类型与 ND 格式的非连续 Tensor 输入。无论你是在做框架适配、模型迁移还是算子二次开发,掌握aclnnFloorDivide/aclnnFloorDivides及其 Inplace 版本的两段式调用方式,并理解其类型推导与 floor 语义细节,都能帮助你在 Ascend NPU 上写出正确、高效的向下取整除法计算代码。
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考