
人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载导读GeluGrad 是 CANN ops-nn 神经网络算子库中用于计算 GELUGaussian Error Linear Unit激活函数梯度的反向算子服务于 Transformer、BERT 等模型在 NPU 上的训练反向传播环节。本文以仓库中 GeluGrad 算子说明 与 aclnnGeluBackward 接口文档 为主体结合算子注册、Shape 推导、Tiling 切分与 AI Core Kernel 实现源码完整讲解该算子的数学原理、原型约束、两段式 aclnn 接口调用流程、错误码语义以及可运行的完整示例帮助读者掌握在 Atlas A2 训练/推理系列产品上开发与调试 GeluGrad 反向算子的实战方法。一、算子概述与数学原理GeluGrad算子OpTypeGeluGrad用于计算 GELU 激活函数的梯度。它接收反向传播中上游传来的梯度dy、正向阶段的输入x与输出y计算并输出对输入x的梯度z。从数学上看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即对输入x的梯度与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}}) $$在实际工程实现中直接计算erf开销较大业界常采用 tanh 近似形式。GELU 近似计算公式为$$ Gelu(x)0.5x(1tanh(\sqrt{2/\pi}(x0.044715x^3))) $$从 AI Core Kernel 实现 的Compute逻辑看NPU 端 Kernel 正是基于 tanh 近似公式的等价变换展开计算通过COEFF0 -0.0713548162726002527220f、COEFF1 -1.595769121605730711759f、COEFF2 0.2140644488178007f等一组常量利用Mul、Muls、Adds、Exp、Div等 Vector 指令组合出近似erf的结果最终乘以dy得到梯度输出。二、算子原型信息GeluGrad 算子在 算子定义文件 中通过OpDef注册原型如下算子类型OpTypeGeluGrad算子输入dytensorfloat32 / float16 / bfloat16ND算子输入xtensorfloat32 / float16 / bfloat16ND算子输入ytensorfloat32 / float16 / bfloat16ND算子输出ztensorfloat32 / float16 / bfloat16ND三个输入dy、x、y均为必选参数ParamType(REQUIRED)数据类型统一限定为DT_FLOAT、DT_FLOAT16、DT_BF16数据格式仅支持FORMAT_ND未知 Shape 场景同样限定 ND 格式。算子通过this-AICore().AddConfig(ascend910b).AddConfig(ascend310b)声明支持 ascend910b 与 ascend310b 两类 AI Core 配置。三、约束与限制在使用 GeluGrad 算子时需要遵守以下约束输入dy、x、y与输出z的数据类型仅支持float32、float16、bfloat16三种输入与输出的数据格式仅支持ND产品支持情况Atlas A2 训练系列产品详见昇腾产品形态说明以及配套 API 文档中补充的Atlas A2 训练系列产品 / Atlas A2 推理系列产品从 Tiling 逻辑见下文看算子目前为 AI CoreVector 核实现Kernel 侧针对 ascend910b 与 ascend310b 使用了两套计算分支。四、aclnnGeluBackward 两段式接口详解GeluGrad 的对外调用接口为aclnnGeluBackward遵循 CANN 算子库通用的两段式接口模式先调用aclnnGeluBackwardGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器executor再调用aclnnGeluBackward执行计算。第一段接口原型aclnnStatus aclnnGeluBackwardGetWorkspaceSize( const aclTensor *gradOutput, // 上游梯度正向输出所乘权重 const aclTensor *self, // GELU 正向输入 const aclTensor *gradInput, // 输出对正向入参的梯度 uint64_t *workspaceSize,// 输出Device 侧所需 workspace 大小 aclOpExecutor **executor) // 输出算子执行器第二段接口原型aclnnStatus aclnnGeluBackward( void *workspace, // Device 侧 workspace 内存地址 uint64_t workspace_size, // 由第一段接口返回的 workspace 大小 aclOpExecutor *executor, // 第一段接口返回的执行器 const aclrtStream stream) // 任务执行 Stream4.1 aclnnGeluBackwardGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutputaclTensor*输入求梯度时的权重即为了将正向输出的 tensor 变为标量所相乘的权重 tensorshape 需与正向 self 的 shape 满足 broadcast 关系dtype 与 self 满足数据类型互推导关系支持空 TensorFLOAT、FLOAT16、BFLOAT16ND0-8√selfaclTensor*输入GELU 的正向输入值shape 需与 gradOutput 满足 broadcast 关系dtype 与 gradOutput 满足互推导关系支持空 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。三个 aclTensor 均支持非连续 Tensor接口内部会自动做 Contiguous 处理详见下文源码分析。4.2 aclnnGeluBackward 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口aclnnGeluBackwardGetWorkspaceSize获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream4.3 返回值与错误场景两段接口均返回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 的数据类型不满足数据类型推导规则五、源码级实现原理5.1 Host 侧 aclnn 接口实现在 aclnn_gelu_backward.cpp 中aclnnGeluBackwardGetWorkspaceSize按以下流程组织创建执行器通过CREATE_EXECUTOR()创建aclOpExecutor唯一指针参数校验CheckParamsCheckNotNull检查三个 tensor 是否为空指针失败返回ACLNN_ERR_PARAM_NULLPTRCheckPromoteType基于CheckPromoteTypeGeluBackward检查 gradOutput 与 self 的 dtype 能否推导出统一类型推导结果需与 gradInput 一致支持列表为{DT_FLOAT, DT_FLOAT16, DT_BF16}失败返回ACLNN_ERR_PARAM_INVALIDCheckShape通过OP_CHECK_BROADCAST_AND_INFER_SHAPE(gradOutput, self, broadcastShape)校验维度不超过MAX_SUPPORT_DIMS_NUMS且 broadcast 后的 shape 必须与 gradInput 的 shape 完全一致空 Tensor 处理若self-IsEmpty()或gradOutput-IsEmpty()为真直接返回workspaceSize 0并结束避免无效计算前置数据处理对 gradOutput、self、gradInput 依次执行l0op::Contiguous处理非连续 Tensor对 gradInput 执行l0op::Cast转为推导出的 promoteType按平台分支针对 ASCEND910 ~ ASCEND910E 平台先 Cast 再直接调用l0op::GeluGrad(gradOutputCasted, selfCasted, gradOutputCasted, gradInputCasted, executor)其他平台含 A2 系列先通过BroadcastTensor判断是否需要l0op::BroadcastTo将 gradOutput、self 广播到统一 shape再 Cast、调用l0op::GeluGrad计算结果Cast回 gradInput 的原始 dtype 后用l0op::ViewCopy回写到可能非连续的输出 tensor返回 workspace 大小*workspaceSize uniqueExecutor-GetWorkspaceSize()后释放执行器指针。第二段接口aclnnGeluBackward则通过CommonOpExecutorRun(workspace, workspace_size, executor, stream)调用框架能力完成计算。5.2 L0 算子与 AI Core 调度在 l0 算子实现 gelu_grad.cpp 中通过OP_TYPE_REGISTER(GeluGrad)注册 L0 算子GeluGradAICORE_DTYPE_SUPPORT_LIST {DT_FLOAT, DT_FLOAT16, DT_BF16}限定 AI Core 支持的数据类型GeluGrad接收四个参数gradOutput、self、unused第三个输入y在 L0 层默认传gradOutput占位、gradInput输出目前无 AI CPU 实现默认全部走 AI Core 路径ADD_TO_LAUNCHER_LIST_AICORE若 dtype 不在支持列表则返回nullptr。5.3 Kernel 侧实现与双版本计算分支Kernel 入口位于 gelu_grad.cpp通过 Tiling Key 区分两种计算方式TILING_KEY_0ascend910b模板参数Is0versionNum trueTILING_KEY_1ascend310b模板参数Is0versionNum false。核心类KernelGeluGrad见 gelu_grad.h采用经典的三段流水结构CopyIn将 Global Memory 中的x、dy按 tile 搬入 Local MemoryVector 输入队列inQueueX、inQueueDYCompute在 Vector 核上完成梯度计算。float16/bfloat16 输入会先Cast到 float32 计算再Cast回原类型RoundMode::CAST_RINTfloat32 输入则直接在原类型上运算中间结果存放在tmp1、tmp2、tmp3、funBuffer、tmpBuffer等VECCALC缓冲区CopyOut将结果z从输出队列写回 Global Memory。整个计算使用双缓冲BUFFER_NUM 2同一 core 上按tileNum循环迭代最后一轮处理tailDataNum尾数数据多 core 间通过bigCoreDataNum/smallCoreDataNum均衡切分数据量core 数少于tailBlockNum的 core 分配更多数据。5.4 Tiling 策略在 gelu_grad_tiling.cpp 中Tiling 函数根据平台信息计算切分参数读取 UBUnified Buffer大小与 core 数仅当 SoC 版本为 ASCEND910B 或 ASCEND310B 且输入为 BF16 时才允许继续否则报socVersion errorversionNum标记平台型号310B 为 1同时设置对应 Tiling Key依据数据类型字节长度确定每个 tile 容纳的元素数float32 场景ubDataNumber 8 versionNum再以(ubSize / blockSize) / ubDataNumber估算 tileBlockNum最终得出tileDataNum等切分信息并写入GeluGradTilingData。5.5 Shape 推导gelu_grad_infershape.cpp 中的InferShapeGeluGrad逻辑非常简单输出z的 shape 直接取输入x的 shape*y_shape *x1_shape。六、完整调用示例以下示例来自 examples/test_aclnn_gelu_grad.cpp仓库 UT 测试的完整可运行版本位于 tests/ut/op_api/test_aclnn_gelu_grad.cpp演示通过 aclnn 接口调用 GeluGrad 算子的完整流程#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); 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); 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); 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]; } *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 手册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. 构造输入与输出 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, 1, 1, 1, 1, 1, 1, 1}; std::vectorfloat gradInputHostData {0, 0, 0, 0, 0, 0, 0, 0}; 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两段式 uint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段接口获取 workspace 大小与执行器 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); } // 第二段接口执行计算 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 侧 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(aclnnGeluBackward result[%ld] is: %f\n, i, resultData[i]); } // 6. 释放 aclTensor aclDestroyTensor(gradOutput); aclDestroyTensor(self); aclDestroyTensor(gradInput); // 7. 释放 device 资源 aclrtFree(selfDeviceAddr); aclrtFree(gradOutputDeviceAddr); aclrtFree(gradInputDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例中的关键要点示例数据self {0,1,2,...,7}shape 4x2gradOutput全 1shape 4x2gradInput初始化为 0。由于x0处 GELU 梯度恰好为 0.5读者可以据此核对输出结果非连续 Tensor 支持接口内部会自动处理非连续输入l0op::Contiguous示例中的CreateAclTensor已按连续 stride 构造更复杂的非连续场景可直接传入接口验证broadcast 支持gradOutput 与 self 的 shape 满足 broadcast 关系 时接口会在内部执行l0op::BroadcastTo输出 shape 为二者广播后的 shapeUT 测试 test_aclnn_gelu_grad.cpp 中Ascend950_gelu_backward_testcase_001_normal_float32即覆盖了 gradOutput 为{1,5}、self 为{2,5}的广播场景空 Tensor当 self 或 gradOutput 为空 Tensor 时第一段接口直接返回workspaceSize 0不会发起实际计算。七、运行验证仓库 README 给出了算子的运行验证命令bash build.sh --run_example gelu_grad eager cust --vendor_namecustom --experimental该命令会编译并运行 GeluGrad 算子的示例eager模式、cust厂商、--experimental标记 experimental 目录下的算子。仓库中还提供了以下可直接查看/复用的示例与测试文件目录描述examples/test_aclnn_gelu_grad.cpp通过 aclnn 接口调用 GeluGrad 算子的完整示例tests/ut/op_api/test_aclnn_gelu_grad.cppaclnn 接口层 UT覆盖 float32 常规用例、Ascend950 广播用例与 GetWorkspaceSize 校验tests/ut/op_host/test_gelu_grad_infershape.cppHost 侧 Shape 推导 UT提示上述测试与示例的编译、执行依赖 CANN 的算子开发编译环境build.sh 位于仓库根目录 build.sh 所在目录 与 experimental 目录机制配合具体编译运行流程可参考仓库的编译与运行示例文档。八、总结GeluGrad 算子是 ops-nn 中面向 Atlas A2 系列产品提供的 GELU 梯度计算算子具备以下工程特征接口规范采用标准的两段式 aclnn 接口GetWorkspaceSize阶段完成参数校验、非连续 Tensor 处理、类型提升与广播第二阶段通过执行器在指定 Stream 上异步执行数值实现Kernel 基于 tanh 近似公式以一组常量系数 Vector 指令组合逼近 erf 项float16/bfloat16 输入提升到 float32 计算以保证精度平台适配通过 Tiling Key 区分 ascend910b / ascend310b 两套计算分支多 core 均衡切分数据并以双缓冲流水隐藏访存开销可验证性仓库同时提供可直接运行的 示例代码 与覆盖常规、广播、错误码等场景的 UT 测试。对于需要在 NPU 上实现 Transformer 类模型训练反传、或参考该算子结构自行开发其他激活函数梯度算子的开发者GeluGrad 从数学公式、算子注册、Shape 推导、Tiling 到 Kernel 的完整链路是一个简洁而典型的参考范本。赞分享人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载相关推荐CANN ops-nn GeluGrad 算子深度解析GELU 激活反向传播的 NPU 实现与 aclnnGeluBackward 两段式调用指南CANN ops nn GeluGrad 算子深度解析GELU 激活反向传播的 NPU 实现与 aclnnGeluBackward 两段式调用指南 本文以 C人工智能算子库深度学习CANNAscendCANN ops-nn SwishGrad 反向算子深度解析从数学公式到 aclnnSwishBackward 调用实战CANN ops nn SwishGrad 反向算子深度解析从数学公式到 aclnnSwishBackward 调用实战 SwishGradSwish 激活人工智能算子库深度学习CANNAscendCANN ops-nn HardSigmoidGrad 反向算子深度解析ACLNN 接口、数学原理与 NPU 实现CANN ops nn HardSigmoidGrad 反向算子深度解析ACLNN 接口、数学原理与 NPU 实现 HardSigmoidGrad 是 CAN人工智能算子库深度学习CANNAscend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考