ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CANN ops-nn 算子解析:HardSwishGrad 反向梯度算子的原理、约束与 aclnn 调用实践

CANN ops-nn 算子解析:HardSwishGrad 反向梯度算子的原理、约束与 aclnn 调用实践 CANN ops-nn 算子解析HardSwishGrad 反向梯度算子的原理、约束与 aclnn 调用实践【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nnHardSwishGrad 是 CANN ops-nn 开源算子库中用于计算 HardSwish 激活函数反向梯度的 NPU 算子本指南以 experimental/activation/hard_swish_grad/README.md 为骨架结合其 aclnn 接口文档、op_host / op_kernel 源码与单元测试系统讲解该算子的数学原理、产品支持情况、参数与约束、aclnn 两级接口的完整调用流程并深入剖析其在 NPU 上的 Tiling 与 AscendC kernel 实现。读完本文你将掌握如何在 Atlas A2/A3 系列产品上正确调用该算子并能依据源码理解其内部计算链路与精度处理细节。算子功能与数学原理HardSwishGrad 是 HardSwish 激活函数的反向传播算子。在训练场景中反向过程需要根据上游回传的梯度grad和前向阶段的输入x计算当前层的局部梯度并将二者相乘得到输出y。其计算公式为引自 README.mdy 0 , x -3 y grad * (x / 3 0.5) , -3 x 3 y grad , x 3也就是说当输入x落在(-3, 3)区间内时局部梯度为x / 3 0.5当x -3时梯度被截断为 0当x 3时局部梯度退化为 1。这与 HardSwish 前向函数x * relu6(x 3) / 6的导数在数学上完全对应relu6(x 3)的导数在x -3时为 0在-3 x 3时为1/3在x 3时为 0再加上对x自身的偏导项后恰好整理为上式的分段形式。产品支持情况根据 README.md 的产品支持说明产品是否支持Atlas A2 训练系列产品√Atlas A3 系列产品√对应地在 hard_swish_grad_def.cpp 的算子注册中通过AICore().AddConfig()为算子配置了ascend910b与ascend910_93两类 AICore 配置分别对应上述产品线。同时在 aclnn_hardswish_backward.cpp 中可以看到按平台区分的 dtype 支持列表ASCEND910BAtlas A2 训练系列支持FLOAT、FLOAT16、BF16ASCEND910Atlas A3 系列所依托的计算平台支持FLOAT、FLOAT16。参数说明HardSwishGrad 算子共包含两个输入、一个输出均为 ND 格式 Tensor具体如下引自 README.md参数名输入/输出/属性描述数据类型数据格式grad输入上游梯度 TensorFLOAT16、FLOAT、BFLOAT16NDx输入HardSwish 前向输入 Tensor用于确定梯度系数与grad一致与grad一致y输出HardSwish 反向梯度 Tensor与grad一致与grad一致在算子定义层hard_swish_grad_def.cpp 中通过Input(grad)、Input(x)、Output(y)声明了三个 Tensor 端口三者均声明为REQUIRED必选数据类型约束为{ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}数据格式约束为{ge::FORMAT_ND}。约束说明使用 HardSwishGrad 算子时需要遵循以下约束引自 README.mdgrad、x和y的数据类型及数据格式必须一致。grad和x的 shape 必须一致不支持广播。y的 shape 与grad一致。支持动态 shape 和动态 rank。支持空 Tensor。这些约束在源码中有多处对应校验。例如在 hard_swish_grad_tiling.cpp 的GetShapeDtypeInfo中会通过IsSameShape检查x与grad的 storage shape 是否完全一致并校验x与grad的 dtype 是否相同不满足即返回失败在 hard_swish_grad_infershape.cpp 中输出 shape 直接由输入grad的 shape 拷贝得到*outputShape *gradShape这从形状推导层面保证了y与grad的 shape 一致性。此外aclnn 接口层还额外支持非连续 Tensor输入会被接口内部转换为连续 Tensor计算结果通过 ViewCopy 写回out详见下文接口实现。aclnn 两级接口与完整调用流程该算子的官方推荐调用方式为 aclnn 接口aclnnHardswishBackward接口说明文档见 docs/aclnnHardswishBackward.md完整可编译的调用样例见 examples/test_aclnn_hard_swish_grad.cpp。两级接口函数原型aclnn L2 接口采用「计算 workspace 执行」的两段式设计。第一段接口负责参数校验、构建计算图并计算 workspace 大小aclnnStatus aclnnHardswishBackwardGetWorkspaceSize( const aclTensor *gradOutput, // 输入上游梯度 Tensor const aclTensor *self, // 输入HardSwish 前向输入 Tensor aclTensor *out, // 输出反向梯度结果 Tensor uint64_t *workspaceSize, // 输出device 侧 workspace 大小 aclOpExecutor **executor); // 输出执行器供第二段接口使用第二段接口负责真正在 NPU 上执行计算aclnnStatus aclnnHardswishBackward( void *workspace, // 输入device 侧 workspace 地址 uint64_t workspaceSize, // 输入workspace 大小由第一段接口返回 aclOpExecutor *executor, // 输入第一段接口返回的执行器 aclrtStream stream); // 输入执行任务的 ACL stream两个接口的返回值为aclnnStatusACLNN_SUCCESS表示执行成功非 0 值表示参数校验、资源申请或算子执行失败。第一段接口参数说明参数名输入/输出描述数据类型数据格式shapegradOutput输入上游梯度 TensorFLOAT16、FLOAT、BFLOAT16ND任意合法 shapeself输入HardSwish 前向输入 Tensor与gradOutput一致与gradOutput一致与gradOutput一致out输出HardSwish 反向梯度结果 Tensor与gradOutput一致与gradOutput一致与gradOutput一致workspaceSize输出返回 device 侧 workspace 大小---executor输出返回执行器供第二段接口使用---第二段接口参数说明参数名输入/输出描述workspace输入device 侧 workspace 地址当workspaceSize为 0 时可传入空指针workspaceSize输入workspace 大小由第一段接口返回executor输入第一段接口返回的执行器stream输入执行任务的 ACL stream接口层实现要点在 aclnn_hardswish_backward.cpp 中可以看到接口层的完整实现逻辑参数校验CheckParams依次检查三个 Tensor 是否为空指针、dtype 是否在支持列表内且三者一致、shape 是否一致且不超过最大维度MAX_SUPPORT_DIMS_NUMS。空 Tensor 提前返回若gradOutput或self为空 Tensor则直接返回workspaceSize 0不构建计算图。非连续输入处理分别对self和gradOutput调用l0op::Contiguous转成连续 Tensor。调用底层算子通过l0op::HardSwishGrad生成算子计算节点。结果写回通过l0op::ViewCopy将计算结果拷贝到可能非连续的输出out上。获取 workspace 大小*workspaceSize uniqueExecutor-GetWorkspaceSize()随后将 executor 转移给调用方。接口注释中给出的计算图路径为gradOutput → Contiguous → HardSwishGrad → ViewCopy → out self → Contiguous → HardSwishGrad第二段接口aclnnHardswishBackward则直接调用CommonOpExecutorRun完成计算执行这是 aclnn L2 接口的标准封装形式。完整调用示例解析examples/test_aclnn_hard_swish_grad.cpp 给出了从环境初始化到结果校验的完整 eager 调用流程核心步骤包括初始化 ACL 环境aclInit(nullptr)、aclrtSetDevice(0)、aclrtCreateStream(stream)。创建 device Tensor通过aclrtMalloc分配 device 内存用aclrtMemcpy将 host 数据拷贝到 device再通过aclCreateTensor构造aclTensor示例中使用ACL_FLOAT与ACL_FORMAT_ND并显式计算 strides。第一段接口调用aclnnHardswishBackwardGetWorkspaceSize(grad, x, y, workspaceSize, executor)获取 workspace 大小与执行器若workspaceSize 0则aclrtMalloc分配 workspace。第二段接口调用aclnnHardswishBackward(workspaceAddr, workspaceSize, executor, stream)执行计算随后aclrtSynchronizeStream同步等待任务完成。结果校验通过aclrtMemcpyDEVICE_TO_HOST取回结果与Golden函数计算的期望值逐元素比对绝对/相对容差均为1e-5。资源释放依次释放 workspace、Tensor、device 内存、stream并调用aclrtResetDevice与aclFinalize。示例选取 shape 为{9}的输入xData {-4, -3, -2, -1, 0, 1, 2, 3, 4}覆盖了x -3、-3 x 3、x 3三个分段区间以及grad正负取值可对算子的分段逻辑做完整的正确性验证。算子实现原理从 Tiling 到 AscendC Kernel除 aclnn 接口外算子还包含标准的 op_host算子定义、shape 推导、Tiling与 op_kernelAscendC 内核实现共同构成完整的昇腾算子开发范式。Tiling多核切分与 UB 分块hard_swish_grad_tiling.cpp 实现了 Tiling 逻辑核心是生成 hard_swish_grad_tiling_data.h 中定义的HardSwishGradTilingData结构struct HardSwishGradTilingData { int64_t totalNum 0; // 总元素数量 int64_t blockFactor 0; // 每个核处理的元素数量 int64_t ubFactor 0; // 每次 UB 循环处理的元素数量 };Tiling 过程的主要步骤通过GetPlatformInfo获取 AIV 核数GetCoreNumAiv与 UB 内存大小GetCoreMemSize为后续切分提供硬件依据通过GetShapeDtypeInfo校验x与gradshape/dtype 一致scalar Tensor 会被归一化为{1}处理并统计总元素数totalNum按blockFactor CeilDiv(totalNum, coreNum)将总元素均分到多个核上usedCoreNum CeilDiv(totalNum, blockFactor)作为实际使用的核数根据 UB 容量预留UB_MASK_RESERVE 1024字节计算单次循环可处理的元素数ubFactor其中按每元素 48 字节估算BYTES_PER_ELEMENT 48对应 FP16/BF16 计算时在 UB 中展开为 FP32 的中间量并对齐到向量对齐单位VECTOR_ALIGN_ELEM 64与 UB block size 取较大值按 dtype 选择 Tiling KeyFP16、BF16、FP32 分别对应HARDSWISHGRAD_TPL_SCH_MODE_FP16/BF16/FP32totalNum 0空 Tensor时设置SetBlockDim(1)直接返回。AscendC Kernel逐元素分段计算hard_swish_grad.cpp 是 kernel 入口按 Tiling Key 对 FP16 / FP32 / BF16 三种模板实例化并调用 hard_swish_grad.h 中的NsHardSwishGrad::HardSwishGradT内核类。内核类使用标准 AscendC 流水线TPipeTQueTBuf实现CopyIn → Compute → CopyOut三段流水每个核根据blockFactor定位自己在全局内存中的偏移blockOffset blockFactor * GetBlockIdx()再按ubFactor分块循环处理。其计算逻辑值得关注常量预置在Init阶段通过Duplicate预置-3.0、3.0、0.0、1.0四个常量缓冲lowerBuf、upperBuf、zeroBuf、oneBuf。类型统一为 FP32 计算对 FP16 / BF16 输入先用CastCAST_NONE模式转换为 FP32再进入统一计算路径保证三种 dtype 的精度行为一致FP32 输入则直接ReinterpretCast复用缓冲。区间掩码用CompareCMPMODE::GT/CMPMODE::LT分别生成x -3与x 3的两个掩码greaterMask/lessMask计算数量按 64 元素对齐AlignComputeNum。斜率计算与分段选择先算slope x * (1/3) 0.5对应Muls(slope, xFp32, 0.333333343f)与Adds(slope, slope, 0.5f)再用两次SelectVSEL_TENSOR_TENSOR_MODE实现分段x -3时选0.0x 3时选1.0区间内保留x / 3 0.5。梯度相乘Mul将上游梯度与斜率相乘得到最终结果FP16 / BF16 输出前再做一次CastBF16 使用CAST_RINT舍入FP16 使用CAST_NONE写回输出队列。这种「先算连续表达式 掩码 Select 分段」的实现方式避免了逐元素分支跳转能够充分利用向量单元的流水化吞吐。单元测试验证仓库为该算子提供了 host 侧与 kernel 侧两级单元测试host 侧 Tiling 测试tests/ut/op_host/test_hard_swish_grad_tiling.cpp用于验证 Tiling 数据totalNum、blockFactor、ubFactor与核数切分结果是否符合预期kernel 侧测试tests/ut/op_kernel/test_hard_swish_grad.cpp用于在 NPU 上验证 kernel 实际计算结果。二者与 examples/test_aclnn_hard_swish_grad.cpp 中的Golden函数相互印证共同覆盖了从 Tiling 参数生成、kernel 计算到 aclnn 接口调用的完整链路可作为二次开发或移植到其他算子的参考范式。小结HardSwishGrad 算子是一个典型的逐元素反向激活算子数学上以x所在区间决定局部梯度系数并与上游梯度相乘工程上则通过 aclnn 两级接口暴露调用入口内部由 op_host 完成 dtype/shape 校验与多核 Tiling由 AscendC kernel 以「FP32 统一计算 掩码 Select 分段」的方式实现向量化计算。若需要在 Atlas A2/A3 系列产品上实现 HardSwish 的反向传播直接参照 aclnnHardswishBackward 的接口文档与 调用示例 即可快速接入若希望深入理解其底层机制则可按本文给出的源码路径依次阅读 op_def、infershape、tiling 与 kernel 实现。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表