CANN ops-nn 算子解析:aclnnSiluBackward 两段式接口实现 SiLU 反向传播梯度计算
2026/9/20 20:11:25 网站建设 项目流程
  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-nn
点击查看免费下载

本篇文章以 CANN 神经网络算子库 ops-nn 中 aclnnSiluBackward 官方文档 为主体,结合仓库内算子定义、Host 侧 API 实现、AscendC Kernel 与 Tiling 源码,系统讲解如何通过 aclnnSiluBackwardGetWorkspaceSize / aclnnSiluBackward 两段式接口在 Atlas A2 系列产品上完成 SiLU(Swish)激活函数反向传播的梯度计算。读完本文,你将掌握该算子的数学原理、参数约束、错误码语义、完整可编译的调用示例,以及从 aclnn API 到 AI Core Kernel 的底层实现链路。

产品支持情况

aclnnSiluBackward 当前支持的产品如下:

产品是否支持
Atlas A2 训练系列产品 / Atlas 800I A2 推理产品

功能说明与计算公式

aclnnSiluBackward 是 aclnnSilu 激活函数(即 SiLU,也称 Swish,β=1)的反向传播接口,用于计算 SiLU 激活函数的梯度。它在网络的反向阶段根据正向输入x与上游传回的梯度gradOutput计算输入侧的梯度gradInput,是训练 SiLU 激活层必不可少的算子。

  • SiLU 函数公式

$$ y = x \cdot \sigma(x) $$

  • SiLU 函数的导数

$$ \sigma(x) = \frac{1}{1 + e^{-x}} $$

$$ y' = \sigma(x) \cdot (1 + x \cdot (1 - \sigma(x))) $$

$$ gradInput = gradOutput \cdot \sigma(x) \cdot (1 + x \cdot (1 - \sigma(x))) $$

其中 $\sigma(x)$ 为 Sigmoid 函数,$y$ 为 SiLU 函数,$y'$ 为 SiLU 函数的导数,gradOutput为反向传播中上游传来的梯度,gradInput为算子的最终输出。

从仓库的 Kernel 实现(experimental/activation/silu_grad/op_kernel/silu_grad.h)可以看到,该公式在 Vector 单元上被拆解为如下指令序列(float 路径):

// SiLU 梯度公式: dx = dy * sigmoid(x) * (1 + x * (1 - sigmoid(x))) Muls(tmp1, xLocal, -1.0f, processDataNum); // tmp1 = -x Exp(tmp1, tmp1, processDataNum); // tmp1 = e^(-x) Adds(tmp1, tmp1, 1.0f, processDataNum); // tmp1 = 1 + e^(-x) Duplicate(dxLocal, 1.0f, processDataNum); // dx = 1 Div(tmp2, dxLocal, tmp1, processDataNum); // tmp2 = sigmoid(x) Sub(tmp1, dxLocal, tmp2, processDataNum); // tmp1 = 1 - sigmoid(x) Mul(tmp1, xLocal, tmp1, processDataNum); // tmp1 = x * (1 - sigmoid(x)) Adds(tmp1, tmp1, 1.0f, processDataNum); // tmp1 = 1 + x * (1 - sigmoid(x)) Mul(tmp1, tmp2, tmp1, processDataNum); // tmp1 = sigmoid(x) * (1 + x * (1 - sigmoid(x))) Mul(dxLocal, dyLocal, tmp1, processDataNum); // dx = dy * tmp1

对于 FLOAT16 / BF16 输入,Kernel 会先将数据Cast到 float32 计算,最后再以CAST_RINT舍入模式转回原类型输出,以保证中间过程精度(见 silu_grad.h)。

两段式接口与函数原型

与 CANN 其他 aclnn 算子一致,aclnnSiluBackward 采用两段式接口:

  1. 先调用aclnnSiluBackwardGetWorkspaceSize:完成入参校验、构图与算子执行器创建,并返回计算所需的 workspace 大小;
  2. 再调用aclnnSiluBackward:传入 workspace 与执行器,在指定 Stream 上异步执行计算。

第一段接口原型:

aclnnStatus aclnnSiluBackwardGetWorkspaceSize( const aclTensor* gradOutput, const aclTensor* self, aclTensor* gradInput, uint64_t* workspaceSize, aclOpExecutor** executor)

第二段接口原型:

aclnnStatus aclnnSiluBackward( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)

从 Host 侧实现(experimental/activation/silu_grad/op_host/op_api/aclnn_silu_backward.cpp)可以还原第一段接口的完整内部流程:

  1. workspaceSizeexecutor做公共入参检查,创建 OpExecutor;
  2. 依次执行空指针、数据类型、shape 三组参数校验(CheckParams);
  3. gradOutputself为空 Tensor,直接返回workspaceSize = 0并快速退出;
  4. 通过l0op::Contiguous将非连续输入转换为连续张量;
  5. 调用底层l0op::SiluGrad构建 SiluGrad 算子计算流程;
  6. 通过l0op::ViewCopy将计算结果回拷到输出gradInput(兼容其非连续场景);
  7. 通过executor->GetWorkspaceSize()获取 workspace 大小并转移执行器。

第二段接口则统一调用CommonOpExecutorRun完成实际计算(aclnn_silu_backward.cpp)。

aclnnSiluBackwardGetWorkspaceSize 参数说明

第一段接口参数如下:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
gradOutput输入表示 SiLU 激活函数正向输出的梯度,公式中的 gradOutput支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致BFLOAT16、FLOAT16、FLOATND0-8
self输入表示用于计算激活函数的张量,公式中的 x支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致BFLOAT16、FLOAT16、FLOATND0-8
gradInput输出backward 计算的输出,为 SiLU 正向输入的梯度值,即对输入求导后的结果,公式中的 gradInput支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致BFLOAT16、FLOAT16、FLOATND0-8
workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----
executor输出返回 op 执行器,包含了算子计算流程-----

上述约束与源码中CheckDtypeValidCheckShapeValid的逻辑一一对应(aclnn_silu_backward.cpp):

  • 支持的数据类型列表在源码中定义为DTYPE_SUPPORT_LIST = {DT_FLOAT16, DT_FLOAT, DT_BF16}
  • 在支持 broadcast 的平台(IsRegbase为真)上,允许gradOutputself做广播后与gradInput对齐;在不支持广播的场景下严格要求三者 shape 一致;
  • 混合精度场景(gradOutput 与 self 类型不同)下,要求输出 gradInput 为 FLOAT;
  • BF16 仅在 DAV_2201(Atlas A2 系列)等架构上受支持,源码通过CheckSocVersionIsSupportBf16做了平台级校验。

返回值与错误码

返回aclnnStatus状态码,具体参见 aclnn 返回码。第一段接口会完成入参校验,出现以下场景时报错:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self 或 gradInput 是空指针
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 或 gradInput 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 和 gradInput 的数据类型不同
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 和 gradInput 的 shape 不同

aclnnSiluBackward 参数说明

第二段接口参数如下:

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnSiluBackwardGetWorkspaceSize 获取
executor输入op 执行器,包含了算子计算流程
stream输入指定执行任务的 Stream

返回值为aclnnStatus状态码,具体参见 aclnn 返回码。

约束说明

无额外约束。实际限制均已通过第一段接口的参数校验与平台能力校验体现(数据类型、shape 对齐、维度上限 8、BF16 平台支持等)。

调用示例

下面为完整调用示例,具体编译和执行过程请参考编译与运行样例。仓库中还提供了可直接参考的工程化样例 test_aclnn_silu_grad.cpp 与对应单元测试 tests/ut/op_api/test_aclnn_silu_grad.cpp。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_silu_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::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } 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::vector<T>& hostData, const std::vector<int64_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::vector<int64_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 == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> gradOutputShape = {2, 3}; std::vector<int64_t> selfShape = {2, 3}; std::vector<int64_t> gradInputShape = {2, 3}; void* gradOutputDeviceAddr = nullptr; void* selfDeviceAddr = nullptr; void* gradInputDeviceAddr = nullptr; aclTensor* gradOutput = nullptr; aclTensor* self = nullptr; aclTensor* gradInput = nullptr; std::vector<float> gradOutputHostData = {1, 1, 1, 1, 1, 1}; std::vector<float> selfHostData = {1, 2, 3, 4, 5, 6}; std::vector<float> gradInputHostData = {0, 0, 0, 0, 0, 0}; // 创建gradOutput aclTensor ret = CreateAclTensor(gradOutputHostData, gradOutputShape, &gradOutputDeviceAddr, aclDataType::ACL_FLOAT, &gradOutput); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建gradInput aclTensor 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; // 调用aclnnSiluBackward第一段接口 ret = aclnnSiluBackwardGetWorkspaceSize(gradOutput, self, gradInput, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSiluBackwardGetWorkspaceSize 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); } // 调用aclnnSiluBackward第二段接口 ret = aclnnSiluBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSiluBackward 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::vector<float> outData(size, 0); ret = aclrtMemcpy(outData.data(), outData.size() * sizeof(outData[0]), gradInputDeviceAddr, size * sizeof(outData[0]), 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("out result[%ld] is: %f\n", i, outData[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(gradOutput); aclDestroyTensor(self); aclDestroyTensor(gradInput); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(gradOutputDeviceAddr); aclrtFree(selfDeviceAddr); aclrtFree(gradInputDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

以示例数据推算:当self = {1, 2, 3, 4, 5, 6}gradOutput全为 1 时,由公式dx = dy · σ(x) · (1 + x·(1 - σ(x)))可得到各位置对应的梯度输出,可作为验证结果正确性的参考。

底层实现纵深:从 aclnn API 到 AI Core Kernel

1. 算子注册与 Shape 推导

SiluGrad 算子的 IR 定义位于 experimental/activation/silu_grad/op_host/silu_grad_def.cpp:输入dyx与输出dx均为必选参数,支持 FLOAT16 / FLOAT / BF16 三种数据类型,数据格式为 ND,AI Core 计算配置为ascend910b(Atlas A2 系列对应的芯片架构)。

Shape 推导位于 experimental/activation/silu_grad/op_host/silu_grad_infershape.cpp:输出dx的 shape 直接继承输入dy的 shape,输出数据类型继承输入数据类型,保证反向传播中张量形状在图上无缝传递。

2. Tiling 策略:单缓冲与双缓冲

Host 侧 Tiling 逻辑位于 experimental/activation/silu_grad/op_host/silu_grad_tiling.cpp,其核心决策流程为:

  1. 获取平台信息:UB 内存大小与可用核数(GetPlatformInfo);
  2. 依据输入 shape 与数据类型(FLOAT 为 4 字节、其余为 2 字节)计算输入总字节数与 UB 块大小;
  3. 以“每核约 1024 个元素”为目标计算实际并行核数(finalCoreNum),并对平台核数、数据块上限做三向取小,避免过度并行;
  4. 通过CalculateCoreBlockNums计算每个核处理的数据量,区分smallCoreDataNumbigCoreDataNumtailBlockNum,实现核间负载均衡;
  5. 当单核 tile 数不超过 2 时选择单缓冲模式(schMode = 0,增大 tile 消除尾部开销),否则选择双缓冲流水线模式(schMode = 1),并通过 TilingKey 下发。

对应的 Tiling 数据结构定义于 silu_grad_tiling_data.h,包含smallCoreDataNumbigCoreDataNumtileDataNumtailBlockNum四个字段;调度模式常量定义于 silu_grad_tiling_key.h。

3. AI Core Kernel 执行

Kernel 主体位于 experimental/activation/silu_grad/op_kernel/silu_grad.cpp 与 silu_grad.h:

  • 每个 AI Core 根据coreIdtailBlockNum计算自身负责的全局数据偏移,分别通过bigCoreDataNumsmallCoreDataNum定位dyxdx三段 Global Memory 缓冲;
  • CopyIn使用DataCopyPad将 dy 与 x 拷入 UB,CopyOut将结果写回;
  • 单缓冲模式(ProcessSingleBuffer)对单个 tile 串行执行 CopyIn → Compute → CopyOut;双缓冲模式(ProcessDoubleBuffer)通过预加载首 tile、主循环重叠“下一 tile 的 CopyIn 与当前 tile 的 Compute/CopyOut”实现 DMA 与 Vector 单元并行,末段排空流水线;
  • 计算统一以 float32 进行,FP16/BF16 输入输出通过Cast完成精度保底转换。

4. L0 层 API 封装

Host 侧 L0 封装位于 experimental/activation/silu_grad/op_host/op_api/silu_grad.cpp:l0op::SiluGrad先对两个输入做 broadcast shape 推导,混合精度下输出类型取 FLOAT,随后调用SiluGradAiCore通过ADD_TO_LAUNCHER_LIST_AICORE将算子加入 AI Core 启动列表,最终返回输出张量,供第一段接口构图与 ViewCopy 使用。

总结

aclnnSiluBackward 是 CANN ops-nn 仓库中 SiLU(Swish)激活函数反向传播的标准化 aclnn 接口,覆盖 Atlas A2 训练系列与 Atlas 800I A2 推理产品,支持 FLOAT16 / FLOAT / BF16 三种数据类型、ND 格式、0-8 维 shape 以及非连续 Tensor。通过两段式接口调用,配合源码级的参数校验、broadcast 推断、Tiling 调度(单/双缓冲)与 float32 中间精度计算,可安全高效地完成神经网络训练中 SiLU 层的梯度回传。算子整体实现、测试与调用样例可在 experimental/activation/silu_grad 目录下完整查阅。

  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-nn
点击查看免费下载

相关推荐

上一篇:Seq2Seq模型未知词替换策略:基于注意力机制的智能解决方案终极指南 🎯
下一篇:Ollama 输出对不上?3 步锁定可复现实验环境

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

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

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

立即咨询