
CANN ops-nn 算子开发实战aclnnGeluBackward 两段式接口实现 Gelu 反向传播【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本篇技术指南以 CANN ops-nn 开源仓库中 aclnnGeluBackward 算子文档 为核心系统讲解 Gelu 激活函数反向传播算子的数学原理、两段式 API 调用流程、参数约束与完整调用示例。结合 activation/gelu_grad 目录下的源码、配置与测试用例读者可以掌握在 Ascend NPU 上通过 aclnn 接口完成 Gelu 梯度计算的完整实战方案。产品支持情况aclnnGeluBackward 算子在不同硬件产品上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持Atlas 训练系列产品支持从源码看算子注册文件 gelu_grad_def.cpp 中通过this-AICore().AddConfig(ascend950, aicoreConfig)与AddConfig(ascend350, aicoreConfig)为 Ascend 950 与 Ascend 350 两个 SoC 版本注册了 AICore 计算配置op_host/config 目录下同时提供了ascend950与ascend350两个平台的二进制算子描述文件与文档中Atlas A3 系列对应 350 平台支持、Atlas 200I/500 A2 推理产品不支持的产品支持矩阵一致。功能说明aclnnGeluBackward 接口的功能是完成 aclnnGelu 的反向传播计算即根据输出侧梯度 gradOutput 与正向输入 self计算输入侧梯度 gradInput。数学原理Gelu 正向计算公式其中 x 可以为标量或者 Tensor$$ Gelu(x)x \cdot \Phi(x)x/2 \cdot [1erf(x/\sqrt{2})] $$其中 erf 的计算公式为$$ erf(x)\frac{2}{\sqrt \pi}\sum^{\infty}_{n0}{\frac{(-1)^n \cdot x^{2n1}}{n! \cdot (2n1)}} $$gradInput 和 gradOutput 的关系可以表示为$$ gradInput gradOutput \cdot (\frac{1}{2}\frac{1}{2} \cdot erf(\frac{x}{\sqrt2})\frac{x}{\sqrt{2\pi}} \cdot e^{-\frac{x^2}{2}}) $$即梯度由三部分组成常量项1/2、误差函数项erf(x/√2)/2以及高斯密度项x·e^(-x²/2)/√(2π)。Gelu 的近似计算公式为$$ Gelu(x)0.5x(1tanh(\sqrt{2/\pi}(x0.044715x^3))) $$实现层面的公式印证在 gelu_grad_dag.h 中可以看到算子内核Kernel对上述公式的逐指令实现内核定义了一组多项式拟合常量BETAN -1.595769121605730711759f、AN -0.0713548162726002527220f、A3 0.2140644488178007f、BETA 1.595769121605730711759f通过Mul平方、Axpy乘加、Exp指数、Adds、Div除法等矢量指令完成exp(x²·AN BETAN·x)形式的高斯核近似计算再与dy逐元素相乘得到梯度输出。整个计算在向量寄存器中按loopNum (count vl - 1) / vl分块循环执行其中vl为向量寄存器一次可容纳的元素数。而在算子API层gelu_grad.cpp 中的IsAiCoreSupport检查gradOutput、self、unused、gradInput四个 Tensor 的数据类型是否都属于DT_FLOAT / DT_FLOAT16 / DT_BF16支持列表随后通过ADD_TO_LAUNCHER_LIST_AICORE(GeluGrad, OP_INPUT(gradOutput, self, unused), OP_OUTPUT(gradInput))将计算任务下发到 AICore 执行当前无 AICPU 实现默认走 AICore 路径。函数原型每个算子分为两段式接口详见 两段式接口说明必须先调用aclnnGeluBackwardGetWorkspaceSize接口获取计算所需 workspace 大小以及包含了算子计算流程的执行器再调用aclnnGeluBackward接口执行计算。aclnnStatus aclnnGeluBackwardGetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, const aclTensor *gradInput, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnGeluBackward( void *workspace, uint64_t workspace_size, aclOpExecutor *executor, const aclrtStream stream)两段式接口的头文件声明位于 aclnn_gelu_backward.h其中还给出了 API 内部的计算基本路径DAGgradOutput与self先分别经过l0op::Contiguous转为连续布局与gradInput一起进入l0op::GeluGrad核心计算输出再经l0op::ViewCopy写回gradInput。aclnnGeluBackwardGetWorkspaceSize第一段接口参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutputaclTensor*输入求梯度时的权重即为了将正向输出的 tensor 变为标量所相乘的权重 tensorshape 需要和正向 self 的 shape 满足 broadcast 关系dtype 与 self 的 dtype 满足数据类型推导规则参见互推导关系支持空 TensorFLOAT、FLOAT16、BFLOAT16ND0-8√selfaclTensor*输入Gelu 的正向输入值shape 需要和 gradOutput 的 shape 满足 broadcast 关系dtype 与 gradOutput 的 dtype 满足数据类型推导规则参见互推导关系支持空 TensorFLOAT、FLOAT16、BFLOAT16ND0-8√gradInputaclTensor*输出backward 计算的输出为 GELU 正向入参的梯度值即对输入进行求导后的结果dtype 与 self 和 gradOutput 进行数据类型推导后的可转换的数据类型参见互转换关系一致shape 与 gradOutput 和 self 进行 broadcast 后的 shape 一致FLOAT、FLOAT16、BFLOAT16ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----补充说明对于Atlas 推理系列产品、Atlas 训练系列产品数据类型仅支持 FLOAT、FLOAT16。三个 Tensor 的数据格式均要求为 ND 格式。不过从 API 层实现看算子内部会自动对非连续输入执行Contiguous化因此三个 Tensor 均支持传入非连续stridedTensor这与文档中非连续 Tensor√的标注一致。返回值aclnnStatus返回状态码具体参见 aclnn 返回码。第一段接口会完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、gradInput 是空指针ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的数据类型和数据格式不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的维度关系不满足可 broadcast 原则ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的数据类型不满足数据类型推导规则形状推导的源码依据shape 校验与推导逻辑可以在 gelu_grad_infershape.cpp 中找到对于 Ascend950 / Ascend350 平台算子按三个输入dy、x、gelu做BroadcastShape三者必须满足可 broadcast 关系输出 shape 为广播后的 shape否则返回GRAPH_FAILED并提示 The shapes of dy, x and gelu must be broadcastable其他平台则将输入x的 shape 直接拷贝给输出。这解释了文档中gradOutput 与 self 的 shape 满足 broadcast 关系、gradInput 的 shape 为 broadcast 后的 shape的参数约束。与之对应op_host/config/ascend950/gelu_grad_binary.json 中三个输入dy、x、y与输出z的 shape 均标记为-2动态 shape并分别按bfloat16、float16、float32三种 dtype 生成了三个二进制算子描述format_match_mode为FormatAgnostic即算子不关心输入的具体布局统一按 ND 语义处理。aclnnGeluBackward第二段接口参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnGeluBackwardGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值aclnnStatus返回状态码具体参见 aclnn 返回码。约束说明确定性计算aclnnGeluBackward 默认确定性实现即相同输入多次执行结果一致便于调试与结果复现可进一步参考 确定性计算 说明。调用示例示例代码如下取自文档完整可编译工程可参考 examples/test_aclnn_gelu_backward.cpp编译与执行过程详见 编译与运行样例#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_gelu_backward.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 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t selfShape {4, 2}; std::vectorint64_t gradOutputShape {4, 2}; std::vectorint64_t gradInputShape {4, 2}; void* selfDeviceAddr nullptr; void* gradOutputDeviceAddr nullptr; void* gradInputDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* gradOutput nullptr; aclTensor* gradInput nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat gradOutputHostData {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; std::vectorfloat gradInputHostData {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor( gradOutputHostData, gradOutputShape, gradOutputDeviceAddr, aclDataType::ACL_FLOAT, gradOutput); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(gradInputHostData, gradInputShape, gradInputDeviceAddr, aclDataType::ACL_FLOAT, gradInput); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnGeluBackward第一段接口 ret aclnnGeluBackwardGetWorkspaceSize(gradOutput, self, gradInput, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnGeluBackwardGetWorkspaceSize 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); } // 调用aclnnGeluBackward第二段接口 ret aclnnGeluBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnGeluBackward 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侧需要根据具体API的接口定义修改 auto size GetShapeSize(gradInputShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), gradInputDeviceAddr, size * sizeof(float), 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和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(gradOutput); aclDestroyTensor(self); aclDestroyTensor(gradInput); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(gradOutputDeviceAddr); aclrtFree(gradInputDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例代码流程解读示例完整覆盖了一次算子调用的标准生命周期共分为 7 个步骤资源初始化aclInit→aclrtSetDevice→aclrtCreateStream建立 Device/Stream 运行环境构造输入与输出通过aclrtMalloc在 Device 侧申请内存aclrtMemcpy将 Host 数据拷入再由aclCreateTensor以 ND 格式创建self、gradOutput、gradInput三个aclTensor示例 shape 均为{4, 2}即 8 个元素两段式调用先调用aclnnGeluBackwardGetWorkspaceSize获得workspaceSize与executor当workspaceSize 0时用aclrtMalloc申请 workspace再调用aclnnGeluBackward提交计算同步等待aclrtSynchronizeStream阻塞等待流中任务执行完毕取回结果用aclrtMemcpy将 Device 侧gradInput数据拷回 Host 并逐元素打印释放 TensoraclDestroyTensor释放三个aclTensor释放设备资源依次释放 device 内存、workspace、Stream并执行aclrtResetDevice与aclFinalize。示例中的gradOutput全部取1.0f此时输出即为 Gelu 函数对输入 x 的导数本身将其代入梯度公式可快速验证结果。测试与验证仓库为 aclnnGeluBackward 提供了完整的测试体系可用于验证算子正确性ST 测试executor_aclnnGeluBackward.py 以torch.ops.aten.gelu_backward(grad_output, self, approximatetanh)作为基准实现与 NPU 上的 aclnn 调用结果对比atk_aclnnGeluBackward.json 提供用例配置。由此可以确认本算子的近似模式approximatetanh与 PyTorch 的 tanh 近似 GELU 梯度语义保持一致UT 测试包括 op_api 单测、op_host 的 infershape 单测、tiling 单测 以及 op_kernel 单测配套数据生成脚本 gen_data.py算子底层内核入口 gelu_grad_apt.cpp 中gelu_grad内核接收dy、x、y、z、workspace、tiling六个参数基于BroadcastSch调度器配合 DAG 描述完成元素级计算并通过KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY)指定纯向量核AIV执行。总结aclnnGeluBackward 是 CANN ops-nn 中 Gelu 激活函数的反向传播算子采用先查 workspace、后执行计算的两段式接口设计。本文从数学公式出发结合 API 声明、算子定义、形状推导、内核实现 与 调用示例 等仓库资源完整梳理了该接口的调用流程、参数约束与底层执行链路。开发者可依据本文的示例代码在支持的产品如 Ascend 950、Atlas A2/A3 系列等上快速接入 Gelu 梯度计算并借助仓库内置的 ST/UT 测试框架进行正确性验证。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考