- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
本篇文章基于 CANN ops-math 开源仓库中math/floor_mod目录的官方算子文档,系统讲解数学基础算子 FloorMod(向下取整求余,即 remainder/floor mod)的功能语义、产品支持情况、x1/x2/y 参数规格、aclnn 两段式接口的完整调用方式,并结合算子原型(floor_mod_proto.h)、Host 侧定义与 Tiling(floor_mod_def.cpp、floor_mod_tiling_arch35.cpp)以及 Kernel 侧 DAG(floor_mod_dag.h)等源码证据,深入剖析其在 NPU 上的实现原理。读完本文,你将掌握 FloorMod 的数学语义(与 C 语言%的区别)、如何在业务代码中通过 aclnnRemainder 系列接口完成标量/张量间的求余运算,以及该算子在 Atlas 系列产品上“Broadcast + 符号校正”的底层计算路径。
一、算子功能与数学语义
FloorMod 算子实现的是逐元素向下取整求余(floor remainder)。其行为与 PyTorch 的torch.remainder、TensorFlow 的FloorMod保持一致:先将标量self(或张量self)broadcast 成与张量other一致的 shape,再对每个元素计算self除以other对应元素后的余数。结果的符号与除数other相同,且结果的绝对值严格小于other的绝对值。
实际计算remainder(self, other)等效于以下公式:
$$ out_i = self - floor(self / other_i) * other_i $$
其中floor表示向下取整(向负无穷方向舍入),这正是该算子与 C 语言%(向零截断的 truncated remainder)的本质区别所在。以官方文档中的示例为例:
self = 5.0 # float other = tensor([[-1, -2], [-3, -4]]).type(int32) result = remainder(self, other) # result的值 # tensor([[ 0., -1.], # [-1., -3.]]) float # 对于元素other中的-4来说,计算结果为5 - floor(5 / -4) * -4 = -3 # 可以看到,最终结果-3的绝对值小于原来的-4的绝对值。推导该示例中的一个元素:5 / -4 = -1.25,floor(-1.25) = -2,代入公式得到5 - (-2) * (-4) = 5 - 8 = -3。若使用向零截断的 C 语言语义,5 % -4只会得到1,两者符号规则完全不同。这一语义在源码注释中同样被明确为:“Consistent with: floor(x1/x2) * x2 + mod(x1, x2) = x1”(见 floor_mod_proto.h),并注明兼容 TensorFlow 的 FloorMod 算子。
对于 Tensor 对 Tensor 的场景,两个输入先广播为一致 shape 再计算,例如接口文档 aclnnRemainderTensorTensor&aclnnInplaceRemainderTensorTensor.md 中的示例:
self = tensor([[-1, -2], [-3, -4]]).type(int64) other = tensor([-3, -3]).type(float16) result = remainder(self, other) # result的值 # tensor([[-1., -2.], # [-0., -1.]], dtype=float16) # 首先是将other broadcast成和self一致的shape,成为 [[-3, -3], [-3, -3]],然后再进行计算。 # 对于元素self中的-3来说,计算结果为(-3) % (-3) = 0二、产品支持情况
根据 math/floor_mod/README.md 的产品支持矩阵,FloorMod 算子在不同产品上的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | × |
| Atlas 训练系列产品 | √ |
需要注意的是,产品支持矩阵在不同接口变体上存在细微差异:例如在 aclnnRemainderTensorTensor 接口文档 中,Atlas 推理系列产品标注为支持,且在该文档中明确了“Atlas 训练系列产品不支持 BFLOAT16 数据类型”等细节;而 aclnnRemainderScalarTensor 接口文档 则补充说明Atlas 训练系列产品不支持 BFLOAT16。实际使用时应以目标产品的具体接口文档为准。从 Host 侧注册代码 floor_mod_def.cpp 可以确认,AICore 计算配置仅注册了ascend950与ascend350两个平台(分别对应 Ascend 950 与 Atlas A3 系列),而 Atlas 训练系列产品(910)通过框架/AICPU 路径支持。
三、参数说明
FloorMod 算子的输入输出参数在 math/floor_mod/README.md 的参数说明中定义如下:
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| x1 | 输入 | 公式中的输入张量self_i | FLOAT16, BFLOAT16, FLOAT, INT32, INT64 | ND |
| x2 | 输入 | 公式中的输入张量other_i | FLOAT16, BFLOAT16, FLOAT, INT32, INT64 | ND |
| y | 输出 | 公式中的输出张量out_i | FLOAT16, BFLOAT16, FLOAT, INT32, INT64 | ND |
从算子原型 floor_mod_proto.h 可见,REG_OP(FloorMod)声明的类型集合为{DT_INT32, DT_INT64, DT_FLOAT, DT_FLOAT16, DT_DOUBLE, DT_BF16},比 README 表格多出DOUBLE——该类型在接口层面(aclnn 文档中self/other支持 DOUBLE)和 AICPU 路径中有效。而 floor_mod_def.cpp 中 OpDef 注册的 AICore 数据类型为{DT_BF16, DT_FLOAT16, DT_FLOAT, DT_INT32, DT_INT64},与 README 表格一致,格式统一为FORMAT_ND,且动态 shape、动态 rank(DynamicRankSupportFlag(true)、DynamicShapeSupportFlag(true))均被开启。
关于 shape 与 broadcast 关系,原型注释(floor_mod_proto.h)给出了以下约束,可在开发中直接参考:
x2的输入数据不支持 0(除零保护见下文“约束与注意事项”);- 当张量元素值超过 2048 时,算子在 mini 平台上的精度无法保证达到双千分位要求;
- 由于架构差异,该算子在 NPU 与 CPU 上的计算结果可能不一致;
- 若 shape 表示为
(D1, D2, …, Dn),则需要满足D1*D2*…*DN ≤ 1000000且n ≤ 8。
四、调用方式与接口变体
4.1 aclnn 两段式调用模型
FloorMod 在 aclnn 层提供remainder语义的多组接口,全部遵循 CANN 的两段式接口调用模型:先调用xxxGetWorkspaceSize第一段接口,完成入参校验并获取计算所需 workspace 大小与包含算子计算流程的执行器(executor);再调用第二段执行接口,传入 workspace 与 stream 真正下发计算。aclnn 返回码的定义可参见 aclnn_return_code.md。
math/floor_mod/README.md 的调用说明给出了基本用法:通过 aclnnRemainderScalarTensor 接口调用 FloorMod 算子,对应示例代码见 test_aclnn_remainder_scalar_tensor.cpp。
4.2 接口变体与选择
根据math/floor_mod/docs与math/floor_mod/examples目录,FloorMod 共提供六种 aclnn 接口(每个接口又包含 GetWorkspaceSize 与执行两段):
| 接口 | 输入形式 | 说明 |
|---|---|---|
| aclnnRemainderScalarTensor | scalarself+ tensorother | 标量与张量求余,输出新建张量 |
| aclnnRemainderTensorScalar | tensorself+ scalarother | 张量与标量求余,输出新建张量 |
| aclnnInplaceRemainderTensorScalar | tensorself+ scalarother | 张量与标量求余,结果直接写回self内存(inplace) |
| aclnnRemainderTensorTensor | tensorself+ tensorother | 两个张量 broadcast 后求余,输出新建张量 |
| aclnnInplaceRemainderTensorTensor | tensorself+ tensorother | 两个张量 broadcast 后求余,结果写回self内存(inplace) |
RemainderTensorTensor与InplaceRemainderTensorTensor实现相同的功能,区别仅在于:前者需新建一个输出张量对象存储计算结果,后者无需新建输出张量对象,直接在输入张量self的内存中存储计算结果(参见 aclnnRemainderTensorTensor 接口文档)。实际业务中若希望避免额外显存开销,可优先考虑 inplace 版本。
4.3 函数原型
以 aclnnRemainderScalarTensor 为例,两段式接口原型为:
aclnnStatus aclnnRemainderScalarTensorGetWorkspaceSize( const aclScalar* self, const aclTensor* other, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnRemainderScalarTensor( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)TensorTensor 与 inplace 变体的原型(接口文档)为:
aclnnStatus aclnnRemainderTensorTensorGetWorkspaceSize(const aclTensor *self, const aclTensor *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnRemainderTensorTensor(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream) aclnnStatus aclnnInplaceRemainderTensorTensorGetWorkspaceSize(aclTensor* selfRef, const aclTensor *other, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnInplaceRemainderTensorTensor(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)4.4 GetWorkspaceSize 参数详解
第一段接口aclnnRemainderScalarTensorGetWorkspaceSize的入参与出参如下(接口文档):
| 参数名 | 输入/输出 | 描述 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|
| self(aclScalar*) | 输入 | 公式中的输入self | INT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16 | - | - | - |
| other(aclTensor*) | 输入 | 公式中的输入other | INT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16 | ND | 0-8 | √ |
| out(aclTensor*) | 输出 | 公式中的输出out | INT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16 | ND | 0-8 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - |
关键使用说明:
self的数据类型与other需满足 TensorScalar 互推导关系,且推导出的数据类型必须能转换为out的数据类型;other、out均支持空 Tensor;out的 shape 需要与other一致;- 若为 TensorTensor 变体,则
self、other的 shape 需满足 broadcast 关系,out的 shape 为两者 broadcast 之后的 shape; - 支持非连续 Tensor,数据格式仅支持 ND,维度数不支持 8 维以上;
- 在
Atlas 训练系列产品(910)上不支持 BFLOAT16 数据类型(接口文档)。
4.5 第一段接口的返回值与错误码
第一段接口完成入参校验,出现以下场景时报错(错误码体系见 aclnn_return_code.md):
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 self、other、out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | other、out 的 shape 不一样 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 无法做数据类型推导 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 推导出的数据类型不属于支持的数据类型 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 other 推导出的数据类型无法转换为指定输出 out 的类型 |
| ACLNN_ERR_PARAM_INVALID | 161002 | other、out 的维度数大于 8 维 |
第二段执行接口aclnnRemainderScalarTensor的入参为:workspace(Device 侧申请的 workspace 内存地址)、workspaceSize(由第一段接口获取)、executor(op 执行器)、stream(指定执行任务的 Stream),返回aclnnStatus状态码。
五、完整调用示例(aclnnRemainderScalarTensor)
以下示例代码取自 aclnnRemainderScalarTensor 接口文档,演示了“标量 self 对张量 other 求余”的完整调用流程:设备初始化 → 构造输入输出 → 两段式接口调用 → 同步等待 → 取回结果 → 资源释放。编译与运行的具体工程步骤请参考编译与运行样例。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_remainder.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> otherShape = {3, 3}; std::vector<int64_t> outShape = {3, 3}; void* otherDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclScalar* self = nullptr; aclTensor* other = nullptr; aclTensor* out = nullptr; std::vector<int64_t> otherHostData = {0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector<int64_t> outHostData = {0, 0, 0, 0, 0, 0, 0, 0, 0}; int64_t Self = 3; // 创建self aclScalar self = aclCreateScalar(&Self, aclDataType::ACL_INT64); CHECK_RET(self != nullptr, return ret); // 创建other aclTensor ret = CreateAclTensor(otherHostData, otherShape, &otherDeviceAddr, aclDataType::ACL_INT64, &other); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_INT64, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnRemainderScalarTensor第一段接口 ret = aclnnRemainderScalarTensorGetWorkspaceSize(self, other, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRemainderScalarTensorGetWorkspaceSize 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); } // 调用aclnnRemainderScalarTensor第二段接口 ret = aclnnRemainderScalarTensor(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRemainderScalarTensor 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<int64_t> 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: %ld\n", i, resultData[i]); } // 6. 释放aclTensor和aclScalar aclDestroyScalar(self); aclDestroyTensor(other); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例中other = [[0..8]]、self = 3,输出结果将逐元素等于3 % other[i](按 floor 语义)。若业务需要“张量对张量”或“原地写回”语义,仅需将 API 替换为aclnnRemainderTensorTensor/aclnnInplaceRemainderTensorTensor等对应变体,调用骨架完全一致。
六、约束与注意事项
综合 aclnnRemainderScalarTensor 接口文档 与算子原型注释,使用 FloorMod 时需注意:
- 确定性计算:
aclnnRemainderScalarTensor默认是确定性实现(关于确定性计算的通用约定可参考 determinism_compute.md); - INT32 精度范围:当
self的数据类型为 INT32 时,优先保障范围[-2^24, 2^24]内的功能与精度; - 整型除零行为:当
other为 0 且self的数据类型为整型时,out的结果为self(原型注释中则表述为“x2 不支持 0”,两者结合看即整型除零时返回被除数以规避未定义行为); - 元素数量限制:shape 各维度乘积
D1*D2*…*DN ≤ 1000000且n ≤ 8; - 数值范围:当张量元素值超过 2048 时,算子在 mini 平台上的精度无法保证达到双千分位要求;
- 跨架构差异:由于架构不同,该算子在 NPU 与 CPU 上的计算结果可能不一致。
七、源码级实现原理
7.1 算子原型注册(op_graph)
floor_mod_proto.h 通过REG_OP(FloorMod)完成算子原型的静态注册,声明输入x1、x2与输出y,三者均支持DT_INT32, DT_INT64, DT_FLOAT, DT_FLOAT16, DT_DOUBLE, DT_BF16六种类型,语义注释中明确了“Integer division by zero on NPU returns x1”以及“Support broadcasting operations”。
7.2 Host 侧算子定义与推导(op_host)
- floor_mod_def.cpp 通过
OpDef注册了算子描述:AICore 配置同时注册ascend950与ascend350两个平台,并开启DynamicCompileStaticFlag、DynamicRankSupportFlag、DynamicShapeSupportFlag,关闭DynamicFormatFlag,同时设置PrecisionReduceFlag(true)(允许精度降低以换取性能),并指定 kernel 实现文件floor_mod_apt。 - floor_mod_infershape.cpp 通过
IMPL_OP_INFERSHAPE(FloorMod).InferShape(Ops::Base::InferShape4Broadcast)直接复用公共的 broadcast 形状推导逻辑,从实现层面印证了该算子对输入 shape 的广播语义。
7.3 Tiling 实现(arch35)
floor_mod_tiling_arch35.cpp 完成了算子的任务切分(Tiling):
CheckDtype校验x1、x2、y三者数据类型必须一致;- 针对不同输入类型选择不同的广播算子 DAG:FLOAT16/BF16 使用
FloorModFloatWithCastOp<half>、FLOAT 使用FloorModFloatOp<float>、INT32 使用FloorModInt32Op<int32_t>、INT64 使用FloorModInt64Op<int64_t>,统一通过BroadcastBaseTiling(KERNEL_TYPE_NDDMA模式)完成切分,并将调度模式写入tilingKey; - 关键的资源规划细节:由于 Kernel 中所有输入类型都会先 cast 到 float32 再进入高阶 API 计算,临时 buffer 按 float 大小估算(
GetFmodTmpBufferFactorSize(sizeof(float), ...));INT32/INT64 路径额外追加DCACHE_SIZE(32KB)的 workspace;PostTiling中还会将 UB 大小扣减 32KB 后再写入上下文。
7.4 Kernel 计算逻辑(op_kernel)
- floor_mod_apt.cpp 是 kernel 入口,按模板参数
DTYPE_X1在编译期分派:半精度/BF16 走FloorModFloatWithCastOp、FLOAT 走FloorModFloatOp、INT32 走FloorModInt32Op、INT64 走FloorModInt64Op,统一用BroadcastSch调度器调用sch.Process(x1, x2, y)。 - floor_mod_dag.h 给出了核心计算 DAG:
- 浮点路径:先调用向量高阶 API
Vec::FmodHighPrecision得到截断余数(truncated remainder),随后由FmodPostCompute/FmodCastFloatPostCompute做符号校正后处理——当fmodRes与除数inputX2的符号不同且fmodRes != 0时,将结果加上除数inputX2,这正是把“向零截断余数”转换为“向下取整余数”的关键一步,与本文第一节的数学公式self - floor(self/other)*other完全等价; - 半精度/BF16 路径:
FloorModFloatWithCastOp先经Vec::Cast<float, T>将输入提升到 float 精度计算,避免低精度中间结果损失,输出前再 cast 回原类型; - 整型路径:
FloorModInt通过 SIMT 向量函数FloorModInt_1(asc_vf_call启动 1024 线程)逐元素执行src1 % src2,并做同样的“符号不同且余数非零则加除数”校正;FmodIntPostCompute还处理了-1除数的边界情况。
- 浮点路径:先调用向量高阶 API
7.5 API 分派逻辑(op_api)
floor_mod.cpp 展示了 l0op 层的分派策略:
AICPU_DTYPE_SUPPORT_LIST = {op::DataType::DT_DOUBLE},即 DOUBLE 类型走 AICPU 实现(FloorModAiCpu,对应 TF-AICPU 路径);- 其余类型(INT32、INT64、FLOAT16、FLOAT、BFLOAT16)走 AICore 实现(
FloorModAiCore); FloorMod顶层函数先通过BroadcastInferShape计算广播后的输出 shape,再用executor->AllocTensor分配输出张量,最后按数据类型分派到 AICore 或 AICPU。
八、测试与验证
仓库为该算子配备了完整的测试资产,可用于验证实现正确性:
- Kernel 基准(golden):golden.py 以 PyTorch 的
torch.remainder作为参考实现,对 INT32/INT64/FLOAT/FLOAT16/BFLOAT16 全类型生成期望输出,并实现了除零保护(整型除零位置置-1、浮点除零位置置NaN),同时通过 numpy 与 torch 之间的 bfloat16 视图转换处理 BF16 数据; - UT 用例:API 层单测见 tests/ut/op_api(如
test_aclnn_remainder_scalar_tensor.cpp、test_remainder_tensor_tensor.cpp、inplace 系列),Host 层单测包括 shape 推导 test_floor_mod_infershape.cpp 与 arch35 Tiling 单测 test_floor_mod_tiling.cpp; - ST 用例:aclnn 系列接口的系统测试配置见 tests/st/aclnnRemainderScalarTensor/atk_aclnnRemainderScalarTensor.json 等,arch35 平台 kernel 级 ST 用例见 ttk_kernel_floor_mod_st.csv。
九、总结
FloorMod 是 CANN ops-math 中语义明确、实现精巧的数学基础算子:它在数学上等价于self - floor(self/other)*other,结果与被除数同符号;在接口上通过 aclnnRemainder 系列六种变体覆盖标量/张量、新建输出/原地写回等全部常见调用形态;在实现上采用“fmod/%截断求余 + 符号校正”的两步法,配合 Broadcast 调度框架在 NPU 上完成高效并行计算。开发者在使用时,只需按两段式接口模型完成 workspace 申请与 executor 下发,即可将向下取整求余能力无缝接入 Atlas 系列产品上的模型计算图。
- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
相关推荐
CANN ops-math FloorMod 算子全解析:向下取整取模的数学语义、aclnn 调用与 AscendC 内核实现
CANN ops math FloorMod 算子全解析:向下取整取模的数学语义、aclnn 调用与 AscendC 内核实现 FloorMod 是 CANN
算子库人工智能CANNCANN ops-math 数学算子库 AsStrided 算子详解:PyTorch as_strided 语义的 NPU 实现与 aclnn 调用指南
CANN ops math 数学算子库 AsStrided 算子详解:PyTorch as_strided 语义的 NPU 实现与 aclnn 调用指南 导读
算子库人工智能CANNCANN ops-math 余弦算子 Cos 深度解析:算子定义、NPU 核函数实现与 aclnn API 调用实战
CANN ops math 余弦算子 Cos 深度解析:算子定义、NPU 核函数实现与 aclnn API 调用实战 CANN ops math 是 CANN
算子库人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考