CANN ops-math 中的 AtanGrad 算子:反正切梯度计算的原理、实现与 aclnn 调用实战
2026/9/19 13:29:33 网站建设 项目流程

CANN ops-math 中的 AtanGrad 算子:反正切梯度计算的原理、实现与 aclnn 调用实战

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

本文以 CANN ops-math 仓库中 experimental/math/atan_grad 算子为核心,系统讲解 AtanGrad(反正切函数 atan 的输入梯度)算子的数学原理、产品支持情况、参数与约束,并结合仓库中的 op_api、op_host、op_kernel 源码,剖析其 aclnn 两段式调用链路、Tiling 多核切分策略与 kernel 精度设计。读完本文,你将掌握 AtanGrad 算子在 Ascend NPU 上的完整实现机制,并能够参照仓库示例完成自定义算子包的编译、安装与 aclnn 接口调用。

算子概述与数学原理

AtanGrad 是用于神经网络反向传播的逐元素梯度算子,计算反正切函数atan(x)对输入x的梯度。其核心公式为:

$$ dx_i = dy_i \times \frac{1}{1 + x_i^2} $$

其中:

  • x为前向计算中 atan 函数的输入张量;
  • dy为上游(loss 侧)反向传入的梯度张量;
  • dx为算子输出的输入梯度张量,与dy逐元素相乘后回传。

该公式来源于 atan 的解析导数d(atan(x))/dx = 1/(1+x²),即dx = dy · atan'(x)。从实现角度看,计算可以拆解为四步等效分步:

  1. t_i = x_i × x_i—— 计算
  2. g_i = t_i + 1.0—— 计算分母1 + x²(值恒不小于 1);
  3. r_i = 1 / g_i—— 取倒数,即 atan 的导数;
  4. dx_i = dy_i × r_i—— 乘以上游梯度。

这一分步在仓库的 kernel 实现 op_kernel/atan_grad.h 中被直接体现:Mulx*xAdds加 1.0、再以Div完成dy/(1+x²)

产品支持情况

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

需要说明的是,算子原型注册文件 op_host/atan_grad_def.cpp 中通过this->AICore().AddConfig("ascend910b", aiCoreConfig)AddConfig("ascend950", aiCoreConfig)将算子绑定到 ascend910b(对应 Atlas A2 系列)与 ascend950(对应 Ascend 950 系列)两条 AICore 配置;kernel 入口文件 op_kernel/atan_grad.cpp 也标注了 arch35 架构、支持 ascend910b/ascend950,与 README 中的产品支持矩阵一致。

参数说明

AtanGrad 共 3 个参数:两个输入、一个输出,均为 ND 格式的稠密张量。

参数名输入/输出描述数据类型数据格式
x输入前向计算输入张量,对应公式中 x,为 atan 函数自变量。FLOAT16、FLOAT、BFLOAT16ND
dy输入上游传入的梯度张量,对应公式中 dy。数据类型须与 x 完全一致。FLOAT16、FLOAT、BFLOAT16ND
dx输出输出的输入梯度张量,对应公式中 dx。数据类型须与 x 完全一致。FLOAT16、FLOAT、BFLOAT16ND

从源码看参数校验的落地

aclnn 接口实现 op_api/aclnn_atan_grad.cpp 中的CheckParams将上述约束转化为四个显式的校验步骤:

  • CheckNotNull:对 x、dy、dx 三个指针执行OP_CHECK_NULL,任一为空即返回ACLNN_ERR_PARAM_NULLPTR
  • CheckDtypeValid:通过OP_CHECK_DTYPE_NOT_MATCH强制dydx的 dtype 与x完全一致,再通过ATAN_GRAD_DTYPE_SUPPORT_LISTDT_FLOAT16DT_FLOATDT_BF16)白名单过滤不支持的类型;
  • CheckFormat:通过IsPrivateFormat拒绝私有格式,仅允许 ND 等公开格式;
  • CheckShape:先用OP_CHECK_MAX_DIM将维度限制在ACLNN_MAX_SHAPE_RANK = 8以内,再强制x->GetViewShape() == dy->GetViewShape(),注释明确指出 shape 不一致会带来 GM 越界访问风险。

在 L0 层 op_api/atan_grad.cpp 中,AtanGradInferShape直接将输出 shape 置为输入 x 的 shape(逐元素算子天然如此),并由IsAiCoreSupport再次核对 dtype 白名单。

约束说明

  • aclnnAtanGrad 为默认确定性实现(算子行为确定,同一输入多次运行结果一致)。
  • x、dy、dx 三者数据类型必须完全一致,不支持隐式类型转换。
  • x、dy、dx 三者 shape 必须完全相同,不支持广播(broadcast)。
  • 支持空 Tensor(元素个数为 0)。
  • 支持 0-8 维 Tensor,0 维表示标量(scalar),此时 dy 和 dx 也必须为 0 维。
  • 当 x 取值极大(如 fp16 最大值)时,可能溢出为 inf,此时1/inf = 0dx = 0,属于正常数值行为。

空 Tensor 与 0 维标量的特殊处理

  • 空 TensoraclnnAtanGradGetWorkspaceSize中通过HasEmptyTensor(x, dy)提前检测,若任一输入为空,直接将*workspaceSize = 0并返回成功,跳过后续建图与调度;
  • 0 维标量:Tiling 侧 op_host/atan_grad_tiling.cpp 用EnsureNotScalar将 0 维 shape 归一为{1},从而与常规向量路径复用同一套多核切分逻辑;
  • 总元素为 0:Tiling 中totalNum == 0时设置blockDim = 0并提前返回,避免空启动。

aclnn 两段式接口调用

调用方式

调用方式调用样例说明
aclnn 调用test_aclnn_atan_grad调用前需完成自定义算子包的编译与安装(bash build.sh --soc=ascend910b)。

README 当前标注"接口文档和设计文档待补充",因此示例代码是本算子最权威的调用参考。

两段式接口的执行链路

从 op_api/aclnn_atan_grad.cpp 的文件头注释可以看到 AtanGrad 采用标准的 ACLNN L2 两段式接口:

  • 第一段aclnnAtanGradGetWorkspaceSizeCREATE_EXECUTORCheckParamsHasEmptyTensorContiguousl0op::AtanGradViewCopy→ 返回workspaceSizeexecutor
  • 第二段aclnnAtanGrad:通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)在指定 stream 上真正执行。

值得注意的两个实现细节:

  1. 输入会先经过l0op::Contiguous归一为连续内存,算子计算完成后通过l0op::ViewCopy将结果写入用户传入的 dx 张量视图;
  2. L0 层AtanGrad内部依次完成AtanGradInferShape(输出 shape = 输入 shape)、IsAiCoreSupport(dtype 与平台支持确认)、executor->AllocTensor(分配输出张量)、AtanGradAiCore(经ADD_TO_LAUNCHER_LIST_AICORE调度 kernel)四个步骤。

示例编译与运行

仓库示例 test_aclnn_atan_grad.cpp 以 fp32、shape[4, 8]的测试数据演示了完整流程:

  1. 编译算子包:cd ops/atan_grad && bash build.sh --soc=ascend910b(安装指令参照 build.sh 输出);
  2. 编译示例:
g++ -std=c++17 -o test_aclnn_atan_grad test_aclnn_atan_grad.cpp \ -I${ASCEND_TOOLKIT_HOME}/include \ -L${ASCEND_TOOLKIT_HOME}/lib64 \ -lacl_op_compiler -lascendcl \ -Wl,-rpath,${ASCEND_TOOLKIT_HOME}/lib64
  1. 运行:./test_aclnn_atan_grad

示例程序的关键步骤依次为:aclInitaclrtSetDevice初始化 ACL →aclrtMalloc在 Device 侧分配内存并通过aclrtMemcpy上传 x、dy →aclCreateTensor构造 ND 格式 aclTensor → 调用aclnnAtanGradGetWorkspaceSize获取 workspace 大小(逐元素算子通常为 0)→ 按需分配 workspace → 调用aclnnAtanGrad执行 →aclrtSynchronizeStream等待完成 → 结果拷回 Host。

精度验证方式

示例在 Host 侧实现了 CPU Golden 参考实现ComputeGolden,用 double 精度计算dy * (1.0 / (1.0 + x*x)),再与 NPU 输出逐元素比较最大相对误差(MARE),并以 fp32 阈值10 * 2^-13 ≈ 0.00122判定 PASS/FAIL。这为在自定义场景中验证算子精度提供了可复制的模板。

kernel 实现与精度设计

数据流与缓冲

kernel 类实现在 op_kernel/atan_grad.h,采用经典的CopyIn → Compute → CopyOut流水结构:

  • CopyIn:通过DataCopyPad配合DataCopyExtParamsblockLen为 uint32_t,规避DataCopyParams.blockLen仅 uint16_t、最大 65535 字节的限制)将 GM 上的 x、dy 搬入 UB;
  • Compute:在 UB 中完成核心计算;
  • CopyOut:将结果 dx 从 UB 写回 GM。

缓冲策略由模板参数BUFFER_MODE决定,BUFFER_NUM = BUFFER_MODE ? 2 : 1,即 0 为单缓冲、1 为双缓冲,通过TQueTBuf实现数据搬运与计算的异步流水。

按 dtype 分叉的精度方案

kernel 注释明确记录了"穿刺验证"得到的精度结论,三种数据类型采用不同计算路径:

  • fp32 路径:直接计算。Reciprocal(INTRINSIC 模式)精度不足(MERE≈2.8e-3),因此改用Div(dx, dy, tmp)一步完成dy/(1+x²),将 MERE 降至 1.19e-7;
  • fp16 / bf16 路径:升精度到 fp32 计算(ComputeUpcast)。先用Cast(CAST_NONE)将 x、dy 无损升到 fp32,在 fp32 下完成Mul → Adds → Div四步计算后,再用Cast(CAST_RINT)(银行家舍入)降回原精度。fp16 直接使用 Reciprocal 时 MERE≈1.07e-3 超阈值,必须升精度;bf16 采用Cast(CAST_NONE) + fp32 计算 + Cast(CAST_RINT)后 MERE=1.091e-3 达标。

对应地,Process中用if constexpr (std::is_same_v<T, float>)在编译期分派:fp32 走Compute,其余走ComputeUpcast

模板组合与 kernel 入口

模板参数在 op_kernel/atan_grad_tiling_key.h 中声明,共 6 个组合:fp16×SB、fp16×DB、fp32×SB、fp32×DB、bf16×SB、bf16×DB。kernel 入口 op_kernel/atan_grad.cpp 通过REGISTER_TILING_DEFAULTGET_TILING_DATA_WITH_STRUCT读取 tiling 参数,实例化NsAtanGrad::AtanGrad<D_T_X, BUFFER_MODE>后执行InitProcess

Tiling 多核切分策略

Tiling 逻辑集中在 op_host/atan_grad_tiling.cpp,核心步骤可概括为:

  1. 获取平台信息:通过PlatformAscendC取得 AIV Core 数量coreNum与 UB 大小ubSize
  2. 多核切分blockFactor = CeilAlign(CeilDiv(totalNum, coreNum), ubBlockSize),即把总元素数按核心数均分并对齐到 DMA 最小粒度(32B / sizeof(T)),实际使用核数为CeilDiv(totalNum, blockFactor)
  3. 双缓冲决策useDoubleBuffer = (totalNum > MIN_SPLIT_THRESHOLD) ? 1 : 0,阈值常量MIN_SPLIT_THRESHOLD = 1024,元素超过 1024 启用双缓冲以隐藏搬运延迟;
  4. UB 切分ubFactor = FloorAlign(FloorDiv(ubSize / typeSize, bufferNum), ubBlockSize)。注意 fp32 与 fp16/bf16 的bufferNum不同——fp32 直接计算只需 4(单缓冲)/7(双缓冲)个 buffer,而 fp16/bf16 升精度路径需要 xFp32、dyFp32、tmp、dxFp32 共 4 个 fp32 临时 buffer,因此为 7(单缓冲)/10(双缓冲);ubFactor下限被钳制为ubBlockSize以保证 CopyIn 正常;
  5. workspace 置 0:逐元素算子无需额外 workspace,SetWorkspacecurrentWorkspace[0]置 0(这也是示例中 workspaceSize 通常为 0 的根因);
  6. 模板选择ASCENDC_TPL_SEL_PARAM(context, dTypeX, useDoubleBuffer)根据 dtype × 缓冲模式实例化对应 kernel 模板。

TilingData 结构体定义在 op_kernel/atan_grad_tiling_data.h,包含三个字段:totalNum(总元素数量)、blockFactor(每核负责的元素数量)、ubFactor(每次 UB 循环处理的元素数量)。

算子原型注册

op_host/atan_grad_def.cpp 通过OP_ADD(AtanGrad)注册算子原型:

  • 三个张量(x、dy、dx)均为REQUIRED,dtype 限定DT_FLOAT16 / DT_FLOAT / DT_BF16,Format 限定 ND,且均声明AutoContiguous
  • OpAICoreConfig开启DynamicCompileStaticFlag(true)DynamicRankSupportFlag(true)DynamicShapeSupportFlag(true),并关闭NeedCheckSupportFlag,声明PrecisionReduceFlag(true)
  • 通过AddConfig将算子绑定到 ascend910b 与 ascend950 两个平台;
  • 构建入口 op_host/CMakeLists.txt 中的add_modules_sources(OPTYPE atan_grad ACLNNTYPE aclnn)将该算子的 op_type 与 aclnn 接口一并纳入模块编译。

总结

AtanGrad 是一个典型的逐元素反向梯度算子:数学上以dx = dy/(1+x²)完成 atan 的梯度回传,工程上则以"算子原型注册 → Tiling 多核/UB 切分 → kernel 模板实例化 → aclnn 两段式接口"的完整链路落地到 Ascend NPU。其实现中的两个亮点值得在开发其他梯度算子时复用:一是按 dtype 分叉的精度策略(fp32 用 Div 替代 Reciprocal、fp16/bf16 升精度计算 + 银行家舍入回降);二是确定性、零 workspace 的逐元素设计,配合[4, 8]形状的 CPU Golden 精度比对示例,可作为快速验证与二次开发的直接参考。

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

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

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

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

立即咨询