- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
本文围绕 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)。
关键数值特性:
- 当
gradOutput与self提升后的计算 dtype 为FLOAT16时,接口内部会进一步将计算精度提升到FLOAT32,完成计算后再 cast 回输出 dtype; BFLOAT16路径则按提升规则直接使用BFLOAT16/FLOAT32路径计算;gradOutput与self允许混合精度输入,接口内部先按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 及后续同代 SoC | ND | 0-8 | √ |
| self(aclTensor*) | 输入 | 前向输入张量,用于生成 HardSigmoid 反向掩码。 | 支持空 Tensor;shape 必须与 gradOutput、out 完全一致;数据类型需要在支持范围内。 | FLOAT16、FLOAT32;BFLOAT16 仅支持 Ascend910B 及后续同代 SoC | ND | 0-8 | √ |
| out(aclTensor*) | 输出 | 计算的出参。 | 支持空 Tensor;shape 必须与 gradOutput、self 完全一致;数据类型需要支持从内部计算 dtype cast 回写。 | FLOAT16、FLOAT32;BFLOAT16 仅支持 Ascend910B 及后续同代 SoC | ND | 0-8 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小。 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含算子计算流程。 | - | - | - | - | - |
需要特别说明的几点:
- 空 Tensor:
gradOutput、self、out均支持空 Tensor。从 Host 实现 可以看到,当gradOutput或self为空时,第一段接口直接返回workspaceSize = 0并提前结束,不再构造执行图。 - 混合精度:
gradOutput与self可以是不同 dtype,例如FLOAT16+FLOAT32,接口内部先按PromoteType(gradOutput, self)提升,若提升结果为FLOAT16则再升到FLOAT32参与计算。 - 非连续 Tensor:三个 Tensor 均支持非连续排布,接口内部通过
Contiguous与ViewCopy自动适配。
返回值与错误码
第一段接口会完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 gradOutput、self 或 out 是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput 或 self 的数据类型不在支持范围内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 和 out 的 shape 不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 或 out 的维度大于 8。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 提升后的计算 dtype 不能转换为 out 的数据类型。 |
这些校验逻辑可以在 aclnn_hardsigmoid_backward.cpp 中逐一对应:
CheckNotNull使用OP_CHECK_NULL检查三个 Tensor 指针是否为空,对应 161001;CheckDtypeValid先通过OP_CHECK_DTYPE_NOT_SUPPORT检查gradOutput、self的 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校验gradOutput与self、out的 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)添加以下计算节点,构成完整的计算图:
- Contiguous:对
gradOutput、self分别调用l0op::Contiguous,将非连续输入归一为连续存储(仅在需要时产生实际拷贝); - Cast 到计算 dtype:按
GetComputeType的结果(PromoteType(gradOutput, self),若为FLOAT16则再提升为FLOAT)将两个输入分别 cast; - 核心算子:调用
l0op::HardSigmoidGradV3(gradOutputContiguousCasted, selfContiguousCasted)完成主体计算; - Cast 回输出 dtype:将 kernel 输出 cast 为
out声明的数据类型; - 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 数量均匀切分。
totalLengthCore由CeilDiv(totalNum, coreNum)得到,再按 512B cache line 对齐为totalLengthCoreAlign(CACHE_LINE_BYTE_LENGTH = 512),blockFactor即每个核分得的元素数,实际使用的核数为CeilDiv(totalNum, totalLengthCoreAlign); - UB 切分:单个核内的数据再按
ubFactor分片迭代处理。UB 缓冲区需容纳 5 份数据(2 路输入队列 + 2 路临时 mask/浮点缓冲 + 1 路输出队列)乘上 buffer 数量; - 双缓冲:当元素总数较大时启用 double buffer 流水(见下文 TilingKey 说明)。
ubFactor的计算要点:
- 缓冲区系数
bufferCoefficient:FLOAT32用FP32_BUFFER_COEFFICIENT = 25(3 个队列缓冲 + 3 个 FP32 临时缓冲 + 1 个 compare mask,折合约 25 字节/元素),FLOAT16用FP16_VECTOR_BUFFER_COEFFICIENT = 13; maxTileElements = ubSize / bufferCoefficient,再按对齐粒度向下取整得到tileLength;- 对齐粒度取
32 / typeSize与COMPARE_ALIGN_BYTES(256) / typeSize中的较大者,保证向量化 compare 指令的对齐要求; - 最终
tileLength还会被blockTileLimit(即totalLengthCoreAlign对齐后的值)钳制,保证ubFactor <= blockFactor,避免中等 shape 整块退化到 tail 标量路径。
空输入(totalNum == 0)时走SetEmptyInputTiling特判:blockFactor = ubFactor = 0、SetBlockDim(1),kernel 直接返回。
TilingKey 模板参数
TilingKey 定义 使用模板编程声明两个参数:
D_T_X:数据类型,从输入 0 推导,支持C_DT_FLOAT16、C_DT_FLOAT、C_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.0f、UPPER_BOUND = 3.0f、SCALE_VALUE = 1/6,通过 4 步向量指令完成计算:
Muls(resultLocal, gradLocal, SCALE_VALUE):梯度先整体乘以1/6;Abs(selfLocal, selfLocal):对self取绝对值,将-3 < self < 3的双重比较收敛为单次Abs(x) < 3;CompareScalar(compareMask, selfLocal, UPPER_BOUND, LT):与上界比较生成掩码;Select<T, uint8_t>(resultLocal, compareMask, resultLocal, 0):按掩码选择,区间外置 0。
这一优化将双 compare 收敛为单次Abs + CompareScalar + Select,减少了指令数。BFLOAT16full-tile 路径则先 cast 到FLOAT32临时缓冲,用标量循环逐元素判断区间并乘1/6,再CAST_ROUND回BFLOAT16输出。
tail 路径(标量安全实现):ComputeTail对剩余元素逐元素读取selfLocal、gradLocal,在(-3, 3)内输出gradValue * SCALE_VALUE,否则置 0,保证边界语义精确。CopyInTail/CopyOutTail使用DataCopyPad以 0 填充补齐对齐长度,避免越界访问。
边界语义:注意区间是严格开区间(-3, 3),即self == -3或self == 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时需要申请,使用aclrtMalloc的ACL_MEM_MALLOC_HUGE_FIRST模式;执行完成后必须aclrtFree释放; - 建议在第二段接口之后调用
aclrtSynchronizeStream(stream)等待计算完成,再进行 Device → Host 的结果拷贝; - 完整的初始化(
aclInit→aclrtSetDevice→aclrtCreateStream)、Tensor 构造(aclCreateTensor,含 ND 格式与 strides 推导)、数据搬运(aclrtMemcpy)与资源释放(aclDestroyTensor、aclrtDestroyStream、aclrtResetDevice、aclFinalize)流程可参考示例源码。
示例中的数据与期望结果如下(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为默认确定性实现; - 数据类型:输入仅支持
FLOAT32、FLOAT16、BFLOAT16,其中BFLOAT16仅在 Ascend910B 及后续同代 SoC 上支持; - 混合精度:
gradOutput与self允许混合精度输入,内部按PromoteType(gradOutput, self)选择计算 dtype;提升结果为FLOAT16时进一步升到FLOAT32计算; - 输出 cast:
out的 dtype 需要支持从内部计算 dtype cast 回写; - Shape 约束:
gradOutput、self和out的 shape 必须完全一致; - 维度:支持 0 到 8 维 Tensor;支持标量输入(Host 侧通过
EnsureNotScalar适配为 shape{1}); - 空 Tensor:三个入参均支持空 Tensor;
- 非连续 Tensor:支持非连续输入/输出,接口内部按需执行
Contiguous与ViewCopy。
测试与运行验证
单元测试(op_api UT)
仓库在 tests/ut/op_api/test_aclnn_hardsigmoid_backward.cpp 中覆盖了以下用例:
| 用例 | 场景 | 精度容差 |
|---|---|---|
| case_001_float | FLOAT32,shape{2,4},取值[-10, 10] | 0.0001 |
| case_002_float16 | FLOAT16,shape{2,5} | 0.001 |
| case_003_bfloat16 | BFLOAT16,按平台断言成败(仅 910B 系支持) | 0.004 |
| case_004_mixed_dtype | FLOAT16+FLOAT32混合精度输入 | 0.0001 |
| case_005_empty_tensor | shape{2,0}空 Tensor | 0.0001 |
| case_006_not_contiguous | strides{4,5}的非连续 Tensor | 0.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 -O2Example 运行
确保 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.shATK 小规模标准化测试
使用 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.md | aclnnHardsigmoidBackward接口文档(本文依据) |
| 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.cpp | Host tiling:多核切分与 UB 分片 |
| op_kernel/hard_sigmoid_grad_v3.h | AscendC kernel 核心类与向量化计算 |
| examples/test_aclnn_hard_sigmoid_grad_v3.cpp | 完整可编译的两段式调用示例 |
| tests/ut/op_api/test_aclnn_hardsigmoid_backward.cpp | op_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上加速计算。
相关推荐
CANN ops-nn 算子解析:aclnnHardswishBackward 两段式 ACLNN 接口实现 HardSwish 反向梯度计算
CANN ops nn 算子解析:aclnnHardswishBackward 两段式 ACLNN 接口实现 HardSwish 反向梯度计算 导读 aclnn
人工智能算子库深度学习CANNAscendCANN ops-nn 中 aclnnHardsigmoidBackward 接口详解:NPU 上 HardSigmoid 激活反向梯度计算
CANN ops nn 中 aclnnHardsigmoidBackward 接口详解:NPU 上 HardSigmoid 激活反向梯度计算 本文围绕 CANN
人工智能算子库深度学习CANNAscendCANN ops-nn 算子详解:aclnnSoftmaxGrad 两段式接口实现 Softmax 反向梯度计算
CANN ops nn 算子详解:aclnnSoftmaxGrad 两段式接口实现 Softmax 反向梯度计算 Softmax 反向传播是神经网络训练中最常见
人工智能算子库深度学习CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考