【CANN训练营】+开源之星+Erf算子Ascend C开发
收藏回复举报
【CANN训练营】+开源之星+Erf算子Ascend C开发
发表于2026-01-21 11:53:34
0 查看

背景

      2025年CANN训练营第二季社区任务聚焦Ascend C算子开发,以真实产业案例为背景,要求算子性能对标内置的TBE版本。任务涵盖算子分析、开发实现、性能调优、功能验证、文档撰写与代码合仓,形成从理论到实践完整的开源开发闭环。除提供丰厚奖励外,训练营还配备资深导师提供全程技术指导,并由专业验收团队对功能与性能进行最终验收,同时CANN小助手实时在线响应,为开发者提供全方位支持,协助解决开发过程中遇到的各种问题。

      本文将以Erf算子(任务编号01)为例,系统介绍其Ascend C算子开发全流程。本次任务要求在“Atlas A2训练系列产品/Atlas 800I A2推理产品/A200I A2 Box异构组件”平台上实现,相关方法与思路对其它昇腾硬件平台开发同样具有参考价值。

一、算子分析

一)、内置TBE算子分析

    通过对Erf算子TBE版本的功能分析,当前支持的能力如下:
    ①input_x支持float16,float32,bfloat16三种格式的输入。
    ②Erf算子逐元素进行运算,不涉及广播。
    ③对输入数据多次调用vabs、vmul、vdiv和vadd等接口,采用数值近似方法计算erf算子,数学表达式:
        erf(x) ≈ sign(x) * [1 - (at + bt² + ct³) * e^(-x²)]
        其中:
        t = 1/(1 + p|x|)
        常数:p=0.47047, a=0.3480242, b=-0.0958798, c=0.7478556

     流程图如下:

erf_1103_OK.jpg

二)、算子原型定义

1、算子原型

名称

类别

dtype

format

shape

介绍

x

输入

fp16/fp32/bf16

ND

all

输入

y

输出

fp16/fp32/bf16

ND

同输入

输出

2、算子支持型号

    Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件

三)、Ascend C实现

    Ascend C算子实现,包括Host侧Tiling实现,和Kernel侧实现两部分。

1、host侧Tiling方案

       根据任务书要求,不处理广播的情形,这样算子计算过程不涉及数据的维度信息,故在host侧将数据视为一维向量,仅考虑数据个数,不考虑数据维度信息。

1)核间切分策略——优先使用满核的原则

      如果核间能均分,可视作无大小核区分,大核小核数据块一致;如果核间不能均分,需要将余出的数据块分配到前几个核上。

 输入数据大小计算:通过GetInputShape和GetDataTypeLength函数获取输入数据的大小和类型长度,计算出输入数据的总字节数。

 UB内存大小和核心数量获取:通过平台信息获取UB内存大小和核心数量,并根据这些信息调整核心数量。

2)核内切分策略——充分使用UB空间的原则

     需要考虑不同硬件的UB大小不同、是否开启double buffer、kernel侧API实现过程中是否需要临时数据的储存,综合考虑单核内切分的大小。

UB内存大小获取:通过GetCoreMemSize函数获取UB内存的大小,用于后续的数据切分计算。

Tile块计算:根据UB内存大小和预定义的BLOCK_SIZE及BUFFER_NUM和不同类型下的ubDataNum,计算出每个Tile块的数据数量。

数据切分:将输入数据按照计算出的Tile块大小进行切分,计算出每个core需要处理的数据块数量和最后一个block的剩余数据量。

设置切分参数:将计算出的切分参数(如每个core的数据量、Tile块大小等)设置到ErfTilingData对象中。

      这些策略确保了数据在多个核心之间的均匀分布,并且在单个核心内进行了合理的切分,以提高并行处理的效率。

3)tilingkey规划策略——针对不同的场景,定制内核,实现性能优化

2、kernel侧设计方案

       包括Init和Process两个阶段,Init是初始化,Process是处理流程的具体实现。根据矢量编程范式,Process包括数据搬入(CopyIn)、计算(Compute)、搬出(CopyOut)三个阶段。

cke_83416.png

  • CopyIn负责搬入操作:将输入数据从Global Memory搬运到Local Memory(VECIN用于表达矢量计算搬入数据的存放位置),完成搬运后执行入队列操作;
  • Compute负责矢量指令计算操作:完成队列出队后,从Local Memory获取数据并计算,计算完成后执行入队操作;
  • CopyOut负责搬出操作:完成队列出队后,将计算结果从Local Memory(VECOUT用于表达矢量计算搬出数据的存放位置)搬运到Global Memory。

    编程范式流程图如下:

    cke_192199.png

2、Ascend C的Erf算子流程见下图

cke_137353.jpeg

二、功能实现

1、编程范式实现

     本算子,是一个vector算子,按照编程方式,可以划分成copyin、compute、copyout三个阶段实现。

cke_3344740.png

2、本算子需要支持float,float16,bfloat16三种数据类型,其中float16,bfloat16,需要先转换成float32,完成计算后,再将结果转换对应的输入数据类型。

cke_4867093.png

3、核心计算功能实现——参照计算公式,代码如下

cke_4878599.png

三、性能优化

一)性能分析

1、使用msprof op采集,不同输入shape下的性能数据,并和相同shape的标杆性能数据进行对比。通过比较发现,性能不达标主要是中,小shape输入。

cke_3995520.png

      从性能对比数据可以看出,大致有三种情况:

1)数据量小,只需要开启单核和双核的情形,由于AscendC启动比TBE慢,普遍性能会差1us左右。

2)数据量大,基本上是满核,且需要多次搬入搬出的情形,由于AscendC开启了DoubleBuffer,性能明显由于标杆。

3)需要开启多核,但数据量又不是很大的情形下,性能相差较大,这部分是优化的重点。

2、对性能不达标的shape性能数据,进一步对比可以发现,主要是scale耗时较多。

cke_3948750.jpeg

二)优化实现

       通常情况下,开启DoubleBuffer,可以提高aicore的利用率,将数据搬入和计算并行,在数据量比较大,需要多次搬入搬出的场景,优势明显。但开启DoubleBuffer需要额外的scale开销,会劣化小shape的输入性能。所以可以根据输入shape的大小,采用不同的kernel进行处理,这个临界点,通常可以通过实验,抓取性能数据获得。

       对本算子而言,选择262144作为分界点:超过262144数据的kernel,开启double buffer,尽量使用aicore的计算能力;不超过262144数据的kernel,不开启double buffer,尽量减少scale开销;

1、为中,小shape定制kernel——不开启doublebuffer,减少scale开销。

       之前,在涉及多个TilingKey的场景中,开发者依赖TilingKey来管理kernel的实现,无论是在管理还是使用上都会遇到相当大的复杂性。现在,为了简化这一过程,采用模板编程的方法来替代传统的TilingKey编程,从而减少对TilingKey数值标识的依赖,使kernel的管理更加直观和高效。包括3部分内容:

1)在自定义算子工程的op_kernel目录下,新增定义模板参数和模板参数组合的头文件,本算子对应的头文件命名erf_tiling_key.h,包含两个kernel,分别处理不同size尺度的shape。

cke_1019414.png

2)host侧调用ASCENDC_TPL_SEL_PARAM接口自动生成并配置TilingKey。

host实现文件中包含步骤1中定义模板参数和模板参数组合的头文件。

cke_1197189.png

调用ASCENDC_TPL_SEL_PARAM接口自动生成并配置TilingKey,ASCENDC_TPL_SEL_PARAM输入参数为模板参数的具体值,传入时需要与定义模板参数和模板参数组合的头文件中的模板参数顺序保持一致。

cke_1204413.png

3)kernel侧实现

       kernel实现文件中包含步骤1中定义模板参数和模板参数组合的头文件。核函数添加template模板,以便支持模板参数的传入,参数顺序需要与定义模板参数和模板参数组合的头文件中的模板参数顺序保持一致。通过对模板参数的分支判断,选择不同的kernel侧实现。

cke_2290750.png

2、对小shape,还可以通过抓取性能数据进行比对的方法,对比启动更多的核,或让单核处理更多的数据这两个方法的性能更优,从而对特定shape进行性能优化。这种方法,对不同算子,分段的阈值不同,需要根据实际上板的性能数据,进行划分。

cke_2918074.png

3、为了避免GM访问冲突,进行核间切分时,让数据512字节对齐。

       如果对代码感兴趣,可以访问:https://gitcode.com/cann/ops-math/tree/master/experimental/math/erf,代码仓库中,还有众多开发者贡献的优秀代码,可以学习,参考,借鉴。

四、测试

       使用AscendOpTest工具,分别批量采集不同维度输入下的Ascend C实现与TBE实现的性能数据,然后进行比对,判断性能是否达标。该工具本质上是调用CANN内置的msProf工具,msProf工具用于采集和分析运行在昇腾AI处理器上算子的关键性能指标,msProf所采集的性能数据,也可辅助开发者快速定位算子软硬件性能瓶颈,从而显著提升性能分析与调优的效率。

      AscendOpTest采集的性能数据截图。

cke_282577.png

五、参考资料

1、典型算子的编程范式

https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/850/opdevg/Ascendcopdevg/atlas_ascendc_10_00015.html

2、AscendOpTest——基于昇腾CANN构建高效易用的Ascend C算子精度、性能测试工具

https://gitee.com/sutonghua/ascendoptest

3、算子调优工具(msProf)

https://www.hiascend.com/document/detail/zh/canncommercial/83RC1/devaids/optool/atlasopdev_16_0082.html

本帖最后由 匿名用户2026/01/21 11:59:38 编辑

我要发帖子