CANN ops-math 中 Atanh 算子的原理与调用指南:从 aclnn 两段式接口到 AICore/AICPU 双实现
2026/9/20 11:15:09 网站建设 项目流程
  • 算子库
  • 人工智能
  • CANN

【免费下载链接】ops-math

本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-math
点击查看免费下载

导读

Atanh(反双曲正切,inverse hyperbolic tangent)是 CANN ops-math 数学基础算子库中提供的一类逐元素(element-wise)激活类算子,用于在 NPU 上对输入张量中的每个元素执行atanh运算,广泛应用于归一化、自注意力打分、数学变换等网络场景。本文以 math/atanh/README.md 为主体,结合算子定义、形状推导、L0/L2 接口实现、AICore 与 AICPU 内核代码,完整讲解 Atanh 算子的数学定义、参数约束、aclnn 两段式调用与图模式调用方法、完整可运行的 C++ 调用示例,以及底层的调度与计算原理,帮助读者在 CANN 环境中独立完成 Atanh 算子的编译、运行与验证。

功能说明与数学原理

Atanh 算子对输入张量中的每一个元素独立计算其反双曲正切值,是典型的逐元素算子。其计算公式为:

$$ y = \text{atanh}(x) = \frac{1}{2} \ln\left(\frac{1+x}{1-x}\right) $$

从公式可以看出,atanh的定义域为开区间 (-1, 1),当x超出该值域时结果具有特殊边界行为(详见后文"约束说明")。该算子在 ops-math 中属于 math/atanh 目录,贡献记录显示该算子由 CANN-BOT SIMT 于 2026/05/22 新增。

产品支持情况

README 中列出的产品支持矩阵如下,所有列出产品均支持 Atanh 算子:

产品是否支持
Ascend 950PR/Ascend 950DT
Atlas A3 训练系列产品/Atlas A3 推理系列产品
Atlas A2 训练系列产品/Atlas A2 推理系列产品
Atlas 200I/500 A2 推理产品
Atlas 推理系列产品
Atlas 训练系列产品

需要说明的是,aclnn 接口文档 math/atanh/docs/aclnnAtanh&aclnnInplaceAtanh.md 中的支持矩阵与 README 略有差异(该文档标注 "Atlas 200I/500 A2 推理产品" 为"不支持"),实际支持情况以所安装 CANN 版本的接口文档为准。

参数说明

Atanh 算子共两个 Tensor 参数:输入x与输出y,均为 ND 格式:

参数名输入/输出/属性描述数据类型数据格式
x输入待进行 atanh 计算的入参,公式中的 x。输入值域为 (-1, 1)。FLOAT16、FLOAT、BF16ND
y输出atanh 计算的结果,公式中的 y。FLOAT16、FLOAT、BF16ND

这一参数约束在算子定义源码 math/atanh/op_host/atanh_def.cpp 中有完全对应的实现:Input("x")Output("y")DataType均声明为{ge::DT_FLOAT, ge::DT_FLOAT16, ge::DT_BF16}Format均为FORMAT_ND,且输入输出都设置了AutoContiguous(),表示框架会自动将非连续 Tensor 处理为连续 Tensor 后参与计算。

约束说明

README 对 Atanh 算子给出如下三条核心约束:

  • 输入x的值域为 (-1, 1):当|x| > 1时输出 NaN,当x = ±1时输出 ±Inf。这是由atanh数学定义(含对数、除法)在定义域边界上的行为决定的。
  • 输出 shape 与输入 shape 完全相同。
  • 输出 dtype 与输入 dtype 相同。

"输出 shape 与输入相同"这一约束在形状推导实现 math/atanh/op_host/atanh_infershape.cpp 中逐维拷贝实现:InferShapeAtanh读取输入 shape 的维数xShapeSize,先SetDimNum再逐维SetDim写回输出,即yShape[i] = xShape[i]

调用说明

README 给出两种调用方式,对应仓库中的两个独立可编译示例:

调用方式调用样例说明
aclnn 调用test_aclnn_atanh参见 算子调用 完成算子编译和验证。
图模式调用test_geir_atanh参见 算子调用 完成算子编译和验证。

图模式调用对应仓库中的图适配实现 math/atanh/op_graph/atanh_graph_infer.cpp(配合atanh_proto.h中的算子原型),以及 TensorFlow 侧插件 math/atanh/framework/atanh_tf_plugin.cpp,用于在图编译框架(GEIR)中完成算子的节点构建与推导。

aclnn 接口详解:aclnnAtanh 与 aclnnInplaceAtanh

接口选择

aclnn 调用方式提供两个功能完全相同的接口(详见 math/atanh/docs/aclnnAtanh&aclnnInplaceAtanh.md):

  • aclnnAtanh:需新建一个输出张量对象存储计算结果(in-place 之外的常规模式);
  • aclnnInplaceAtanh:无需新建输出张量对象,直接在输入张量的内存中存储计算结果,可节省一次输出 Tensor 的申请与拷贝。

两个接口均为两段式接口(参见 两段式接口说明):必须先调用*GetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器,再调用第二段接口执行计算。

函数原型

aclnnStatus aclnnAtanhGetWorkspaceSize( const aclTensor* input, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnAtanh( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) aclnnStatus aclnnInplaceAtanhGetWorkspaceSize( aclTensor* inputRef, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnInplaceAtanh( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)

aclnnAtanhGetWorkspaceSize 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 Tensor
input(aclTensor*)输入输入 tensor,进行反双曲正切运算。shape 需要与 out 一致。INT8、INT16、INT32、INT64、UINT8、BOOL、FLOAT、FLOAT16、DOUBLEND不超过 8 维
out(aclTensor*)输出输出 tensor,存储计算结果。shape 需要与 input 一致。FLOAT、FLOAT16、DOUBLEND-
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小。-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程。-----

平台扩展说明:在 Atlas A3 训练/推理系列产品上,inputout的数据类型额外支持 COMPLEX64、COMPLEX128、BFLOAT16。

返回值(错误码):第一段接口完成入参校验,返回aclnnStatus状态码(具体参见 aclnn 返回码)。出现如下场景时报错:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 input 或 out 是空指针。
ACLNN_ERR_PARAM_INVALID161002input 或 out 的数据类型不在支持的范围之内。
ACLNN_ERR_PARAM_INVALID161002input 和 out 的 shape 不一致。
ACLNN_ERR_PARAM_INVALID161002input 或 out 的维数大于 8。

aclnnAtanh 参数说明

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址。
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnAtanhGetWorkspaceSize 获取。
executor输入op 执行器,包含了算子计算流程。
stream输入指定执行任务的 Stream。

aclnnInplaceAtanhGetWorkspaceSize 参数说明

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 Tensor
inputRef(aclTensor*)输入/输出输入输出 tensor,进行反双曲正切运算,计算结果存储在 inputRef 中。-FLOAT、FLOAT16、DOUBLEND不超过 8 维
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小。-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程。-----

平台扩展说明:在 Atlas A3 训练/推理系列产品上,inputRef数据类型额外支持 COMPLEX64、COMPLEX128、BFLOAT16。

in-place 接口的校验错误场景:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 inputRef 是空指针。
ACLNN_ERR_PARAM_INVALID161002inputRef 的数据类型不在支持的范围之内。
ACLNN_ERR_PARAM_INVALID161002inputRef 的维数大于 8。

确定性约束:aclnnAtanh 与 aclnnInplaceAtanh 默认均为确定性实现,同一输入在多次运行中产生确定一致的结果。

完整调用示例

以下示例代码源自 math/atanh/docs/aclnnAtanh&aclnnInplaceAtanh.md 与仓库样例 math/atanh/examples/test_aclnn_atanh.cpp,完整演示了两段式接口的标准用法(编译与运行请参考 编译与运行样例):

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_atanh.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 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); 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() { // 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::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, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; std::vector<float> 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:aclnnAtanh(两段式) uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnAtanhGetWorkspaceSize(self, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAtanhGetWorkspaceSize 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); } // 调用aclnnAtanh第二段接口 ret = aclnnAtanh(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAtanh failed. ERROR: %d\n", ret); return ret); // 3'. aclnnInplaceAtanh接口调用示例(结果直接写回self) uint64_t inplaceWorkspaceSize = 0; aclOpExecutor* inplaceExecutor; ret = aclnnInplaceAtanhGetWorkspaceSize(self, &inplaceWorkspaceSize, &inplaceExecutor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceAtanhGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); 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); } ret = aclnnInplaceAtanh(inplaceWorkspaceAddr, inplaceWorkspaceSize, inplaceExecutor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceAtanh 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::vector<float> 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; }

对 shape 为{4, 2}、输入{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}的示例,输出约为{0.100335, 0.202733, 0.309520, 0.423649, 0.549306, 0.693147, 0.867301, 1.098612}

源码级实现原理

L2 接口层:入参校验、自动类型转换与工作流编排

L2 层接口实现在 math/atanh/op_api/aclnn_atanh.cpp,其工作流体现了 CANN 单算子 API 的通用编排模式:

  1. dtype 支持列表:L2 层支持比 L0/内核更宽泛的输入类型(FLOAT、FLOAT16、DOUBLE、INT8/16/32/64、UINT8、BOOL、COMPLEX64/128,910B 系额外支持 BF16),输出类型支持 FLOAT、FLOAT16、DOUBLE、COMPLEX64、COMPLEX128、BF16。
  2. 自动 cast 链路:对于 INT8/INT16/INT32/INT64/BOOL/UINT8 等输入(NEED_CAST_DTYPE_LIST_ATANH),实现先调用l0op::Cast转成 FLOAT 再执行 atanh,计算完成后再次l0op::Cast回目标输出类型,从而让 L0 层只需聚焦浮点计算。
  3. 连续化与结果写回:输入先经l0op::Contiguous保证连续,计算结果最后通过l0op::ViewCopy写回可能非连续的输出out上。
  4. workspace 获取:第一段接口通过CREATE_EXECUTOR()创建执行器、完成上述节点编排后,以uniqueExecutor->GetWorkspaceSize()返回 workspace 大小,并将 executor 转移给调用方;第二段接口aclnnAtanh/aclnnInplaceAtanh通过CommonOpExecutorRun在指定 stream 上真正执行。aclnnInplaceAtanhGetWorkspaceSize则直接复用ExecAtanhGetWorkspaceSize(inputRef, inputRef, ...),将输入同时作为输出,实现 in-place 语义。
  5. 空 Tensor 处理:当input->IsEmpty()时第一段接口直接返回workspaceSize = 0,无需下发计算。

L0 层:AICore/AICPU 双路调度

L0 层实现在 math/atanh/op_api/atanh.cpp,核心是IsAiCoreSupport的按平台 + dtype 的调度决策:

  • 在 ASCEND910B、ASCEND910_93 以及 RegBase(寄存器级底座)平台上,支持DT_FLOATDT_FLOAT16DT_BF16走 AICore;
  • 其他平台仅DT_FLOATDT_FLOAT16走 AICore;
  • 不满足条件的输入回退到 AICPU 路径(AtanhAiCpu),保证算子在所有产品上的可用性。

AICore 路径通过宏ADD_TO_LAUNCHER_LIST_AICORE将算子加入任务队列,AICPU 路径通过ADD_TO_LAUNCHER_LIST_AICPU创建AicpuTaskSpace任务,二者共享executor->AllocTensor(input->GetViewShape(), input->GetDataType())分配的输出 Tensor——这从实现上印证了"输出 shape/dtype 与输入一致"的约束。

AICore 内核与 tiling

AICore 内核入口为 math/atanh/op_kernel/atanh_apt.cpp,通过AtanhTilingKey(FP32=0、FP16=1、BF16=2)在编译期展开为三个特化分支,分别调用NsAtanh::AtanhSimt::Process<float|half|bfloat16_t>(SIMT 实现见 math/atanh/op_kernel/arch35/atanh_simt.h),配合 math/atanh/op_host/arch35/atanh_tiling_arch35.cpp 完成 arch35 平台的 tiling 切分(tiling 数据结构见 math/atanh/op_kernel/arch35/atanh_tiling_data.h)。算子定义 math/atanh/op_host/atanh_def.cpp 中为ascend950配置了DynamicShapeSupportFlag(true)DynamicRankSupportFlag(true)PrecisionReduceFlag(true)等能力开关,说明该算子在 AICore 上支持动态 shape 与动态 rank。

AICPU 内核

AICPU 回退路径实现于 math/atanh/op_kernel_aicpu/atanh_aicpu.cpp,要点如下:

  • 标量计算调用标准库std::atanh,对Eigen::half特化为先转float计算再转回半精度;
  • 支持的数据类型比 AICore 更宽:DT_FLOAT16、DT_FLOAT、DT_DOUBLE、DT_COMPLEX64、DT_COMPLEX128(对应 L2 层 A3 平台扩展的复数支持);
  • 数据量超过kAtanhParallelNum(64×1024 个元素)时,按 CPU 核数(cores - 2保底)切分区间,通过CpuKernelUtils::ParallelFor并行计算,否则单线程串行执行;
  • 计算前完成数据指针、输入输出 dtype 与数据大小一致性校验。

图模式适配

图模式(GEIR)路径由 math/atanh/op_graph/atanh_graph_infer.cpp 提供形状推导钩子,配合 math/atanh/framework/atanh_tf_plugin.cpp 在 TensorFlow 前端完成算子注册映射,使 Atanh 可像原生算子一样出现在计算图中。示例程序见 math/atanh/examples/test_geir_atanh.cpp(arch35 平台版本位于 math/atanh/examples/arch35)。

测试与验证

仓库为 Atanh 算子提供了覆盖 L2 API、AICPU 内核与端到端(ST)的多层测试:

  • L2 API 单测:math/atanh/tests/ut/op_api/test_aclnn_atanh.cpp 覆盖 FLOAT、FLOAT16、DOUBLE、BF16 四种数据类型,1 维/3 维/5 维/空 Tensor 等多种 shape(输入值域统一控制在 (-0.9, 0.9) 内,精度容差 1e-4),以及nullptr输入/输出返回ACLNN_ERR_PARAM_NULLPTR的异常路径;
  • AICPU 内核单测:math/atanh/tests/ut/op_kernel_aicpu/test_atanh.cpp;
  • ST 场景:math/atanh/tests/st/aclnnAtanh/atk_aclnnAtanh.json 定义端到端场景,math/atanh/tests/assets/golden.py 提供 golden 数据生成逻辑用于结果比对,arch35 的 kernel 级用例见 math/atanh/tests/st/arch35/ttk_kernel_atanh_st.csv。

常见问题与注意事项

  • 值域越界行为:输入严格限制在 (-1, 1) 内。由于实现直接调用数学库/标准库的atanh|x| > 1时自然产生 NaN,x = ±1时产生 ±Inf,属于符合 IEEE 语义的预期行为,业务侧需在调用前做好数据裁剪。
  • 平台差异:AICore 与 AICPU 支持的 dtype 集合不同(如复数、DOUBLE 仅在 AICPU 路径支持,BF16 仅在部分平台走 AICore),最终由 L0 层IsAiCoreSupport自动选择执行路径,用户无需感知,但若需保证跨平台一致的精度表现,建议在目标平台上执行 math/atanh/tests 下对应用例。
  • 两段式接口不可省略:必须先调用GetWorkspaceSize获取 executor,再以aclnnrtMalloc按返回大小申请 workspace(大小为 0 时可跳过申请)后调用第二段接口,最后通过aclrtSynchronizeStream同步等待任务完成,再拷贝结果回 Host。
  • 算子库
  • 人工智能
  • CANN

【免费下载链接】ops-math

本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-math
点击查看免费下载
上一篇:Angular Query 的 injectIsMutating 注入选项解析:掌握 InjectIsMutatingOptions.injector 与全局 mutation 状态追踪
下一篇:Ice 使用指南:5分钟让 Mac 菜单栏回归整洁

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询