Ascend C Silu 激活函数接口实战解析:调用方式、约束边界与源码实现
Ascend C Silu 激活函数接口实战解析:调用方式、约束边界与源码实现
Silu(Sigmoid Linear Unit)是深度学习模型中常用的平滑激活函数,本项目(asc-devkit,CANN 昇腾 AI 处理器算子开发语言)在 SIMD 高阶 API 中提供了 AscendC::Silu 核函数侧接口与配套的 AscendC::GetSiluTmpSize Host Tiling 接口。本文以 Silu_interface 接口说明 为主体,结合仓库内公开头文件、Kernel 实现、参数校验代码、单元测试与 Swish/Silu 样例工程,系统讲解该接口的数学定义、产品支持范围、参数语义、约束条件、源码级计算流程,以及如何在真实算子工程中落地使用。读者读完本文后,将能够在自己的 Ascend C 算子中正确调用 Silu 并完成 Tiling 参数获取、精度对比与调试。
一、接口定位:Silu 是什么
Silu 定义见 Silu.md,其数学形式为:
silu(x) = x / (1 + e^(-x))
即输入张量按元素做 Silu 运算,输出与输入形状一致。从数学关系上看,Silu 是 Swish 激活函数在参数 beta=1 时的特例(Swish 一般形式为 y = x / (1 + exp(-beta * x)))。该关系在仓库样例 examples/01_simd_cpp_api/04_advanced_api/01_activation/swish/swish.asc 中被明确使用:样例通过编译宏 USE_SILU_MODE 在 Swish(beta=1.702,GELU 近似)与 Silu(beta=1)两种模式间切换,且源码注释直接指出 AscendC::Silu<T, false>(dstLocal, srcLocal, dataSize) 与 AscendC::Swish(dstLocal, srcLocal, dataSize, 1.0f) 两种调用结果完全等价。
与 ReLU 相比,Silu 曲线平滑、梯度不会完全消失,在 Transformer 等结构中广泛用于 FFN 的激活环节;本项目将其归入高阶 API 的 activation_functions 目录(docs/zh/api/SIMD-API/adv_api/activation_functions/),与 GeGLU、Sigmoid、SoftMax、SwiGLU、Swish 等激活接口并列。
二、产品支持情况
根据原文档,Silu 接口在各产品系列上的支持情况如下表所示(不支持的产品需在算子设计阶段提前规避):
| 产品系列 | 支持情况 |
|---|---|
| Ascend 950PR&950DT 系列产品 | 支持 |
| Atlas A3 系列产品 | 支持 |
| Atlas A2 系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 AI Core | 支持 |
| Atlas 推理系列产品 Vector Core | 不支持 |
| Atlas 训练系列产品 | 不支持 |
| Kirin X90 处理器系列产品 | 支持 |
| Kirin 9030 处理器系列产品 | 支持 |
在仓库测试代码 tests/api/adv_api/activation/silu/test_operator_silu.cpp 中可以看到与产品架构相关的编译期分支:当 __NPU_ARCH__ 为 3003/3113(对应 Kirin X90/9030)时,测试会裁剪过大的用例规模,注释为"用例超出 kirinx90 9030 ub size"——这说明 Kirin 系列虽然支持该接口,但其 UB 空间相对有限,大数据量用例需要单独规避。
三、函数原型与参数说明
3.1 函数原型
template <typename T, bool isReuseSource = false>
__aicore__ inline void Silu(const LocalTensor<T>& dstLocal, const LocalTensor<T>& srcLocal, uint32_t dataSize)
该原型与公开头文件 include/adv_api/activation/silu.h 中定义的接口一致,接口被标记为 __inout_pipe__(V),即通过 Vector 流水(PIPE_V)完成计算;文件头部还通过编译期检查禁止在 --enable-simt(纯 SIMT 编译)模式下使用该头文件。
3.2 模板参数说明
| 参数名 | 描述 |
|---|---|
| T | 操作数的数据类型。支持的数据类型为:half、float。 |
| isReuseSource | 是否允许修改源操作数。该参数预留,传入默认值 false 即可。 |
3.3 接口参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| dstLocal | 输出 | 目的操作数。类型为 LocalTensor,支持的 TPosition 为 VECIN/VECCALC/VECOUT。 |
| srcLocal | 输入 | 源操作数。类型为 LocalTensor,支持的 TPosition 为 VECIN/VECCALC/VECOUT。源操作数的数据类型需要与目的操作数保持一致。 |
| dataSize | 输入 | 实际计算数据元素个数。 |
需要说明的是,TPosition 中的 VECIN/VECCALC/VECOUT 均映射到 Unified Buffer(UB)物理内存,参见 通用说明和约束 中的 TPosition 与物理内存映射关系表。
3.4 返回值说明
无返回值(void)。
四、约束说明
使用 Silu 接口前必须满足以下约束,否则可能产生错误结果或编译/运行失败:
- 操作数地址偏移对齐要求:请参见 通用说明和约束。该文档明确指出,UB 存储单元要求 32Byte 对齐,Ascend C API 操作数的起始地址对齐要求应与存储单元对齐要求保持一致。
- 不支持源操作数与目的操作数地址重叠:这是 Silu 相对其他基础向量接口更严格的约束。尽管 通用说明和约束 允许部分基础 API 在特定条件下做地址重叠(100% 完全重叠),Silu 接口自身明确禁止重叠使用,实现时需为 dst 与 src 分别申请独立缓冲。
- 仅支持 ND 格式的输入,不支持其他格式(如 NZ 等)。
- 针对 Ascend 950PR&950DT 系列产品:接口内部计算对 Subnormal(非规格化浮点数)的处理方式受编译选项
--cce-ftz控制(默认值为true):- 配置为
false时,计算过程中保留 Subnormal,并按照其实际数值参与后续计算; - 配置为
true时,启用 FTZ(Flush-To-Zero)模式,计算过程中产生或参与运算的 Subnormal 将按 0 处理,可能导致计算结果与保留 Subnormal 时存在精度差异。
- 配置为
五、源码级实现原理
Silu 接口的公开头文件 include/adv_api/activation/silu.h 根据 __NPU_ARCH__ 选择两套实现路径:架构号为 3510/9201/5102/5162/3003/3113 时编译 silu_3510_impl.h,否则编译通用实现 silu_common_impl.h。
5.1 通用实现的四步分解
silu_common_impl.h 中的 SiluCalcSimplified 将 silu(x) = x / (1 + e^(-x)) 拆解为 4 个基础向量指令的串行流水,每步之间通过 PipeBarrier<PIPE_V>() 插入流水屏障保证依赖顺序:
// silu(x) = x / (1 + e^(-x))
Muls<T, false>(dstAddr, srcAddr, T(-1), MASK_PLACEHOLDER, repeatTimes, unaryParams); // 1. dst = x * (-1)
PipeBarrier<PIPE_V>();
Exp<T, false>(dstAddr, dstAddr, MASK_PLACEHOLDER, repeatTimes, unaryParams); // 2. dst = e^(-x)
PipeBarrier<PIPE_V>();
Adds<T, false>(dstAddr, dstAddr, 1.0f, MASK_PLACEHOLDER, repeatTimes, unaryParams); // 3. dst = 1 + e^(-x)
PipeBarrier<PIPE_V>();
Div<T, false>(dstAddr, srcAddr, dstAddr, MASK_PLACEHOLDER, repeatTimes, binaryParams);// 4. dst = x / (1 + e^(-x))
PipeBarrier<PIPE_V>();
由此可以推断,Silu 的计算完全基于现有基础 API(Muls、Exp、Adds、Div)组合而成,中间结果直接复写目的缓冲区,这也是接口不支持源/目的地址重叠的根因之一:第 1 步 Muls 会改写 dst 缓冲,若 dst 与 src 部分重叠,将破坏后续第 4 步 Div 仍需要的原始 src 数据。
在 SiluCompute 的调度层,实现按数据类型区分单次 repeat 可处理的最大元素数(MAX_REPEAT_HALF_SIZE / MAX_REPEAT_FLOAT_SIZE),对 dataSize 拆分为整数 repeat 块、余数块(repeatRemaining)和尾数块(tail)三段处理,尾数块通过 SetVectorMask<T>(tail) 设置矢量掩码后单独计算,最后统一 ResetMask() 复位掩码。
5.2 3510 系列产品的寄存器实现
对于 Ascend 950PR&950DT 等架构,silu_3510_impl.h 采用基于寄存器(RegTensor)的 SIMD 向量函数 SiluComputeVF 实现同样的四步计算:Reg::LoadAlign 装载、Reg::Muls 取负、Reg::Exp 指数、Reg::Adds 加 1、Reg::Div 相除、Reg::StoreAlign 回写。该实现入口处先判断 ASCEND_IS_AIC(AI Vector Core)则直接返回,并附带 static_assert 约束 T 必须为 half 或 float,同时通过 CheckTensorPosition、CheckCalCount 校验 Tensor 位置与计算元素个数。需要留意的是,该头文件属于内部实现(impl 目录),注释明确要求开发者"必须通过 #include "adv_api/activation/silu.h" 使用公开接口",不应直接包含内部头文件。
5.3 参数校验链
在 CPU 调试(ASCENDC_CPU_DEBUG)场景下,SiluCompute 会额外执行两类检查:一是断言 dataSize 不超过 src/dst Tensor 的尺寸且 T 为 half/float(silu_common_impl.h 第 61-67 行);二是通过 CHECK_FUNC_HIGHLEVEL_API 宏调用高阶 API 校验体系。校验类 CheckFuncClassSilu 定义于 silu_check_common.h,依次完成:
DataTypeVerifyingParameters<T, half, float>:模板参数 T 必须是 half 或 float;IsReuseSourceVerifyingParameters<false>:isReuseSource 仅接受默认值 false;TensorVerifyingParameters:dst/src 的 TPosition 必须为 VECIN/VECOUT/VECCALC(即 UB);TensorReuseVerifyingParameters:校验 dst 与 src 不允许地址重叠;CalCountVerifyingParameters:dataSize 不得超过 dst/src Tensor 的容量。
对应的失败用例可在 kernel_silu_check.cpp 中看到:传入 uint8_t 类型、dstTensor[1] 与 srcTensor[1] 地址重叠、dataSize 超限时会触发 KernelRaise 计数上升,验证了上述每一项约束都被实际执行。
六、调用示例与结果验证
6.1 核函数侧调用
原文档给出的最小调用方式如下(dataSize 为 512):
// dstLocal: 存放Silu计算结果的Tensor
// srcLocal: 存放Silu计算输入的Tensor
// 需要参与计算的元素个数为512
AscendC::Silu(dstLocal, srcLocal, 512);
配合 TPipe/TQue 的完整可运行 Kernel 类见测试文件 test_operator_silu.cpp:Init 中通过 pipe.InitBuffer(inQueueX, 1, dataSize * sizeof(srcType)) 为输入/输出队列各申请一块缓冲;Compute 中先 inQueueX.DeQue 取出输入 Tensor,再调用 Silu(dstLocal, srcLocal, dataSize),随后 EnQue 输出并释放输入。
6.2 结果示例
原文档给出的输入输出示例如下(输入覆盖 -100 到 100 的典型区间,含 0 附近的过渡区):
输入数据(srcLocal):
[-100. -50. -20. -10. -5. -4. -3. -2.
-1. -0.5 -0.4 -0.3 -0.2 -0.1 -0.01 0.
0. 0.01 0.1 0.2 0.3 0.4 0.5 1.
2. 3. 4. 5. 10. 20. 50. 100. ]
输出数据(dstLocal):
[ -0. -0. -0.00000004 -0.00045398 -0.03346425 -0.07194484 -0.14227761 -0.23840587
-0.2689414 -0.18877034 -0.16052495 -0.12766725 -0.0900332 -0.04750208 -0.004975 0.
0. 0.005025 0.05249792 0.10996681 0.17233276 0.23947507 0.31122968 0.7310586
1.761594 2.8577225 3.928055 4.9665356 9.999546 20. 50. 100. ]
可以对照验证其数学性质:当 x 取较大的正值(如 50、100)时输出近似等于 x(1+e^(-x) 趋近 1);当 x 取较大的负值时输出趋近于 0(x/(1+e^(-x)) 中分母指数爆炸),例如 x=-100 时输出约 -0.0;在 0 附近则呈现平滑过渡,整体曲线单调且处处可导,这正是 Silu 作为激活函数平滑性的体现。
6.3 测试覆盖
test_operator_silu.cpp 通过参数化测试对 half 与 float 两种类型分别覆盖 32、64、256、512、1024、2048、4096、8192、16384 等数据规模(Kirin X90/9030 架构除外追加 32768/16384 的大规模用例),说明该接口对小数据量(含 tail 掩码路径)与大数据量(多 repeat 分块路径)均有覆盖验证。
七、Host 侧 Tiling 接口 GetSiluTmpSize
7.1 功能说明
GetSiluTmpSize.md 描述的接口用于获取 Silu Tiling 参数:即 Silu 接口能完成计算所需的最大临时空间大小和最小临时空间大小,供 Host 端在 Tiling 阶段为 Kernel 预留/申请空间。
7.2 函数原型
inline void GetSiluTmpSize(const AscendC::TensorShape& srcShape, const uint32_t typeSize, const bool isReuseSource, uint32_t& maxValue, uint32_t& minValue)
该声明定义于公开头文件 include/adv_api/activation/silu_tiling.h,头文件同时引入 register/tilingdata_base.h,说明其属于高阶 API 配套的 Host Tiling 接口体系,使用高阶 API 的工程需要链接 libtiling_api.a(参见 通用说明和约束)。
7.3 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| srcShape | 输入 | 输入的 shape 信息,参数类型为 AscendC::TensorShape。 |
| typeSize | 输入 | 输入的数据类型大小,单位为字节。比如输入的数据类型为 half,此处应传入 2,即 sizeof(half)。 |
| isReuseSource | 输入 | 是否复用源操作数输入的空间,与核函数(Kernel)侧接口一致。 |
| maxValue | 输出 | Silu 接口能完成计算所需的最大临时空间大小,超出该值的空间不会被该接口使用。在最小临时空间-最大临时空间范围内,随着临时空间增大,核函数(Kernel)侧接口计算性能会有一定程度的优化提升。为了达到更好的性能,开发者可以根据实际的内存使用情况进行空间预留/申请。最大空间大小为 0 表示计算不需要临时空间。maxValue 仅作为参考值,有可能大于 Unified Buffer(UB)剩余空间的大小,该场景下,开发者需要根据 UB 剩余空间的大小来选取合适的临时空间大小。 |
| minValue | 输出 | Silu 接口能完成计算所需最小临时空间大小。为保证功能正确,接口计算时预留/申请的临时空间不能小于该数值。最小空间大小为 0 表示计算不需要临时空间。 |
7.4 调用示例
// 输入shape信息为1024;算子输入的数据类型为half;不允许修改源操作数
std::vector<int64_t> shape_vec = {1024};
AscendC::TensorShape shape(shape_vec);
uint32_t maxValue;
uint32_t minValue;
AscendC::GetSiluTmpSize(shape, 2, false, maxValue, minValue);
7.5 返回值与实现观察
该接口无返回值,结果通过 maxValue/minValue 两个引用参数带出。对照实现文件 silu_tiling.cpp 可以看到:当前实现中 GetSiluTmpSize 直接置 maxValue = 0、minValue = 0,即当前版本 Silu 计算不需要额外临时空间(中间结果复用目的缓冲区完成,见 5.1 节的四步原地计算流程)。这与单元测试 test_tiling.cpp 中 TestAscendSiluTiling 的断言一致:EXPECT_EQ(minValue, 0); EXPECT_EQ(maxValue, 0);。因此开发者在 Host Tiling 阶段调用该接口后,无需为 Silu 预留额外 UB 空间,但依然推荐保持调用习惯,以便接口未来演进(例如引入需要临时空间的实现)时算子无需改动。
八、端到端样例:在真实工程中集成 Silu
仓库自带的 Swish/Silu 样例 examples/01_simd_cpp_api/04_advanced_api/01_activation/swish/README.md 展示了 Silu 从 Host Tiling 到 Kernel 计算再到精度对比的完整链路,可作为算子开发的参考模板。
8.1 样例规格
- 输入:src,shape 为
[1, 32],数据类型 float,格式 ND; - 输出:dst,shape 为
[1, 32],数据类型 float,格式 ND; - 核函数名:swish_custom;
- 通过编译宏
USE_SILU_MODE在 Swish 与 Silu 模式间切换。
8.2 Kernel 侧调用
swish.asc 的 Compute 阶段在 USE_SILU_MODE 下直接调用:
AscendC::Silu<T, false>(dstLocal, srcLocal, dataSize);
数据搬运流程为:CopyIn(GlobalTensor → LocalTensor,经 VECIN 队列)→ Compute(调用高阶 API)→ CopyOut(LocalTensor → GlobalTensor,经 VECOUT 队列),与 6.1 节测试用例的结构一致。
8.3 Host 侧 Tiling 与调用
Host 主函数中先构造 AscendC::TensorShape 并调用 Tiling 接口获取临时空间:
std::vector<int64_t> shapeVec = {inputSize};
AscendC::TensorShape srcShape(shapeVec);
uint32_t maxValue = 0;
uint32_t minValue = 0;
AscendC::GetSiluTmpSize(srcShape, sizeof(float), false, maxValue, minValue);
随后使用 ACL 接口完成设备内存分配、H2D 拷贝,并通过内核调用符启动核函数:
swish_custom<<<numBlocks, 0, stream>>>(srcDevice, dstDevice);
8.4 编译运行与精度对比
在样例根目录执行(Silu 模式):
mkdir -p build && cd build;
cmake -DUSE_SILU_MODE=ON -DCMAKE_ASC_ARCHITECTURES=dav-2201 ..; make -j;
python3 ../scripts/gen_data.py --silu-mode
./demo
CMAKE_ASC_ARCHITECTURES:dav-2201(默认,对应 Atlas A2/A3 系列产品)、dav-3510(对应 Ascend 950PR&950DT 系列产品);CMAKE_ASC_RUN_MODE:npu(默认)、cpu(CPU 调试)、sim(NPU 仿真);切换模式前需清理 build 目录下的 CMakeCache.txt;USE_SILU_MODE:OFF(默认,Swish)/ ON(Silu)。
样例输出真值由 scripts/gen_data.py --silu-mode 生成,Host 端以 EPS=1e-4 的绝对/相对误差准则与 golden 数据对比,通过时打印 test pass!。
九、常见误区与使用建议
- 不要直接包含 impl 内部头文件:silu 实现头文件与校验头文件均带有
__ASCENDC_INCLUDE_INTERNAL_HEADERS__保护并给出编译期告警,应统一通过#include "kernel_operator.h"或#include "adv_api/activation/silu.h"调用公开接口。 - dst 与 src 必须分缓冲:Silu 不支持源/目的地址重叠,且实现采用目的缓冲原地分步计算,与 dst 共享缓冲的写法在 CPU 调试模式下会被
TensorReuseVerifyingParameters捕获报错。 - 不要误用 isReuseSource:该模板参数当前为预留项,仅接受默认 false,传入 true 会触发校验异常。
- 注意产品差异:Silu 不支持 Atlas 训练系列与 Atlas 200I/500 A2 推理产品,Atlas 推理系列仅 AI Core 支持(Vector Core 不支持),跨产品适配前应核对支持矩阵。
- 精度敏感场景关注 Subnormal:Ascend 950PR&950DT 系列上
--cce-ftz的取值会影响极小数值的精度表现,若需保留 Subnormal 参与运算,应将编译选项显式配置为false。
综上,Silu 接口在 asc-devkit 中具备完整的三层支撑:公开接口声明(include)、分架构 Kernel 实现(impl)与参数校验体系(api_check),以及配套的 Tiling 空间查询接口与可运行的端到端样例。开发者依据本文的调用方式、约束清单与样例工程,即可在 SIMD 高阶 API 场景下正确、高效地集成 Silu 激活计算。