资讯动态

CANN ops-math aclnnMinN 算子完全指南:多输入逐元素取最小值的两段式调用与源码解析

发布时间:2026/9/21 16:39:00 来源:尧图企业网站定制
算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载aclnnMinN 是 CANN ops-math 数学算子库math/minimum中用于对任意多个输入 Tensor 逐元素取最小值的高级 APIaclnn 接口常见于求取一组张量的元素级下界、做数值裁剪合并等场景。本文基于 aclnnMinN 官方接口文档 展开结合仓库内 op_api 层实现、示例代码 与 单元测试完整讲解其功能定义、两段式调用流程、参数与返回码约束、源码实现原理并给出可编译运行的完整调用示例帮助读者在 NPU 上快速接入多输入 min 计算。一、功能说明对 Tensor 列表逐元素求最小值aclnnMinN 接收一个aclTensorList即多个输入 Tensor 构成的列表对其中每一个输入 Tensor 的对应元素求 min输出一个新的 Tensor$$ out_i \min(input0_i, input1_i, ...) $$其中每个输入的 shape 不必完全一致只要满足 broadcast 关系算子会先按广播规则对齐 shape再逐元素取最小值。这一点与同目录下的 aclnnMinimum仅支持两个输入 x1、x2不同——aclnnMinN 面向可变数量输入N 个 Tensor因此采用aclTensorList作为入参。从源码角度看其接口语义定义在 aclnn_minn.h 中domain aclnn_math属于数学域算子第一段接口注释明确说明计算输入 tensors 列表中每个 tensor 对应元素求 min支持非连续 Tensor数据格式支持 ND。二、产品支持情况aclnnMinN 在 接口文档 中声明的产品支持矩阵如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品310P 系列支持Atlas 训练系列产品910 系列支持该矩阵与 math/minimum/README.md 中 Minimum 算子的产品支持情况完全一致。需要注意的是产品支持矩阵只表示算子整体可用性具体到数据类型上不同产品仍有差异详见下文数据类型支持与产品差异。三、两段式接口架构先规划、再执行每个 aclnn 算子都采用 两段式接口 设计aclnnMinN 也不例外。必须先调用第一段接口aclnnMinNGetWorkspaceSize获取计算所需的 workspace 大小以及封装了算子计算流程的执行器executor再调用第二段接口aclnnMinN真正执行计算。aclnnStatus aclnnMinNGetWorkspaceSize( const aclTensorList *tensors, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnMinN( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)从 aclnn_minn.cpp 的实现可以清晰看到这种分工第一段接口aclnnMinNGetWorkspaceSize负责参数校验空指针、dtype、维度、broadcast→ 将输入转换为连续 Tensor → 用l0op::Minimum两两迭代构建计算图 → 通过l0op::ViewCopy将结果写回可能非连续的out→ 调用uniqueExecutor-GetWorkspaceSize()汇总计算所需 workspace 大小并返回 executor。第二段接口aclnnMinN实现极简仅调用框架统一入口CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成异步计算下发。这种先 GetWorkspaceSize 规划、后执行的模式使框架能够在执行前完成内存规划与算子融合优化是 CANN 高级算子 API 的标准范式。四、aclnnMinNGetWorkspaceSize 参数详解下表完整列出第一段接口的四个参数与 接口文档 保持一致并补充源码侧依据参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensortensorsaclTensorList*输入需要计算的输入 tensors 列表需要与 out 数据类型相同tensors 中各 tensor 的 shape 需要与 out 满足 broadcast 关系FLOAT16、FLOAT、INT8、UINT8、INT32、INT64、BFLOAT16ND不大于 8√outaclTensor*输出输出 tensor需要与 tensors 数据类型相同shape 需要与 tensors 中各 tensor 的 shape 满足 broadcast 关系FLOAT16、FLOAT、INT8、UINT8、INT32、INT64、BFLOAT16ND-√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----关键约束逐条解读shape 不超过 8 维对应源码中CheckMaxDimension内使用的MAX_SUPPORT_DIMS_NUMS宏见 aclnn_minn.cpp输入与输出维度均被检查。支持非连续 Tensor两个入参均标注 √。源码通过l0op::Contiguous先将非连续输入统一为连续布局再参与计算最终通过l0op::ViewCopy把结果写回非连续的out见 aclnn_minn.cpp因此调用方无需手动 contiguous可直接传入视图/切片产生的非连续张量。数据格式仅 ND源码中若检测到非FORMAT_ND存储格式会打印告警日志 Format only supports ND见 aclnn_minn.cpp。数据类型支持与产品差异接口整体支持 FLOAT16、FLOAT、INT8、UINT8、INT32、INT64、BFLOAT16但部分产品对其中个别类型不支持需要在开发时留意以下约束来自 接口文档Atlas 推理系列产品、Atlas 训练系列产品不支持 BFLOAT16、UINT8 数据类型Atlas A2 训练/推理系列产品、Atlas A3 训练/推理系列产品不支持 UINT8 数据类型。从源码 aclnn_minn.cpp 可以印证这一按架构分发 dtype 支持列表的设计GetDtypeSupportList()依据当前 NPU 架构返回三份不同的支持列表——ASCEND910_DTYPE_SUPPORT_LIST910/310P 架构仅 FLOAT16、FLOAT、INT8、INT32、INT64ASCEND910B_DTYPE_SUPPORT_LIST910B/A2 架构在上一份基础上增加 BF16不含 UINT8REGBASE_DTYPE_SUPPORT_LISTA3/950 等 RegBase 架构FLOAT16、FLOAT、INT8、UINT8、INT32、INT64、BF16 全量支持。这与文档中A2/A3 不支持 UINT8、910/310P 不支持 BFLOAT16 与 UINT8的表述一一对应也解释了为何 math/minimum/README.md 中底层 Minimum 算子声明的 dtype 范围含 DOUBLE比 aclnn 接口更宽——aclnn 层按产品线做了收敛。五、返回值与错误码两个接口的返回值均为aclnnStatus具体返回码含义参见 aclnn 返回码。第一段接口aclnnMinNGetWorkspaceSize会完成入参校验出现如下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 tensors 或 out 是空指针时ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 或 out 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002tensors 列表和 out 的数据类型不一致ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 和 out 的 shape 不满足 broadcast 规则或者 broadcast 后的 shape 与 out 不一致ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 最大维度超过 8这些错误码与 aclnn_minn.cpp 中CheckParams的校验链一一对应CheckNotNulltensors 列表本身及其每个成员、out、workspaceSize 任一为空 → 返回ACLNN_ERR_PARAM_NULLPTRCheckDtypeValiddtype 不在当前架构支持列表、输入输出 dtype 不一致、非 ND 格式 → 返回ACLNN_ERR_PARAM_INVALIDCheckMaxDimension维度超过 8 → 返回ACLNN_ERR_PARAM_INVALIDCheckInAndOutShapetensors 列表为空、逐对BroadcastInferShape失败、或广播后 shape 与 out 不一致 → 返回ACLNN_ERR_PARAM_INVALID。其中 broadcast 合法性校验的实现细节值得关注CheckInAndOutShape以第一个 tensor 的 view shape 为起点用BroadcastInferShape依次与后续每个 tensor 求广播结果见 aclnn_minn.cpp最终得到的 shape 再与 out 比对。这与底层算子注册的 InferShape 逻辑minimum_infershape.cpp 中的BroadcastShape保持了一致的两两广播语义。六、aclnnMinN 参数详解第二段接口aclnnMinN的四个参数均为输入参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnMinNGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream注意workspaceSize 为 0 时无需申请 workspace可传空指针这一点在示例代码中通过if (workspaceSize 0)判断处理。另外当out是空 Tensorout-IsEmpty()时第一段接口会直接返回workspaceSize 0并提前释放 executor见 aclnn_minn.cpp空 Tensor 场景由 kernel 侧天然支持。七、约束说明确定性计算确定性计算aclnnMinN 默认为确定性实现。确定性计算意味着同一输入多次运行时结果可复现便于调试与结果比对。更系统的确定性说明可参考 确定性计算。八、源码级实现原理从入参到执行的完整链路将 aclnn_minn.cpp 的实现与上层文档对照可以还原出 aclnnMinN 的完整内部执行链路入参校验CheckParams按上文四步完成空指针、dtype、维度、broadcast 校验空输出短路out为空 Tensor 时 workspace 置 0 直接返回输入连续化对每个输入调用l0op::Contiguous保证后续 kernel 拿到连续内存布局两两迭代求 min以第一个连续化结果作为初值用l0op::Minimum(firstContiguous, secondContiguous, ...)依次与第 2、3…N 个输入两两合并形成链式的最小值计算图见 aclnn_minn.cpp。这也是 N 个输入被折叠为多次二元 Minimum 的实现方式与底层 Minimum 算子 的计算语义out[i] min(x1[i], x2[i])完全一致结果回写l0op::ViewCopy(minOut, out, ...)将链式结果拷贝到用户提供的out天然支持 out 为非连续 Tensor 的场景workspace 汇总uniqueExecutor-GetWorkspaceSize()返回整个计算图所需的 Device 侧临时内存大小执行下发第二段接口aclnnMinN调用CommonOpExecutorRun完成实际计算。值得说明的是底层l0op::Minimum依赖的二元 Minimum 算子已在 op_graph/minimum_proto.h、op_kernel/minimum_apt.cpp 等文件中完成图协议与 kernel 注册并针对 arch35 等架构提供专用 tiling 实现见 op_host/arch35/minimum_tiling_arch35.cpp。aclnnMinN 通过 l0op 原语将这些底层能力组合为多输入求最小的高层接口这也是它可以同时支持 1 个乃至几十个输入的灵活性的来源。九、完整调用示例以下示例来自 examples/test_aclnn_MinN.cpp文档中的 调用示例 与其一致。它演示了 shape 为 {2,3} 与 {1,3} 的两个 FLOAT Tensor 如何通过广播规则计算逐元素最小值。编译与执行的具体流程请参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_minn.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 selfShape1 {2, 3}; std::vectorint64_t selfShape2 {1, 3}; std::vectorint64_t outShape {2, 3}; void* input1DeviceAddr nullptr; void* input2DeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* input1 nullptr; aclTensor* input2 nullptr; aclTensor* out nullptr; std::vectorfloat input1HostData {11, 12, 13, 4, 5, 6}; std::vectorfloat input2HostData {7, 8, 9}; std::vectorfloat outHostData(6, 0); // 创建input1 aclTensor ret CreateAclTensor(input1HostData, selfShape1, input1DeviceAddr, aclDataType::ACL_FLOAT, input1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建input2 aclTensor ret CreateAclTensor(input2HostData, selfShape2, input2DeviceAddr, aclDataType::ACL_FLOAT, input2); 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); std::vectoraclTensor* tmp{input1, input2}; aclTensorList* tensorList aclCreateTensorList(tmp.data(), tmp.size()); // 3.调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnMinN第一段接口 ret aclnnMinNGetWorkspaceSize(tensorList, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnMinNGetWorkspaceSize 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); } // 调用aclnnMinN第二段接口 ret aclnnMinN(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnMinN 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]); } // 6.释放aclTensor和aclScalar需要根据具体API的接口定义修改 aclDestroyTensorList(tensorList); aclDestroyTensor(out); // 7.释放Device资源需要根据具体API的接口定义修改 aclrtFree(input1DeviceAddr); aclrtFree(input2DeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }上述示例的预期输出为input1 与广播后的 input2 逐元素取 min即min(11,7)7, min(12,8)8, min(13,9)9, min(4,7)4, min(5,8)5, min(6,9)6。十、测试与验证UT 用例覆盖的边界场景仓库在 tests/ut/op_api/test_minn.cpp 中为 aclnnMinN 提供了完整的 GTest 单元测试可作为功能正确性的权威参照主要覆盖以下场景用例验证点期望结果l2_minn_test_nullptr_input/nullptr_output输入或输出为空指针ACLNN_ERR_PARAM_NULLPTRl2_minn_test_empty_tensors存在空 Tensor 输入ACLNN_ERR_PARAM_INVALIDl2_minn_test_broadcast_failed1/2shape 不可广播 / 广播后与 out 不一致ACLNN_ERR_PARAM_INVALIDl2_minn_test_dim_over_8维度超过 8ACLNN_ERR_PARAM_INVALIDl2_minn_test_dypte_float32/float16/int64/int32/int8等各 dtype 正常路径 精度校验ACLNN_SUCCESS通过精度比对ascend910B2_minn_dypte_bfloat16BF16 在 910B 架构下的精度ACLNN_SUCCESSl2_minn_test_dypte_tensor_num_1/tensor_num_20仅 1 个输入、多达 20 个输入ACLNN_SUCCESSl2_minn_test_empty_tensor_21含 0 维的空 TensorINT8ACLNN_SUCCESS其中20 个输入与空 Tensor用例分别印证了前文提到的两点aclnnMinN 通过链式l0op::Minimum折叠可以天然支持任意数量的输入空 Tensor 由 kernel 侧直接支持第一段接口会以 workspace0 短路返回。十一、相关文档与进一步阅读同算子族文档aclnnMinimum两输入 Minimum、math/minimum/README.md上下文概念两段式接口、broadcast 关系、aclnn 返回码、确定性计算、非连续 Tensor、编译与运行样例源码与测试op_api/aclnn_minn.cpp、op_api/aclnn_minn.h、examples/test_aclnn_MinN.cpp、tests/ut/op_api/test_minn.cpp、op_host/minimum_infershape.cpp赞分享算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载相关推荐CANN ops-math 算子解析aclnnGcd 逐元素最大公约数算子的两段式调用与 Ascend C 实现原理CANN ops math 算子解析aclnnGcd 逐元素最大公约数算子的两段式调用与 Ascend C 实现原理 aclnnGcd 是 CANN ops算子库人工智能CANNCANN ops-math 算子实战AddN 多输入逐元素求和算子详解与 NPU 调用指南CANN ops math 算子实战AddN 多输入逐元素求和算子详解与 NPU 调用指南 本文以 CANN ops math 仓库中的 AddN 算子文档算子库人工智能CANNCANN ops-math aclnnAtan2 算子接口解析两段式调用流程与逐元素反正切计算实战CANN ops math aclnnAtan2 算子接口解析两段式调用流程与逐元素反正切计算实战 本文以 CANN ops math 仓库中 experim算子库人工智能CANN上一篇NectarJS 项目使用教程下一篇VideoEnabledWebView实战5步快速集成Android WebView视频播放功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价