CANN学习资源开源仓的中级算子开发三泛化Tiling

举报
黄生 发表于 2026/03/27 22:47:13 2026/03/27
【摘要】 如果开发者想支持“一类” 算子,能适合任何合法的数据类型、形状,甚至多种昇腾AI处理器型号,这种场景,称之为算子的泛化。其中,泛化tiling开发的原则有三:内存对齐(32字节是最小粒度单位)、访存优化(单次多搬,减少搬运次数)、多核均衡。基本概念回顾:每次搬运的那一部分数据块,叫做Tiling块;根据不同输入形状确定搬入基本块大小的相关算法,叫做Tiling算法(或Tiling策略)。根据...

如果开发者想支持“一类” 算子,能适合任何合法的数据类型、形状,甚至多种昇腾AI处理器型号,这种场景,称之为算子的泛化。其中,泛化tiling开发的原则有三:内存对齐(32字节是最小粒度单位)、访存优化(单次多搬,减少搬运次数)、多核均衡。

基本概念回顾:每次搬运的那一部分数据块,叫做Tiling块;根据不同输入形状确定搬入基本块大小的相关算法,叫做Tiling算法(或Tiling策略)。根据每个核计算的数据量是否相同、核内每个数据块的数据量是否相同,切分策略可有:

  • 核间均分,核内均分。
  • 核间均分,核内不均分:通过尾块Tiling处理尾块数据的计算。
  • 核间不均分,核内均分:通过尾核Tiling的处理解决数据无法在各核间均匀分配的问题。
  • 核间不均分,核内不均分:需要同时考虑尾核&尾块。

以状为(1,660)的half类型输入数据,要分配到4个aicore上完成Add计算为例。660×2=1320B无法被32整除,向上补齐至32B对齐,为1344B。将补齐后的 42 个 32B 数据块 分配到 4 个 核,则每核10块,余2块由前 2 个核各多处理 1 块。

假设UB(Unified Buffer)容量限制为768B,单核单次处理数据量受此约束,需按UB大小对核内数据进行批次拆分。

为存储 Tiling 相关参数,基于前述的核间拆分、UB 批次拆分逻辑,需要设计以下字段:

结构体字段 对应数值 计算逻辑/业务含义
smallCoreDataNum 320B 小核(后2个核)总数据量:10个32B块 × 32B/块 = 320B
bigCoreDataNum 352B 大核(前2个核)总数据量:11个32B块 × 32B/块 = 352B
finalBigTileNum 2 大核批次次数:单批次最多8块(256B),11块需分2批(8块+3块)
finalSmallTileNum 2 小核批次次数:单批次最多8块(256B),10块需分2批(8块+2块)
tileDataNum 256B 单核单次最大搬运量:UB 768B ÷ 3(2输入+1输出)= 256B(8个32B块)
smallTailDataNum 64B 小核最后一批数据量:2个32B块 × 32B/块 = 64B
bigTailDataNum 96B 大核最后一批数据量:3个32B块 × 32B/块 = 96B
tailBlockNum 2 大核个数:42个32B块 ÷ 4核,余数为2,前2个核为大核

在计算以上字段的过程中,会涉及到以下中间计算

  • GetCoreNum:获取当前环境的核数
  • GetShapeSize:获取输入的元素数量
  • GetDataTypeLength: 获取输入数据类型所占内存大小
  • everyCoreInputBlockNum: 每个核处理的32B数据块个数(舍去余数),然后
  • tailBlockNum: 前tailBlockNum个核每个核多计算一个32B数据块(余数)
  • tileBlockNum:和tileDataNum表达同一东西,只是单位不一样,此处单位是Block(32B数据块)
  • GetCoreMemSize: 获取硬件平台存储空间的内存大小
  • smallTileNum: 每个核分到数据不能一次运算完成时需要循环计算的次数(舍去余数),然后前面加final就看有没有余数了,有余数final就加一,无余数则相等。

最后在算大核的时候,对于大核,因需多处理1个32B数据块,其实际处理的32B数据块总数为everyCoreInputBlockNum + 1,所以代码里直接everyCoreInputBlockNum += 1; 这个是不是有点粗鲁了,如果核间均分,就没有大小核之分,那这里怎么说呢?

最后说明,此tiling方案可全面覆盖4种不同的Tiling场景(未考虑开启Double Buffer模式)。

然后看device侧,算子类通过模板接收输入和输出的数据类型,Init函数入参包含输入输出张量GM地址、Tiling切分数据。

  1. 先基于大核处理元素数,计算当前核的GlobalMemory地址偏移。
  2. 通过当前核索引与tailBlockNum的对比结果判断核类型
    • 若为大核,直接配置算子类的大核规格参数(元素数、切分次数、尾块元素数);
    • 若为小核,则配置小核规格参数,修正GlobalMemory地址偏移(因初始偏移量基于大核计算)。
  3. 最后根据已配置的核规格参数,完成GlobalTensor的初始化与LocalMemory的内存分配。

代码:

    __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t smallCoreDataNum,
                                uint32_t bigCoreDataNum, uint32_t finalBigTileNum, 
                                uint32_t finalSmallTileNum, uint32_t tileDataNum, 
                                uint32_t smallTailDataNum, uint32_t bigTailDataNum, 
                                uint32_t tailBlockNum) 
    {
        uint32_t coreNum = AscendC::GetBlockIdx();
        uint32_t globalBufferIndex = bigCoreDataNum * AscendC::GetBlockIdx();
        this->tileDataNum = tileDataNum;
        if (coreNum < tailBlockNum) { 
          this->coreDataNum = bigCoreDataNum;
          this->tileNum = finalBigTileNum;
          this->tailDataNum = bigTailDataNum;
        }
        else { 
          this->coreDataNum = smallCoreDataNum;
          this->tileNum = finalSmallTileNum;
          this->tailDataNum = smallTailDataNum;
          globalBufferIndex -= (bigCoreDataNum - smallCoreDataNum) * (AscendC::GetBlockIdx() - tailBlockNum);
        }
        xGm.SetGlobalBuffer((__gm__ TYPE_X*)x + globalBufferIndex, this->coreDataNum);
        yGm.SetGlobalBuffer((__gm__ TYPE_Y*)y + globalBufferIndex, this->coreDataNum);
        zGm.SetGlobalBuffer((__gm__ TYPE_Z*)z + globalBufferIndex, this->coreDataNum);
        pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileDataNum * sizeof(TYPE_X));
        pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileDataNum * sizeof(TYPE_Y));
        pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileDataNum * sizeof(TYPE_Z));
    }

//核函数中搬运和运算会使用到的变量,和tiling结构体相比已大大减少
private:
    AscendC::TPipe pipe;
    AscendC::TQue<AscendC::QuePosition::VECIN, BUFFER_NUM> inQueueX, inQueueY;
    AscendC::TQue<AscendC::QuePosition::VECOUT, BUFFER_NUM> outQueueZ;
    AscendC::GlobalTensor<TYPE_X> xGm;
    AscendC::GlobalTensor<TYPE_Y> yGm;
    AscendC::GlobalTensor<TYPE_Z> zGm;

    uint32_t coreDataNum;
    uint32_t tileNum;
    uint32_t tileDataNum;
    uint32_t tailDataNum;
    uint32_t processDataNum;

至于考入运算考出的循环,以及算子调用,不在赘述

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。