Ascend C算子开发能力中级认证(Sigmoid算子)学习分享
收藏回复举报
Ascend C算子开发能力中级认证(Sigmoid算子)学习分享
新人帖
发表于2025-11-02 22:10:15
0 查看

算子开发能力中级认证需要补充完整两方面代码,分别为:

op_host侧(sigmoid_custom_tiling.h+sigmoid_custom.cpp)

op_kernel侧(sigmoid_custom.cpp)

op_host侧:

sigmoid_custom_tiling.h:

这里我们需要定义一个tiling结构体以便后续用来给张量分块,结构体包含两个参数即可,一个为要处理张量的总长度(张量元素总数),另一个为一个AIcore要处理的数据块数,定义结构体用宏来实现,定义结构体模板如下:

BEDIN_TILING_DATA_DEF(结构体名)//开始定义结构体

TILING_DATA_FIELD_DEF(变量类型 变量名);//定义一个成员变量

....//定义..个成员变量

END_TILING_DATA_DEF();//结束结构体定义

结构体定义完成后,要将“自定义tiling数据结构”与“目标算子绑定”,注册到昇腾框架中

使用宏 REGISTER_TILING_DATA_CLASS(算子名,结构体名)

具体实现如下:

namespace optiling {
BEGIN_TILING_DATA_DEF(SigmoidCustomTilingData)
//考生自行定义tiling结构体成员变量
TILING_DATA_FIELD_DEF(uint32_t, totalLength);
TILING_DATA_FIELD_DEF(uint32_t, tileNum);
END_TILING_DATA_DEF;

REGISTER_TILING_DATA_CLASS(SigmoidCustom, SigmoidCustomTilingData)
}

sigmoid_custom.cpp:

这里我们主要要在TilingFunc函数中实现对tiling对象成员变量的赋值,并将tiling对象与要在多少个AIcore上运行的变量保存到context中。

context是什么?context是昇腾AI框架与自定义算子之间“数据交互与控制中枢”,他封装了算子运行的关键信息(包括输入输出形状,Tiling参数,硬件配置等)

我们要实现tiling结构体,就要对结构体中的成员变量赋值。我们可以将tile_num设置为8,而totallength的值从context获取(上段中所说context中存储了输入输出形状,我们可以从context中获取张量的元素总数)

获取元素总数步骤如下:1 获取第0个张量的形状对象

context->GetInputShape(0)

2 从张量的形状对象中获取张量的原始形状(未经过框架自动优化调整的形状)

context->GetInputShape(0)->GetOriginShape()

3 计算张量所有维度的乘积,调用GetShapeSize()函数即可

context->GetInputShape(0)->GetOriginShape().GetShapeSize();

tiling对象成员变量的设置,用到了两个函数

tiling.set_totalLength(totalLength);
tiling.set_tileNum(TILE_NUM);

这样我们tiling对象的成员变量都已经设置好了,接下来要将tiling保存到context中,并将tiling的总字节数告诉context,实现代码如下:

tiling.SaveToBuffer(context->GetRawTilingData()->GetData(), 
context->GetRawTilingData()->GetCapacity());
context->GetRawTilingData()->SetDataSize(tiling.GetDataSize());

context->GetRawTilingData()获得框架预分配的原始tiling数据缓冲区对象

context->GetRawTilingData()->GetData()获得上述缓冲区的首地址

context->GetRawTilingData()->GetCapacity()获得上述缓冲区的最大容量

context->GetRawTilingData()->SetDataSize(tiling.GetDataSize())将tiling对象的真实大小保存到缓冲区中

需要用到的核心数自己定义,并通过context->SetBlockDim(核心数)保存到context中

此外,还需要申请算子运行时可能需要临时内存空间(该空间位于Global Memory中)我们这里不需要这片空间,以下是声明无需这片空间的写法:

size_t *currentWorkspace = context->GetWorkspaceSizes(1);
currentWorkspace[0] = 0;

host侧搞定

op_kernel侧(sigmoid_custom.cpp):

先写初始化函数 Init,我们要在其中实现在获取在Global Memory 中的本AIcore要处理的张量块首地址以及要处理的元素个数,要处理的元素个数=张量总元素个数/使用的核心数

要处理的张量首地址=总张量首地址+一个核心要处理的元素数量(this->blockLength = totalLength / GetBlockNum();//GetBlockNum()获取一共有多少个核心)*核心id

xGm.SetGlobalBuffer((__gm__ DTYPE_X *)x + this->blockLength * GetBlockIdx(), 
        this->blockLength);

在 Ascend C(以及 C/C++ 的指针运算逻辑)中,blockLength不需要手动乘以元素大小,核心原因是指针的 “强类型特性” 会自动处理元素大小的偏移计算

同时要获取计算完要存储的位置的首地址和元素个数

yGm.SetGlobalBuffer((__gm__ DTYPE_Y *)y + this->blockLength * GetBlockIdx(), 
        this->blockLength);

我们还要初始化输入输出队列

pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(DTYPE_X));
pipe.InitBuffer(outQueueY, BUFFER_NUM, this->tileLength * sizeof(DTYPE_Y));

初始化队列的三个参数(队列名,缓冲区个数,单个缓冲区大小)单个缓冲区的大小就是要处理的最小元素块大小 this->tileLength = this->blockLength / tileNum / BUFFER_NUM;为一个最小元素块包含的元素个数

pipe为管道对象,在算子类的私有变量中定义:

TPipe pipe;
    //create queue for input, in this case depth is equal to buffer num
    TQue<QuePosition::VECIN, BUFFER_NUM> inQueueX;
    //create queue for output, in this case depth is equal to buffer num
    TQue<QuePosition::VECOUT, BUFFER_NUM> outQueueY;

上述代码已经写好,不需要改变 TQue&lt;QuePosition::逻辑位置, 缓冲区个数&gt; inQueueX;

次数init函数大致写好,后续还需要在此处为零时缓冲区初始化

接下来要补充对Process()函数中的“loopCount”的定义,要循环次数就是一个核心要处理的最小块的个数,也就是一个核心要处理的元素数/最小快包含的元素数  int32_t loopCount = this-&gt;blockLength / this-&gt;tileLength;

以上用到的变量定义在算子类私有变量中

uint32_t blockLength;
    uint32_t tileNum;
    uint32_t tileLength;

copyin:

思路:定义本地张量,将gm上的数据复制到本地张量中并让本地张量入队参与后续计算

LocalTensor<DTYPE_X> xLocal = inQueueX.AllocTensor<DTYPE_X>();
        DataCopy(xLocal, xGm[progress * this->tileLength ], this->tileLength);
        inQueueX.EnQue(xLocal);

DataCopy(目的张量,源张量,处理元素个数)

Compute:

需要取出本地张量

// 从输入队列中取出当前块的数据
            LocalTensor<DTYPE_X> xLocal=inQueueX.DeQue<DTYPE_X>();

创建一个本地张量存储计算结果

// 从输出队列分配空间用于存储结果
            LocalTensor<DTYPE_Y> yLocal=outQueueY.AllocTensor<DTYPE_Y>();

本地张量xlocal进过一系列计算(调用各种api(用到的api有

Muls(tmpTensor1, xLocal, inputVal1, this->tileLength);
        
        Exp(tmpTensor2, tmpTensor1, this->tileLength);

      
        Adds(tmpTensor3, tmpTensor2, inputVal2, this->tileLength);

        Duplicate(tmpTensor4,inputVal2,this->tileLength);

        Div(yLocal,tmpTensor4,tmpTensor3,this->tileLength);

)),将结果保存到ylocal中,并入队输出队列

outQueueY.EnQue<DTYPE_Y>(yLocal);
        // 释放输入数据占用的空间
        inQueueX.FreeTensor(xLocal);

期间需要用到一些临时缓冲区用于中间计算,临时缓冲区定义在算子类的私有变量中

TBuf<QuePosition::VECCALC> tmpBuffer1, tmpBuffer2, tmpBuffer3, tmpBuffer4;

init中临时缓冲区的初始化

pipe.InitBuffer(tmpBuffer1, this->tileLength * sizeof(DTYPE_Y));
        pipe.InitBuffer(tmpBuffer2, this->tileLength * sizeof(DTYPE_Y));
        pipe.InitBuffer(tmpBuffer3, this->tileLength * sizeof(DTYPE_Y));
        pipe.InitBuffer(tmpBuffer4, this->tileLength * sizeof(DTYPE_Y));

计算中会需要一些标量,定义如下

DTYPE_X reallone=static_cast&lt;DTYPE_X&gt;(-1.0f);

最后需要释放xlocal

 inQueueX.FreeTensor(xLocal);

copyout:

将定义ylocal,从输出队列中取出数据块给ylocal,并将ylocal传输到gm中,最后释放ylocal

AscendC::LocalTensor<half> yLocal = outQueueY.DeQue<half>();
        DataCopy(yGm[progress * this->tileLength], yLocal, this->tileLength);
        outQueueY.FreeTensor(yLocal);

最后补充核函数中算子对象调用初始化与执行的代码

//补充init和process函数调用内容
    op.Init(x, y, tiling_data.totalLength, tiling_data.tileNum);
    op.Process();

环境搭建参考中级认证给的华为云环境搭建文档

每次启动华为云之后执行一下两条指令

source ~/.bashrc

source /home/ma-user/Ascend/ascend-toolkit/set_env.sh

完结

我要发帖子