- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
导读
HardSwishV2 是 CANN ops-nn 神经网络算子库中位于experimental/activation/hard_swish_v2目录下的一类逐元素激活算子,它对输入张量 x 按 HardSwish 公式逐元素计算输出 y,用于在 Atlas A2 训练/推理系列产品的 NPU 上加速神经网络中的激活计算。读完本文,你将掌握 HardSwishV2 的数学定义与参数约束、通过 aclnnHardSwishV2 双段式接口在 C++ 工程中完成算子调用的完整流程,并能沿着算子原型注册、shape 推导、tiling 切分到 Vector 核 Kernel 实现的源码路径,理解其在昇腾硬件上的底层执行原理。
一、功能说明:HardSwish 激活的数学定义
根据 README 的定义,HardSwishV2 算子对输入张量 x 逐元素计算 HardSwish 激活函数值,计算公式为:
y = (x * min(max(x + 3, 0), 6)) / 6该公式等价于x * max(0, min(1, (x + 3) / 6)),这一点在算子原型文件 hard_swish_v2_proto.h 的接口注释中也有明确表述。HardSwish 是 Swish 激活函数(x * sigmoid(x))的硬近似版本,用分段线性函数替代了计算开销较高的 sigmoid,在保证近似精度的同时显著降低计算成本,常用于 MobileNetV3 等轻量级网络的激活层。
从数值行为上看,该函数具备如下分段特征:
- 当
x <= -3时,max(x + 3, 0) = 0,输出恒为 0; - 当
-3 < x < 3时,输出为x * (x + 3) / 6,即二次曲线段; - 当
x >= 3时,min(x + 3, 6) = 6,输出恒等于 x(线性段)。
二、产品支持情况与数据类型
README 中明确的产品支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | √ |
该平台支持情况在算子定义中也能得到印证。hard_swish_v2_def.cpp 中通过this->AICore().AddConfig("ascend910b", aicoreConfig)注册了 ascend910b(Atlas A2 系列)的 AICore 配置,并开启了动态编译、动态 shape 与动态 rank 支持。
数据类型支持方面,README 与源码完全一致,输入输出支持的数据类型为:
| 类型 | 说明 |
|---|---|
| float(float32) | 单精度浮点 |
| half(float16) | 半精度浮点 |
| bfloat16 | 脑浮点格式 |
该约束在 hard_swish_v2_def.cpp 中通过DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16})声明,在 hard_swish_v2_binary.json 中则为 float32、float16、bfloat16 分别登记了对应的 kernel 二进制入口。
三、参数说明
HardSwishV2 的输入输出参数定义如下(与 README 参数表一致):
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| self(x) | 输入 | 输入张量 x | float、half、bfloat16 | ND |
| out(y) | 输出 | 输出张量 y | float、half、bfloat16 | ND |
补充说明如下:
- 数据格式:输入输出均为 ND 格式(即通用多维格式),
Format({ge::FORMAT_ND, ...})同时被设置为静态与动态 shape 场景下的统一格式。 - shape 关系:输出 y 与输入 x 的 shape 完全一致。在 hard_swish_v2_infershape.cpp 中通过
*yShape = *xShape直接继承输入 shape;tiling 阶段 hard_swish_v2_tiling.cpp 还会对输入输出逐维度校验 shape 是否匹配,不匹配将直接报错返回失败。 - dtype 推导:输出的数据类型与输入保持一致,见 hard_swish_v2_graph_infer.cpp 中
InferDataType的实现。
四、约束说明与当前限制(待更新)
README 中"约束说明"一节明确为无特殊约束;但在"待更新"一节中指出了当前版本的已知限制,开发者在使用时需特别注意:
- 支持的数据类型:目前仅支持 float32、float16、bfloat16 三种数据类型;
- 不支持 broadcast:输入与输出张量必须逐维度 shape 一致(tiling 阶段会强制校验),后续版本会持续改进。
另外从代码注释与 test_geir_hard_swish_v2.cpp 示例看,该算子同时提供 aclnn 与 GEIR(Graph Engine IR)两种接入形态,当前仓库 README 以 aclnn 接口为主要推荐调用方式。
五、调用说明:aclnn 双段式接口调用完整流程
README 的调用说明给出了核心入口:test_hard_swish_v2,即通过aclnnHardSwishV2接口调用该算子。以下基于示例代码拆解完整的 aclnn 调用范式,该范式也是 CANN 算子库中所有单算子调用的通用流程。
5.1 调用方式速览
| 调用方式 | 样例代码 | 说明 |
|---|---|---|
| aclnn 接口 | test_aclnn_hard_swish_v2.cpp | 通过 aclnnHardSwishV2 接口方式调用 HardSwishV2 算子 |
5.2 第一步:初始化 ACL 运行环境
调用算子前必须先完成 ACL 初始化、设置 device 并创建 stream:
#include "acl/acl.h" #include "aclnn_hard_swish_v2.h" int Init(int32_t deviceId, aclrtStream* stream) { auto ret = aclInit(nullptr); // 1. 初始化 ACL CHECK_RET(ret == ACL_SUCCESS, return ret); ret = aclrtSetDevice(deviceId); // 2. 设置计算设备 CHECK_RET(ret == ACL_SUCCESS, return ret); ret = aclrtCreateStream(stream); // 3. 创建 stream CHECK_RET(ret == ACL_SUCCESS, return ret); return 0; }示例默认使用deviceId = 0,并以 4 x 8(共 32 个元素)、值全为 1 的 float32 张量作为输入进行验证。
5.3 第二步:构造 aclTensor 输入与输出
需要通过aclCreateTensor将 host 侧数据搬运到 device 并包装为aclTensor。示例中的CreateAclTensor模板函数完整展示了这一过程:先按 shape 计算连续张量的 strides,再aclrtMalloc申请 device 内存、aclrtMemcpy拷贝数据,最后以ACL_FORMAT_ND格式创建 tensor:
// 计算连续 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);输入张量 x 与输出张量 out 均需按此方式构造,且输出 shape 与输入 shape 保持一致(示例中均为{4, 8})。
5.4 第三步:双段式接口执行算子
aclnn 单算子 API 采用"先获取 workspace 大小、再执行"的双段式设计:
// 第一段:获取 workspaceSize 与 executor uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnHardSwishV2GetWorkspaceSize(selfX, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, return ret); // 按需为 workspace 申请 device 内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, return ret); } // 第二段:真正下发算子任务到 stream ret = aclnnHardSwishV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, return ret); // 同步等待任务执行结束 ret = aclrtSynchronizeStream(stream);aclnnHardSwishV2GetWorkspaceSize会基于算子 tiling 结果计算执行所需的 workspace 大小(本算子在 hard_swish_v2_tiling.cpp 中通过GetWorkspaceSize申请 16MB 系统 workspace 与平台库 workspace 之和);执行完成后必须调用aclrtSynchronizeStream同步等待。
5.5 第四步:结果回拷与资源释放
执行完成后将 device 侧结果拷回 host 并打印,最后依次释放 tensor、device 内存与 stream:
PrintOutResult(outShape, &outDeviceAddr); // aclrtMemcpy(DEVICE_TO_HOST) 回拷并打印 aclDestroyTensor(selfX); // 释放 aclTensor aclDestroyTensor(out); aclrtFree(selfXDeviceAddr); // 释放 device 内存 aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); // ACL 去初始化5.6 GEIR 调用形态
除 aclnn 外,仓库还提供了 test_geir_hard_swish_v2.cpp 作为 GEIR 图模式下的调用示例,适用于通过图引擎(Graph Engine IR)方式构建算子图的场景,其算子原型即由 hard_swish_v2_proto.h 中REG_OP(HardSwishV2)注册。
六、源码级原理:从算子注册到 NPU Kernel 执行
HardSwishV2 在仓库中的完整实现遵循 CANN 算子开发的标准五件套结构,各文件职责如下:
| 目录/文件 | 职责 |
|---|---|
| op_graph/hard_swish_v2_proto.h | 算子原型(Proto)注册,声明输入输出与数据类型 |
| op_graph/hard_swish_v2_graph_infer.cpp | 图模式下的 dtype 推导 |
| op_host/hard_swish_v2_def.cpp | 算子定义注册(OpDef),声明输入输出、AICore 配置 |
| op_host/hard_swish_v2_infershape.cpp | shape 推导:输出继承输入 shape |
| op_host/hard_swish_v2_tiling.cpp | tiling 切分:按核数、UB 大小计算切分参数 |
| op_kernel/hard_swish_v2.cpp | Kernel 入口(模板调度) |
| op_kernel/hard_swish_v2.h | 算子 Kernel 主体实现(CopyIn/Compute/CopyOut) |
6.1 算子定义与动态 shape 支持
hard_swish_v2_def.cpp 中OpAICoreConfig的配置揭示了该算子的关键能力:
DynamicCompileStaticFlag(true):支持静态 shape 下的动态编译;DynamicShapeSupportFlag(true)与DynamicRankSupportFlag(true):支持动态 shape 与动态 rank,即输入维度数可变;PrecisionReduceFlag(true):开启精度优化;ExtendCfgInfo("opFile.value", "hard_swish_v2"):将算子与 kernel 入口文件hard_swish_v2.cpp绑定。
6.2 tiling 切分策略
tiling 阶段(hard_swish_v2_tiling.cpp)是整个算子的核心调度逻辑,主要完成:
- 通过
GetPlatformInfo获取 AIV 核数量coreNum与片上 UB 内存大小ubSize; - 校验输入输出 shape 逐维度一致,并校验 dtype 属于支持集合;
- 处理空输入(
totalIdx <= 0)的边界情况,直接设置SetBlockDim(1)返回; - 按
BLOCK_SIZE = 32字节、双缓冲BUFFER_NUM = 2计算每 tile 元素数tileDataNum; - 将总数据按核数均分,并对余数块采用"前
tailBlockNum个核多分一个 block"的 big-core/small-core 均衡策略; - 将切分结果写入
HardSwishV2TilingData结构体(定义见 hard_swish_v2_tiling_data.h),包括smallCoreDataNum、bigCoreDataNum、tileDataNum、tailDataNum、finalTileNum、tailBlockNum等字段。
6.3 Kernel 计算流程与精度处理
Kernel 入口 hard_swish_v2.cpp 为模板调度函数:解析 tiling 数据后实例化NsHardSwishV2::HardSwishV2<T>并依次执行Init、Process。主体实现 hard_swish_v2.h 中按标准流水线组织:
- CopyIn / CopyOut:通过
DataCopy在 Global Memory 与 Local Memory 之间搬运数据,配合TPipe与双缓冲队列inputQueueX / outputQueueZ实现数据搬运与计算的流水重叠; - Compute:按公式逐段计算,值得关注的是其精度策略——对 bfloat16 与 float16 输入,先将数据
Cast到 float 精度完成全部中间运算(Adds +3、Maxs 与 0 取大、Mins 与 6 取小、Duplicate 6.0、Div 除以 6、Mul 乘以原始 x),bfloat16 回写时使用CAST_RINT舍入模式以保证精度;而 float32 输入则直接在同精度下完成同样的向量指令序列。
该实现将公式y = x * min(max(x + 3, 0), 6) / 6精确拆解为一条条 Vector 指令:Adds(加 3)→Maxs(与 0 取大)→Mins(与 6 取小)→Div(除以 6)→Mul(乘回原始 x),每个步骤之间以PipeBarrier<PIPE_V>保证向量流水线同步,最终结果通过EnQue送入输出队列写回。
七、小结与使用建议
HardSwishV2 作为 CANN ops-nn 中面向 Atlas A2 系列产品的逐元素激活算子,数学定义简单清晰(y = x * min(max(x + 3, 0), 6) / 6),接口形态规范:既可通过aclnnHardSwishV2双段式接口完成单算子调用,也可通过 GEIR 接入图模式。使用时的关键注意点集中在三处:
- 数据类型:仅支持 float32、float16、bfloat16,且输入输出类型一致;
- shape 约束:不支持 broadcast,输出与输入需逐维度一致;
- 调用范式:务必遵循"GetWorkspaceSize → 申请 workspace → 执行 → SynchronizeStream → 释放资源"的完整流程,不可遗漏 workspace 内存申请与 stream 同步。
如需进一步研究,可继续阅读该算子目录下的 tiling 与 kernel 源码,或对照仓库中其他激活算子(如 hard_swish 系列)的同类实现,理解 CANN 算子的通用开发模式。
- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
相关推荐
Obsidian插件汉化终极指南:5分钟实现全界面中文的简单方法
Obsidian插件汉化终极指南:5分钟实现全界面中文的简单方法 Obsidian i18n是一款专为Obsidian设计的插件汉化和本地化工具,它能让普通用户
人工智能算子库深度学习CANNAscendCANN ops-nn 中 LeakyRelu 算子解析:从 aclnn 两段式接口调用到 NPU 内核实现
CANN ops nn 中 LeakyRelu 算子解析:从 aclnn 两段式接口调用到 NPU 内核实现 本篇技术文章围绕 CANN 开源算子库 ops n
人工智能算子库深度学习CANNAscendInsGallery与PictureSelector深度整合:如何借助开源力量实现产品级媒体选择功能
InsGallery与PictureSelector深度整合:如何借助开源力量实现产品级媒体选择功能 在移动应用开发中,媒体选择功能是用户交互的核心环节之一。I
人工智能算子库深度学习CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考