Ascend C Silu 激活函数接口实战解析:调用方式、约束边界与源码实现

原创2026-10-15 17:20:082,224 阅读
文章标签:人工智能深度学习高性能计算CANN

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!。

九、常见误区与使用建议

  1. 不要直接包含 impl 内部头文件:silu 实现头文件与校验头文件均带有 __ASCENDC_INCLUDE_INTERNAL_HEADERS__ 保护并给出编译期告警,应统一通过 #include "kernel_operator.h" 或 #include "adv_api/activation/silu.h" 调用公开接口。
  2. dst 与 src 必须分缓冲:Silu 不支持源/目的地址重叠,且实现采用目的缓冲原地分步计算,与 dst 共享缓冲的写法在 CPU 调试模式下会被 TensorReuseVerifyingParameters 捕获报错。
  3. 不要误用 isReuseSource:该模板参数当前为预留项,仅接受默认 false,传入 true 会触发校验异常。
  4. 注意产品差异:Silu 不支持 Atlas 训练系列与 Atlas 200I/500 A2 推理产品,Atlas 推理系列仅 AI Core 支持(Vector Core 不支持),跨产品适配前应核对支持矩阵。
  5. 精度敏感场景关注 Subnormal:Ascend 950PR&950DT 系列上 --cce-ftz 的取值会影响极小数值的精度表现,若需保留 Subnormal 参与运算,应将编译选项显式配置为 false。

综上,Silu 接口在 asc-devkit 中具备完整的三层支撑:公开接口声明(include)、分架构 Kernel 实现(impl)与参数校验体系(api_check),以及配套的 Tiling 空间查询接口与可运行的端到端样例。开发者依据本文的调用方式、约束清单与样例工程,即可在 SIMD 高阶 API 场景下正确、高效地集成 Silu 激活计算。

登录后查看全文
asc-devkit