资讯详情

CANN ops-nn GeluGradV2 算子实战解析:GELU 反向梯度计算原理与 aclnnGeluBackwardV2 两段式调用

📅 2026/9/20 3:27:09 | 华诺云谱 👁 阅读
CANN ops-nn GeluGradV2 算子实战解析:GELU 反向梯度计算原理与 aclnnGeluBackwardV2 两段式调用
人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载本文以 CANN ops-nn 开源算子库中的 GeluGradV2 算子文档 为核心系统讲解该算子在 Atlas A2 训练/推理系列产品上完成 GELU 激活函数反向传播梯度计算的数学原理、输入输出参数约束、aclnn 两段式接口调用流程并结合仓库内 op_host 与 op_kernel 源码深入剖析 L2 接口封装、Tiling 切分与 AI Core Kernel 实现细节。读者读完可掌握aclnnGeluBackwardV2接口的完整调用方法与 GELU 梯度算子的底层计算路径。一、算子概述与产品支持情况GeluGradV2 是 CANN ops-nn 中用于求 GELUGaussian Error Linear Unit高斯误差线性单元函数梯度的反向算子通常与正向算子 GeluV2aclnnGeluV2 配对使用是 Transformer 类网络如 BERT、GPT 系列反向训练链路中常见的激活层梯度计算环节。该算子目前的产品支持情况如下产品是否支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品√从算子注册代码 gelu_grad_v2_def.cpp 可以看到算子内部通过AICore().AddConfig(ascend910b, aicoreConfig)配置 AICore 计算核适配并开启了动态编译DynamicCompileStaticFlag(true)、动态 shapeDynamicShapeSupportFlag(true)、动态 rankDynamicRankSupportFlag(true)等能力这意味着算子可以适配不同 shape 与 rank 的输入张量。二、数学原理GELU 正向公式与其梯度推导2.1 GELU 正向公式精确 erf 模式GELU 正向计算其中 x 可以为标量或者 Tensor定义为$$ Gelu(x)x \cdot \Phi(x)x/2 \cdot [1erf(x/\sqrt{2})] $$其中 $\Phi(x)$ 为标准正态分布的累积分布函数$erf$ 为误差函数Gauss error function其级数展开定义为$$ erf(x)\frac{2}{\sqrt \pi}\sum^{\infty}_{n0}{\frac{(-1)^n \cdot x^{2n1}}{n! \cdot (2n1)}} $$2.2 梯度计算公式对上述正向公式求导可以得到反向传播中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}}) $$即每个输入元素x处的局部梯度为 $\frac{1}{2}(1erf(x/\sqrt{2})) \frac{x}{\sqrt{2\pi}}e^{-x^2/2}$再乘以来自下游的gradOutput即得到gradInput。这正是 GeluGradV2 在approximatenoneerf 模式下要计算的表达式。2.3 GELU 近似公式tanh 模式为降低 erf 的计算开销业界常用 tanh 形式对 GELU 做近似近似正向公式为$$ Gelu(x)0.5x(1tanh(\sqrt{2/\pi}(x0.044715x^3))) $$当属性approximate取值为tanh时GeluGradV2 即使用上述近似公式对应的导数进行反向计算。两种模式在 kernel 侧被编译为不同的实现见下文源码解析。三、参数说明GeluGradV2 算子原生算子语义的输入、属性与输出如下表所示参数名输入/输出/属性描述数据类型数据格式x输入反向传播梯度计算所需的 GELU 正向输入值FLOAT、FLOAT16、BFLOAT16ND、FRACTAL_NZ、NC1HWC0dy输入与x具有相同的类型、格式和形状FLOAT、FLOAT16、BFLOAT16ND、FRACTAL_NZ、NC1HWC0approximate属性可选激活函数模式取值为none或tanhstr-z输出公式中的输出张量即 gradInputFLOAT、FLOAT16、BFLOAT16ND、FRACTAL_NZ、NC1HWC0对照算子定义源码 gelu_grad_v2_def.cpp可以印证以下几点实现细节approximate属性通过this-Attr(approximate).AttrType(OPTIONAL).String(none)声明属于可选属性默认值为none即未显式指定时走 erf 精确模式两个输入dy、x与输出z均声明了REQUIRED参数类型并支持FORMAT_NC1HWC0、FORMAT_ND、FORMAT_FRACTAL_NZ三种格式每种格式对应一组 FLOAT16/FLOAT/BF16 数据类型组合Shape 推导逻辑位于 gelu_grad_v2_infershape.cpp输出z的 shape 直接继承输入x的 shape*yShape *xShape因此梯度输出的形状与正向输入一致。注由于算子信息库op_def中approximate被声明为字符串属性aclnn 层接口详见下文使用char*形式传入而在 kernel 执行时该属性会通过 Tiling 阶段解析为具体的计算分支ISTANH1或ISNONE0。四、aclnn 两段式接口调用说明GeluGradV2 在仓库中对外暴露为Aclnn 模式调用样例代码位于 test_aclnn_gelu_backward_v2.cpp对应接口文档为 aclnnGeluBackwardV2 接口文档。与其他 CANN 算子一致aclnnGeluBackwardV2采用 两段式接口 设计第一段GetWorkspaceSize接口完成入参校验、算子流程编排并返回所需 workspace 大小与执行器第二段接口将 workspace、执行器与 stream 传入真正在 Device 侧启动计算。4.1 函数原型第一段接口获取 workspace 大小与执行器aclnnStatus aclnnGeluBackwardV2GetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, char *approximate, aclTensor *gradInput, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口执行计算aclnnStatus aclnnGeluBackwardV2( void* workspace, uint64_t workspace_size, aclOpExecutor* executor, const aclrtStream stream)4.2 第一段接口参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutputaclTensor*输入求梯度时的权重即为了将正向输出的 tensor 变为标量所相乘的权重 tensor公式中的 gradOutputshape 需要和正向 self 的 shape 满足 broadcast 关系dtype 与 self 的 dtype 满足数据类型推导规则参见互推导关系支持空 TensorFLOAT16、BFLOAT16、FLOAT32ND0-8√selfaclTensor*输入Gelu 的正向输入值公式中的 xshape 需要和 gradOutput 的 shape 满足 broadcast 关系dtype 与 gradOutput 的 dtype 满足数据类型推导规则支持空 TensorFLOAT16、BFLOAT16、FLOAT32ND0-8√approximatechar*输入计算使用的激活函数模式可配置为none或者tanh其中none代表使用 erf 模式tanh代表使用 tanh 模式----gradInputaclTensor*输出backward 计算的输出为 GELU 正向入参的梯度值即对输入进行求导后的结果公式中的 gradInputdtype 与 self 和 gradOutput 进行数据类型推导后的结果一致shape 与 gradOutput 和 self 进行 broadcast 后的 shape 一致支持空 Tensor 输入直接返回调用成功FLOAT16、BFLOAT16、FLOAT32ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程----4.3 返回码与错误场景第一段接口会完成入参校验返回码为 aclnnStatus具体参见 aclnn 返回码。出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、approximate、gradInput 是空指针ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的维度关系不满足可 broadcast 原则ACLNN_ERR_PARAM_INVALID161002gradOutput、self、gradInput 的数据类型不满足数据类型推导规则ACLNN_ERR_PARAM_INVALID161002approximate 的数值不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002shape 与 gradOutput 和 self 进行 broadcast 后的 shape 不一致这些校验规则在 aclnn_gelu_backward_v2.cpp 的CheckParams中有完整实现依次执行CheckNotNull空指针检查、CheckPromoteType类型推导检查推导结果必须与 gradInput 一致、CheckShapebroadcast 后的 shape 必须等于 gradInput 的 shape、CheckAttrValueapproximate 仅允许none或tanh。4.4 第二段接口参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnGeluBackwardV2GetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream五、Aclnn 模式完整调用示例仓库在 examples/test_aclnn_gelu_backward_v2.cpp 提供了可直接参考的完整示例。以下代码演示以 shape 为{4, 2}的 FLOAT 张量为例通过 aclnn 两段式接口计算gradInput完整编译与运行步骤可参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_gelu_backward_v2.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手册 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::vectorint gradOutputHostData {1, 1, 1, 1, 1, 1, 1, 1}; std::vectorint gradInputHostData {0, 0, 0, 0, 0, 0, 0, 0}; char approximate[] tanh; 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 aclnnGeluBackwardV2GetWorkspaceSize(gradOutput, self, approximate, gradInput, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnGeluBackwardV2GetWorkspaceSize 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 aclnnGeluBackwardV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnGeluBackwardV2 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(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; }示例中approximate取值为tanh即使用 2.3 节的 tanh 近似模式计算梯度。若需使用 erf 精确模式将其改为none即可。六、源码级实现解析从 aclnn 接口到 AI Core Kernel6.1 L2 接口层参数校验、broadcast 与隐式类型转换aclnnGeluBackwardV2GetWorkspaceSize的实现位于 aclnn_gelu_backward_v2.cpp其核心执行流程如下参数检查通过CheckParams完成空指针、dtype 推导、broadcast shape、approximate 属性四项校验任一失败直接返回对应错误码空 Tensor 处理若self或gradOutput为空 Tensor直接置*workspaceSize 0并返回成功类型推导op::PromoteType(gradOutput-GetDataType(), self-GetDataType())得到隐式提升后的计算类型promoteType随后用l0op::Cast将两个输入统一转换到该类型连续性处理对非连续输入调用l0op::Contiguous转连续Broadcast调用BroadcastInferShape计算两个输入的 broadcast 结果 shape再通过BroadcastTensor对需要广播的输入执行l0op::BroadcastTo该函数封装在GetShapel0op::BroadcastTo中核心计算调用 L0 层算子l0op::GeluGradV2(gradOutputCasted, selfCasted, approximate, executor)声明见 gelu_grad_v2.h完成梯度计算输出视图还原如果出参 gradInput 是非连续 Tensor通过l0op::ViewCopy将计算出的连续结果写回非连续视图最后通过uniqueExecutor-GetWorkspaceSize()汇总整个计算图所需的 workspace 大小并返回执行器。第二段接口aclnnGeluBackwardV2则直接调用CommonOpExecutorRun(workspace, workspace_size, executor, stream)完成 Device 侧执行属于固定框架写法。6.2 Tiling 层基于 UB 容量与 core 数的数据切分Tiling 逻辑位于 gelu_grad_v2_tiling.cpp整体分为四步获取平台信息GetPlatformInfo通过platform_ascendc::PlatformAscendC获取当前平台的 UB 内存大小ubSize与 AIV 核数coreNum解析 shape 与属性GetShapeAttrsInfo解析输入元素总数inputNum、数据类型字节数typeLength并按 256 字节BLOCK_SIZE对齐得到inputLengthAlgin同时根据approximate属性值设置haveTanhnone→0tanh→1并根据数据类型 × 是否 tanh × 是否开启双缓冲组合选择 UB 内可容纳的缓冲区份数组合条件单缓冲份数双缓冲份数FLOAT tanh7UB_NUM_F32_TANH_ONE10UB_NUM_F32_TANH_TWOFLOAT none5UB_NUM_F32_ONE8UB_NUM_F32_TWOFLOAT16/BF16 none11UB_NUM_F16_BF16_ONE14UB_NUM_F16_BF16_TWOFLOAT16/BF16 tanh14UB_NUM_F16_BF16_TANH_ONE18UB_NUM_F16_BF16_TANH_TWO其中bufferOpen1表示开启双缓冲double buffer当数据量较小时会自动退化为单缓冲bufferOpen0以节省 UB 空间workspace 申请GetWorkspaceSize通过框架获取一块 workspace大小为用户 workspace 与系统 API workspace 之和core 负载均衡CalculateCoreBlockNums将数据按 core 数均分计算小核smallCoreDataNum、大核bigCoreDataNum、尾块tailBlockNum等切分参数并写入GeluGradV2TilingData结构定义见 gelu_grad_v2_tiling_data.h最后通过context-SetBlockDim(coreNum)与context-SetTilingKey(tilingKey)设置并行度与 kernel 分支。6.3 Kernel 层erf 模式与 tanh 模式的计算实现Kernel 入口位于 gelu_grad_v2.cpp通过TILING_KEY_IS(0)/TILING_KEY_IS(1)分发到两个不同的算子类TilingKey0noneerf 模式NsGeluGradV2::KernelGeluGradV2TilingKey1tanhtanh 模式NsGeluGradV2Tanh::KernelGeluGradV2Tanh。两个 Kernel 类的完整实现位于 gelu_grad_v2.h其计算结构有以下特点erf 模式的工程实现KernelGeluGradV2::Compute为避免在 Vector 单元上直接计算高开销的 erfkernel 将梯度公式重写为数值稳定的多项式近似——先取符号位构造符号分量再对|x|用多项式有理函数逼近 erf 相关项配合exp(-x²/2)与Mins(x, 30.0)的截断保护防止大数值溢出最终完成gradInput gradOutput * (0.5 0.5*erf(x/√2) x/√(2π)·e^{-x²/2})的整体计算。FLOAT 输入全程以 float 精度计算FLOAT16/BF16 输入则先Cast到 float 计算、结果再Cast回原类型FLOAT16 用CAST_NONEBF16 用CAST_RINT四舍五入。tanh 模式的工程实现KernelGeluGradV2Tanh::Compute基于 2.3 节近似公式的导数先计算exp后通过1/(1e^(-·))构造 sigmoid 结构再组合多项式项并使用CompareSelect掩码剔除数值异常NaN 防护后与dy相乘。流水线结构Process两个 Kernel 均采用 CopyIn → Compute → CopyOut 的 Vector 流水范式配合TQue输入队列与TBuf临时缓冲区默认开启双缓冲DOUBLE_BUFFER_NUM2当 Tiling 判定bufferOpen0时退化为单缓冲SINGLE_BUFFER_NUM1。最后一个 tile 以tailDataNum尾块数据量单独处理保证任意元素个数都能被正确覆盖。6.4 测试用例验证仓库为 GeluGradV2 提供了分层测试op_api 层test_gelu_backward_v2.cpp 验证 aclnn 接口调用与数值正确性op_host 层test_gelu_grad_v2_tiling.cpp 验证 Tiling 数据生成op_kernel 层test_gelu_grad_v2.cpp 验证 Kernel 计算。七、约束说明与注意事项接口文档 aclnnGeluBackwardV2 明确aclnnGeluBackwardV2默认确定性实现确定性计算的相关约定可参考 确定性计算说明approximate属性仅支持none与tanh两个取值传其他字符串会在第一段接口校验阶段返回ACLNN_ERR_PARAM_INVALID错误码 161002相关校验见 aclnn_gelu_backward_v2.cpp 的CheckAttrValuegradInput的 shape 必须等于gradOutput与self经 broadcast 后的 shapedtype 必须等于两者类型推导promote后的结果否则同样返回ACLNN_ERR_PARAM_INVALID输入输出张量的 shape 维度范围为 0-8支持非连续 Tensor接口内部会自动转连续计算支持空 Tensor 输入第一段接口直接返回成功不触发实际计算当前算子注册的 AICore 配置面向ascend910b平台即文档所述 Atlas A2 训练/推理系列产品。八、贡献说明贡献者贡献方贡献算子贡献时间贡献内容ilovescrapy个人开发者GeluGradV22026GeluGradV2 算子适配开源仓该算子从正向 GeluV2 的反向需求出发完整覆盖了 op_def 算子信息注册、InferShape、Tiling、AI Core Kernel 与 aclnn L2 接口封装为社区在 NPU 上训练含 GELU 激活的网络提供了可直接使用的梯度计算能力。赞分享人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载相关推荐CANN ops-nn 算子库 aclnnGeluBackwardV2 接口详解GELU 反向梯度计算的两段式调用指南CANN ops nn 算子库 aclnnGeluBackwardV2 接口详解GELU 反向梯度计算的两段式调用指南 本文围绕 CANN ops nn 神经人工智能算子库深度学习CANNAscendCANN ops-nn 算子实战GLUGrad 反向梯度算子原理与 aclnnGluBackward 两段式调用指南CANN ops nn 算子实战GLUGrad 反向梯度算子原理与 aclnnGluBackward 两段式调用指南 本篇技术指南围绕 CANN ops nn人工智能算子库深度学习CANNAscendCANN ops-nn 算子接口解析aclnnEluBackward 两段式调用与 ELU 反向梯度计算CANN ops nn 算子接口解析aclnnEluBackward 两段式调用与 ELU 反向梯度计算 本篇技术指南聚焦 CANN 神经网络算子库 ops人工智能算子库深度学习CANNAscend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。