昇腾社区首页
中文
注册

尾块处理场景

如下图中的示例,算子的输入shape为(1,2048),支持的数据类型为half类型,可以对齐到一个datablock的大小(32B),也可以平均分配到每个核上(假设使用8个核),每个核上处理256个数,16个datablock。此时不需要进行尾块处理。

图1 shape对齐场景

针对一些shape,比如算子的输入shape为(1,1999),支持的数据类型为half类型,既无法对齐到一个datablock的大小(32B),也无法平均分配到每个核上。多核切分后需要进行尾块处理。

  • 因为昇腾AI处理器在进行数据搬运和Vector计算时,对于搬运的数据长度和UB首地址都有必须32B对齐的要求,首先待处理数据需要先保证对齐到datablock的大小。该场景下后续搬运和计算的处理细节请参考非对齐场景。下图和代码片段展示了将数据对齐到datablock大小的示例:
    图2 对齐到datablock大小
    constexpr uint32_t SIZE_OF_HALF = 2;
    constexpr uint32_t BLOCK_SIZE = 32;
    constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / SIZE_OF_HALF;
    // shape需要对齐到的datablock,假设原totalLength为1999,向上满足32字节对齐后为2000
    uint32_t totalLengthAligned = ((totalLength + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
  • 满足datablock对齐后的数据,应尽可能的均分到每个核上。如果无法均分,那么先将可以均分的部分平均分配,剩余的部分分配给部分核,会有部分核多算一个datablock。下图展示了无法均分时将数据进行多核切分的示例。对齐到datablock后为2000个half类型的数据,共125个datablock。125除以8,商为15,余数为5,说明:可以均分的部分平均分配,每个核分配到15个datablock; 还剩余5个datablock,分配给5个核,所以会有5个核分配到16个datablock,剩余3个核分配到15个datablock。
    图3 无法均分到每个核上的示例

    基于上文的描述,可以设计如下的Tiling参数:

    • formerNum:分配到大块的核数
    • tailNum:分配到小块的核数
    • formerLength:大块计算的数据量
    • tailLength:小块计算的数据量
    • alignNum:一个datablock包含的元素个数

    这些Tiling参数的计算方法如下:

    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,可以称之为大块
        
        uint32_t formerNum = (totalLengthAligned / ALIGN_NUM) % BLOCK_DIM;
        // 有3个核会分到15个datablock:8 - 5 = 3,可以称之为小块
        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侧,使用获取到的信息计算得到每个核上的偏移量、每个分块大小的样例如下。

__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t formerNum, uint32_t tailNum, uint32_t formerLength, uint32_t tailLength, uint32_t alignNum)
{
    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, BUFFER_NUM, this->tileLength * sizeof(half));
    pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(half));
    pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(half));
}