news 2026/9/19 20:13:16

CANN ops-nn 算子库 aclnnSwish 接口详解:Swish 激活算子的两段式调用与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-nn 算子库 aclnnSwish 接口详解:Swish 激活算子的两段式调用与源码实现

CANN ops-nn 算子库 aclnnSwish 接口详解:Swish 激活算子的两段式调用与源码实现

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

本文以 CANN ops-nn 开源仓库中的 aclnnSwish 接口文档 为核心,系统讲解 Swish 激活算子的数学定义、产品支持情况、两段式接口原型、参数约束与返回码,并深入 activation/swish 目录下的 op_graph、op_host、op_kernel、op_api 源码与测试用例,说明该算子从 Host 侧校验、Tiling 到 NPU 内核计算的完整实现链路。读完本文,你将掌握在 CANN 环境下通过 aclnnSwishGetWorkspaceSize / aclnnSwish 两段式接口编写可运行样例的方法,并能读懂算子底层按 scale 取值分派的内核实现。

一、功能说明与数学原理

aclnnSwish 接口实现的算子功能为Swish 激活函数:对输入 Tensor 逐元素进行 Swish 函数运算,并输出结果 Tensor。

其计算公式为:

$$ s(x) = x*\sigma(\beta x) $$

$$ \sigma(x) = {\frac{1} {1+{e}^{-x}}} $$

其中 $\sigma(x)$ 为 sigmoid 函数,$\beta$ 为可调节参数(接口参数 betaOptional / 算子属性 scale)。将两式合并,即 Swish 的完整表达式:

$$ s(x) = \frac{x}{1 + e^{-\beta x}} $$

当 $\beta = 1$ 时即为标准的 Swish 函数(与 PyTorch 的torch.nn.SiLU等价,在 swish_proto.h 的算子原型注释中亦注明 "Compatible with the Torch operator Swish")。$\beta$ 用于控制函数曲线的形状与斜率:$\beta$ 越大,函数越接近 ReLU 的形态;$\beta$ 越小,函数越接近线性;$\beta = 0$ 时退化为 $s(x) = x / 2$。

从 kernel 侧实现 可以印证该公式的逐元素计算过程:内核按矢量寄存器宽度(VECTOR_REG_WIDTH = 256UL,float 下vl = 64)分块循环处理,对每个元素依次执行取负(Muls乘 -1)、乘以 scale、指数运算(Exp)、加 1(Adds)与除法(Div),即:

dst = x / (1 + exp(-scale * x))

二、产品支持情况

根据 aclnnSwish.md 的说明,aclnnSwish 接口在各产品上的支持情况如下:

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

需要注意的是,不同产品在数据类型支持范围上存在差异。接口文档中特别注明:Atlas 推理系列产品、Atlas 训练系列产品仅支持 FLOAT16、FLOAT两种数据类型(不包含 BFLOAT16)。这一差异在 aclnn_swish.cpp 源码中得到印证——源码中定义了两张数据类型支持列表:

// 面向 Atlas 910 系列(推理/训练系列产品) static const std::initializer_list<op::DataType> ASCEND910_DTYPE_SUPPORT_LIST = { op::DataType::DT_FLOAT, op::DataType::DT_FLOAT16}; // 面向 Atlas 910B 系列(A2 训练/推理系列产品) static const std::initializer_list<op::DataType> ASCEND910B_DTYPE_SUPPORT_LIST = { op::DataType::DT_FLOAT, op::DataType::DT_FLOAT16, op::DataType::DT_BF16};

并在校验阶段通过GetDtypeSupportListV2(ASCEND910B_DTYPE_SUPPORT_LIST, ASCEND910_DTYPE_SUPPORT_LIST)依据当前运行平台选择适用的支持列表,进而调用CheckDtypeValidActivation完成入参校验。另外,从 swish_def.cpp 的算子定义看,该算子的 AICore 配置已为ascend950ascend350两个平台注册,对应 config 目录 下 ascend950 / ascend350 两套二进制配置。

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

aclnnSwish 属于 CANN 标准的两段式接口——每算子先调用GetWorkspaceSize 段接口获取计算所需 workspace 大小与包含算子计算流程的执行器,再调用执行段接口真正下发计算。本文开头引用的链接路径以仓库根目录为基准,即 两段式接口说明。

接口声明位于 aclnn_swish.h,对外导出原型如下:

aclnnStatus aclnnSwishGetWorkspaceSize( const aclTensor* self, const aclScalar* betaOptional, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)
aclnnStatus aclnnSwish( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)

第一段接口完成入参校验、workspace 大小计算并构建执行器;第二段接口接收第一段产出的 workspace、executor 与用户指定的 stream,实际执行算子计算。

四、aclnnSwishGetWorkspaceSize 参数详解

第一段接口共 5 个参数,各参数含义、约束与取值范围如下:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
self(aclTensor*)输入表示用于计算激活函数的张量,公式中的 x支持空 Tensor;self 的 shape 和数据类型与 out 一致BFLOAT16、FLOAT16、FLOATND0-8
betaOptional(aclScalar*)输入表示可调节参数,用于控制 Swish 函数形状和斜率的标量,公式中的 β数据类型需为可转换为 FLOAT 的数据类型(参见互转换关系);为空指针时接口以 1.0 进行计算----
out(aclTensor*)输出表示 Swish 函数的输出,公式中的 s(x)支持空 Tensor;out 的 shape 和数据类型与 self 一致BFLOAT16、FLOAT16、FLOATND0-8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含算子计算流程-----

需要重点理解的两个输入参数:

  • self:输入张量,公式中的 x,支持 0-8 维、ND 格式,支持非连续 Tensor(接口内部会通过l0op::Contiguous处理非连续输入,见 aclnn_swish.cpp)。BFLOAT16 的支持需以具体产品为准(见上文产品差异)。
  • betaOptional:公式中的 β。当传空指针时,Host 侧实现float scale = 1.0f; if (betaOptional != nullptr) { scale = betaOptional->ToFloat(); },即默认以 1.0 计算,等价于标准 Swish/SiLU。其数据类型必须能够转换为 FLOAT,接口内部通过CanCast(betaOptional->GetDataType(), DataType::DT_FLOAT)检查,不满足时返回ACLNN_ERR_PARAM_INVALID

提示:在算子 IR 层面(swish_def.cpp 与 swish_proto.h),β 对应的是名为scale的 Float 属性(this->Attr("scale").AttrType(OPTIONAL).Float(1.0),默认值 1.0),两者语义一致。

五、返回值与入参校验逻辑

两个接口均返回aclnnStatus状态码,具体含义参见 aclnn 返回码。

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

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 self 或 out 是空指针
ACLNN_ERR_PARAM_INVALID161002self、betaOptional 或 out 的数据类型不在支持的范围内
ACLNN_ERR_PARAM_INVALID161002self 和 out 的数据类型不一致
ACLNN_ERR_PARAM_INVALID161002self 和 out 的 shape 不一致

以上校验在源码CheckParams函数中按序执行(见 aclnn_swish.cpp),对应关系为:

  1. CheckNotNull2Tensor(self, out)→ 空指针检查,返回ACLNN_ERR_PARAM_NULLPTR
  2. CheckDtypeValidActivation(self, out, supportList)→ 数据类型范围与一致性检查;
  3. CheckDtypeValidBetaToFloat(betaOptional)→ β 类型可转 FLOAT 检查;
  4. CheckDim(self, out)OP_CHECK_MAX_DIM,上限MAX_SUPPORT_DIMS_NUMS)与CheckSameShapeNotlimit1In1Out(self, out)→ 维度与 shape 一致性检查。

校验通过后,若 self 或 out 为空 Tensor,接口直接返回*workspaceSize = 0并释放执行器;否则继续构建l0op::Swish计算图并计算 workspace 大小。

六、约束说明:确定性计算

接口文档中给出的约束如下:

  • 确定性计算:aclnnSwish 为默认确定性实现。

即同一输入在相同软硬件环境下多次执行,计算结果保持一致(不会引入不确定性归约等行为)。

七、调用示例(完整可运行代码)

以下示例代码摘自接口文档,完整展示了从环境初始化、Tensor 构造、两段式接口调用、结果回拷到资源释放的全过程。具体编译和执行过程请参考 编译与运行样例,运行前需确保 Device 侧环境已就绪。示例同样可参考仓库中的 examples/test_aclnn_swish.cpp 与 UT 用例 tests/ut/op_api/test_aclnn_swish.cpp。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_swish.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 shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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 == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclScalar* betaOptional = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<float> outHostData = {0, 0, 0, 0, 0, 0, 0, 0}; float betaValue = 1.1f; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建betaOptional aclScalar betaOptional = aclCreateScalar(&betaValue, aclDataType::ACL_FLOAT); CHECK_RET(betaOptional != 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; // 调用aclnnSwish第一段接口 ret = aclnnSwishGetWorkspaceSize(self, betaOptional, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSwishGetWorkspaceSize 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); } // 调用aclnnSwish第二段接口 ret = aclnnSwish(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSwish 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侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float), 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,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyScalar(betaOptional); aclDestroyTensor(out); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

该示例以 β = 1.1、输入{0,1,2,3,4,5,6,7}为例,按s(x) = x / (1 + e^{-1.1x})计算。你可以通过修改betaValue为其他值、或将betaOptional置为nullptr(等价于 β = 1.0)来观察不同曲线参数下的输出;也可以将selfHostData换成 FLOAT16/BFLOAT16 数据并相应调整CreateAclTensordataType参数。

八、源码级实现原理

8.1 算子原型(op_graph)

swish_proto.h 通过REG_OP(Swish)注册算子原型:输入x(TensorType 为 DT_FLOAT16、DT_FLOAT、DT_BF16),输出y(与 x 同类型),属性scale(Float,默认 1.0),并注明与 PyTorch Swish 算子兼容。

8.2 Host 侧算子定义与 Shape 推导(op_host)

swish_def.cpp 定义了算子输入输出的数据类型(FLOAT16/BF16/FLOAT)与格式(ND),并注册了 ascend950、ascend350 两个平台的 AICore 配置(支持动态 Shape、动态 Rank、精度降低标志等)。

swish_infershape.cpp 通过IMPL_OP_INFERSHAPE(Swish).InferShape(Ops::Base::InferShape4Elewise)直接复用逐元素算子的通用 shape 推导,即输出 shape 与输入 shape 保持一致——这与接口文档中 "self 和 out 的 shape 一致" 的约束一一对应。

8.3 Tiling 策略:按 scale 取值三分派(op_host/arch35)

Tiling 阶段(swish_tiling_arch35.cpp)先校验输入/输出数据类型必须为 FLOAT16/BF16/FLOAT 且两者一致、shape 必须一致,随后在SetAttr中读取 scale 属性(缺省 1.0f),并根据其取值分为三类模板分派:

scale 取值枚举(attrWork)说明
-1.0TPL_SCALE_NEG_ONE内核走SwishNegOne专用 DAG
0.0TPL_SCALE_ZERO内核走SwishZero专用 DAG
其他TPL_SCALE_OTHER内核走通用SwishOtherDAG

该分派结果通过GET_TPL_TILING_KEY(schMode, attrWork)编码为 tilingKey 下发给内核,内核据此在编译期选择不同的模板实例。此外 Tiling 还会计算 block 维度(SetBlockDim)与 workspace 大小(当前实现为 0),并通过TilingPrepareForSwish获取 AIV 核数与 UB 内存大小。

8.4 NPU 内核实现(op_kernel)

内核入口 swish_apt.cpp 根据 attrWork 在编译期实例化三类 DAG:SwishNegOneSwishZeroSwishOther(均定义于 swish_dag.h),每个 DAG 由CopyIn → 计算 → CopyOut组成,并使用 L2 内存优化配置:

  • SwishNegOne(scale = -1):此时exp(-(-1)*x) = exp(x),内核仅需Exp → Adds(+1) → Div,省去一次乘法;
  • SwishZero(scale = 0):此时s(x) = x / (1 + e^0) = x / 2,内核直接退化为Muls(x, 0.5),无需任何指数/除法运算;
  • SwishOther(其他 scale):通用路径,执行Muls(x, -1) → Muls(·, scale) → Exp → Adds(+1) → Div的完整公式。

上述特化体现出算子对编译期常量优化的运用:针对 -1 与 0 两个特殊 scale 值生成最简计算图,在常见场景(如 β=0 的退化、β=-1 的变体)下显著减少指令开销。对于非 float 类型(FLOAT16/BFLOAT16),内核会先 Cast 到 float 计算,再 Cast 回原类型存储,以保证中间精度。

8.5 Host API 组装(op_api)

aclnn_swish.cpp 中,第一段接口完成校验后,将 betaOptional 转为 float 标量并构造l0op::Swish(reshapeSelf, scale, executor)算子节点,随后依次处理非连续输入(Contiguous)、超过最大支持维度的长 Tensor(Reshape/ViewCopy)等场景,最终通过uniqueExecutor->GetWorkspaceSize()得到 workspace 大小;第二段接口aclnnSwish则直接调用通用执行入口CommonOpExecutorRun(workspace, workspaceSize, executor, stream)下发计算。

九、测试与验证

仓库为 Swish 算子提供了完整的 ST(系统测试)与 UT(单元测试)覆盖:

  • ST 测试:tests/st/aclnnSwish 目录下的atk_aclnnSwish.json(用例配置)与 executor_aclnnSwish.py。后者以 PyTorch 实现参考公式作为 golden 基准:
x = input_data.kwargs['self'].to(torch.float32) scale = torch.tensor(input_data.kwargs['betaOptional'], dtype=torch.float32) output = x / (1 + torch.exp(-scale * x))

这从测试层面再次印证了本文第一节的数学公式,且测试实现同样先转 float32 计算再转回原精度,与内核的 "Cast 到 float 计算、再 Cast 回原类型" 策略一致。

  • UT 测试:tests/ut 目录下覆盖 op_api(test_aclnn_swish.cpp)、op_host(test_swish_infershape.cpp、test_swish_tiling.cpp,含 arch35 变体)与 op_kernel(test_swish_apt.cpp,配套 swish_data/gen_data.py 数据生成脚本)。

  • 二进制配置:op_host/config 下 ascend950 / ascend350 的swish_binary.json列出了 x/y 的 dtype 与 ND 格式组合(bfloat16 / float16 / float32),与算子定义保持严格一致。

十、与 aclnnSilu 的关系

在 activation/swish/docs 目录下还提供了 aclnnSilu.md 文档。SiLU 本质是 β 固定为 1.0 的 Swish(silu(x) = x · sigmoid(x)),对应 op_api/aclnn_silu.cpp 中的 aclnnSilu 接口;而 aclnnSwish 通过 betaOptional 参数提供了更通用的 β 可调能力。两者共享同一套算子实现(swish_def / swish_apt / tiling),在需要显式调节激活曲线形态(如训练中学习/调度 β)时优先选用 aclnnSwish。

总结

aclnnSwish 是 CANN ops-nn 提供的通用 β 参数 Swish 激活算子接口,采用标准的 GetWorkspaceSize + Execute 两段式调用模型,支持 0-8 维 ND 张量、三种浮点数据类型与空 Tensor,并针对 β = -1、0 等特殊取值在内核层做了计算图特化优化。通过本文的接口参数说明、完整调用示例与源码实现剖析,你可以直接在 CANN 环境中将其接入自己的推理或训练链路,也可以此为模板理解 ops-nn 仓库中其他激活算子(如 aclnnSilu)的接口设计与实现套路。

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 20:11:58

Gource多版本控制实战指南:SVN、Mercurial、Bazaar与CVS可视化详解

Gource多版本控制实战指南&#xff1a;SVN、Mercurial、Bazaar与CVS可视化详解 【免费下载链接】Gource software version control visualization 项目地址: https://gitcode.com/gh_mirrors/go/Gource Gource 是一款将软件版本控制仓库渲染成 3D 动画树的开源可视化工具…

作者头像 李华
网站建设 2026/9/19 20:10:00

Alpine.js 扩展指南:自定义指令、魔术属性与插件开发实战

Alpine.js 扩展指南&#xff1a;自定义指令、魔术属性与插件开发实战 【免费下载链接】alpine A rugged, minimal framework for composing JavaScript behavior in your markup. 项目地址: https://gitcode.com/gh_mirrors/al/alpine 导读 Alpine.js 拥有高度开放的架…

作者头像 李华
网站建设 2026/9/19 20:09:54

MATLAB风电功率预测阈值优化与GUI设计

简介&#xff1a;面向新能源发电与电力系统调度场景的MATLAB项目实例文档&#xff0c;适合具备一定MATLAB编程基础的研究人员、工程师及高校师生&#xff0c;用于解决风电功率随机波动大、单一模型预测精度与鲁棒性不足的问题。压缩包内含1个docx文件&#xff0c;约85KB&#x…

作者头像 李华
网站建设 2026/9/19 20:08:48

光伏储能与三相并网逆变系统核心技术解析

1. 光伏储能与三相并网逆变系统概述在新能源发电领域&#xff0c;光伏储能系统与三相并网逆变器的结合正成为行业新趋势。这种组合方案不仅能有效解决光伏发电的间歇性问题&#xff0c;还能实现电能的智能调度和高效利用。作为一名从事新能源系统集成多年的工程师&#xff0c;我…

作者头像 李华