尾核Tiling

对于不同shape的输入进行数据切分时,可能会发生数据无法平均分配到多个核的情况。例如当算子的输入shape为[1, 1999],使用核数为8,数据类型为half时,需要计算的数据总量为1 * 1999 * sizeof(half) = 3998字节,3998字节既不满足32字节对齐,也无法平均分配到8个核上。因此该场景下,对数据进行多核切分后,每个核的计算数据量不同。此种情况下,应该尽可能均匀的分配数据,所有核上的计算数据量有两种情况,将计算量较多的核称为整核,计算量较少的核称为尾核。

图1 数据对齐示意图

Tiling实现

基于上文,设计如下的算子Tiling结构体成员:

Tiling参数的计算代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
constexpr uint32_t BLOCK_DIM = 8;
constexpr uint32_t SIZE_OF_HALF = 2;
constexpr uint32_t BLOCK_SIZE = 32;
// shape需要对齐到的最小单位
constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / SIZE_OF_HALF;
...
uint8_t *GenerateTiling()
{
    // shape需要对齐到的datablock,假设原totalLength为1999,向上满足32字节对齐后为2000
    uint32_t totalLengthAligned = ((totalLength + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
    // 核心数为8,一个datablock包含16个数,那么:datablock的总数:2000 / 16 = 125
    // 有5个核会分到16个datablock:125 % 8 =5,可以称之为整核
    // 有3个核会分到15个datablock:8 - 5 = 3,可以称之为尾核
    uint32_t formerNum = (totalLengthAligned / ALIGN_NUM) % BLOCK_DIM; 
    uint32_t tailNum = BLOCK_DIM - formerNum;
    // 整核计算的数据量:totalLengthAligned / BLOCK_DIM为每个核上计算的元素个数,formerLength为上述元素个数向上32字节对齐的结果
    uint32_t formerLength = ((totalLengthAligned / BLOCK_DIM + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
    // 尾核计算的数据量:totalLengthAligned / BLOCK_DIM为每个核上计算的元素个数,tailLength 为上述元素个数向下32字节对齐的结果
    uint32_t tailLength = (totalLengthAligned / BLOCK_DIM / ALIGN_NUM) * ALIGN_NUM;
    ...
}

算子类实现

在Kernel侧的Init函数中,计算输入在Global Memory上的内存偏移地址时,应对整核和尾核加以区分。

整核上,输入的内存偏移地址计算代码如下:

1
xGm.SetGlobalBuffer((__gm__ half *)x + formerLength * AscendC::GetBlockIdx(), formerLength);

尾核上,计算输入的内存偏移地址时,需在全部整核的数据长度基础上加上尾核的偏移量,代码如下:

1
xGm.SetGlobalBuffer((__gm__ half *)x + formerLength * formerNum + tailLength * (AscendC::GetBlockIdx() - formerNum), tailLength);

完整的Init函数实现代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, AddCustomTilingData tiling)
{
    if (AscendC::GetBlockIdx() < formerNum) {
        this->tileLength = formerLength;
        xGm.SetGlobalBuffer((__gm__ half *)x + formerLength * AscendC::GetBlockIdx(), formerLength);
        yGm.SetGlobalBuffer((__gm__ half *)y + formerLength * AscendC::GetBlockIdx(), formerLength);
        zGm.SetGlobalBuffer((__gm__ half *)z + formerLength * AscendC::GetBlockIdx(), formerLength);
    } else {
        this->tileLength = tailLength;
        xGm.SetGlobalBuffer((__gm__ half *)x + formerLength * formerNum + tailLength * (AscendC::GetBlockIdx() - formerNum), tailLength);
        yGm.SetGlobalBuffer((__gm__ half *)y + formerLength * formerNum + tailLength * (AscendC::GetBlockIdx() - formerNum), tailLength);
        zGm.SetGlobalBuffer((__gm__ half *)z + formerLength * formerNum + tailLength * (AscendC::GetBlockIdx() - formerNum), tailLength);
    }
    pipe.InitBuffer(inQueueX, 1, this->tileLength * sizeof(half));
    pipe.InitBuffer(inQueueY, 1, this->tileLength * sizeof(half));
    pipe.InitBuffer(outQueueZ, 1, this->tileLength * sizeof(half));
}

其余实现与多核Tiling中的实现一致,这里不重复进行说明。