CANN ops-nn 算子详解:aclnnHardsigmoidBackward 两段式 ACLNN 接口与 HardSigmoid 反向实现
2026/9/19 23:40:05 网站建设 项目流程
  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

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

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

本文围绕 CANN ops-nn 仓库中 experimental/activation/hard_sigmoid_grad_v3 模块对外导出的aclnnHardsigmoidBackward算子,完整讲解其功能语义、两段式 ACLNN 接口原型、参数与返回码约定、Host 侧精度提升与调用链,以及 AscendC kernel 的 tiling 与向量化实现。读完本文,你将掌握如何在实际工程中调用该算子完成 HardSigmoid 反向梯度计算,并能基于仓库中的示例与测试用例快速搭建验证环境。

产品支持情况

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

功能说明

aclnnHardsigmoidBackward对输入gradOutput(上游梯度)和self(前向输入)执行 HardSigmoid 反向梯度计算,并将结果写入独立的输出 Tensorout。目录 experimental/activation/hard_sigmoid_grad_v3 对外导出普通版aclnnHardsigmoidBackward,与原始实现保持一致。

计算公式如下:

$$ \operatorname{hardsigmoid_backward}(gradOutput, self)= \begin{cases} \frac{gradOutput}{6}, & -3 < self < 3 \ 0, & \text{otherwise} \end{cases} $$

即:前向输入self落在开区间(-3, 3)时,反向梯度为gradOutput / 6,否则为 0。该逻辑与前向 HardSigmoid 分段线性函数的导数一致(前向 HardSigmoid 在(-3, 3)内斜率为1/6,区间外斜率为 0)。

关键数值特性:

  • gradOutputself提升后的计算 dtype 为FLOAT16时,接口内部会进一步将计算精度提升到FLOAT32,完成计算后再 cast 回输出 dtype;
  • BFLOAT16路径则按提升规则直接使用BFLOAT16/FLOAT32路径计算;
  • gradOutputself允许混合精度输入,接口内部先按PromoteType(gradOutput, self)对齐计算 dtype(见下文 Host 实现剖析)。

两段式接口与函数原型

与其他 ACLNN 算子一致,aclnnHardsigmoidBackward采用两段式接口:必须先调用第一段接口aclnnHardsigmoidBackwardGetWorkspaceSize完成入参校验、构图并获取 workspace 大小与 op 执行器,再调用第二段接口aclnnHardsigmoidBackward在指定 Stream 上执行计算。

第一段接口原型:

aclnnStatus aclnnHardsigmoidBackwardGetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);

第二段接口原型:

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

两段接口均返回aclnnStatus状态码,具体取值可参考 aclnn 返回码说明。

aclnnHardsigmoidBackwardGetWorkspaceSize 参数说明

第一段接口共 5 个参数,前 3 个为 Tensor 参数,后 2 个为输出参数:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
gradOutput(aclTensor*)输入上游梯度张量。支持空 Tensor;shape 必须与 self、out 完全一致;数据类型需要在支持范围内。FLOAT16、FLOAT32;BFLOAT16 仅支持 Ascend910B 及后续同代 SoCND0-8
self(aclTensor*)输入前向输入张量,用于生成 HardSigmoid 反向掩码。支持空 Tensor;shape 必须与 gradOutput、out 完全一致;数据类型需要在支持范围内。FLOAT16、FLOAT32;BFLOAT16 仅支持 Ascend910B 及后续同代 SoCND0-8
out(aclTensor*)输出计算的出参。支持空 Tensor;shape 必须与 gradOutput、self 完全一致;数据类型需要支持从内部计算 dtype cast 回写。FLOAT16、FLOAT32;BFLOAT16 仅支持 Ascend910B 及后续同代 SoCND0-8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小。-----
executor(aclOpExecutor**)输出返回 op 执行器,包含算子计算流程。-----

需要特别说明的几点:

  • 空 TensorgradOutputselfout均支持空 Tensor。从 Host 实现 可以看到,当gradOutputself为空时,第一段接口直接返回workspaceSize = 0并提前结束,不再构造执行图。
  • 混合精度gradOutputself可以是不同 dtype,例如FLOAT16+FLOAT32,接口内部先按PromoteType(gradOutput, self)提升,若提升结果为FLOAT16则再升到FLOAT32参与计算。
  • 非连续 Tensor:三个 Tensor 均支持非连续排布,接口内部通过ContiguousViewCopy自动适配。

返回值与错误码

第一段接口会完成入参校验,出现以下场景时报错:

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self 或 out 是空指针。
ACLNN_ERR_PARAM_INVALID161002gradOutput 或 self 的数据类型不在支持范围内。
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 和 out 的 shape 不一致。
ACLNN_ERR_PARAM_INVALID161002gradOutput、self 或 out 的维度大于 8。
ACLNN_ERR_PARAM_INVALID161002提升后的计算 dtype 不能转换为 out 的数据类型。

这些校验逻辑可以在 aclnn_hardsigmoid_backward.cpp 中逐一对应:

  • CheckNotNull使用OP_CHECK_NULL检查三个 Tensor 指针是否为空,对应 161001;
  • CheckDtypeValid先通过OP_CHECK_DTYPE_NOT_SUPPORT检查gradOutputself的 dtype 是否在支持列表中,再调用GetComputeType得到提升后的计算 dtype,并用OP_CHECK_RESULT_DTYPE_CAST_FAILED检查该 dtype 能否 cast 回out的 dtype,对应 161002;
  • CheckShape使用OP_CHECK_MAX_DIM限制维度不超过 8(源码中MAX_DIM_LEN = 8),并用OP_CHECK_SHAPE_NOT_EQUAL校验gradOutputselfout的 shape 一致。

其中 dtype 支持列表由GetDtypeSupportList依据平台动态决定:

static const std::initializer_list<op::DataType> ASCEND910_DTYPE_SUPPORT_LIST = { op::DataType::DT_FLOAT, op::DataType::DT_FLOAT16}; static const std::initializer_list<op::DataType> ASCEND910B_DTYPE_SUPPORT_LIST = { op::DataType::DT_FLOAT, op::DataType::DT_FLOAT16, op::DataType::DT_BF16};

在 Ascend910B 至 Ascend910E 区间以及 Regbase 平台返回ASCEND910B_DTYPE_SUPPORT_LIST(含BFLOAT16),其余平台返回ASCEND910_DTYPE_SUPPORT_LIST。这与 单元测试 case_003_bfloat16 的行为一致:仅在 Ascend910B / Ascend910_93 / Ascend910E 上期望成功,其他平台期望返回ACLNN_ERR_PARAM_INVALID

aclnnHardsigmoidBackward 参数说明

第二段接口共 4 个参数:

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

第二段接口的返回值同样为aclnnStatus,参见 aclnn 返回码说明。从实现看,第二段接口内部通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)驱动执行器完成整条构图流水线的执行。

Host 侧实现剖析:从 ACLNN 接口到 l0op 调用链

aclnnHardsigmoidBackward的 Host 入口位于 op_host/op_api/aclnn_hardsigmoid_backward.cpp,第一段接口ExecHardsigmoidBackwardGetWorkspaceSize在通过参数校验后,会依次向执行器(aclnnOpExecutor)添加以下计算节点,构成完整的计算图:

  1. Contiguous:对gradOutputself分别调用l0op::Contiguous,将非连续输入归一为连续存储(仅在需要时产生实际拷贝);
  2. Cast 到计算 dtype:按GetComputeType的结果(PromoteType(gradOutput, self),若为FLOAT16则再提升为FLOAT)将两个输入分别 cast;
  3. 核心算子:调用l0op::HardSigmoidGradV3(gradOutputContiguousCasted, selfContiguousCasted)完成主体计算;
  4. Cast 回输出 dtype:将 kernel 输出 cast 为out声明的数据类型;
  5. ViewCopy:通过l0op::ViewCopy将结果回写到out,因此支持非连续输出 Tensor。
auto promoteType = GetComputeType(gradOutput->GetDataType(), self->GetDataType()); auto gradOutputContiguous = l0op::Contiguous(gradOutput, uniqueExecutor.get()); auto gradOutputContiguousCasted = l0op::Cast(gradOutputContiguous, promoteType, uniqueExecutor.get()); auto selfContiguous = l0op::Contiguous(self, uniqueExecutor.get()); auto selfContiguousCasted = l0op::Cast(selfContiguous, promoteType, uniqueExecutor.get()); auto gradInput = l0op::HardSigmoidGradV3(gradOutputContiguousCasted, selfContiguousCasted, uniqueExecutor.get()); auto gradInputCasted = l0op::Cast(gradInput, out->GetDataType(), uniqueExecutor.get()); auto viewCopyResult = l0op::ViewCopy(gradInputCasted, out, uniqueExecutor.get());

需要强调的是:混合精度语义只在 ACLNN 封装层处理,AscendC kernel 始终接收对齐后的单一 dtype 输入,kernel 内部不需要感知混合精度。

l0op::HardSigmoidGradV3封装位于 op_host/op_api/hard_sigmoid_grad_v3.cpp,通过OP_TYPE_REGISTER(HardSigmoidGradV3)注册算子类型,为输出张量分配与gradOutput同 shape、同 dtype 的存储,并通过ADD_TO_LAUNCHER_LIST_AICORE将算子加入 AICore 启动列表。与之配套的算子定义(hard_sigmoid_grad_v3_def.cpp)、inferShape(hard_sigmoid_grad_v3_infershape.cpp)与 tiling 入口(hard_sigmoid_grad_v3_tiling.cpp)共同构成 Host 侧完整实现。

Tiling 与 Kernel 实现:多核切分与向量化优化

Tiling 策略

Tiling 实现位于 op_host/hard_sigmoid_grad_v3_tiling.cpp,整体策略为:

  • 多核切分:将总元素数按 AIV core 数量均匀切分。totalLengthCoreCeilDiv(totalNum, coreNum)得到,再按 512B cache line 对齐为totalLengthCoreAlignCACHE_LINE_BYTE_LENGTH = 512),blockFactor即每个核分得的元素数,实际使用的核数为CeilDiv(totalNum, totalLengthCoreAlign)
  • UB 切分:单个核内的数据再按ubFactor分片迭代处理。UB 缓冲区需容纳 5 份数据(2 路输入队列 + 2 路临时 mask/浮点缓冲 + 1 路输出队列)乘上 buffer 数量;
  • 双缓冲:当元素总数较大时启用 double buffer 流水(见下文 TilingKey 说明)。

ubFactor的计算要点:

  • 缓冲区系数bufferCoefficientFLOAT32FP32_BUFFER_COEFFICIENT = 25(3 个队列缓冲 + 3 个 FP32 临时缓冲 + 1 个 compare mask,折合约 25 字节/元素),FLOAT16FP16_VECTOR_BUFFER_COEFFICIENT = 13
  • maxTileElements = ubSize / bufferCoefficient,再按对齐粒度向下取整得到tileLength
  • 对齐粒度取32 / typeSizeCOMPARE_ALIGN_BYTES(256) / typeSize中的较大者,保证向量化 compare 指令的对齐要求;
  • 最终tileLength还会被blockTileLimit(即totalLengthCoreAlign对齐后的值)钳制,保证ubFactor <= blockFactor,避免中等 shape 整块退化到 tail 标量路径。

空输入(totalNum == 0)时走SetEmptyInputTiling特判:blockFactor = ubFactor = 0SetBlockDim(1),kernel 直接返回。

TilingKey 模板参数

TilingKey 定义 使用模板编程声明两个参数:

  • D_T_X:数据类型,从输入 0 推导,支持C_DT_FLOAT16C_DT_FLOATC_DT_BF16
  • BUFFER_MODE:0 表示单缓冲、1 表示双缓冲。

tiling 函数通过ASCENDC_TPL_SEL_PARAM(context, dTypeX, useDoubleBuffer)选择对应的 kernel 实例。值得注意的实现细节:当前实现固定启用 double buffer(useDoubleBuffer = 1),tiling 源码注释解释了原因——BFLOAT16full-tile kernel 与 FP32 共用同一套双缓冲 UB 模型,若关闭双缓冲,当fullTileNum > 1时,流水线Process()路径中CopyIn(next)会在Compute(curr)释放唯一队列槽位之前执行,从而死锁。

Kernel 计算逻辑

Kernel 入口在 op_kernel/hard_sigmoid_grad_v3.cpp,核心类NsHardSigmoidGradV3::HardSigmoidGradV3<T, BUFFER_MODE>定义于 op_kernel/hard_sigmoid_grad_v3.h,采用标准 TPipe 三阶段流水(CopyIn → Compute → CopyOut):

int64_t fullTileNum = blockLength_ / ubLength_; if (fullTileNum > 0) { CopyInFull(0); for (int64_t i = 0; i < fullTileNum - 1; ++i) { CopyInFull(i + 1); ComputeFull(); CopyOutFull(i); } ComputeFull(); CopyOutFull(fullTileNum - 1); } int64_t tailLength = blockLength_ - fullTileNum * ubLength_; if (tailLength > 0) { CopyInTail(fullTileNum, tailLength); ComputeTail(tailLength); CopyOutTail(fullTileNum, tailLength); }

full-tile 路径(向量化)FLOAT16/FLOAT32使用常量LOWER_BOUND = -3.0fUPPER_BOUND = 3.0fSCALE_VALUE = 1/6,通过 4 步向量指令完成计算:

  1. Muls(resultLocal, gradLocal, SCALE_VALUE):梯度先整体乘以1/6
  2. Abs(selfLocal, selfLocal):对self取绝对值,将-3 < self < 3的双重比较收敛为单次Abs(x) < 3
  3. CompareScalar(compareMask, selfLocal, UPPER_BOUND, LT):与上界比较生成掩码;
  4. Select<T, uint8_t>(resultLocal, compareMask, resultLocal, 0):按掩码选择,区间外置 0。

这一优化将双 compare 收敛为单次Abs + CompareScalar + Select,减少了指令数。BFLOAT16full-tile 路径则先 cast 到FLOAT32临时缓冲,用标量循环逐元素判断区间并乘1/6,再CAST_ROUNDBFLOAT16输出。

tail 路径(标量安全实现)ComputeTail对剩余元素逐元素读取selfLocalgradLocal,在(-3, 3)内输出gradValue * SCALE_VALUE,否则置 0,保证边界语义精确。CopyInTail/CopyOutTail使用DataCopyPad以 0 填充补齐对齐长度,避免越界访问。

边界语义:注意区间是严格开区间(-3, 3),即self == -3self == 3时输出 0。示例数据中self = 3.0对应的输出为 0,可验证该边界行为。

调用示例

以下代码改编自 examples/test_aclnn_hard_sigmoid_grad_v3.cpp,展示最小化的两段式调用流程:

#include "aclnnop/aclnn_hardsigmoid_backward.h" aclnnStatus RunHardsigmoidBackward(const aclTensor *gradOutput, const aclTensor *self, aclTensor *out, aclrtStream stream) { uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; auto ret = aclnnHardsigmoidBackwardGetWorkspaceSize( gradOutput, self, out, &workspaceSize, &executor); if (ret != ACL_SUCCESS) { return ret; } void *workspace = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); if (ret != ACL_SUCCESS) { return ret; } } ret = aclnnHardsigmoidBackward(workspace, workspaceSize, executor, stream); if (workspace != nullptr) { aclrtFree(workspace); } return ret; }

调用注意事项:

  • workspace 只在workspaceSize > 0时需要申请,使用aclrtMallocACL_MEM_MALLOC_HUGE_FIRST模式;执行完成后必须aclrtFree释放;
  • 建议在第二段接口之后调用aclrtSynchronizeStream(stream)等待计算完成,再进行 Device → Host 的结果拷贝;
  • 完整的初始化(aclInitaclrtSetDeviceaclrtCreateStream)、Tensor 构造(aclCreateTensor,含 ND 格式与 strides 推导)、数据搬运(aclrtMemcpy)与资源释放(aclDestroyTensoraclrtDestroyStreamaclrtResetDeviceaclFinalize)流程可参考示例源码。

示例中的数据与期望结果如下(shape 为{4, 2}self取值覆盖边界与区间内外):

self = {-4, -2, -1, 0, 1, 2, 3, 4} grad = { 1, 2, 3, 4, 5, 6, 7, 8} out = { 0, 2/6, 3/6, 4/6, 5/6, 1, 0, 0}

示例程序会逐元素与期望值比较(容差1e-5),全部通过后打印hard_sigmoid_grad_v3 example passed

约束说明

  • 确定性计算aclnnHardsigmoidBackward为默认确定性实现;
  • 数据类型:输入仅支持FLOAT32FLOAT16BFLOAT16,其中BFLOAT16仅在 Ascend910B 及后续同代 SoC 上支持;
  • 混合精度gradOutputself允许混合精度输入,内部按PromoteType(gradOutput, self)选择计算 dtype;提升结果为FLOAT16时进一步升到FLOAT32计算;
  • 输出 castout的 dtype 需要支持从内部计算 dtype cast 回写;
  • Shape 约束gradOutputselfout的 shape 必须完全一致;
  • 维度:支持 0 到 8 维 Tensor;支持标量输入(Host 侧通过EnsureNotScalar适配为 shape{1});
  • 空 Tensor:三个入参均支持空 Tensor;
  • 非连续 Tensor:支持非连续输入/输出,接口内部按需执行ContiguousViewCopy

测试与运行验证

单元测试(op_api UT)

仓库在 tests/ut/op_api/test_aclnn_hardsigmoid_backward.cpp 中覆盖了以下用例:

用例场景精度容差
case_001_floatFLOAT32,shape{2,4},取值[-10, 10]0.0001
case_002_float16FLOAT16,shape{2,5}0.001
case_003_bfloat16BFLOAT16,按平台断言成败(仅 910B 系支持)0.004
case_004_mixed_dtypeFLOAT16+FLOAT32混合精度输入0.0001
case_005_empty_tensorshape{2,0}空 Tensor0.0001
case_006_not_contiguousstrides{4,5}的非连续 Tensor0.0001

运行 op_api 单元测试:

source /usr/local/Ascend/cann/set_env.sh cd <ops-nn-repo> bash build.sh --experimental --ops=hard_sigmoid_grad_v3 -u --opapi -j8 -O2

Example 运行

确保 custom run 包已安装并加载 CANN 环境后执行:

source /usr/local/Ascend/cann/set_env.sh export LD_LIBRARY_PATH=/usr/local/Ascend/cann/opp/vendors/customize_nn/op_api/lib:${LD_LIBRARY_PATH} cd <ops-nn-repo>/experimental/activation/hard_sigmoid_grad_v3/examples bash run.sh

ATK 小规模标准化测试

使用 tests/st/aclnnHardsigmoidBackward/all_aclnnHardsigmoidBackward.json(适用于 ATK 的小规模标准化测试集)与 tests/st/aclnnHardsigmoidBackward/executor_aclnnHardsigmoidBackward.py(ATK CPU benchmark 执行器):

export ATK_BIND_CPU_TYPE=2 source /usr/local/Ascend/cann/set_env.sh source /root/src/kernel/ascend-kernel/.venv/bin/activate cd <testcase-repo> atk node --backend npu --devices 2 \ node --backend cpu task --task accuracy \ -c <ops-nn-repo>/experimental/activation/hard_sigmoid_grad_v3/tests/st/aclnnHardsigmoidBackward/all_aclnnHardsigmoidBackward.json \ -p <ops-nn-repo>/experimental/activation/hard_sigmoid_grad_v3/tests/st/aclnnHardsigmoidBackward/executor_aclnnHardsigmoidBackward.py

目录导航

路径说明
docs/aclnnHardsigmoidBackward.mdaclnnHardsigmoidBackward接口文档(本文依据)
README.md模块说明、约束与运行方式
op_host/op_api/aclnn_hardsigmoid_backward.cpp对外 ACLNN 接口入口与构图逻辑
op_host/op_api/hard_sigmoid_grad_v3.cpp内部l0op::HardSigmoidGradV3封装
op_host/hard_sigmoid_grad_v3_tiling.cppHost tiling:多核切分与 UB 分片
op_kernel/hard_sigmoid_grad_v3.hAscendC kernel 核心类与向量化计算
examples/test_aclnn_hard_sigmoid_grad_v3.cpp完整可编译的两段式调用示例
tests/ut/op_api/test_aclnn_hardsigmoid_backward.cppop_api 单元测试
tests/st/aclnnHardsigmoidBackward/ATK 标准化测试集与执行器

至此,从接口语义、参数约束、错误码到 Host 构图与 AscendC kernel 向量化实现,再到单元测试与运行验证,aclnnHardsigmoidBackward的完整技术脉络已经清晰:外层 ACLNN 封装负责 dtype 提升、连续性适配与回写,内层HardSigmoidGradV3kernel 通过 cache line 对齐的多核切分、Abs + CompareScalar + Select的向量化收敛与 BF16 升精度策略,在保证-3 < self < 3精确边界语义的前提下完成高效的 HardSigmoid 反向梯度计算。

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

【免费下载链接】ops-nn

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

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

相关推荐

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

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

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

立即咨询