【昇腾CANN训练营】激活函数GELU算子开发学习

举报
黄生 发表于 2025/07/30 21:49:21 2025/07/30
【摘要】 GELU简介当我们谈论神经网络中的激活函数时,可能会听到一些专业名词,比如Sigmoid、ReLU,或者GELU。这些名字听起来或许有些高深,但它们的核心思想其实可以用浅显的道理来解释。早期的神经网络常用Sigmoid函数作为激活函数。它的曲线平滑,输出范围在0到1之间,非常适合模拟概率。但问题是,Sigmoid在输入值较大或较小时,梯度会变得非常小,导致训练速度极慢,这就是所谓的“梯度消失...

GELU简介

当我们谈论神经网络中的激活函数时,可能会听到一些专业名词,比如Sigmoid、ReLU,或者GELU。这些名字听起来或许有些高深,但它们的核心思想其实可以用浅显的道理来解释。

早期的神经网络常用Sigmoid函数作为激活函数。它的曲线平滑,输出范围在0到1之间,非常适合模拟概率。但问题是,Sigmoid在输入值较大或较小时,梯度会变得非常小,导致训练速度极慢,这就是所谓的“梯度消失”问题。

为了解决这个问题,ReLU(Rectified Linear Unit)登场了。它的规则很简单:输入大于零时直接输出,小于零时输出为零。这种设计让梯度在正区间内保持稳定,大大加快了训练速度。但ReLU也有缺点,比如“神经元死亡”问题——一旦某些神经元的输入为负,它们可能永远无法被激活。

于是,研究者们开始寻找更优的解决方案,GELU(Gaussian Error Linear Unit)便是其中之一。GELU结合了ReLU的简洁性和概率分布的平滑性。它的核心思想是:让激活值不仅依赖于输入的正负,还依赖于输入的概率分布。简单来说,GELU会根据输入的大小,以一定的概率“决定”是否激活。这种设计既保留了ReLU的高效性,又避免了神经元死亡的问题。

GELU算子开发学习

首先要找到CANN-OPS算子仓中的GELU算子源码,开发者通常会注意到它在 cann-ops/src/math/gelu目录下。math目录不仅包含了GELU算子的实现,还存放着其他常用神经网络的算子。但问题是,为什么我们要从这个算子开始学习?相信专家会有更系统的训练方法,只是框架原理一类的概念并不是我们当前的重点。本文只是希望用尽量清晰的路径,帮助理解看似复杂的算子开发流程。

1.png

图-目录结构

接下来我们可以从学习顺序上梳理一下,如何高效掌握GELU算子的实现。在算子仓中,每个算子都会附带详细的说明文档,为开发者带来关键的知识养分。第一层目录下的 README.md就是那个最显眼的入口,而其他的说明文档则藏在更下层的目录中。学习GELU算子的最佳顺序可以分四步走:

  1. 阅读文档:先看 README.md,了解算子的功能、输入输出和约束条件,就像在动手前先看说明书。

  2. 掌握调用方式:学习 ACLNN的接口定义,搞清楚如何在框架中正确使用这个算子。

    在昇腾论坛的算子开发教学课程中,很多课程会先讲解算子实现,再学习调用方式。但这样的顺序就像让人先研究发动机原理,再学怎么开车——也许对初学者并不友好。这里我们采用"先用再究"的方式,试图能让复杂的技术概念更快变得清晰可触。

    完整的调用流程,本文不打算展开详述,这里只聚焦两个最关键的操作节点,也就是课程里常说的二段式调用:获取工作空间大小和调用GELU算子的执行接口。

    //获取工作空间大小
    ret = aclnnGeluGetWorkspaceSize(inputX, outputZ, &workspaceSize, &executor);
    // 执行算子
    ret = aclnnGelu(workspaceAddr, workspaceSize, executor, stream);
    复制
  3. 深入实现细节:拆解 host端(CPU侧逻辑)和 kernel端(NPU侧计算)的代码,理解数据流和计算逻辑。

    在GELU算子的Host端接口实现中,最关键的语句是分片计算函数 TilingFunc 和注册部分。

    
    // 设置分片参数
    tiling.set_totalLength(totalLength);
    tiling.set_ALIGN_NUM(ALIGN_NUM);
    tiling.set_tiling_size(tiling_size);
    tiling.set_block_size(block_size);
    tiling.set_aivNum(aivNum);
    tiling.set_core_size(core_size);
    tiling.set_core_remain(core_remain);
    
    // 设置计算核心数
    context->SetBlockDim(aivNum);
    
    //注册
    this->AICore().SetTiling(optiling::TilingFunc);
    复制

    在GELU算子的Kernel端计算逻辑中,计算核心浓缩在Compute()方法里,其调用Ascend C算子基础API进行运算。

    2.jpg

     图-GELU表达式

    //计算前加入输入tensor对float32的转换,计算后转回原类型
    if constexpr ( ! std::is_same_v<T, float32_t>)
    {
        LocalTensor<float> p1 = tmp1.Get<float>();
        LocalTensor<float> p2 = tmp2.Get<float>();
        Cast(p1, srcLocal, RoundMode::CAST_NONE, length);
        Cast(p2, srcLocal, RoundMode::CAST_NONE, length);
        Mul(p2, p1, p1, length);
        Mul(p2, p2, p1, length);
        Muls(p2, p2, (float)this->param1, length);
        Add(p2, p2, p1, length);
        Muls(p2, p2, (float)this->param2, length);
        Exp(p2,p2,length);
        Adds(p2, p2, (float)1, length);
        Div(p2,p1,p2,length);  
        Cast(dstLocal, p2, RoundMode::CAST_RINT, length);
    tmp1.FreeTensor(p1);
    tmp2.FreeTensor(p2);
    }
    else
    {
        Mul(dstLocal, srcLocal, srcLocal, length);
        Mul(dstLocal, dstLocal, srcLocal, length);
        Muls(dstLocal, dstLocal, (T)this->param1, length);
        Add(dstLocal, dstLocal, srcLocal, length);
        Muls(dstLocal, dstLocal, (T)this->param2, length);
        Exp(dstLocal,dstLocal,length);
        Adds(dstLocal, dstLocal, (T)1, length);
        Div(dstLocal,srcLocal,dstLocal,length);
    }  
    复制
  4. 验证与测试:通过 ST(System Test)测试用例,确保算子的正确性符合预期。

    在GELU算子的系统测试用例中,最关键的语句是计算GELU函数的预期结果数据的核心逻辑。

    golden = x / (1 + np.exp(-1.595769122 * (x + 0.0455399241 * x**3)))
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。