开发者
资源
[object Object]

本节介绍非32字节对齐数据的更多处理方式,包括数据搬入、计算和搬出的处理。用户在实际算子开发中,可以参考如下方案介绍和算子样例灵活地处理非对齐场景。

[object Object]
[object Object]

下文描述中的Global指Global Memory上的tensor,Local指Local Memory上的tensor。

下面是一些非对齐搬运和计算的例子。

  • 非对齐搬入

    当需要从Global拷贝11个half数值到Local时,为保证搬运的数据长度32字节对齐,使用DataCopy拷贝16个half(32B)数据到Local上,Local[11]~Local[15]被写成无效数据-1。

    图 1 非对齐搬入[object Object][object Object]

  • 非对齐搬出

    当需要从Local拷贝11个half数值到Global时,为保证搬运的数据长度32字节对齐,使用DataCopy拷贝16个half(32B)数据到Global上,Global[11]~Global[15]被写成无效数据-1。

    图 2 非对齐搬出[object Object][object Object]

  • 矢量计算起始地址非32字节对齐的错误示例

    矢量计算时需要保证起始地址32字节对齐,如下的示例中,从Local1[7],即LocalTensor的第8个数开始计算,起始地址不满足32字节对齐,是错误示例。

    图 3 矢量计算起始地址非32字节对齐的错误示例[object Object][object Object]

[object Object]

DataCopyPad接口提供非对齐搬运的功能,如果基于该接口支持的产品开发算子(参见产品支持情况),则可以直接使用该接口解决非对齐场景下的搬运问题。使用DataCopyPad的完整示例请参考

部分型号不支持DataCopyPad接口,需要参考如下的方案处理。

图 4 非对齐处理方案示意图[object Object][object Object]

由于搬入时搬运的数据长度必须保证32字节对齐。数据长度非对齐的情况下,从Global逐行搬运Tensor数据到Local中,Local中每行都存在冗余数据。

搬入后,进行矢量计算时对冗余数据的处理方式有以下几种:

  • 冗余数据参与计算。一般用于elewise计算场景。
  • 通过mask参数掩掉冗余数据。一般用于轴归约计算等场景。
  • 通过Duplicate逐行清零。计算前,针对每一行数据,调用基础API 对冗余数据位置填充0值。
  • 通过Pad一次性清零。计算前,针对多行数据,可以采用高阶API 接口对冗余数据一次性清零。

由于搬出时搬运的数据长度和操作数的起始地址(UB上)必须保证32字节对齐,搬出时可以选择去除冗余数据或者带着冗余数据搬出的方式。

  • 使用UnPad接口去除冗余数据后搬出。待搬出的有效数据总长度满足32字节对齐时,可使用高阶API 接口去除冗余数据并完整搬出。
  • 使用GatherMask收集有效数据后搬出。待搬出的有效数据总长度大于等于32字节时,可使用重新收集有效数据,保证搬出的有效数据起始地址和数据长度32字节对齐。
  • 带冗余数据搬出。注意多核处理时开启原子加(使用接口),防止数据踩踏。

下面分别对上述几种处理方案做详细说明。

  • 冗余数据参与计算

    如下图所示,对前11个half数据进行Abs计算,冗余数据可以参与计算,不影响最终结果。步骤为:

    1. 使用DataCopy从Global搬运16个half数据到Local1中,包含冗余数据-11~-15;
    2. 直接使用Abs做整块计算,不用计算尾块大小,冗余数据参与计算。

    图 5 冗余数据参与计算[object Object][object Object]

  • 使用mask掩掉冗余数据

    如下图所示,假设输入数据的shape为16 * 4,将输入数据搬入到UB后每行数据前4个half数据为有效数据,其余为冗余数据。为只对前4个half数据进行ReduceMin计算,可以通过设置mask参数的方法掩掉冗余数据。针对每行数据的处理步骤为:

    1. 使用DataCopy从Global搬运16个half数据到Local1中;
    2. 对归约计算的目的操作数Local2清零,如使用Duplicate等;
    3. 进行归约操作,将ReduceMin的mask模式设置为前4个数据有效,从而掩掉冗余数据。

    图 6 使用mask掩掉脏数据[object Object][object Object]

  • 通过Duplicate逐行清零。

    如下图所示,对于搬入后的非对齐数据,逐行进行Duplicate清零处理,步骤为:

    1. 使用DataCopy从Global搬运16个half数据到Local中;

    2. 使用基础API Duplicate,按照如下方式设置mask值,控制仅后5个元素位置有效,将冗余数据填充为0。

      [object Object]

    图 7 通过Duplicate逐行清零[object Object][object Object]

  • 通过Pad一次性清零。

    如下图所示,假设输入数据的shape为16 * 6,搬入Local后大小为16 * 16,每行都包含冗余数据,逐行清零性能较差,可以使用Pad一次性清零,步骤为:

    1. 将16 * 6的数据从GM上逐行搬入UB后,每行有6个有效数据;
    2. 使用Pad接口将冗余数据位置填充为0。(对应Pad接口使用场景为:tensor的width已32B对齐,但是有部分冗余数据)。

    图 8 通过Pad一次性清零[object Object][object Object]

  • 使用UnPad接口去除冗余数据后搬出。

    如下图所示,Local内存大小为16*16,每行中只有前6个数为有效数据, 要搬出的有效数据16 * 6满足32B对齐,可以使用UnPad接口去除冗余数据并完整搬出。步骤如下:

    1. 使用UnPad高阶API去除冗余值;
    2. 使用DataCopy搬运出连续的16 * 6个half数据到Global中。

    图 9 使用UnPad接口去除冗余数据后搬出[object Object][object Object]

  • 使用GatherMask收集有效数据后搬出。

    如下图所示,为搬出19个half数据到Global中,有16-18这3个数据的搬运无法满足对齐要求,使用GatherMask对有效数据进行重新收集,收集3-18这16个数据并搬出。步骤如下:

    1. 完整拷贝前16个half(32B)数据到Global中;
    2. 使用GatherMask接口,将Local1[3]~[18]的数Gather到Local2中,Local2从对齐地址开始;
    3. 从Local2中搬运Gather的数据(32B整数倍)到Global中。

    图 10 使用GatherMask收集有效数据后搬出。[object Object][object Object]

  • 带冗余数据搬出

    如下图所示,有4个核参与计算,每个核拷贝出4个数,每个核上拷贝的数据长度不满足32字节对齐,采用将冗余数据一起搬出的方式,步骤如下:

    1. 将目标Global完整清零,可以通过在host清零或者在kernel侧用UB覆盖的方式处理;

    2. 将本核内的Local数据,除了要搬出的4个有效数据,其余冗余部分清零(使用Duplicate接口);

    3. 使用原子累加的方式拷贝到Global,原子累加结合冗余数据清零,确保不会出现数据踩踏。

      图 11 带冗余数据搬出[object Object][object Object]

[object Object]
  • 样例一:

    本样例中展示了shape为128 * 18的tensor进行Abs计算的算子实现。针对每行数据的处理方案如下:

    搬入后,每行数据的后14个数为冗余数据。Abs接口入参BLOCKLEN_CEIL为32个数,是18个数进行32字节对齐后的结果,有14个冗余数据参与计算。

    [object Object]

    计算完成后,通过GatherMask的bufPattern入参控制收集18个数中的后16个数。

    [object Object]

    首先使用DataCopy搬运前16个数,然后搬运后16个数,中间的14个数存在重复搬运。注意:因为DataCopy的目的地址存在重叠所以需要通过PipeBarrier添加流水同步。

    [object Object]

    搬入时要保证32字节对齐,所以要将输入的最后一行补齐到32字节对齐,防止访问非法数据,main.cpp中对GM上输入的长度的定义如下:

    [object Object]
  • [object Object][object Object]样例二:

    本样例中展示了shape为64 * 11的tensor进行Abs计算的算子实现。 共使用4个核,每个核处理16 * 11个数据。

    搬入后,每行数据的后5个数为冗余数据。通过Duplicate接口对每行数据中的后5个数据进行清零。

    [object Object]

    搬出时,带冗余数据搬出并开启原子累加,BLOCKLEN_CEIL中包含冗余数据。

    [object Object]

    所以在初始化时,需要对GM数据进行清零,清零代码如下,示例中多个核都调用接口进行清零,需要调用SyncAll进行核间同步。

    [object Object]

    搬入时要保证32字节对齐,需要将输入的最后一行补齐到32字节对齐,防止访问非法数据;搬出时带冗余数据搬出,输出的最后一行也需要补齐到32字节对齐。main.cpp中对GM上输入输出的长度的定义如下:

    [object Object]
  • 样例三:

    本样例中展示了shape为2048 * 14的tensor进行Abs计算的算子实现。 共使用8个核,每个核处理256 * 14个数据。

    搬入后,每行数据的后2个数为冗余数据。Abs接口入参BLOCK_GROUP_NUM * BLOCKLEN_CEIL为连续的16行数据,每行16个数,每行的冗余数据参与计算。

    [object Object]

    计算后,使用UnPad接口去除冗余数据后再搬出,通过unPadParams.rightPad参数控制去除每行最后的2个冗余数据。

    [object Object]

    注意:UnPad接口需要传入tiling参数。abs_unpad_tiling.cpp中关键计算过程如下:

    [object Object]

    tiling参数需要通过核函数的入参传入到kernel侧,供UnPad高阶API使用。

    [object Object]

    搬入时要保证32字节对齐,所以要将输入的最后一行补齐到32字节对齐,防止访问非法数据,main.cpp中对GM上输入的长度的定义如下。

    [object Object]
  • 示例四:

    本样例中展示了shape为2048 * 7的tensor进行Abs计算的算子实现。 共使用8个核,每个核处理256 * 7个数据。

    搬入后,每行数据的后9个数为冗余数据。每个核上通过Pad接口将256 * 9的冗余数据块整体清零后参与Abs计算。

    [object Object]

    计算后带冗余数据搬出的代码解释和一致。

    注意:Pad接口需要传入tiling参数。abs_pad_tiling.cpp中关键计算过程如下:

    [object Object]

    tiling参数需要通过核函数的入参传入到kernel侧,供Pad高阶API使用。

    [object Object]

    搬入时要保证32字节对齐,需要将输入的最后一行补齐到32字节对齐,防止访问非法数据;搬出时带冗余数据搬出,输出的最后一行也需要补齐到32字节对齐。main.cpp中对GM上输入输出的长度的定义如下:

    [object Object]
  • 样例五:

    本样例中展示了shape为16 * 4的tensor每行数据进行ReduceMin计算的算子实现。 共使用4个核,每个核处理4 * 4个数据。

    搬入后,每行数据的后12个数为冗余数据。通过ReduceMin的入参Mask控制只有前4个数参与计算。

    [object Object]

    计算后带冗余数据搬出的代码解释和一致。

    搬入时要保证32字节对齐,需要将输入的最后一行补齐到32字节对齐,防止访问非法数据;搬出时带冗余数据搬出,输出的最后一行也需要补齐到32字节对齐。main.cpp中对GM上输入输出的长度的定义如下:

    [object Object]