如何高效处理Ascend C非对齐数据?优化技巧全解析
对于Ascend C算子开发者而言,为了更高效地操作变量,通常需要满足相应的数据对齐要求。例如在使用DataCopy接口进行数据传输时,必须确保搬运的数据长度和操作数在UB上的起始地址为32字节对齐;在进行向量计算时,操作数的起始地址也需满足32字节对齐的要求。然而,在大多数场景下,会遇到大量非对齐的数据情况。
本文提供了完整的处理方案,帮助开发者更加灵活地应对数据非对齐的情况。
1. 数据非对齐的典型场景
首先介绍一些非对齐搬运和计算的实例。
-
非对齐搬入:当需要从Global拷贝11个half数值到Local时,为保证搬运的数据长度32字节对齐,使用DataCopy拷贝16个half(32B)数据到Local上,Local[11]~Local[15]被设置成无效数据-1。下文描述中的Global指Global Memory上的tensor,Local指Local Memory上的tensor。
(非对齐搬入)
-
非对齐搬出:当需要从Local拷贝11个half数值到Global时,为保证搬运的数据长度32字节对齐,使用DataCopy拷贝16个half(32B)数据到Global上,Global[11]~Global[15]被设置成无效数据-1。
(非对齐搬出)
-
矢量计算起始地址非32字节对齐的错误示例:矢量计算时需要保证起始地址32字节对齐,如下的示例中,从Local1[7],即LocalTensor的第8个数开始计算,起始地址不满足32字节对齐,是错误示例。
(矢量计算起始地址非32字节对齐的错误示例)
2. DataCopyPad接口提供非对齐数据搬运功能
目前,对于Atlas A2 训练系列产品、Atlas 800I A2 推理产品、A200I A2 Box 异构组件、Atlas A3 训练系列产品、Atlas A3 推理系列产品以及Atlas 200I/500 A2 推理产品,DataCopyPad接口提供了非对齐数据搬运的功能,开发者可以使用该接口解决非对齐场景下的数据搬运问题。
使用DataCopyPad接口时,主要关注DataCopyExtParams和DataCopyPadExtParams的参数配置,DataCopyExtParams用于控制搬运的数据块大小、步长间隔等信息,DataCopyPadExtParams用于控制Pad补齐的相关信息。
表2-1DataCopyExtParams结构体参数定义
表2-2DataCopyPadExtParams<T>结构体参数定义
接下来我们通过Global Memory->Local Memory和Local Memory->Global Memory通路的DataCopyPad接口参数配置示例来进一步了解DataCopyPad接口的使用方法。
对于Global Memory->Local Memory(GM->VECIN/VECOUT)的数据搬运,各参数使用说明如下:
-
当blockLen+leftPadding+rightPadding满足32字节对齐时,isPad为false,左右两侧填充的数据值会默认为随机值;否则为paddingValue。
-
当blockLen+leftPadding+rightPadding不满足32字节对齐时,框架会填充一些假数据dummy,保证填充后的数据(左右填充的数据+blockLen+假数据)为32字节对齐。若leftPadding、rightPadding都为0:dummy会默认填充待搬运数据块的第一个元素值;若leftPadding/rightPadding不为0:isPad为false,左右两侧填充的数据值和dummy值均为随机值;否则为paddingValue。
配置示例1:
-
blockLen为64,每个连续传输数据块包含64Bytes;srcStride为1,因为源操作数的逻辑位置为GM,srcStride的单位为Byte,也就是说源操作数相邻数据块之间间隔1Byte;dstStride为1,因为目的操作数的逻辑位置为VECIN/VECOUT,dstStride的单位为dataBlock(32Bytes),也就是说目的操作数相邻数据块之间间隔1个dataBlock。
-
blockLen+leftPadding+rightPadding满足32字节对齐,isPad为false,左右两侧填充的数据值会默认为随机值;否则为paddingValue。此处示例中,leftPadding、rightPadding均为0,则不填充。
-
blockLen+leftPadding+rightPadding不满足32字节对齐时,框架会填充一些假数据dummy,保证填充后的数据(左右填充的数据+blockLen+假数据)为32字节对齐。leftPadding/rightPadding不为0:若isPad为false,左右两侧填充的数据值和dummy值均为随机值;否则为paddingValue。
配置示例2:
-
blockLen为47,每个连续传输数据块包含47Bytes;srcStride为1,表示源操作数相邻数据块之间间隔1Byte;dstStride为1,表示目的操作数相邻数据块之间间隔1个dataBlock。
-
blockLen+leftPadding+rightPadding不满足32字节对齐,leftPadding、rightPadding均为0:dummy会默认填充待搬运数据块的第一个元素值。
-
blockLen+leftPadding+rightPadding不满足32字节对齐,leftPadding/rightPadding不为0:若isPad为false,左右两侧填充的数据值和dummy值均为随机值;否则为paddingValue。
对于Local Memory->Global Memory(VECIN/VECOUT->GM)的数据搬运,不需要开发者自行填充数据,所以无需关注DataCopyPadExtParams,仅需要关注DataCopyExtParams。
当每个连续传输数据块长度blockLen不满足32字节对齐,由于UB要求32字节对齐,框架在搬出时会自动补充一些假数据来保证对齐,但在当搬到GM时会自动将填充的假数据丢弃掉。
下图呈现了该场景下需要传入的DataCopyParams示例和假数据补齐的原理。blockLen为47,每个连续传输数据块包含47Bytes,不满足32字节对齐;srcStride为1,表示源操作数相邻数据块之间间隔1个dataBlock;dstStride为1,表示目的操作数相邻数据块之间间隔1Byte。框架在搬出时会自动补充17Bytes的假数据来保证对齐,搬到GM时再自动将填充的假数据丢弃掉。
3. 不支持DataCopyPad接口,如何处理非对齐数据
然而,某些型号(如Atlas 推理系列产品和Atlas 训练系列产品 ),目前仍不支持DataCopyPad接口,需要参考如下方案处理。
(非对齐处理方案示意图)
由于搬入时搬运的数据长度必须保证32字节对齐。数据长度非对齐的情况下,从Global逐行搬运Tensor数据到Local中,Local中每行都存在冗余数据。
搬入后,进行矢量计算时对冗余数据的处理方式有以下几种:
-
冗余数据参与计算。一般用于elewise计算场景。
-
通过mask参数掩掉冗余数据。一般用于轴归约计算等场景。
-
通过Duplicate逐行清零。计算前,针对每一行数据,调用基础API Duplicate对冗余数据位置填充0值。
-
通过Pad一次性清零。计算前,针对多行数据,可以采用高阶API Pad接口对冗余数据一次性清零。
由于搬出时搬运的数据长度和操作数的起始地址(UB上)必须保证32字节对齐,搬出时可以选择去除冗余数据或者带着冗余数据搬出的方式。
-
使用UnPad接口去除冗余数据后搬出。待搬出的有效数据总长度满足32字节对齐时,可使用高阶API UnPad接口去除冗余数据并完整搬出。
-
使用GatherMask收集有效数据后搬出。待搬出的有效数据总长度大于等于32字节时,可使用GatherMask重新收集有效数据,保证搬出的有效数据起始地址和数据长度32字节对齐。
-
带冗余数据搬出。注意多核处理时开启原子加(使用SetAtomicAdd接口),防止数据踩踏。
下面分别对上述几种处理方案做详细说明,相关代码样例请参考样例介绍。
1)冗余数据参与计算
如下图所示,对前11个half数据进行Abs计算,冗余数据可以参与计算,不影响最终结果。步骤为:
a.使用DataCopy从GLobal搬运16个half数据到Local1中,包含冗余数据-11~-15;
b.直接使用Abs做整块计算,不用计算尾块大小,冗余数据参与计算。
(冗余数据参与计算)
2)使用mask掩掉冗余数据
如下图所示,假设输入数据的shape为16 * 4,将输入数据搬入到UB后每行数据前4个half数据为有效数据,其余为冗余数据。为只对前4个half数据进行ReduceMin计算,可以通过设置mask参数的方法掩掉冗余数据。针对每行数据的处理步骤为:
a.使用DataCopy从Global搬运16个half数据到Local1中;
b.对归约计算的目的操作数Local2清零,如使用Duplicate等;
c.进行归约操作,将ReduceMin的mask模式设置为前4个数据有效,从而掩掉冗余数据。
(使用mask掩掉脏数据)
3)通过Duplicate逐行清零。
如下图所示,对于搬入后的非对齐数据,逐行进行Duplicate清零处理,步骤为:
a.使用DataCopy从Global搬运16个half数据到Local中;
b.使用基础API Duplicate,按照如下方式设置mask值,控制仅后5个元素位置有效,将冗余数据填充为0。
uint64_t mask0 = ((uint64_t)1 << 16) - ((uint64_t)1 << 11); uint64_t mask[2] = {mask0, 0};
(通过Duplicate逐行清零)
4)通过Pad一次性清零。
如下图所示,假设输入数据的shape为16 * 6,搬入Local后大小为16 * 16,每行都包含冗余数据,逐行清零性能较差,可以使用Pad一次性清零,步骤为:
a.将16 * 6的数据从GM上逐行搬入UB后,每行有6个有效数据;
b.使用Pad接口将冗余数据位置填充为0。(对应Pad接口使用场景为:tensor的width已32B对齐,但是有部分冗余数据)。
(通过Pad一次性清零)
5)使用UnPad接口去除冗余数据后搬出。
如下图所示,Local内存大小为16*16,每行中只有前6个数为有效数据, 要搬出的有效数据16 * 6满足32B对齐,可以使用UnPad接口去除冗余数据并完整搬出。步骤如下:
a.使用UnPad高阶API去除冗余值;
b.使用DataCopy搬运出连续的16 * 6个half数据到Global中。
(使用UnPad接口去除冗余数据后搬出)
6)使用GatherMask收集有效数据后搬出。
如下图所示,为搬出19个half数据到Global中,有16-18这3个数据的搬运无法满足对齐要求,使用GatherMask对有效数据进行重新收集,收集3-18这16个数据并搬出。步骤如下:
a.完整拷贝前16个half(32B)数据到Global中;
b.使用GatherMask接口,将Local1[3]~[18]的数Gather到Local2中,Local2从对齐地址开始;
c.从Local2中搬运Gather的数据(32B整数倍)到Global中。
(使用GatherMask收集有效数据后搬出)
7)带冗余数据搬出
如下图所示,有4个核参与计算,每个核拷贝出4个数,每个核上拷贝的数据长度不满足32字节对齐,采用将冗余数据一起搬出的方式,步骤如下:
a.将目标Global完整清零,可以通过在host清零或者在kernel侧用UB覆盖的方式处理;
b.将本核内的Local数据,除了要搬出的4个有效数据,其余冗余部分清零(使用Duplicate接口);
c.使用原子累加的方式拷贝到Global,原子累加结合冗余数据清零,确保不会出现数据踩踏。
(带冗余数据搬出)
4. 更多学习资源
欢迎访问昇腾社区Ascend C信息专区,获取Ascend C更多学习资源。
- 点赞
- 收藏
- 关注作者
评论(0)