优化bank分配以提升读写性能

【优先级】高

【描述】Unified Buffer中bank结构如下图所示。UB总大小(假设为192K)会被分成48个bank:每个bank由128(bank_depth)行,长度为32B(C0)的二维结构组成;bank间组成为16bank group(bank_group = 16),每个group3bank(bank_num = 3,bank15,bank31,bank47组成一个bank group),即bank_group * bank_num的二维结构

图1 bank结构示意图

Vector单元每拍(一拍为一个指令周期)能够从每个bank group中读取一行数据,因此每拍最多能够读取16组 * C0(32B)= 512B数据。Vector单元每拍能够向每个bank group中写入一行数据,因此每拍最多能够写入16组 * C0(32B)= 512B数据。当多个操作尝试同时访问同一个bank或者bank group时,可能会发生bank冲突,这种冲突会导致访问排队,降低性能。

bank冲突会出现在以下三种场景:

假设,0x10000地址在bank16上,0x10020在bank17上,0x20020在bank33上,如下图所示:

图2 地址分配示意图

通过MsProf工具可以进行资源冲突占比的相关性能数据采集。

工具的具体使用方法请参考算子调优(msProf)。资源冲突占比文件性能数据文件说明请参考ResourceConflictRatio(资源冲突占比)

【反例】

对一个输入或输出tensor使用tensor高维切分接口实现跳读跳写,当dataBlockStride为16的整数倍时将发生读读冲突。假设我们要对一个shape为(8, 16, 16)的输入做(1, 0, 2)的transpose,输出shape为(16, 8, 16)。

如下“跳读,连续写”的代码,将导致同一repeat内输入的8个datablock都在同一个bank_group而发生读读冲突。

1
2
3
4
5
6
7
uint64_t mask = 128;
UnaryRepeatParams params;
params.dstBlkStride  = 1;
params.srcBlkStride = 16;
for(uint32_t i=0; i<16; i++)   {
    AscendC::Adds(dstLocal[i * 128], srcLocal[i * 16], 0, mask, 1, params);
}
图3 “跳读,连续写”数据读写示意图

【正例】

通过修改取数规则为“连续读,跳写”避免冲突问题,示例代码如下:

1
2
3
4
5
6
7
uint64_t mask = 128;
UnaryRepeatParams params;
params.dstBlkStride  = 8;
params.srcBlkStride = 1;
for(uint32_t i=0; i<8; i++)   {
    AscendC::Adds(dstLocal[i * 16], srcLocal[i * 256], 0, mask, 2, params);
}
图4 “连续读,跳写”数据读写示意图

【正例】

如果在申请的一块workBuffer中有两个输入向量,则两个向量的起始地址不能在同一个bank group中,避免方法为:多申请32B字节LocalTensor,使得workBuffer2个输入错开32字节

例如,计算z = x + y时,xworkBuffer0地址开始,长度为8K字节,y从8k字节地址开始,长度为8K字节,则x和y物理地址会落到同一块bank中。在分配地址中,增加一定长度,可避免bank冲突,示例代码和地址分配示意图如下:

1
2
3
4
LocalTensor<float> srcLocal;
LocalTensor<float> dstLocal;
UnaryRepeatParams params;
AscendC::Add(dstLocal, srcLocal[0], srcLocal[(8 * 1024 + 32) / sizeof(float)], mask, (8 * 1024) / 256 , params);
图5 地址分配示意图