aclnnThresholdBackward:CANN ops-nn 中 Threshold 反向传播算子的两段式接口与源码级解析
2026/9/20 11:44:02 网站建设 项目流程
  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

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

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

aclnnThresholdBackward是 CANN ops-nn 开源算子库中 aclnnThreshold 前向算子的反向实现,用于在 NPU 上计算 Threshold 激活函数的梯度。本文基于 aclnnThresholdBackward.md 展开,完整讲解其计算公式、两段式接口原型、全部参数约束、返回码校验逻辑与可直接编译运行的调用示例,并结合threshold_grad_v2_d目录下的 host 侧(tiling/算子定义)与 kernel 侧(AscendC 实现)源码,揭示该接口在底层如何被映射到ThresholdGradV2DReluGrad两个核函数并完成多核切分计算。

一、功能说明与计算公式

aclnnThresholdBackward完成 Threshold 前向函数的反向传播:给定反向梯度gradOutput与前向输入self,输出out仅在self元素严格大于阈值threshold时保留对应位置的梯度,其余位置置 0。其数学定义为:

$$ output = \begin{cases} gradOutput(i) & \text{if } self(i) > threshold \ 0 & \text{otherwise} \end{cases} $$

从计算语义上看,该接口与同目录下的核算子ThresholdGradV2D(见 README.md,其公式为input_feature > threshold时输出input_gradient,否则输出 0)完全一致,是这一核算子的上层 Aclnn 封装。值得注意的一个工程细节是:当threshold取值为 0.0 时,该函数退化为self(i) > 0的梯度门控,与 ReLU 反向(gradOutput(i) * (self(i) > 0))在语义上完全等价,因此 host 侧实现会直接复用ReluGrad核算子来消除冗余计算(详见第五节源码解析)。

二、产品支持情况

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

该支持范围与底层核算子ThresholdGradV2D保持一致。需要特别说明的是,不同产品代际在数据类型支持上存在差异:在 aclnn_threshold_backward.cpp 中,源码按 NPU 架构维护了三个 dtype 支持列表:

  • ASCEND910_DTYPE_DTYPE_SUPPORT_LIST:FLOAT、INT32、FLOAT16、INT8、UINT8;
  • ASCEND910B_DTYPE_DTYPE_SUPPORT_LIST:在上一列表基础上增加 BF16;
  • REGBASE_DTYPE_DTYPE_SUPPORT_LIST:在 910B 列表基础上再增加 INT64(仅在threshold == 0.0走 ReluGrad 路径时启用)。

由此可以推断:文档中列出的 INT64 支持是有条件的(依赖 threshold 取值与产品架构),而 threshold_grad_v2_d_def.cpp 中核算子自身声明的数据类型仅为 FLOAT16、FLOAT、BF16、INT32、INT8、UINT8 六种。在 Atlas A2/800I A2 上实际支持 FLOAT、BFLOAT16、FLOAT16、INT32、INT8、UINT8 六种数据类型。

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

与 CANN 算子库的通用约定一致(详见 两段式接口说明),该 API 由“获取 workspace 大小”与“执行计算”两段组成,必须先调用第一段获取入参校验结果、workspace 大小与执行器,再调用第二段提交计算任务。

第一段接口(获取 workspace 大小并完成入参校验):

aclnnStatus aclnnThresholdBackwardGetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, const aclScalar *threshold, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)

第二段接口(执行计算):

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

其中第一段接口内部会依次完成:创建OpExecutor→ 空指针校验 → 数据类型校验 → 维度校验 → 输入 broadcast 校验 → 输出 shape 校验 → 空 Tensor 短路处理 → 构图(生成ContiguousReluGrad/ThresholdGradV2DViewCopy算子节点)→ 计算并返回 workspace 大小。

四、aclnnThresholdBackwardGetWorkspaceSize 参数详解

第一段接口共 6 个参数,其中 4 个是计算相关的张量/标量参数,2 个是框架返回参数:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
gradOutput输入公式中的 gradOutput支持空 Tensor;dtype 需与 self 一致;shape 需与 self 满足 broadcast 关系FLOAT、BFLOAT16、FLOAT16、INT32、INT8、UINT8、INT64ND0-8
self输入前向输入张量(公式中的比较基准)数据类型与 gradOutput 满足互推导关系FLOAT、BFLOAT16、FLOAT16、INT32、INT8、UINT8、INT64ND0-8
threshold输入公式中的阈值dtype 需与 gradOutput 一致;shape 需与 gradOutput 满足 broadcast 关系FLOAT、BFLOAT16、FLOAT16、INT32、INT8、UINT8、INT64ND0-8
out输出公式中的输出 outdtype 需与 self 相同;shape 需等于 self 与 gradOutput broadcast 之后的 shapeFLOAT、BFLOAT16、FLOAT16、INT32、INT8、UINT8、INT64ND0-8
workspaceSize输出需要在 Device 侧申请的 workspace 大小-----
executor输出op 执行器,包含算子计算流程-----

关于上表需要补充几点实现层面的说明:

  • 支持空 Tensor:在 aclnn_threshold_backward.cpp 中,当selfgradOutput为空 Tensor 时,接口直接返回workspaceSize = 0并释放执行器,不进入 kernel 计算路径。
  • 非连续 Tensor 支持:源码在构图前会分别对gradOutputself调用l0op::Contiguous转为连续内存,最终结果通过l0op::ViewCopy写回可能非连续的out,因此三个张量均允许非连续排布。
  • broadcast 校验:接口通过OP_CHECK_BROADCAST_AND_INFER_SHAPE推导selfgradOutput的广播后 shape,并要求out的 view shape 与之严格相等,否则返回ACLNN_ERR_PARAM_INVALID(源码第 122-128 行)。

返回值与第一段接口报错场景

第一段接口返回aclnnStatus状态码,具体取值参见 aclnn返回码。在 op_api 单元测试 中可以看到这些错误场景的实测覆盖:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput 或 self 是空指针
ACLNN_ERR_PARAM_INVALID161002gradOutput 或 self 的数据类型不在支持范围之内
ACLNN_ERR_PARAM_INVALID161002gradOutput 或 self 的 shape 超过 8 维
ACLNN_ERR_PARAM_INVALID161002gradOutput、out 与 self 数据类型不一致
ACLNN_ERR_PARAM_INVALID161002out 的 shape 与 self、gradOutput broadcast 后的 shape 不一致

此外源码中thresholdoutworkspaceSize指针为空也会分别触发ACLNN_ERR_PARAM_NULLPTR。对应测试用例(如l2_test_unsupport_dtypel2_test_unmatch_dtypel2_test_invalid_out_shapel2_test_nullptr)验证了这些返回码的实际行为。

五、aclnnThresholdBackward 参数详解

第二段接口仅负责提交执行,参数含义如下:

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

其实现非常简洁:在 aclnn_threshold_backward.cpp 中,第二段接口直接调用框架统一入口CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成异步计算,返回值同样为aclnnStatus

底层计算路径:ThresholdGradV2D 与 ReluGrad 的动态选择

这是本算子最值得关注的源码设计点。第一段接口在完成连续化与校验后,会根据 threshold 的取值动态选择底层核算子(aclnn_threshold_backward.cpp):

const aclTensor* opOut; if (IsFloatEqual(thresholdVal_, 0.0)) { opOut = l0op::ReluGrad(gradOutputContiguous, selfContiguous, uniqueExecutor.get()); } else { opOut = l0op::ThresholdGradV2D(gradOutputContiguous, selfContiguous, thresholdVal_, uniqueExecutor.get()); }

即:

  • threshold == 0.0(用浮点 epsilon 判等,见IsFloatEqual)时复用 activation/relu_grad 的 ReluGrad 核,此时可额外支持 INT64(见前文 REGBASE 列表);
  • 否则调用ThresholdGradV2D核算子(tiling 入口位于 threshold_grad_v2_d_tiling.cpp)。

ThresholdGradV2D的算子定义见 threshold_grad_v2_d_def.cpp,其中Attr("threshold")的默认值为 1.0,输入输出均为 ND 格式,并启用了动态 shape、动态 rank 与精度降低(PrecisionReduceFlag(true))支持。其 shape 推导(threshold_grad_v2_d_infershape.cpp)将输出 shape 直接复制自输入input_gradient

Kernel 侧实现:CompareScalar + Select 的门控逻辑

核函数实现位于 threshold_grad_v2_d.cpp 与 threshold_grad_v2_d.h。以 FLOAT 路径为例,核心计算仅两步(KernelThresholdGradV2D::Compute):

  1. CompareScalar(maskLocal, fLocal, threshold, CMPMODE::GT, ...):逐元素比较input_feature > threshold,生成 0/1 mask;
  2. Select(outLocal, maskLocal, gLocal, 0.0, VSEL_TENSOR_SCALAR_MODE, ...):mask 为 1 的位置取input_gradient,否则取 0。

对于 INT8/UINT8 等整型输入,会先经Cast提升到 half/float 参与比较与选择,再以CAST_TRUNC截断回原类型;BF16 路径使用CAST_RINT四舍五入回写。数据流采用标准 AscendC 流水:双缓冲TQueBUFFER_NUM = 2)实现CopyIn → Compute → CopyOut三段流水,Process()中前tileNum - 1个 tile 以满tileDataNum处理,最后一个 tile 按tailDataNum处理尾部数据。

Tiling 切分策略

tiling 计算位于 threshold_grad_v2_d_tiling.cpp,整体流程为:

  1. 通过PlatformAscendC获取 UB 大小与 AIV 核数(GetCoreNumAiv);
  2. 按数据类型选择单核可容纳的数据块数ubDataNumber(F32/F16 为 7、INT32 为 8、BF16 为 10、INT8/UINT8 为 14),再结合BLOCK_SIZE = 256推导tileDataNum
  3. 将输入按 256 字节对齐后均分到各核,计算大核/小核数据量(bigCoreDataNum/smallCoreDataNum)、每核 tile 数及尾部数据量(bigTailDataNum/smallTailDataNum)、尾核数tailBlockNum,实现核间负载均衡;
  4. 将全部切分参数写入ThresholdGradV2DTilingData,并设置 block dim 与 tiling key。

六、约束说明

  • 确定性计算aclnnThresholdBackward默认采用确定性实现,即相同输入与配置下多次运行结果一致。

七、调用示例(可直接编译运行)

以下示例来自 examples/test_aclnn_threshold_backward.cpp,使用{2, 2}的 FLOAT 张量演示完整调用流程。整体编译与执行步骤请参考 编译与运行样例。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_threshold_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> selfShape = {2, 2}; std::vector<int64_t> gradOutputShape = {2, 2}; std::vector<int64_t> outShape = {2, 2}; void* selfDeviceAddr = nullptr; void* gradOutputDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* gradOutput = nullptr; aclScalar* threshold = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0.2, 1.2, 2.2, 3.2}; std::vector<float> gradOutputHostData = {4.5, 4.4, 4.3, 4.2}; std::vector<float> outHostData = {0.0, 0.0, 0.0, 0.0}; float thresholdValue = 1.0f; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建gradOutput aclTensor ret = CreateAclTensor(gradOutputHostData, gradOutputShape, &gradOutputDeviceAddr, aclDataType::ACL_FLOAT, &gradOutput); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建threshold aclScalar threshold = aclCreateScalar(&thresholdValue, aclDataType::ACL_FLOAT); CHECK_RET(threshold != nullptr, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnThresholdBackward第一段接口 ret = aclnnThresholdBackwardGetWorkspaceSize(gradOutput, self, threshold, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnThresholdBackwardGetWorkspaceSize 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); } // 调用aclnnThresholdBackward第二段接口 ret = aclnnThresholdBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnThresholdBackward 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(self); aclDestroyTensor(gradOutput); aclDestroyScalar(threshold); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(gradOutputDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

示例运行结果分析

以上示例中self = {0.2, 1.2, 2.2, 3.2}threshold = 1.0gradOutput = {4.5, 4.4, 4.3, 4.2}。按公式逐元素计算:

  • 0.2 > 1.0为假 → 输出 0;
  • 1.2 > 1.0为真 → 输出 4.4;
  • 2.2 > 1.0为真 → 输出 4.3;
  • 3.2 > 1.0为真 → 输出 4.2。

即最终输出约为{0.0, 4.4, 4.3, 4.2}。若将thresholdValue改为 0.0,则接口内部会切换到 ReluGrad 路径,输出变为{0.0, 4.4, 4.3, 4.2}与 0 阈值门控一致。该示例可直接用于验证接口的正确性与两段式调用的完整性。

八、关联测试与工程入口汇总

用途仓库路径
API 文档(本文主体)experimental/activation/threshold_grad_v2_d/docs/aclnnThresholdBackward.md
可运行示例examples/test_aclnn_threshold_backward.cpp
两段式接口实现op_host/op_api/aclnn_threshold_backward.cpp
算子定义(IR 注册)op_host/threshold_grad_v2_d_def.cpp
shape 推导op_host/threshold_grad_v2_d_infershape.cpp
tiling 切分op_host/threshold_grad_v2_d_tiling.cpp
AscendC 核函数op_kernel/threshold_grad_v2_d.cpp、op_kernel/threshold_grad_v2_d.h
op_api 单元测试tests/ut/op_api/test_aclnn_threshold_grad_v2_d.cpp
前向算子文档activation/threshold/docs/aclnnThreshold&aclnnInplaceThreshold.md

九、总结

aclnnThresholdBackward是一个典型的“薄封装、强校验”的两段式 Aclnn 接口:对外提供标准的 workspace/executor 编程模型与完善的参数校验,对内则通过 threshold 取值的分支判断,灵活复用ThresholdGradV2DReluGrad两个经过多核 tiling 优化的核算子,兼顾了代码复用与计算效率。开发者在使用时只需牢记三点:第一段接口必须先于第二段调用、out的 shape 必须等于selfgradOutput广播后的 shape、gradOutputself的数据类型必须一致;其余的内存管理与异步执行细节均可参照本文示例完整落地。

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

【免费下载链接】ops-nn

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

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

相关推荐

上一篇:大数据管道监控终极指南:吞吐量、延迟与错误率三大核心指标详解
下一篇:Tuigreet 项目常见问题解决方案

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

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

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

立即咨询