- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
本文围绕 CANN ops-math 数学算子库中的 IsPosInf 算子(正无穷判断算子)展开,系统介绍其功能语义、产品支持矩阵、aclnnIsPosInf 两段式接口的参数约束与错误码、基于 aclnn 的完整调用示例,并结合仓库源码深入剖析其算子定义、shape 推导、tiling 与 kernel DAG 的底层实现链路。读者阅读后可独立完成 IsPosInf 算子在 Ascend 平台上的应用开发与结果验证。
算子概述与功能说明
IsPosInf 是 CANN ops-math 项目中位于 math/is_pos_inf 目录下的基础数学算子,其核心功能是逐元素判断输入张量中的每个元素是否为正无穷(+inf),并将判断结果输出为布尔张量。
功能语义
- 算子功能:判断张量中哪些元素是正无穷,即为 +inf。
- 计算公式:
$$ out_i = (input_i == +\infty) $$
- 示例:若 x = [9, 6, +inf],则 isPosInf(x) 的结果为 [False, False, True]。
值得说明的是,该算子与仓库中同目录族系的 is_neg_inf(负无穷判断)在语义上互为镜像,两者常用于数值校验、NaN/Inf 过滤等场景。
测试基准验证
仓库在 math/is_pos_inf/tests/assets/golden.py 中给出了算子结果的黄金基准实现,直接复用 NumPy 的语义等价函数:
def is_pos_inf_golden(x, **kwargs): return np.isposinf(x)即 IsPosInf 的输出与np.isposinf(x)完全一致,这为算子正确性提供了明确的对照标准,也便于开发者在本地用 NumPy 快速验证自己的理解。
产品支持情况
IsPosInf 算子在当前仓库版本中的产品支持矩阵如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | √ |
| Atlas 训练系列产品 | × |
从源码配置看,该支持矩阵与 math/is_pos_inf/op_host/is_pos_inf_def.cpp 中算子注册的 AICore 平台配置一致——该文件为算子配置了ascend950与ascend350两个平台的 AICore 配置,并在 math/is_pos_inf/op_host/config/ascend350/is_pos_inf_binary.json 与 math/is_pos_inf/op_host/config/ascend950/is_pos_inf_binary.json 中提供了对应的二进制编译配置。在开发部署时,需要结合实际的硬件产品型号确认算子可用性。
参数说明
IsPosInf 算子的参数定义如下:
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| x | 输入 | 待进行判断的入参,公式中的 input_i。 | FLOAT、FLOAT16、BFLOAT16 | ND |
| y | 输出 | 待进行判断的出参,公式中的 out_i。 | BOOL | ND |
参数说明要点:
- 输入 x 支持 3 种浮点类型:FLOAT(FP32)、FLOAT16、BFLOAT16,输出 y 恒为 BOOL 类型,每个元素为 0/1 的布尔值。
- 输入与输出均采用 ND 数据格式。
- 输出 y 的 shape 与输入 x 完全一致(逐元素一一对应)。
上述约束在算子定义源码中有严格体现。math/is_pos_inf/op_host/is_pos_inf_def.cpp 中通过OpDef注册:
this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("y") .ParamType(REQUIRED) .DataType({ge::DT_BOOL, ge::DT_BOOL, ge::DT_BOOL}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND});同时该算子还开启了多项动态能力标志:DynamicCompileStaticFlag(true)(支持动态编译静态化)、DynamicRankSupportFlag(true)(支持动态维度)、DynamicShapeSupportFlag(true)(支持动态 shape)、PrecisionReduceFlag(true)(允许精度降级处理),并指定了 kernel 实现文件为is_pos_inf_apt。
调用说明:aclnn 两段式接口
IsPosInf 算子支持通过aclnn(Ascend CANN 算子库)接口进行调用。仓库提供了完整的调用样例 math/is_pos_inf/examples/test_aclnn_isposinf.cpp,以及对应的接口说明文档 math/is_pos_inf/docs/aclnnIsPosInf.md。
两段式接口架构
aclnnIsPosInf 采用 CANN 标准的两段式接口(Two-Phase API)设计,即必须先调用aclnnIsPosInfGetWorkspaceSize接口获取计算所需 workspace 大小以及包含了算子计算流程的执行器,再调用aclnnIsPosInf接口执行计算。这与 ops-math 项目中其他 aclnn 算子的调用范式保持一致。
第一段接口原型:
aclnnStatus aclnnIsPosInfGetWorkspaceSize( const aclTensor* self, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口原型:
aclnnStatus aclnnIsPosInf( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)第一段接口 aclnnIsPosInfGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 输入张量,公式中的 self。 | - | FLOAT、FLOAT16、DOUBLE、BFLOAT16 | ND | 0-8 | √ |
| out(aclTensor*) | 输出 | 输出张量,公式中的 out。 | 数据类型为 BOOL,shape 与 self 相同。 | BOOL | ND | 0-8 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小。 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程。 | - | - | - | - | - |
需要特别说明的是,aclnn 接口层(math/is_pos_inf/op_api/aclnn_isposinf.cpp)在 aclnn 文档允许的输入类型范围上比算子定义更宽:接口层自检支持 FLOAT、FLOAT16、DOUBLE、BFLOAT16,但算子 AICore 实现仅支持 FLOAT、FLOAT16、BFLOAT16。因此文档中特别注明限制:
Atlas 训练系列产品、Atlas 推理系列产品:不支持 BFLOAT16。
且 DOUBLE 类型入参在接口层校验后,会因 AICore 不支持而无法进入实际计算(详见下文"底层实现链路"一节中对IsAiCoreSupport的说明),实际以算子定义的数据类型为准。
返回值与错误码
两段接口均返回aclnnStatus状态码。第一段接口完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 self 或 out 是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 或 out 的数据类型不在支持范围之内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 out 的维度超过 8 维。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 out 的 shape 不一致。 |
第二段接口 aclnnIsPosInf 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnIsPosInfGetWorkspaceSize 获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
约束说明
- 确定性计算:aclnnIsPosInf 默认确定性实现,同一输入在多次运行下输出结果确定。
完整调用示例(可运行)
以下示例代码来自 math/is_pos_inf/docs/aclnnIsPosInf.md,具体编译和执行过程可参考 编译与运行样例。代码演示了从 ACL 环境初始化、张量创建、两段式接口调用到结果回拷与资源释放的完整流程:
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_isposinf.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 shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); 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() { int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); std::vector<int64_t> selfShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<char> outHostData = {1, 1, 1, 1, 0, 0, 0, 0}; ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_BOOL, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnIsPosInfGetWorkspaceSize(self, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnIsPosInfGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); 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); } ret = aclnnIsPosInf(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnIsPosInf failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); auto size = GetShapeSize(outShape); std::vector<char> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(char), 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: %d\n", i, resultData[i]); } aclDestroyTensor(self); aclDestroyTensor(out); // 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例执行逻辑拆解:
- 环境初始化:
aclInit初始化 ACL 运行环境,aclrtSetDevice绑定 deviceId=0 的昇腾设备,aclrtCreateStream创建任务流。 - 张量创建:通过
aclrtMalloc申请 Device 侧内存、aclrtMemcpy将 host 数据(shape 为 {4, 2} 的 8 个 float 元素)拷入 Device,再用aclCreateTensor按 ND 格式与连续 strides 构造输入 self 与输出 out(输出为 ACL_BOOL 类型)。 - 第一段接口:
aclnnIsPosInfGetWorkspaceSize完成入参校验并返回 workspace 大小与 executor;若 workspaceSize > 0 则用aclrtMalloc申请 workspace。 - 第二段接口:
aclnnIsPosInf在指定 stream 上异步执行计算。 - 同步与取数:
aclrtSynchronizeStream等待计算完成,aclrtMemcpy将结果从 Device 拷回 host,逐元素打印(1 表示 true,0 表示 false)。 - 资源释放:依次销毁 tensor、释放 Device 内存与 workspace、销毁 stream、
aclrtResetDevice复位设备并aclFinalize收尾。
仓库同时提供了使用 RAII 智能指针管理资源、并封装aclGuard确保异常路径下资源释放的增强版示例 math/is_pos_inf/examples/test_aclnn_isposinf.cpp,其核心调用流程与上述示例一致,适合作为工程化改造的参考。
底层实现链路:从接口到 Kernel
为了让读者对算子有更深的理解,下面沿调用链剖析 IsPosInf 在仓库中的各层实现。整个实现遵循 CANN 算子的分层结构:op_api(L0 接口层)→ op_host(算子定义/shape 推导/tiling)→ op_kernel(AscendC kernel)。
op_api 层:L0 接口实现
math/is_pos_inf/op_api/isposinf.cpp 实现了 L0 层的IsPosInf逻辑,其关键流程为:
- 通过
IsAiCoreSupport检查输入数据类型是否属于{DT_FLOAT, DT_FLOAT16, DT_BF16}(见 math/is_pos_inf/op_api/isposinf.h 中的声明),不满足则直接返回空指针——这是 DOUBLE 入参虽在接口层文档中列出、但实际无法完成计算的原因; - 通过
executor->AllocTensor依据输入 view shape 自动申请 BOOL 类型的输出张量; - 调用
IsPosInfAiCore,经ADD_TO_LAUNCHER_LIST_AICORE将算子任务加入 AICore 执行列表,并注册OP_TYPE_REGISTER(IsPosInf)。
op_host 层:算子定义与 shape 推导
- 算子定义:math/is_pos_inf/op_host/is_pos_inf_def.cpp 注册输入 x(FLOAT16/FLOAT/BF16,ND 格式)与输出 y(BOOL,ND 格式),并为 ascend950、ascend350 两个平台添加 AICore 配置(详见上文"参数说明"一节)。
- shape 推导:math/is_pos_inf/op_host/is_pos_inf_infershape.cpp 复用通用逐元素推导逻辑
IMPL_OP_INFERSHAPE(IsPosInf).InferShape(Ops::Base::InferShape4Elewise),即 IsPosInf 被视为逐元素(elementwise)算子,输出 shape 与输入保持一致。该文件配套的单元测试见 math/is_pos_inf/tests/ut/op_host/test_is_pos_inf_infershape.cpp。
tiling 层:arch35 平台切分逻辑
math/is_pos_inf/op_host/arch35/is_pos_inf_tiling_arch35.cpp 实现 arch35(Ascend 950 等)平台下的 tiling 计算,核心步骤包括:
CalcInputDtype/CalcOutputDtype:校验输入仅支持 FLOAT16/BF16/FLOAT,输出必须为 BOOL,不满足时报非法数据类型错误;CheckShape:校验维度不超过 8 维(MAX_DIM_NUM = 8),且输入输出 shape 必须一致;RunTiling:按输入数据类型分别实例化IsPosInfDAG<half>、IsPosInfDAG<bfloat16_t>、IsPosInfDAG<float>,调用ElewiseBaseTiling::DoTiling完成切分,并设置 workspace(固定ASCEND_WORKSPACE = 16 * 1024 * 1024,即 16MB)、tiling key 与 blockDim(核数);TilingPrepareForIsPosInf:通过PlatformAscendC获取 AIV 核数与 UB 内存大小,写入编译信息。
kernel 层:AscendC DAG 实现
math/is_pos_inf/op_kernel/arch35/is_pos_inf_dag.h 以算子 DAG(有向无环图)形式描述计算流水,这也是该算子区别于普通 AscendC 手写 kernel 的实现特点:
template <typename U, typename T = float> struct IsPosInfDAG { using ConstPosInf = MAKE_CONST(T, CONST_POS_INF_FP32); // 常量:+inf using OpCopyIn = Bind<Vec::CopyIn<U>, Placeholder::In0<U>>; // 1. 数据搬入 using OpCast = Bind<Vec::Cast<T, U, CAST_MODE>, OpCopyIn>; // 2. 类型转换(统一到 T) using CompareMask = Bind<Vec::Compare<uint8_t, T, CMP_MODE>, OpCast, ConstPosInf>; // 3. 与 +inf 比较 using SelectRes = Bind<Vec::Select<uint8_t, uint8_t, SELECT_MODE>, CompareMask, DataOne, ConstZero>; // 4. 选择 true/false using OpCopyOut = Bind<Vec::CopyOut<uint8_t>, Placeholder::Out0<uint8_t>, SelectRes>; // 5. 结果写出 ... };其中CONST_POS_INF_FP32 = INFINITY(__builtin_inff()),比较结果通过Select将命中 +inf 的位置置 1、其余置 0,最终以 BOOL(uint8_t)写出。整条流水将"搬入 → 转换 → 比较 → 选择 → 写出"五步串联,配合MemOptCfg<MemLevel::LEVEL_2>完成二级内存优化,具体内核入口见 math/is_pos_inf/op_kernel/is_pos_inf_apt.cpp。
测试与验证
仓库为 IsPosInf 提供了多维度的测试覆盖,可用于功能验证与回归:
- 接口调用用例:math/is_pos_inf/tests/ut/op_api/test_aclnn_isposinf.cpp,验证 aclnn 两段式接口的调用路径;
- shape 推导单测:math/is_pos_inf/tests/ut/op_host/test_is_pos_inf_infershape.cpp;
- tiling 单测:math/is_pos_inf/tests/ut/op_host/arch35/test_is_pos_inf_tiling.cpp;
- ST 用例:ATK 接口用例 math/is_pos_inf/tests/st/aclnnIsPosInf/atk_aclnnIsPosInf.json 与 arch35 核函数用例 math/is_pos_inf/tests/st/arch35/ttk_kernel_is_pos_inf_st.csv;
- 黄金基准:math/is_pos_inf/tests/assets/golden.py 提供的
is_pos_inf_golden基准实现。
小结
IsPosInf 是一个语义简洁但工程链路完整的数学算子:功能上,它对输入浮点张量逐元素判断是否为 +inf 并输出 BOOL 结果;接口上,遵循 CANN 标准的两段式 aclnn 接口范式,支持 FLOAT/FLOAT16/BFLOAT16 输入与 ND 格式、0~8 维动态 shape;实现上,由 op_api 的 L0 逻辑、op_host 的算子定义/shape 推导/tiling 与 op_kernel 的 DAG 流水共同支撑,并配套了从单测、ST 到黄金基准的完整验证体系。开发者既可直接参考 aclnnIsPosInf 接口文档 与 调用示例 快速上手,也可沿本文剖析的源码路径深入理解算子底层机制,为在昇腾 NPU 上构建 NaN/Inf 检测、数值安全校验等上层能力提供坚实基础。
- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
相关推荐
CANN ops-math 中 Roll 算子的 aclnnRoll 接口使用指南与实现原理
CANN ops math 中 Roll 算子的 aclnnRoll 接口使用指南与实现原理 本文以 CANN ops math 开源仓库中 experimen
算子库人工智能CANNCANN ops-math 算子解析:IsPosInf 逐元素正无穷判定算子的原理、ACLNN 接口与源码实现
CANN ops math 算子解析:IsPosInf 逐元素正无穷判定算子的原理、ACLNN 接口与源码实现 导读 本文围绕 CANN ops math 数学
算子库人工智能CANNCANN ops-math 算子接口指南:aclnnClampMaxTensor 与 aclnnInplaceClampMaxTensor 的使用与原理
CANN ops math 算子接口指南:aclnnClampMaxTensor 与 aclnnInplaceClampMaxTensor 的使用与原理 本指南
算子库人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考