CANN ops-nn 中 LeakyReluGrad 算子详解:LeakyReLU 反向的数学定义、aclnn 调用与源码实现
2026/9/18 22:39:33 网站建设 项目流程

CANN ops-nn 中 LeakyReluGrad 算子详解:LeakyReLU 反向的数学定义、aclnn 调用与源码实现

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

本文围绕 CANN ops-nn 仓库中 LeakyReluGrad 算子文档 展开,完整继承文档中的产品支持矩阵、计算公式、参数表与错误码,并结合 aclnnLeakyReluBackward 接口文档、官方调用样例 以及 op_api、op_host、op_kernel 三层源码,讲解该算子的两段式接口调用方式、参数校验逻辑与内核实现细节。读完本文,你能够独立完成 LeakyReLU 激活反向的 NPU 调用、排查常见返回码,并理解其广播推导、类型提升与 DAG 计算图的底层机制。

一、算子概述:LeakyReLU 反向的数学定义

LeakyReluGrad 是 aclnnLeakyRelu 前向激活接口 的反向算子,用于深度学习训练中将上游梯度gradOutput按 LeakyReLU 的导数规则回传到self张量。其计算公式为:

$$ output = \begin{cases} gradOutput, & \text{if } self > 0 \ gradOutput \times negativeSlope, & \text{if } self \le 0 \end{cases} $$

语义上,self表示前向阶段参与判断符号的张量:当self > 0时梯度原样通过,当self <= 0时梯度被缩放negativeSlope倍。negativeSlope与正向 LeakyReLU 中的斜率属性保持一致,因此反向计算才能与正向激活严格互逆。

产品支持情况

根据 README 的定义,各产品支持情况如下:

产品是否支持
Ascend 950PR / Ascend 950DT
Atlas A3 训练系列 / Atlas A3 推理系列产品
Atlas A2 训练系列 / Atlas A2 推理系列产品
Atlas 200I / 500 A2 推理产品×
Atlas 推理系列产品
Atlas 训练系列产品

需要注意:Atlas 200I/500 A2 推理产品不支持该算子,这是使用前的第一道筛选条件。

二、参数说明

以下参数表完整继承自算子文档,并补充了接口文档中的维度、非连续 Tensor 支持与使用说明:

参数名输入/输出/属性描述数据类型数据格式维度非连续 Tensor
gradOutput输入表示梯度。支持空 Tensor;数据类型与 self 需满足数据类型互推导关系;shape 需与 self 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持
self输入表示输入张量(前向输出或前向输入)。支持空 Tensor;推导与广播约束同上FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持
negativeSlope输入(aclScalar)表示 self < 0 时的斜率FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT8、BOOL、INT16、UINT8、BFLOAT16---
selfIsResult输入(bool)标记 self 是否为前向 LeakyReLU 的输出张量;为 true 时 negativeSlope 不可以是负数----
out输出表示计算输出。数据类型需是 gradOutput 与 self 推导之后可转换的数据类型;其他数据类型通过自动 cast 支持,但会额外申请空间FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持
workspaceSize输出(第一段接口)返回需要在 Device 侧申请的 workspace 大小----
executor输出(第一段接口)返回 op 执行器,包含算子计算流程----

产品差异说明:Atlas 推理系列、Atlas 训练系列上,gradOutput、self 和 out 的数据类型支持 FLOAT、FLOAT16、DOUBLE(无 BFLOAT16);negativeSlope 支持 FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT8、BOOL、INT16、UINT8。这一点与源码中的按架构区分数据类型列表相互印证(见下文第四节)。

selfIsResult 语义解析selfIsResult用于区分两种典型场景——self 是前向 LeakyReLU 的输出(in-place 反向场景),还是前向的原始输入。文档约定:当selfIsResult为 true 时,negativeSlope不可以是负数;违反该约定时第一段接口会报ACLNN_ERR_PARAM_INVALID(错误码 161002)。

文档「约束说明」章节明确:本算子无额外约束;确定性计算方面,aclnnLeakyReluBackward默认采用确定性实现。

三、两段式接口与函数原型

与 CANN 其他 aclnn 接口一致,本算子采用两段式调用:先调用aclnnLeakyReluBackwardGetWorkspaceSize获取 workspace 大小与执行器,再调用aclnnLeakyReluBackward在指定 stream 上执行。

aclnnStatus aclnnLeakyReluBackwardGetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, const aclScalar *negativeSlope, bool selfIsResult, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnLeakyReluBackward( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)

第二段接口的入参:workspace(Device 侧申请的 workspace 内存地址)、workspaceSize(由第一段接口返回的大小)、executor(op 执行器)、stream(指定执行任务的 Stream)。

第一段接口的错误码

第一段接口会完成入参校验,出现以下场景时报错(返回值类型均为aclnnStatus):

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、negativeSlope 或 out 是空指针
ACLNN_ERR_PARAM_INVALID161002gradOutput、self、negativeSlope 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 或 out 的 shape 超过 8 维
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 的 shape 不满足广播关系,或 out 的 shape 与广播结果不一致
ACLNN_ERR_PARAM_INVALID161002selfIsResult 为 true 时,negativeSlope 是负数

四、端到端调用示例

官方样例 test_aclnn_leaky_relu_backward.cpp 展示了完整的两段式调用流程,可整体复制后按需修改 deviceId 与张量数据编译运行。核心结构如下:

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_leaky_relu_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初始化,根据自己的实际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. 构造输入与输出 std::vector<int64_t> gradShape = {4}; std::vector<int64_t> selfShape = {4}; std::vector<int64_t> outShape = {4}; void* gradDeviceAddr = nullptr; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* grad = nullptr; aclTensor* self = nullptr; aclScalar* negativeSlope = nullptr; aclTensor* out = nullptr; std::vector<float> gradHostData = {2, 3, 4, 5}; std::vector<float> selfHostData = {1, 2, 3, 4}; std::vector<float> outHostData = {0, 0, 0, 0}; float negativeSlopeValue = 0.01f; // 本例 self 全为正值,梯度将原样通过 bool selfIsResultValue = true; // 创建grad aclTensor ret = CreateAclTensor(gradHostData, gradShape, &gradDeviceAddr, aclDataType::ACL_FLOAT, &grad); 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); // 创建negativeSlope aclScalar negativeSlope = aclCreateScalar(&negativeSlopeValue, aclDataType::ACL_FLOAT); CHECK_RET(negativeSlope != nullptr, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnLeakyReluBackward第一段接口 ret = aclnnLeakyReluBackwardGetWorkspaceSize(grad, self, negativeSlope, selfIsResultValue, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLeakyReluBackwardGetWorkspaceSize 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); } // 调用aclnnLeakyReluBackward第二段接口 ret = aclnnLeakyReluBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLeakyReluBackward 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(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[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("result[%ld] is: %f\n", i, resultData[i]); } // 6. 释放aclTensor和aclScalar aclDestroyTensor(grad); aclDestroyTensor(self); aclDestroyScalar(negativeSlope); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(gradDeviceAddr); aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

示例中self全部取正值,因此输出out应逐元素等于gradOutput({2, 3, 4, 5});若将selfHostData改为含负值,负值位置的输出将变为gradOutput * 0.01,可直接验证公式的两个分支。

五、源码解读:参数校验与数据类型策略

aclnn 实现入口 中的CheckParams函数(约 L131-L151)按顺序执行五步校验,与上文错误码表逐条对应:

  1. 空指针检查CheckNotNull依次检查 gradOutput、self、negativeSlope、out,任一为空即返回 161001;
  2. 数据类型检查CheckDtypeValid(约 L75-L84)按当前 NPU 架构选择数据类型支持列表——DAV_2201或 Regbase 架构(A2 系列)的张量列表为 FLOAT/FLOAT16/DOUBLE/BFLOAT16,其他架构(910/310p)仅 FLOAT/FLOAT16/DOUBLE,标量列表额外包含 INT32、INT64、INT8、UINT8、BOOL、INT16(Regbase 架构标量还多支持 BFLOAT16)。这与文档「Atlas 推理/训练系列不支持 BFLOAT16 张量」的说明完全一致;
  3. shape 检查CheckShape(约 L86-L100)校验 gradOutput/self 不超过MAX_DIM = 8维,通过OP_CHECK_BROADCAST_AND_INFER_SHAPE推导广播形状,并要求 out 的 view shape 与广播结果一致;
  4. selfIsResult 检查CheckIsResult(约 L102-L113)在selfIsResult == truenegativeSlope < 0时输出明确的错误日志,提示这是以负斜率调用了 in-place 前向导致的,应改用 out-of-place 版本;
  5. 类型推导检查CheckPromoteType(约 L115-L129)调用PromoteType校验 gradOutput 与 self 可互推导,且推导结果可转换为 out 的数据类型。

六、源码解读:算子编排与执行流程

理解aclnnLeakyReluBackwardGetWorkspaceSize(约 L153-L208)的内部流程,可以看出两段式接口是如何把用户输入"加工"为可执行的算子链的:

  1. 空 Tensor 快速路径:若self为空 Tensor,直接置workspaceSize = 0并返回成功,输出也是空 Tensor;
  2. 连续化l0op::Contiguous分别把 gradOutput、self 转为连续 Tensor(用户可传入非连续张量);
  3. 类型统一l0op::Cast将两路输入 cast 到PromoteType推导出的隐式数据类型;
  4. 核心计算l0op::LeakyReluGrad(gradOutputCast, selfCast, negativeSlope->ToFloat(), ...)调用真正的算子 kernel,注意 negativeSlope 标量在此以 float 值注入;
  5. 输出类型转换l0op::Cast把计算结果 cast 回 out 声明的数据类型;
  6. 结果写回l0op::ViewCopy把结果拷贝到可能是非连续的 out 张量;
  7. 最终*workspaceSize = uniqueExecutor->GetWorkspaceSize()汇总整条算子链的 workspace 需求,执行器移交executor输出参数,供第二段接口调用CommonOpExecutorRun在 stream 上执行。

这一编排解释了文档中"out 的其他数据类型通过自动 cast 支持,但会额外申请空间"的原因——cast 中间张量本身就要占用 workspace。

七、算子定义、Shape 推导与 Tiling

算子定义:leaky_relu_grad_def.cpp(L18-L48)声明了图模式下的算子签名——输入gradientsfeatures,输出backprops,三者的数据类型均为 BF16/FLOAT16/FLOAT、格式均为 ND;属性negative_slope为 OPTIONAL 的 Float,默认值 0.0。AICore 配置针对ascend950ascend350两个平台注册,并开启动态 shape/rank 支持(DynamicShapeSupportFlag(true)DynamicRankSupportFlag(true)),因此算子才能支撑 0–8 维的任意形状输入。

Shape 推导:leaky_relu_grad_infershape.cpp 仅一行核心逻辑——注册InferShape4Broadcast,即输出 shape 由两路输入按广播规则推导,与 aclnn 层CheckShape的验证规则保持一致。

Tiling:leaky_relu_grad_tiling.cpp 的DoOpTiling(约 L37-L98)揭示了图模式下的一条硬约束:gradientsfeaturesbackprops三者的数据类型必须相同,否则直接返回失败。随后根据输入类型实例化BroadcastBaseTiling(FLOAT16/BF16/FLOAT 三个分支),生成对应模板的 tilingKey,并把negativeSlope以 float 标量形式写入 tiling 数据(SetScalar)。由于 aclnn 路径在进入 kernel 前已完成类型统一 cast,因此该约束在 API 调用层面不会暴露给使用者。

八、内核实现:基于 DAG 的广播计算图

Kernel 入口:leaky_relu_grad_apt.cpp 定义了一个__aicore__模板函数,任务类型为KERNEL_TYPE_AIV_ONLY(仅 AI Vector 核执行),内部实例化BroadcastSch<schMode, OpDag>调度器,Process(gradients, features, backprops)一条语句完成全部数据搬运与计算。

计算图定义:leaky_relu_grad_dag.h(L31-L47)以数据流图精确刻画了公式的每个环节:

  • OpCopyInY/OpCopyInX:把 gradients 与 features 分别按广播方式搬入向量寄存器;
  • CompareVec::Compare<uint8_t, U, COMPARE_MODE_GT>对 features 与 0 做"大于"比较,产生掩码;
  • OpCopyInYCastMul:将 gradients cast 到 float 后与Placeholder::Var<float, 0>(即 tiling 中注入的 negativeSlope 标量)相乘;
  • Select:按掩码二选一——features > 0 取原梯度,否则取gradOutput * negativeSlope
  • SelectCast/OpCopyOut:cast 回原始类型并写回输出。

可见 kernel 与文档公式逐分支对应:features > 0分支直接取 gradients,features <= 0分支乘 negativeSlope,比较、选择、标量乘法全部在 AI Core 上以向量指令完成。

九、测试与验证方式

仓库为算子提供了多层测试,可作为自研调用的验证参考:

  • op_api UT:test_leaky_relu_backward.cpp 覆盖 aclnn 接口的正常与异常路径;
  • Tiling UT:test_leaky_relu_grad_tiling.cpp 验证 tiling 生成逻辑;
  • Kernel 数据驱动测试:test_leaky_relu_grad_apt.cpp 配合 gen_data.py 生成数据,并可用 golden.py 生成参考结果对比;
  • ST 模糊测试:ttk_kernel_leaky_relu_grad_st.csv 定义了不同数据类型、shape 与negative_slope取值的 fuzz 用例(覆盖 FP32/FP16、多 shape、极端数值区间如 nan/inf,容差 1e-08)。

十、相关目录与参考资料汇总

内容相对路径
算子 README(产品支持、公式、参数、调用方式)activation/leaky_relu_grad/README.md
aclnnLeakyReluBackward 接口文档(原型、错误码、示例)activation/leaky_relu_grad/docs/aclnnLeakyReluBackward.md
官方调用样例activation/leaky_relu_grad/examples/test_aclnn_leaky_relu_backward.cpp
aclnn 参数校验与算子编排实现activation/leaky_relu_grad/op_api/aclnn_leaky_relu_backward.cpp
算子定义(输入/输出/属性)activation/leaky_relu_grad/op_host/leaky_relu_grad_def.cpp
Shape 广播推导注册activation/leaky_relu_grad/op_host/leaky_relu_grad_infershape.cpp
Tiling 实现activation/leaky_relu_grad/op_host/arch35/leaky_relu_grad_tiling.cpp
Kernel 入口activation/leaky_relu_grad/op_kernel/leaky_relu_grad_apt.cpp
DAG 计算图定义activation/leaky_relu_grad/op_kernel/arch35/leaky_relu_grad_dag.h
前向算子 LeakyReLU 文档activation/leaky_relu/docs/aclnnLeakyRelu&aclnnInplaceLeakyRelu.md

适用前提与限制小结:本文所有描述以当前仓库代码与文档为准;调用前请确认目标产品不在"Atlas 200I/500 A2 推理产品"之列,张量维度不超过 8 维,selfIsResult为 true 时保证negativeSlope非负,Atlas 推理/训练系列上避免对张量输入使用 BFLOAT16。

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

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

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

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

立即咨询