已关闭
[Requirement|需求建议]: SiluMul 算子AscendC实现 #309
wuxs68创建于  2025年12月30日关闭于  1月4日
wuxs68
2025年12月30日 创建

Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.

Backgroud(背景信息)

SiluMul 算子是一种将 SiLU 激活函数与乘法运算(Mul)进行融合的高效算子。在深度学习领域,特别是 LLaMA 等大语言模型(LLM)中,SiluMul 是实现 SwiGLU(Gated Linear Units)结构的核心组件。该算子能够通过内核融合减少内存搬运次数,显著提升推理性能。本项目实现了 SiluMul 算子的 AscendC 版本,以替代原有 TBE 算子在昇腾硬件上的适配。

Origin(信息来源)

联通(广东)产业互联网有限公司

Benefit / Necessity (价值/作用)

使用 AscendC 对 TBE 实现的 SiluMul 算子进行重构,实现了 AscendC 实现的 SiluMul 算子对 Atlas 200/500 A2 推理产品和 Atlas 800I/T A2 硬件的适配。
支持设备:
Atlas A3 训练系列产品/Atlas A3 推理系列产品
Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件

Design(设计方案)

目前支持的场景:

  • 支持数据类型:float32float16bfloat16

host 侧设计:

tiling 策略:

  1. 分核策略
  • 计算 Batch 大小(batchSize = inputShapeSize / lastDimSize)。
  • 若 lastDimSize / 2 大于最大计算数量(PPMaxCalNum),则需要的核心数(needCoreNum)等于 batchSize;否则计算单核可处理的行数 n(由 PPMaxCalNum 除以对齐后的 lastDimSize/2 得到),needCoreNum 为 batchSize 除以 n 向上取整。
  • 若 needCoreNum 超过平台可用核心数(coreNumPlatform),则使用平台核心数;若计算结果为 0,强制设置为 1。
  1. 单 core 内切分策略
  • PPMaxCalNum 通过 UB 大小(192KB)/ CALC_BUF_NUM(8)/ float 类型字节数估算得出,用于控制 Ping-Pong 流水线的最大计算量。
  1. 结构体写回
  • SiluMulTilingData 中记录末维大小(lastDimSize)、Batch 大小(batchSize)、流水线最大计算数(PPMaxCalNum)及所需核心数(needCoreNum)。
  1. tilingkey 规划
  • 根据输入数据类型(dataType)映射到不同的 tilingKey,用于区分 Kernel 侧的分支:Float16 对应 1,Float 对应 2,Bfloat16 对应 3。
  1. 数据检测与容错:
  • 检查末维大小(lastDimSize)是否超过 1024 或为奇数,若满足则报错。
  • 检查 batchSize 是否超过 200000,输入维度是否小于 2,以及 lastDimSize 是否为 0,若满足则报错。
  • 设置固定 Workspace 大小为 16MB(16 * 1024 * 1024)。

kernel 侧设计:

进行 Init 和 Process 两个阶段,其中 Process 包括数据搬入(CopyIn)、计算(Compute)、搬出(CopyOut)三个阶段。

2.1 初始化(Init)

获取 Tiling 参数:包括 batchSizelastDimSizeneedCoreNumPPMaxCalNum。计算输入数据最后维度的一半 d = lastDimSize / 2
初始化全局内存映射:设置 inputGmoutputGm 的全局缓冲地址。
初始化 UB 缓冲区:通过 pipe.InitBuffer 初始化 ubTBuf,并获取 tmpTensor
确定核索引:获取 blockIdx,若当前核索引大于等于 needCoreNumber,则停止处理。

2.2 计算流程(Compute)

重解释指针:将输入的本地张量 x1Tensorx2Tensor 重解释为 float 类型 (x1TensorFp32, x2TensorFp32)。
类型转换(分支处理):
若数据类型为 bfloat16_thalf,调用 Castx1TensorFp32x2TensorFp32 从原类型转换为 float 类型存储于临时 Tensor 中,并插入 PipeBarrier 同步。
核心计算(SiLU Mul 逻辑):

  1. Sigmoid 计算:调用 Sigmoid(tempResTensor, x1TensorFp32)
  2. SiLU 计算:调用 Mul(x1TensorFp32, x1TensorFp32, tempResTensor),实现 x1=x1×Sigmoid(x1)x1 = x1 \times Sigmoid(x1)
  3. 最终乘法:调用 Mul(x1TensorFp32, x1TensorFp32, x2TensorFp32),实现 Result=SiLU(x1)×x2Result = SiLU(x1) \times x2
    插入 PipeBarrier 确保向量计算指令完成。

2.3 数据搬入(CopyIn)与搬出(CopyOut)

  • CopyIn:根据 pingPongFlag 计算偏移并进行同步等待,通过 DataCopyPad 将 GM 中的输入数据切分搬入 UB 缓冲区,并设置同步信号告知计算单元数据就绪。
  • CopyOut:根据需要将计算结果进行类型转换(Cast),在同步确保计算完成后,利用 DataCopyPad 将结果从 UB 搬运至 GM 对应位置,并释放缓冲区供下一轮循环使用。

2.4 流程调度(Process)

  • 循环处理本核分配的所有数据块(batch 或分块),并针对末尾非对齐数据调整处理规模。
  • 严格按“CopyIn → Compute → CopyOut”顺序调度每个 tile,利用双缓冲机制掩盖内存搬运延迟。
  • 通过大块切分或多行聚合模式优化任务划分,确保硬件计算单元与搬运单元并行执行,完成全量数据回写。
likedislike
wuxs68
2025年12月30日 评论:

/assign

likedislike
CANN-robotCANN-robot成员
2025年12月30日 将 wuxs68 设为负责人
Wwuxs68
2025年12月30日 关联了pull request:SiluMul 算子AscendC实现贡献
Wwuxs68
2025年12月30日 修改了issue 的描述
liubo75成员
2025年12月30日 评论:

SiLU 计算这步骤,为啥会x1要做了sigmoid之后还要乘以x1? 这个和swiglu公式好像不一样。
另外,我们ascendc的算子本来就有swi_glu算子,可以看下这个算子目录,也在activation目录下。

likedislike
wuxs68
2025年12月30日 评论:

SiLU 计算这步骤,为啥会x1要做了sigmoid之后还要乘以x1? 这个和swiglu公式好像不一样。
另外,我们ascendc的算子本来就有swi_glu算子,可以看下这个算子目录,也在activation目录下。

@liubo75

1、这个x1是因为公式上是这样计算的,可以在运算上简化
2、silu_mul和swi_glu计算公式相近,但是实现方式上存在区别:
silu_mul算子的实现是通过调用Sigmoid指令实现,且针对β=1的默认情况处理,Tiling策略未1D切分,针对小Shape有优化,流水线管理使用显式Ping-Pong,
swi_glu则是通过自定义Exp+Add+Div实现Sigmoid,且允许β设置,Tiling策略属于2D通用切分,流水线管理使用TQue机制队列,
两者在实现方式、通用性和运算策略上有均有区别

likedislike
liubo75成员
2025年12月31日 评论:

SiLU 计算这步骤,为啥会x1要做了sigmoid之后还要乘以x1? 这个和swiglu公式好像不一样。
另外,我们ascendc的算子本来就有swi_glu算子,可以看下这个算子目录,也在activation目录下。

@liubo75

1、这个x1是因为公式上是这样计算的,可以在运算上简化
2、silu_mul和swi_glu计算公式相近,但是实现方式上存在区别:
silu_mul算子的实现是通过调用Sigmoid指令实现,且针对β=1的默认情况处理,Tiling策略未1D切分,针对小Shape有优化,流水线管理使用显式Ping-Pong,
swi_glu则是通过自定义Exp+Add+Div实现Sigmoid,且允许β设置,Tiling策略属于2D通用切分,流水线管理使用TQue机制队列,
两者在实现方式、通用性和运算策略上有均有区别

@wuxs68

如果silu_mul在公式实现上是swi_glu在β=1的特殊情况,是否应该在swi_glu里面实现β=1的场景优化实现就行,而不需要新增一个算子来实现特殊场景?

likedislike
wuxs68
2025年12月31日 评论:

SiLU 计算这步骤,为啥会x1要做了sigmoid之后还要乘以x1? 这个和swiglu公式好像不一样。
另外,我们ascendc的算子本来就有swi_glu算子,可以看下这个算子目录,也在activation目录下。

@liubo75

1、这个x1是因为公式上是这样计算的,可以在运算上简化
2、silu_mul和swi_glu计算公式相近,但是实现方式上存在区别:
silu_mul算子的实现是通过调用Sigmoid指令实现,且针对β=1的默认情况处理,Tiling策略未1D切分,针对小Shape有优化,流水线管理使用显式Ping-Pong,
swi_glu则是通过自定义Exp+Add+Div实现Sigmoid,且允许β设置,Tiling策略属于2D通用切分,流水线管理使用TQue机制队列,
两者在实现方式、通用性和运算策略上有均有区别

@wuxs68

如果silu_mul在公式实现上是swi_glu在β=1的特殊情况,是否应该在swi_glu里面实现β=1的场景优化实现就行,而不需要新增一个算子来实现特殊场景?

@liubo75

silu_mul 能够直接调用硬件原生的 Sigmoid 指令并配合显式 Ping-Pong 调度,是针对高频场景的特化;而 swi_glu 为了通用性,退化为 Exp+Add 组合指令软实现并依赖 TQue 机制,两者实现指令和方式上有明显不同,不适合合并为一个算子。

likedislike
chenqi317成员
2025年12月31日 评论:

你好 您提交的算子贡献需求 已接纳,感谢您为CANN社区的贡献。

likedislike
CANN-robotCANN-robot成员
1月4日 关闭了 issue
CANN-robotCANN-robot成员
3月20日 添加了label:resolved