算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载Sign符号函数算子是 CANN ops-math 数学算子库中一个典型的逐元素element-wise计算算子它读取输入 tensor 的每一个元素判断其符号并输出-1 / 0 / 1实数场景对应的结果 tensor。本文基于 math/sign/README.md 与其配套的接口文档、源码与测试完整梳理 Sign 算子的产品支持范围、三类计算公式、参数约束、两段式 aclnn 调用流程并结合 op_api/aclnn_sign.cpp、op_kernel/sign_apt.cpp 等源码剖析其内部实现原理帮助开发者在 Atlas 训练/推理产品上快速完成 Sign 算子的接入与验证。产品支持情况Sign 算子在当前仓库中的支持范围如下表所示不同产品系列的支持差异由 NPU 架构决定接入前请先确认目标设备型号产品是否支持Ascend 950PR / Ascend 950DT√Atlas A3 训练系列产品 / Atlas A3 推理系列产品√Atlas A2 训练系列产品 / Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品×这一支持矩阵同样反映在接口级文档 math/sign/docs/aclnnSign.md 与算子注册配置中从 op_host/sign_def.cpp 可以看到sign算子只注册了ascend950与ascend350两个 AICore 配置.AddConfig(ascend950, aicoreConfig)与.AddConfig(ascend350, aicoreConfig)其中 ascend350 对应 A2/A3 系列产品这与 README 中支持的产品矩阵相互印证。功能说明与计算公式Sign 算子对输入 tensor 逐元素执行符号函数运算输入的第 i 个元素input_i对应输出第 i 个元素resultput_i输出 tensor 与输入 tensor 形状完全一致属于逐元素映射无广播、无规约。实数类型计算公式对常规数值类型Sign 的计算规则为$$ resultput_i \left{ \begin{aligned} 1,\quad input_i 0\ 0,\quad input_i 0\ -1,\quad input_i 0 \end{aligned} \right. $$即正数输出 1零输出 0负数输出 -1。BOOL 类型计算公式当输入为 BOOL 类型时Sign 退化为恒等映射$$ resultput_i \left{ \begin{aligned} \text{True},\quad input_i \text{True}\ \text{False},\quad input_i \text{False}\ \end{aligned} \right. $$也就是说True输入仍输出TrueFalse输入仍输出False不产生数值意义上的符号变换。复数类型计算公式对于复数输入COMPLEX64 / COMPLEX128Sign 的结果仍是单位圆上的复数其计算借助实部real(input_i)与虚部imag(input_i)的符号以及幅角θ_i完成$$ resultput_i \alpha \cdot cos(\theta_i) \beta \cdot sin(\theta_i) $$其中$$ \alpha \left{ \begin{aligned} 1,\quad real(input_i) 0 \ 0,\quad real(input_i) 0 \ -1,\quad real(input_i) 0 \ \end{aligned} \right. \qquad \beta \left{ \begin{aligned} 1,\quad imag(input_i) 0 \ 0,\quad imag(input_i) 0 \ -1,\quad imag(input_i) 0 \ \end{aligned} \right. $$幅角定义为$$ \theta_i arctan\left(\frac{imag(input_i)}{real(input_i)}\right) $$直观理解复数 Sign 的结果是保留原复数方向的单位模长向量——实部、虚部的符号决定结果落在哪个象限/坐标轴上cos(θ)与sin(θ)将幅角映射回单位圆上。参数说明Sign 算子的 IRge 图层参数非常简单仅包含一个输入与一个输出均使用 ND 数据格式参数名输入/输出/属性描述数据类型数据格式x输入待进行 sign 计算的入参即公式中的input_iFLOAT、FLOAT16、BFLOAT16、INT32、INT64、DOUBLE、INT8、INT16、UINT8、UINT16、UINT32、UINT64、COMPLEX64、COMPLEX128、BOOLNDy输出sign 计算的出参即公式中的resultput_i与 x 相同的数据类型集合ND使用时有两点调用通道差异需要特别留意INT8、INT16、UINT8、UINT16、UINT32、UINT64 类型只有 geir图模式算子调用支持BOOL、DOUBLE、COMPLEX64、COMPLEX128 类型只有 aclnnAscendCL 单算子调用调用支持。这一点在源码中可以得到印证IR 层注册 op_host/sign_def.cpp 中Input(x)与Output(y)的DataType列表为DT_BF16、DT_FLOAT16、DT_FLOAT、DT_INT32、DT_INT64、DT_INT8、DT_INT16、DT_UINT8、DT_UINT16、DT_UINT32、DT_UINT64不含 BOOL/DOUBLE/复数而 aclnn 层 op_api/aclnn_sign.cpp 中的DTYPE_SUPPORT_LIST则为DT_DOUBLE、DT_FLOAT、DT_FLOAT16、DT_INT32、DT_INT64、DT_COMPLEX64、DT_COMPLEX128、DT_BOOL并且在高阶架构DAV_2201、DAV_3510上还通过DTYPE_SUPPORT_LIST_WITH_BF16额外放开了 BFLOAT16。两条调用通道各自维护自己的支持列表集成时务必按调用方式选择合法类型。约束说明算子本身无额外约束README 中约束说明一节为空。不过在使用 aclnnSign 接口时接口文档 math/sign/docs/aclnnSign.md 补充了一条重要特性aclnnSign 默认为确定性deterministic实现即相同输入在相同环境下多次执行结果完全一致适合对可复现性有要求的训练与调试场景。调用说明两段式 aclnnSign 接口README 给出的调用方式是 aclnn 调用对应接口为aclnnSign示例见 math/sign/examples/test_aclnn_sign.cpp。aclnnSign 遵循 CANN 单算子调用的通用两段式接口模式详见 docs/zh/context/two_phase_api.md先调用aclnnSignGetWorkspaceSize获取 workspace 大小与执行器再调用aclnnSign执行计算。函数原型aclnnStatus aclnnSignGetWorkspaceSize(const aclTensor *self, aclTensor *result, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnSign(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)aclnnSignGetWorkspaceSize 参数参数输入/输出描述self输入Device 侧输入 tensorconst aclTensor *支持 1~8 维支持非连续 tensor数据格式仅支持 ND详见 docs/zh/context/data_format.mdresult输出Device 侧输出 tensor维数、shape、dtype 均需与 self 一致支持非连续 tensor格式 NDworkspaceSize输出返回需要在 Device 侧申请的 workspace 大小uint64_t *executor输出返回 op 执行器aclOpExecutor **封装了算子计算流程支持的产品上self 与 result 的数据类型为DOUBLE、FLOAT、FLOAT16、INT32、INT64、COMPLEX64、COMPLEX128、BOOL、BFLOAT16A2/A3 系列与 Ascend 950 系列一致。第一段接口的错误码aclnnSignGetWorkspaceSize会完成入参校验非法入参时返回如下错误码aclnn 返回码的完整说明参见 docs/zh/context/aclnn_return_code.md返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self 或 result 是空指针ACLNN_ERR_PARAM_INVALID161002self 和 result 的数据类型/数据格式不在支持范围内或 self 与 result 的 shape 不匹配或 self 与 result 的 type 不匹配这些校验逻辑对应 op_api/aclnn_sign.cpp 中的CheckParams依次执行CheckNotNull2Tensor空指针检查、CheckDtypeValid类型合法性含不同架构分支与 self/result 类型一致性检查与CheckShapeshape 一致性检查任一失败即返回对应错误码。aclnnSign 参数参数输入/输出描述workspace输入Device 侧申请的 workspace 内存地址第一段接口返回的 workspaceSize 大于 0 时需用aclrtMalloc申请workspaceSize输入workspace 大小由aclnnSignGetWorkspaceSize获取executor输入第一段接口返回的 op 执行器stream输入指定执行任务的 Stream完整调用示例以下示例代码可在实际环境中编译运行完整源码见 math/sign/examples/test_aclnn_sign.cpp演示了从设备初始化、tensor 构造、两段式接口调用到结果回拷、资源释放的完整流程编译与运行样例的通用步骤可参考 docs/zh/context/compile_and_run_sample.md。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_sign.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 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::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 outShape {4, 2}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; // 正数输入 → 输出全为 1 std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); 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); // 3.调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnSign第一段接口 ret aclnnSignGetWorkspaceSize(self, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSignGetWorkspaceSize 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); } // 调用aclnnSign第二段接口 ret aclnnSign(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSign 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::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 aclDestroyTensor(self); aclDestroyTensor(out); // 7.释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }运行上述示例对输入{1, 2, 3, 4, 5, 6, 7, 8}shape{4, 2}输出应为全 1若将输入改为包含负数和零的混合数据如{-3, 0, 2, -1, 5, 0, -8, 4}则输出依次为{-1, 0, 1, -1, 1, 0, -1, 1}可据此验证结果是否符合公式。源码级原理剖析aclnn 层的类型适配与连续化处理aclnnSignGetWorkspaceSize的实现op_api/aclnn_sign.cpp展示了 aclnn 单算子封装层的典型处理链路对理解算子行为很有帮助空 tensor 短路self或result为空 tensor 时workspaceSize 直接置 0 并返回成功避免无效下发。连续化对非连续输入调用l0op::Contiguous转为连续内存出参若为非连续 tensor计算完成后通过l0op::ViewCopy将连续结果写回非连续视图因此用户侧直接传非连续 tensor 亦可。BOOL 特化由于内核不直接处理 BOOL实现将 BOOL 输入l0op::Cast为 INT32 参与 Sign 计算输出前再Cast回 BOOL与 README 中BOOL 类型输出恒等于输入的公式一致。高维 reshape 兜底当维数超过内核支持的最大维度MAX_SUPPORT_DIMS_NUMS时先l0op::Reshape展平计算后再恢复原始 shape。workspace 汇总所有中间算子Cast/Contiguous/Reshape/ViewCopy产生的 workspace 需求由executor-GetWorkspaceSize()统一汇总返回。内核层的向量化实现从 op_kernel/sign_apt.cpp 可以看到内核入口sign(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)通过TILING_KEY_IS(...)按数据类型分发到对应的计算模板halfkey 101、bfloat16_tkey 102、floatkey 103、int32_tkey 104、int64_tkey 105、int8_tkey 106、int16_tkey 111、uint8_tkey 107、uint16_tkey 108、uint32_tkey 109、uint64_tkey 110全部复用ElementwiseSch16B逐元素调度框架KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY)指定纯 AIV 向量核执行。每种类型的具体计算逻辑定义在 op_kernel/arch35/sign.h 中核心思想是把符号函数改写为两次比较相减y compare(x 0) - compare(x 0)内核模板SignCustom用向量寄存器实现Compares分别生成x 0与x 0的比较掩码Select将大于掩码映射为ones、小于掩码映射为zeros最后Sub(vregLeft, vregRight)得到-1 / 0 / 1输出并按向量长度VECTOR_REG_WIDTH / sizeof(T)分块循环处理全部元素SignCustomInt64针对 64 位类型使用双倍向量宽度VECTOR_REG_WIDTH_2XVL的独立模板。BF16 由于硬件不支持直接比较SignForBf在 DAG 中先Castfloat计算再Cast回 BF16CastModeBf16ToFp32/CastModeFp32ToBf16。图模式侧的注册与 shape 推导若走 geir 图模式接入算子信息由 op_host/sign_def.cpp 通过OP_ADD(Sign)注册输入x与输出y均为 REQUIRED 参数格式仅 NDAICore 配置开启了DynamicRankSupportFlag(true)与DynamicShapeSupportFlag(true)即支持动态 rank 与动态 shape同时PrecisionReduceFlag(true)允许精度降低优化。shape 推导复用逐元素算子通用逻辑InferShape4Elewise见 op_host/sign_infershape.cpp即输出 shape 直接继承输入 shape与逐元素、shape 不变的语义一致。测试与验证仓库为 Sign 算子提供了多级测试可作为接入验证的参考infershape 单测tests/ut/op_host/test_sign_infershape.cpp以{4, 3, 4}的 FLOAT16/ND 输入为例断言输出 shape 与输入一致验证逐元素 shape 推导正确性。op_api 单测tests/ut/op_api/test_aclnn_sign.cpp覆盖格式组合、数据类型等入参场景其中test_sign_format遍历ACL_FORMAT_UNDEFINED、ACL_FORMAT_NCHW、ACL_FORMAT_NHWC等多种格式验证非法格式/类型能被第一段接口正确拦截并返回ACLNN_ERR_PARAM_INVALID。端到端测试配置tests/st/aclnnSign/atk_aclnnSign.json 与 tests/st/arch35/ 下的用例参数如ttk_aclnn_sign_st.csv、ttk_kernel_sign_st.csv覆盖 aclnn 接口级与内核级的 ST 用例测试数据的期望值由 tests/assets/golden.py 生成。总结Sign 算子是一个结构简单但调用链路完整的典型逐元素算子IR 层通过OP_ADD(Sign)注册并复用InferShape4Elewise完成 shape 推导aclnn 层通过两段式接口完成参数校验、BOOL/高维/非连续 tensor 的类型适配与 workspace 汇总内核层以两次比较相减的向量化模板在 AIV 向量核上逐类型分发执行。开发者接入时只需把握三点确认目标产品在支持矩阵内、按调用通道选择合法数据类型geir 不含 BOOL/DOUBLE/复数aclnn 不含 INT8/INT16/UINT 系列、遵循先aclnnSignGetWorkspaceSize再aclnnSign的两段式流程即可。赞分享算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载相关推荐CANN ops-math Sign 符号算子解析功能、参数约束与两段式 aclnnSign 调用实践CANN ops math Sign 符号算子解析功能、参数约束与两段式 aclnnSign 调用实践 Sign符号函数算子按元素提取 Tensor 的符算子库人工智能CANNCANN ops-math 算子实战AddN 多输入逐元素求和算子详解与 NPU 调用指南CANN ops math 算子实战AddN 多输入逐元素求和算子详解与 NPU 调用指南 本文以 CANN ops math 仓库中的 AddN 算子文档算子库人工智能CANNCANN ops-math 算子 API 详解使用 aclnnNeg / aclnnInplaceNeg 在 NPU 上完成逐元素取反计算CANN ops math 算子 API 详解使用 aclnnNeg / aclnnInplaceNeg 在 NPU 上完成逐元素取反计算 本文以 CANN算子库人工智能CANN上一篇Steam Deck模拟器常见问题解决黑屏、无声音、控制器失效终极方案下一篇Cosmos可视化工具使用指南深入分析模型输出与中间结果创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考