CANN ops-math FloorMod 算子全解析:数学语义、aclnn 调用与 NPU 源码实现
2026/9/20 1:29:17 网站建设 项目流程
  • 算子库
  • 人工智能
  • CANN

【免费下载链接】ops-math

本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-math
点击查看免费下载

本篇文章基于 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.25floor(-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 计算配置仅注册了ascend950ascend350两个平台(分别对应 Ascend 950 与 Atlas A3 系列),而 Atlas 训练系列产品(910)通过框架/AICPU 路径支持。

三、参数说明

FloorMod 算子的输入输出参数在 math/floor_mod/README.md 的参数说明中定义如下:

参数名输入/输出/属性描述数据类型数据格式
x1输入公式中的输入张量self_iFLOAT16, BFLOAT16, FLOAT, INT32, INT64ND
x2输入公式中的输入张量other_iFLOAT16, BFLOAT16, FLOAT, INT32, INT64ND
y输出公式中的输出张量out_iFLOAT16, BFLOAT16, FLOAT, INT32, INT64ND

从算子原型 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 ≤ 1000000n ≤ 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/docsmath/floor_mod/examples目录,FloorMod 共提供六种 aclnn 接口(每个接口又包含 GetWorkspaceSize 与执行两段):

接口输入形式说明
aclnnRemainderScalarTensorscalarself+ tensorother标量与张量求余,输出新建张量
aclnnRemainderTensorScalartensorself+ scalarother张量与标量求余,输出新建张量
aclnnInplaceRemainderTensorScalartensorself+ scalarother张量与标量求余,结果直接写回self内存(inplace)
aclnnRemainderTensorTensortensorself+ tensorother两个张量 broadcast 后求余,输出新建张量
aclnnInplaceRemainderTensorTensortensorself+ tensorother两个张量 broadcast 后求余,结果写回self内存(inplace)

RemainderTensorTensorInplaceRemainderTensorTensor实现相同的功能,区别仅在于:前者需新建一个输出张量对象存储计算结果,后者无需新建输出张量对象,直接在输入张量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*)输入公式中的输入selfINT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16---
other(aclTensor*)输入公式中的输入otherINT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16ND0-8
out(aclTensor*)输出公式中的输出outINT32、INT64、FLOAT16、FLOAT、DOUBLE、BFLOAT16ND0-8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程----

关键使用说明:

  • self的数据类型与other需满足 TensorScalar 互推导关系,且推导出的数据类型必须能转换为out的数据类型;
  • otherout均支持空 Tensor;out的 shape 需要与other一致;
  • 若为 TensorTensor 变体,则selfother的 shape 需满足 broadcast 关系,out的 shape 为两者 broadcast 之后的 shape;
  • 支持非连续 Tensor,数据格式仅支持 ND,维度数不支持 8 维以上;
  • Atlas 训练系列产品(910)上不支持 BFLOAT16 数据类型(接口文档)。

4.5 第一段接口的返回值与错误码

第一段接口完成入参校验,出现以下场景时报错(错误码体系见 aclnn_return_code.md):

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 self、other、out 是空指针
ACLNN_ERR_PARAM_INVALID161002other、out 的 shape 不一样
ACLNN_ERR_PARAM_INVALID161002self 和 other 无法做数据类型推导
ACLNN_ERR_PARAM_INVALID161002self 和 other 推导出的数据类型不属于支持的数据类型
ACLNN_ERR_PARAM_INVALID161002self 和 other 推导出的数据类型无法转换为指定输出 out 的类型
ACLNN_ERR_PARAM_INVALID161002other、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 ≤ 1000000n ≤ 8
  • 数值范围:当张量元素值超过 2048 时,算子在 mini 平台上的精度无法保证达到双千分位要求;
  • 跨架构差异:由于架构不同,该算子在 NPU 与 CPU 上的计算结果可能不一致。

七、源码级实现原理

7.1 算子原型注册(op_graph)

floor_mod_proto.h 通过REG_OP(FloorMod)完成算子原型的静态注册,声明输入x1x2与输出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 配置同时注册ascend950ascend350两个平台,并开启DynamicCompileStaticFlagDynamicRankSupportFlagDynamicShapeSupportFlag,关闭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校验x1x2y三者数据类型必须一致;
  • 针对不同输入类型选择不同的广播算子 DAG:FLOAT16/BF16 使用FloorModFloatWithCastOp<half>、FLOAT 使用FloorModFloatOp<float>、INT32 使用FloorModInt32Op<int32_t>、INT64 使用FloorModInt64Op<int64_t>,统一通过BroadcastBaseTilingKERNEL_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:
    • 浮点路径:先调用向量高阶 APIVec::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_1asc_vf_call启动 1024 线程)逐元素执行src1 % src2,并做同样的“符号不同且余数非零则加除数”校正;FmodIntPostCompute还处理了-1除数的边界情况。

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.cpptest_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上加速计算。

项目地址:https://gitcode.com/cann/ops-math
点击查看免费下载

相关推荐

上一篇:MinerU PDF 解析使用指南:从一条命令到 3 种部署模式
下一篇:如何用Python实现Windows微信自动群发:3倍效率提升的实用方案

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

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

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

立即咨询