MC2通算融合算子的性能收益主要来自于通信、计算的并行执行,即将输入数据切分为多个子块,子块的计算和通信任务形成两条流水线,通过两条流水线上任务的并行执行,实现流水掩盖,从而提升算子性能。如下图所示,MC2算子先做Matmul计算、后通信的场景,输入矩阵沿M轴被切分为两块,第二块数据的Matmul计算和第一块数据的通信可以并行执行,从而达到计算和通信时间相互掩盖的目的。本节的所有图示中MM代表Matmul计算,hcom代表通信任务。
本案例将介绍如何分析通算融合算子的性能收益、如何制定较好的数据切分策略。更多MC2算子的完整样例请参考MatmulAllReduce样例、MatmulReduceScatter样例、AllGatherMatmul样例。
通过msProf算子调优工具获取算子性能数据:
MC2算子性能收益公式为:
融合后MC2算子的执行耗时,受以下因素制约,从而影响算子性能收益。
若计算和通信任务的执行时间相差不大,则融合后,MC2算子的计算和通信并行执行,能得到较好的流水掩盖,性能收益较大。
若计算和通信任务的执行时间差异较大,则融合后,MC2算子内计算和通信并行执行,能够掩盖的时间较少,算子整体执行耗时与未切分串行时的算子执行耗时接近,此时无法获得较大性能收益。
当对输入数据进行切分后,原本的整块数据被切分成若干小数据块,对若干小数据块分别做Matmul计算或者执行通信任务,此时相比切分前,计算或者通信任务的执行时间可能发生膨胀(即执行时间变长)。该膨胀产生的原因包括:切分后的数据块过小导致计算或通信的效率降低、切分的数据块过多导致增加额外的调度开销、并行执行后计算和通信对L2 Cache或device存储内存的访问冲突等。以Matmul计算为例,简单说明数据切分后执行时间可能发生的膨胀情况。
数据切分前,Matmul执行时间为200us,将Matmul的输入均匀切分为两块,假设切分后,每块数据的Matmul执行时间都是100us,通过计算的并行执行,下图实际性能收益为100us。
数据切分前,Matmul执行时间为200us,将Matmul的输入均匀切分为两块,假设切分后,每块数据的Matmul执行时间都是150us,通过计算的并行执行,下图实际性能收益为50us。
数据切分前,Matmul执行时间为200us,将Matmul的输入均匀切分为两块,假设切分后,每块数据的Matmul执行时间都是200us,通过计算的并行执行,下图实际性能收益为劣化50us。
综合上述分析,计算和通信执行时间较均衡的场景,有更好的流水掩盖和性能收益;同时,性能收益也受到数据切分导致的执行时间膨胀的影响。下文将介绍如何制定数据切分策略,以达到最佳流水掩盖效果。
以
该算子的数据切分策略应满足如下要求:
如上文所述,数据切分的目标是达成尽可能多的流水掩盖。根据计算与通信任务的执行时间差异,实际场景可以分解为如下两个具体场景,两个场景有各自细分的切分目标。
前置工作:
在进行最终的数据切分前,需要做的前置工作有:判定bound场景、分别对Matmul计算和AllReduce通信的数据量与执行时间关系做公式拟合。具体步骤如下。
数据量x = m * N * sizeof(dataType),单位是Bytes。该拟合公式表示为:
切分算法步骤:
注意:通信和计算并行执行时,可能出现抢占内存带宽的情况,导致执行时间增加,一般按经验在拟合公式中乘以1.15的系数,用户可以根据实测情况调整该系数。
本MatmulAllreduce案例中,给定的输入矩阵Shape为M=4096,K=3072,N=8192,数据类型为half,分核数为8,通过融合前msProf工具采集,得到该输入的Matmul计算执行时间为803us,AllReduce通信的执行时间为1071us,总耗时1874us,属于通信bound场景。按照上述的切分算法,本案例的具体切分情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | MatmulAllReduceCustomTilingData *tiling = context->GetTilingData<MatmulAllReduceCustomTilingData>(); tiling->param.rankDim = 8; tiling->param.tileM = 512; // 短块大小 tiling->param.tileNum = 1; // 短块个数 tiling->param.tailM = 896; // 长块大小 tiling->param.tailNum = 4; // 长块个数 tiling->param.rankM = 4096; tiling->param.rankN = 8192; tiling->param.rankK = 4096; tiling->param.isTransposeA = 0; tiling->param.isTransposeB = 0; tiling->param.cToFloatLen = 0; tiling->param.nd2NzWorkLen = true; tiling->param.dataType = static_cast<uint8_t>(HCCL_DATA_TYPE_MAP.at(aType)); |
MC2算子通过数据切分后计算和通信的并行执行,获得性能收益,但受数据切分后执行时间膨胀的影响。对MC2算子进行性能调优的主要方式是制定数据切分策略,开发人员需要根据理论推导找到理想切分策略,然后根据实测结果调整,最终找到最优切分策略。