news 2026/9/20 23:43:26

CANN ops-math 算子 aclnnTan 完整指南:NPU 逐元素正切计算 API 用法与实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-math 算子 aclnnTan 完整指南:NPU 逐元素正切计算 API 用法与实现原理

CANN ops-math 算子 aclnnTan 完整指南:NPU 逐元素正切计算 API 用法与实现原理

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

aclnnTan是 CANN ops-math 数学算子库中用于计算输入张量逐元素正切值的 aclnn(Ascend CANN Lite Native)API,通过tan(x) = sin(x) / cos(x)的恒等式在 Atlas A2 训练/推理系列产品上实现 NPU 加速计算。本文以 experimental/math/tan/docs/aclnnTan.md 为骨架,结合仓库中算子 Host 侧定义、Tiling 与 Device 侧 Kernel 源码,完整讲解aclnnTanGetWorkspaceSize/aclnnTan双阶段调用的参数语义、错误码、约束边界,并深入剖析其 InferShape、多核 Tiling、float16 升精度计算等底层实现。读完本文,你将能够独立编写、编译、运行并验证基于 aclnnTan 的 NPU 正切计算程序。

功能描述

Tan 算子计算输入张量x的逐元素正切值:

$$out = \tan(x) = \frac{\sin(x)}{\cos(x)}$$

其功能对标 PyTorch 的torch.tan(见 experimental/math/tan/README.md 功能说明)。该算子具备以下核心特征:

  • 不支持广播out的 shape 与x完全相同,属于单输入单输出逐元素算子;
  • 支持数据类型:float32、float16,且out的数据类型必须与x一致;
  • 支持标量与空 tensor:标量输入内部按 shape{1}处理;空 tensor(元素数为 0)时无需 workspace 直接返回成功;
  • 支持动态 shape / 动态 rank:算子定义中显式开启了动态 shape、动态 rank 支持(详见下文源码分析)。

支持的产品型号

产品是否支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品

从源码角度进一步验证:算子在 tan_def.cpp 中通过OpAICoreConfig注册了ascend910b(arch22 架构)的 AICore 配置,并开启DynamicCompileStaticFlagDynamicRankSupportFlagDynamicShapeSupportFlag,说明该算子面向的是 arch22 架构的昇腾 AI Core。

函数原型

aclnn 算子采用“查询 workspace → 执行”的双阶段异步调用模型,Tan 算子对外暴露两个 API:

aclnnStatus aclnnTanGetWorkspaceSize( const aclTensor *x, const aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnTan( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);

调用约定如下:

  1. 先调用aclnnTanGetWorkspaceSize完成参数校验、shape 推导(InferShape)与执行器(aclOpExecutor)创建,并返回算子执行所需的 workspace 大小;
  2. 调用方依据返回的workspaceSize自行分配设备内存;
  3. 再调用aclnnTan将 workspace 与执行器提交到指定 ACL stream 上异步执行;
  4. 通过aclrtSynchronizeStream同步等待执行完成后再释放资源。

aclnnTanGetWorkspaceSize

参数说明

参数名输入/输出描述
x输入数据类型:float32、float16。数据格式:ND。支持非连续 tensor。
out输出数据类型与 x 相同。数据格式:ND。shape 须与 x 相同。支持非连续 tensor。
workspaceSize输出算子执行所需 workspace 大小,单位为 Byte。由本函数返回,调用方须据此分配 workspace 内存。
executor输出算子执行器,包含算子计算流信息,由本函数返回后传入 aclnnTan 执行。

从实现看,Tan 算子实际不依赖 workspace:在 tan_tiling.cpp 中,GetWorkspaceSize将 workspace 设置为WS_SYS_SIZE = 0。因此对于常规输入,aclnnTanGetWorkspaceSize返回的workspaceSize为 0,调用aclnnTan时传入nullptr即可;仅当输入为空 tensor 等边界场景时才会走“直接返回成功”的短路路径。示例代码中的“若 workspaceSize > 0 才分配”写法正是为了兼容这一约定。

返回值说明

返回aclnnStatus错误码,详见下文错误码章节。

aclnnTan

参数说明

参数名输入/输出描述
workspace输入workspace 内存地址。若 workspaceSize 为 0,可传入 nullptr。
workspaceSize输入workspace 大小,由 aclnnTanGetWorkspaceSize 返回。
executor输入算子执行器,由 aclnnTanGetWorkspaceSize 返回。
stream输入ACL stream,用于异步调度算子执行。

返回值说明

返回aclnnStatus错误码,详见下文错误码章节。

错误码

错误码描述
ACLNN_SUCCESS(0)执行成功。
ACLNN_ERR_PARAM_NULLPTR输入/输出 tensor 指针为空。
ACLNN_ERR_PARAM_INVALID参数非法,包括:数据类型不支持、out shape 与 x 不一致等。
ACLNN_ERR_INNER_CREATE_EXECUTOR内部创建算子执行器失败。
ACLNN_ERR_INNER_NULLPTR内部 tensor 分配失败。
ACLNN_ERR_INNER_INFERSHAPE_ERROR内部 InferShape 失败。

其中ACLNN_ERR_PARAM_INVALID与源码中的两道校验环节对应:其一是算子注册层面的数据类型约束,tan_def.cpp 中输入输出均声明为{ge::DT_FLOAT, ge::DT_FLOAT16}且格式限定FORMAT_ND;其二是 InferShape 阶段 shape 一致性校验,见下节。

约束说明

  • 输入x支持 float32 和 float16 数据类型;
  • out的数据类型须与x相同;
  • out的 shape 须与x相同(不支持广播);
  • 支持标量输入(内部转为 shape{1}处理);
  • 支持空 tensor(元素数为 0),此时 workspaceSize 为 0,直接返回成功;
  • workspace 须在调用aclnnTan之前分配,在 stream 中算子执行完成后方可释放;
  • 当输入值接近 $\frac{\pi}{2} + k\pi$($k$ 为整数)时,正切函数结果趋向无穷大,可能出现精度下降或溢出。

最后一条约束是正切函数本身的数学特性:在 $\pi/2$ 的奇数倍附近 $\cos(x) \to 0$,sin/cos除法结果趋于无穷,应用侧应结合数值范围评估精度需求。

调用示例

以下示例展示了 Tan 算子的完整调用流程(与仓库 examples/test_aclnn_tan.cpp 中的官方示例同源):

#include <cstdio> #include <vector> #include "acl/acl.h" #include "aclnn_tan.h" int main() { // 1. 初始化 ACL 及设备 aclInit(nullptr); aclrtSetDevice(0); aclrtStream stream; aclrtCreateStream(&stream); // 2. 准备输入数据(fp32,shape=[2,4]) // x = [0.0, 0.5, 1.0, -1.0, 0.25, -0.5, 2.0, -2.0] // out = tan(x) int64_t shape[] = {2, 4}; int64_t strides[] = {4, 1}; float x_host[] = {0.0f, 0.5f, 1.0f, -1.0f, 0.25f, -0.5f, 2.0f, -2.0f}; float out_host[8] = {0}; void *x_dev = nullptr, *out_dev = nullptr; size_t nbytes = 8 * sizeof(float); aclrtMalloc(&x_dev, nbytes, ACL_MEM_MALLOC_NORMAL_ONLY); aclrtMalloc(&out_dev, nbytes, ACL_MEM_MALLOC_NORMAL_ONLY); aclrtMemcpy(x_dev, nbytes, x_host, nbytes, ACL_MEMCPY_HOST_TO_DEVICE); // 3. 创建 aclTensor aclTensor *x = aclCreateTensor(shape, 2, ACL_FLOAT, strides, 0, ACL_FORMAT_ND, shape, 2, x_dev); aclTensor *out = aclCreateTensor(shape, 2, ACL_FLOAT, strides, 0, ACL_FORMAT_ND, shape, 2, out_dev); // 4. 查询 workspace 大小并分配 uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; aclnnTanGetWorkspaceSize(x, out, &workspaceSize, &executor); void *workspace = nullptr; if (workspaceSize > 0) aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_NORMAL_ONLY); // 5. 执行算子 aclnnTan(workspace, workspaceSize, executor, stream); aclrtSynchronizeStream(stream); // 6. 取回结果 aclrtMemcpy(out_host, nbytes, out_dev, nbytes, ACL_MEMCPY_DEVICE_TO_HOST); printf("out = [%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f]\n", out_host[0], out_host[1], out_host[2], out_host[3], out_host[4], out_host[5], out_host[6], out_host[7]); // 期望: [0.0000, 0.5463, 1.5574, -1.5574, 0.2553, -0.5463, -2.1850, 2.1850] // 7. 释放资源 if (workspace) aclrtFree(workspace); aclrtFree(x_dev); aclrtFree(out_dev); aclDestroyTensor(x); aclDestroyTensor(out); aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); return 0; }

调用流程要点拆解

上述示例可归纳为七个标准步骤,这也是所有 aclnn 单算子调用的通用范式:

  1. 初始化环境aclInitaclrtSetDeviceaclrtCreateStream
  2. 数据准备:Host 端构造输入数据,aclrtMalloc分配设备内存,aclrtMemcpyACL_MEMCPY_HOST_TO_DEVICE)上传;
  3. 创建 aclTensor:通过aclCreateTensor描述 shape、rank、数据类型(ACL_FLOAT)、strides、格式(ACL_FORMAT_ND)与设备地址;示例中的strides = {4, 1}表示行主序连续存储;
  4. 查询并分配 workspaceaclnnTanGetWorkspaceSize返回workspaceSizeexecutor,按返回值条件分配 workspace(Tan 常规输入下为 0,可传nullptr);
  5. 异步执行aclnnTan提交任务后调用aclrtSynchronizeStream等待完成;
  6. 结果回传aclrtMemcpyACL_MEMCPY_DEVICE_TO_HOST)取回结果并与预期值比对;
  7. 资源释放:按 workspace → 设备内存 → tensor → stream → 设备 →aclFinalize的顺序逆序释放。

仓库示例 test_aclnn_tan.cpp 进一步演示了工程化写法:封装Init/CreateAclTensor辅助函数、通过CHECK_RET宏检查每个 ACL 调用的返回值、并以atol = 1e-4rtol = 1e-4的相对/绝对误差阈值与std::tan的 golden 值逐元素比对(见该文件第 135-148 行)。若需要创建 float16 输入,只需将数据类型改为ACL_HALF,并在 Host 侧使用half类型数组即可。

源码级实现原理

Shape 推导:输出 shape 恒等于输入 shape

Tan 的 InferShape 实现位于 tan_infershape.cpp。InferShape4TanInferShapeContext中取出输入 shape,直接赋值给输出 shape:

static ge::graphStatus InferShape4Tan(gert::InferShapeContext* context) { const gert::Shape* input_shape = context->GetInputShape(0); ... *output_shape = *input_shape; return ge::GRAPH_SUCCESS; }

这正是“不支持广播、outshape 与x相同”这一约束在框架层的落地:无论输入是标量、1D 还是 8D,输出 shape 均被推导为与输入完全一致。

算子注册与动态 shape 支持

tan_def.cpp 通过OP_ADD(Tan)将算子注册到算子定义注册表:

  • 输入x与输出y均声明DataType({ge::DT_FLOAT, ge::DT_FLOAT16})Format({ge::FORMAT_ND, ge::FORMAT_ND}),并调用.AutoContiguous()支持非连续输入;
  • aicoreConfig910B开启DynamicRankSupportFlag(true)DynamicShapeSupportFlag(true),说明算子支持动态 rank 与动态 shape;
  • PrecisionReduceFlag(true)允许精度降低优化,与 float16 升精度计算路径配合使用。

Tiling:多核切分 + UB 切分

Tan 的 Tiling 逻辑位于 tan_tiling.cpp,核心任务是计算出TanTilingData三个字段(定义于 tan_tiling_data.h):

struct TanTilingData { int64_t totalNum = 0; // 总元素数 int64_t blockFactor = 0; // 每个 AI Core 处理的元素数 int64_t ubFactor = 0; // 每次 UB 循环迭代处理的元素数 };

多核切分blockFactor = CeilDiv(totalNum, coreNum)usedCoreNum = CeilDiv(totalNum, blockFactor),将总元素均匀分摊到各 AI Core;当totalNum < coreNum时,部分 Core 因blockLength_被钳制为 0 而处于空闲状态(见 tan.h)。

UB 切分与 buffer 规划:每个 Core 内按 UB 容量分块,且 float32 与 float16 使用不同的 buffer 配比:

数据类型buffer 数分配依据(代码注释)
float326(BUFFER_NUM_FP32输入队列 ×2 + 输出队列 ×2 + 中间结果 tmpBuf1/tmpBuf2 ×2,均按 float 大小计
float164(BUFFER_NUM_FP16输入/输出队列按 half(共 4 个 half = 2 个 float 大小)+ 两个 float 中间缓冲(4+4),合计等效 4 个 float

对应代码为tiling->ubFactor = FloorAlign(FloorDiv((ubCanUse / typeSize), BUFFER_NUM_XXX), ubBlockSize),其中typeSize = 4,因为float16 路径内部也会提升到 float32 计算(见下节)。

TilingKey 选择:根据输入 dtype 设置不同的 TilingKey——TAN_TPL_SCH_MODE_0(float32)或TAN_TPL_SCH_MODE_1(float16),定义见 tan_tiling_key.h。

空 tensor 短路:当totalNum == 0时,TilingData 三个字段全部置 0,SetBlockDim(1),直接返回成功,与文档“空 tensor 时 workspaceSize 为 0”的描述对应。

Kernel:float32 直算与 float16 升精度计算

Device 侧 Kernel 入口位于 tan.cpp,根据 TilingKey 模板分发到NsTan::Tan<float>NsTan::Tan<half>(见 tan.h)。两条计算路径如下:

float32 路径(直接计算)

sinVal = Sin(x) // 计算 sin(x) cosVal = Cos(x) // 计算 cos(x) y = Div(sinVal, cosVal) // tan(x) = sin(x) / cos(x)

对应 tan.h 中Tan<float>::Compute特化实现,使用tmpBuf1tmpBuf2两块 VECCALC 缓冲暂存 sin、cos 中间结果。

float16 路径(升精度计算)

x_fp32 = Cast(x, CAST_NONE) // half -> float32 cosVal = Cos(x_fp32) // 先算 cos(避免输入被覆盖) sinVal = Sin(x_fp32) // 再算 sin result = Div(sinVal, cosVal) // tan = sin / cos y = Cast(result, CAST_ROUND) // float32 -> half

对应 tan.h 中Tan<half>::Compute特化实现。这里的执行顺序有讲究:先把 half 输入 Cast 到 float32 存入sinVal缓冲,随后先算 Cos 再算 Sin,因为 Sin 会原地覆盖sinVal,必须先保存好 cos 结果;最后 Div 复用sinVal缓冲,再以CAST_ROUND舍入模式 Cast 回 half。整个流程将 float16 的运算提升到 float32 精度域完成,这正是 README 中“float16 全量 20 条用例 100% 通过(rtol=1e-3, atol=1e-3)”的精度保障。

流水线调度:Kernel 使用双缓冲(BUFFER_NUM = 2)与TPipe队列机制,通过inputQueue/outputQueue实现 CopyIn(GM→UB)→ Compute(UB 向量计算)→ CopyOut(UB→GM)三级流水重叠;CopyIn/CopyOut使用DataCopyPad完成带 pad 的数据搬运,Process主循环按ubLength_分块迭代,末块用余数currentNum处理(见 tan.h)。

构建与测试

前提条件

  • CANN Toolkit 已安装(如/home/developer/Ascend/cann-9.0.0);
  • 已设置环境变量:source /home/developer/Ascend/ascend-toolkit/set_env.sh

编译自定义算子包

cd ops/tan bash build.sh --soc=ascend910b --pkg

编译成功后,算子包位于build/custom_opp_ubuntu_aarch64.run,执行安装:

bash build/custom_opp_ubuntu_aarch64.run

算子将安装到$ASCEND_HOME_PATH/opp/vendors/tan_custom/,安装完成后即可在业务代码中通过#include "aclnn_tan.h"调用 aclnnTan。

测试方法

bash build.sh --soc=ascend910b --pkg -a # 编译 + UT + ST bash build.sh --soc=ascend910b --pkg -u # 编译 + 仅 UT bash build.sh --soc=ascend910b --pkg -s # 编译 + 仅 ST

全量 56 条测试用例(36 条 float32 + 20 条 float16)。ST 测试支持两种模式:Mock 模式(CPU Golden,无需 NPU,用于开发验证)与真实 NPU 模式(需要 NPU 设备,验证实际精度)。官方精度表现:float32 在rtol=1e-4, atol=1e-6标准下全量通过,float16 在rtol=1e-3, atol=1e-3标准下全量通过(见 README.md 精度说明)。

总结

aclnnTan 是 ops-math 中结构清晰的单输入逐元素数学算子范例:对外以aclnnTanGetWorkspaceSize+aclnnTan双阶段异步接口提供服务,支持 float32/float16、动态 shape、标量与空 tensor;对内由 tan_def.cpp 注册算子、tan_infershape.cpp 完成 shape 推导、tan_tiling.cpp 完成多核与 UB 两级切分、tan.h 实现 float32 直算与 float16 升精度两条 Kernel 路径。理解 aclnnTan 的调用约定与实现细节,不仅能直接用于 NPU 上的正切计算场景,也为阅读 ops-math 中其他 aclnn 算子的 API 文档与源码提供了一套可复用的方法论。

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 23:43:15

用开源工具搭建本地优先的科研工作台:从文献管理到写作全流程

经常听到身边朋友抱怨&#xff1a;课题一多&#xff0c;手头堆积的文献、实验记录、会议笔记全乱成一锅粥&#xff0c;想找一篇去年读过的论文&#xff0c;翻遍文件夹都找不到。这两年我也一直在折腾怎么把整个研究流程管起来&#xff0c;后来索性用一系列开源工具拼装了一套自…

作者头像 李华
网站建设 2026/9/20 23:43:15

C#上位机曲线编辑器:基于PictureBox与GDI+的核心实现

简介&#xff1a;面向C# WinForm初学者的曲线编辑器开发演示工程&#xff0c;以自绘曲线面板为核心&#xff0c;展示在PictureBox控件中实现数据曲线显示、绘制与交互修改的完整思路。工程覆盖两种曲线绘制方法、曲线识别检测、外部TXT数据加载、关键帧数据点绘制及拖动改值等常…

作者头像 李华
网站建设 2026/9/20 23:42:56

质量问题归零报告编写规范与技术闭环实践

简介&#xff1a;本资源为《质量问题归零报告编写要求》规范性文档&#xff0c;面向质量管理人员、航天/军工领域工程技术人员及高校质量管理相关专业师生&#xff0c;系统解决技术与管理两类质量问题的标准化归零报告撰写难题。文档严格依据GJB质量管理体系要求&#xff0c;完…

作者头像 李华
网站建设 2026/9/20 23:41:29

OpenToonz快速跑起来:这份免费2D动画软件实战指南

OpenToonz快速跑起来&#xff1a;这份免费2D动画软件实战指南 【免费下载链接】opentoonz OpenToonz - An open-source full-featured 2D animation creation software 项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz OpenToonz 是日本 DWANGO 发布的免费…

作者头像 李华
网站建设 2026/9/20 23:40:20

Dify+NL2SQL+ECharts端到端可视化链路实战

简介&#xff1a;本资源是一套轻量级NL2SQL与数据可视化融合实践方案&#xff0c;面向AI应用开发初学者、数据分析工程师及低代码平台实践者&#xff0c;解决自然语言查询数据库并自动生成交互式图表的核心痛点。压缩包仅2个文件&#xff08;11KB&#xff09;&#xff0c;含关键…

作者头像 李华
网站建设 2026/9/20 23:40:11

开放科研工程实践:从数据代码到可复现论文的完整指南

1. 先搞清楚OpenResearch到底在说什么这几年我听到"OpenResearch"的频率越来越高&#xff0c;但大家聊的很多时候不是一回事。有人说是开放获取论文&#xff0c;有人说是开源代码&#xff0c;有人说是数据共享&#xff0c;还有人把它当成某个特定项目代号。我的理解是…

作者头像 李华