CANN ops-math 算子深度解析:aclnnFloorDivides 与 aclnnInplaceFloorDivides 向下取整除法 API 使用指南 CANN ops-math 算子深度解析aclnnFloorDivides 与 aclnnInplaceFloorDivides 向下取整除法 API 使用指南【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math本文以 CANN ops-math 仓库 math/floor_div 目录下的算子为对象系统讲解aclnnFloorDivides与aclnnInplaceFloorDivides两个单算子 API 的功能语义、函数原型、参数约束、错误码、两段式调用流程以及完整可编译运行的示例代码。读完本文你将掌握在 NPU 上通过 aclnn 接口完成张量除以标量、结果向下取整计算的标准写法并理解其底层从 aclnn 入口到算子定义、再到 AICore kernel 的完整实现链路。一、产品支持情况aclnnFloorDivides与aclnnInplaceFloorDivides在不同 NPU 产品上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品支持说明原文档中 Atlas 推理系列产品Atlas 推理系列产品 310P标注为不支持但仓库内 math/floor_div/README.md 的产品支持表中Atlas 推理系列产品标注为支持两处存在差异实际可用性请以所安装 CANN 版本配套的算子清单为准。二、功能说明接口功能完成除法计算对结果向下取整floor。计算公式$$ out_i floor(\frac{input_i}{other}) $$其中input即self为被除数张量other为除数标量out为除法结果逐元素向下取整后得到的张量。向下取整意味着结果向负无穷方向取整例如-3.5 → -4、3.5 → 3这与截断除法truncate向零取整的行为不同。2.1 两个接口的使用区别aclnnFloorDivides和aclnnInplaceFloorDivides实现的功能相同区别仅在于输出张量的管理方式aclnnFloorDivides需要调用方新建一个输出张量对象aclTensor* out来存储计算结果输入、输出分离。aclnnInplaceFloorDivides无需新建输出张量对象计算完成后结果直接写回输入张量selfRef的内存中即原地in-place计算可节省一份输出张量的 Device 侧内存。三、两段式接口调用模型每个算子都采用 CANN aclnn 的两段式接口设计必须先调用xxxGetWorkspaceSize第一段接口用于完成入参校验、算子信息构建并计算本次调用所需的 workspace 大小同时返回封装了算子计算流程的执行器aclOpExecutor*再调用第二段接口xxx将第一段申请的 workspace 与 executor 传入在指定的 stream 上真正执行计算。// 第一段获取 workspace 大小与执行器 aclnnStatus aclnnFloorDividesGetWorkspaceSize(self, other, out, workspaceSize, executor); // 根据 workspaceSize 在 Device 侧申请内存aclrtMalloc // 第二段执行计算 aclnnStatus aclnnFloorDivides(workspaceAddr, workspaceSize, executor, stream);其中 workspace 指除输入/输出之外算子在 NPU 上完成计算所需的临时内存workspaceSize为临时内存大小。需要注意第二段接口不能重复调用同一 executor 只允许执行一次。四、函数原型aclnnFloorDivides系列四个接口的原型如下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)四个接口的关系前两个组成非原地两段式调用对后两个组成原地两段式调用对两对接口的other均为aclScalar*标量因此该算子属于 Tensor-Scalar 二元运算即 PyTorch 语义下的x // scalar。五、aclnnFloorDividesGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入被除数张量数据类型需要与 other 满足数据类型推导规则参见互推导关系FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOLND不超过8维√otheraclScalar*输入除数标量数据类型需要与 self 满足数据类型推导规则FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL---outaclTensor*输出计算结果数据类型需要是 self 与 other 推导之后可转换的数据类型shape 需要是 self 与 other broadcast 之后的 shapeFLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOLND不超过8维√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----平台差异补充Atlas A2 训练/推理系列产品、Atlas A3 训练/推理系列产品self额外支持BFLOAT16other额外支持BFLOAT16out额外支持BFLOAT16。Ascend 950PR/Ascend 950DTself、other、out额外支持BFLOAT16且数据类型需满足 Tensor-Scalar 互推导关系。返回值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 的维度大于 8六、aclnnFloorDivides 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnFloorDividesGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值aclnnStatus返回状态码具体参见 aclnn 返回码。七、aclnnInplaceFloorDividesGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfRefaclTensor*输入/输出被除数张量同时作为结果输出数据类型需要与 other 满足数据类型推导规则FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOLND不超过8维√otheraclScalar*输入除数标量数据类型需要与 selfRef 满足数据类型推导规则FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT16、INT8、UINT8、BOOL---workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----平台差异补充Atlas A2 训练/推理系列产品、Atlas A3 训练/推理系列产品selfRef额外支持BFLOAT16other额外支持BFLOAT16。Ascend 950PR/Ascend 950DTselfRef、other额外支持BFLOAT16且数据类型需满足 Tensor-Scalar 互推导关系。返回值aclnnStatus返回状态码具体参见 aclnn 返回码。第一段接口完成入参校验出现如下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 selfRef 或 other 是空指针时ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 的数据类型和数据格式不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002selfRef 和 other 不满足数据类型推导规则ACLNN_ERR_PARAM_INVALID161002selfRef 的维度大于 8八、aclnnInplaceFloorDivides 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnInplaceFloorDividesGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值aclnnStatus返回状态码具体参见 aclnn 返回码。九、约束说明确定性计算aclnnFloorDivides与aclnnInplaceFloorDivides默认确定性实现即相同输入在多次运行中结果一致关于确定性计算的通用说明可参见确定性计算。低精度取整误差Atlas A2 训练/推理系列产品、Atlas A3 训练/推理系列产品上因FLOAT16 / BFLOAT16 数据类型精度有限无法表示所有小数在向下取整时存在一定误差。若对精度敏感建议选择更高精度的数据类型如 FLOAT32。十、源码级实现解析为了让读者不仅会调用还能理解其内部机理下面结合仓库源码剖析该算子的实现链路。10.1 aclnn 入口入参校验与计算编排aclnn 入口实现在 math/floor_div/op_api/aclnn_floor_divide.cpp。对于 Tensor-Scalar 形态即本文的aclnnFloorDivides核心流程为aclnnFloorDividesGetWorkspaceSize → CalcFloorDivides → 构建 executor其中入参校验CheckParamsScalar依次检查空指针ACLNN_ERR_PARAM_NULLPTR、数据类型是否在支持列表内ACLNN_ERR_PARAM_INVALID、self 与 other 能否完成数据类型推导CheckPromoteType、self 维度是否超过 8 维MAX_SUPPORT_DIMS_NUMS以及输出 shape 是否与 self 一致。数据类型推导由于other是标量代码走InferFloorDivTensorScalarDtype逻辑——在寄存器级RegBase架构上标量默认按 FLOAT 处理GetScalarDefaultDtype再与 self 的类型做 promotion并且若推导结果为 FLOAT16/BFLOAT16 等低精度类型会进一步提升到 FLOAT 参与内部计算GetOpMathDtype这正是低精度取整有误差约束的根源。计算编排CalcFloorDivides中先将 self 连续化l0op::Contiguous并 Cast 到推导类型随后通过executor-ConvertToTensor(other, promoteType)把aclScalar转成张量再调用底层l0op::FloorDiv完成逐元素除法最后将结果 Cast 回输出类型并ViewCopy到 out 张量。而原地版本aclnnInplaceFloorDividesGetWorkspaceSize的实现更为轻量它直接复用selfRef作为输出auto out const_castaclTensor*(selfRef)再调用与普通版本相同的CalcFloorDivides完成计算因此无需额外申请输出张量。10.2 算子定义Host 侧声明算子注册信息在 math/floor_div/op_host/floor_div_def.cpp 中FloorDiv算子声明了两个必选输入x1被除数、x2除数和一个必选输出y支持的数据类型为DT_BF16、DT_FLOAT16、DT_FLOAT、DT_INT32、DT_UINT8、DT_INT8、DT_INT64数据格式为 ND并配置了动态 Shape 支持DynamicShapeSupportFlag(true)与动态 Rank 支持DynamicRankSupportFlag(true)AICore 侧在ascend950与ascend350两个平台注册kernel 文件指向floor_div_apt。对应架构相关的 tiling 与 infershape 实现可分别查看 math/floor_div/op_host/arch35/floor_div_tiling_arch35.cpp 与 math/floor_div/op_host/floor_div_infershape.cpp。10.3 AICore kernel按数据类型分派NPU 侧计算核心在 math/floor_div/op_kernel/floor_div_apt.cppkernel 函数floor_div根据DTYPE_X1输入数据类型在编译期分派不同的算子 DAGINT8 / UINT8先升位到 int16/uint16 再计算FloorDivIntegerS8/FloorDivIntegerU8避免 8 位整数除法溢出INT32 / INT64直接使用整数向下取整除法FloorDivIntegerS32/FloorDivIntegerS64FLOAT16 / BFLOAT16先 Cast 到 float 再计算FloorDivFloatWithCast返回后再转回原精度——这与上文低精度有误差的约束完全对应FLOAT 等其他浮点类型直接以原类型计算FloorDivFloatWithoutCast。所有分支最终都通过BroadcastSch调度框架完成广播语义下的逐元素计算因此该算子天然支持 self 与 other 之间满足广播关系的 shape 输入。十一、调用示例完整可编译以下示例来自 math/floor_div/examples/test_aclnn_floor_divides.cpp一次性演示了普通版本与原地版本两个两段式调用的完整写法原文档调用示例与此一致#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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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::vectorint64_t selfShape {4, 2}; std::vectorint64_t otherShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclScalar* other nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat outHostData(8, 0); float otherValue 2.0f; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建other aclScalar other aclCreateScalar(otherValue, aclDataType::ACL_FLOAT); CHECK_RET(other ! nullptr, 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; // 调用aclnnFloorDivides第一段接口 ret aclnnFloorDividesGetWorkspaceSize(self, other, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFloorDividesGetWorkspaceSize 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); } // 调用aclnnFloorDivides第二段接口 ret aclnnFloorDivides(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFloorDivides failed. ERROR: %d\n, ret); return ret); uint64_t inplaceWorkspaceSize 0; aclOpExecutor* inplaceExecutor; // 调用aclnnInplaceFloorDivides第一段接口 ret aclnnInplaceFloorDividesGetWorkspaceSize(self, other, inplaceWorkspaceSize, inplaceExecutor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnInplaceFloorDividesGetWorkspaceSize 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); } // 调用aclnnInplaceFloorDivides第二段接口 ret aclnnInplaceFloorDivides(inplaceWorkspaceAddr, inplaceWorkspaceSize, inplaceExecutor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnInplaceFloorDivides 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::vectorfloat 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::vectorfloat 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); aclDestroyScalar(other); aclDestroyTensor(out); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }对示例代码的要点说明头文件需包含acl/acl.h与算子声明头文件aclnnop/aclnn_floor_divide.h示例输入为 shape{4, 2}、值{0, 1, 2, 3, 4, 5, 6, 7}的 FLOAT 张量other标量值为2.0f因此普通版与原地版的计算结果均为{0, 0, 1, 1, 2, 2, 3, 3}other通过aclCreateScalar(otherValue, aclDataType::ACL_FLOAT)构造用完需aclDestroyScalar释放workspace 的申请采用按需申请仅当workspaceSize 0时才调用aclrtMalloc对应的释放也同样需要条件判断。仓库中另一个接口对Tensor-Tensor 形态的aclnnFloorDivide/aclnnInplaceFloorDivide的完整样例见 math/floor_div/examples/test_aclnn_floor_divide.cpp其中other为aclTensor*可作对照阅读对应的 UT 测试见 math/floor_div/tests/ut/op_api/test_aclnn_floor_divide.cpp。十二、编译与运行编译运行上述示例的完整流程参见编译与运行样例核心步骤概括如下准备环境确保驱动、固件、CANN 软件包、ops 包等基础环境已搭建完成。准备文件将示例代码保存为test_aclnn_floor_divides.cpp并编写如下 CMakeLists.txt请根据实际 CANN 安装路径修改ASCEND_PATHcmake_minimum_required(VERSION 3.14) project(ACLNN_EXAMPLE) add_compile_options(-stdc11) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin) set(CMAKE_CXX_FLAGS_DEBUG -fPIC -O0 -g -Wall) set(CMAKE_CXX_FLAGS_RELEASE -fPIC -O2 -Wall) add_executable(opapi_test test_aclnn_floor_divides.cpp) if(NOT $ENV{ASCEND_CUSTOM_PATH} STREQUAL ) set(ASCEND_PATH $ENV{ASCEND_CUSTOM_PATH}) else() set(ASCEND_PATH /usr/local/Ascend/cann) endif() set(INCLUDE_BASE_DIR ${ASCEND_PATH}/include) include_directories( ${INCLUDE_BASE_DIR} ${INCLUDE_BASE_DIR}/aclnn ) target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libascendcl.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_math.so) install(TARGETS opapi_test DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})配置环境变量source ${INSTALL_DIR}/set_env.sh其中${INSTALL_DIR}为 CANN 软件安装后文件存储路径请根据实际情况替换。编译mkdir -p build cd build cmake ../ -DCMAKE_CXX_COMPILERg -DCMAKE_SKIP_RPATHTRUE make编译成功后会在build/bin目录下生成可执行文件opapi_test。运行cd bin ./opapi_test若执行结果报错可使用aclGetRecentErrMsg接口获取报错具体信息例如空指针场景下会打印错误码161001以及形如AclNN_Parameter_Error(EZ1001): Expected a value of type [aclTensor] for argument [self] but instead found nullptr.的详细描述。十三、总结aclnnFloorDivides与aclnnInplaceFloorDivides是 CANN ops-math 中执行张量除以标量后向下取整的 Tensor-Scalar 二元算子接口二者功能等价区别仅在于原地与否。使用时遵循两段式调用模型先通过GetWorkspaceSize接口获取 workspace 大小与执行器再执行真正的计算接口。结合仓库源码可以看到该算子内部会依据互推导关系完成类型提升低精度类型会被提升到 FLOAT 参与计算这也是低精度取整存在误差的原因并在 AICore kernel 中针对不同数据类型分派专用 DAG从而保证确定性与计算精度。希望本文能帮助你快速上手该算子并为其在 NPU 上的性能与行为调优提供源码级参考。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考