下载
中文
注册

多核场景

多核切分

为了实现多核并行,提升计算效率,需要将矩阵数据进行切分,分配到不同的核上进行处理。主要的切分策略有切分K轴和不切分K轴两种。

不切分K轴、仅切分M、N轴的策略如下:

  • 对于A矩阵,沿着M轴进行切分,切分成多份的singleCoreM,单核上处理SingleCoreM * K大小的数据。
  • 对于B矩阵,沿着N轴进行切分,切分成多份的singleCoreN,单核上处理K * SingleCoreN大小的数据。
  • 对于C矩阵,SingleCoreM * K大小的A矩阵和K * SingleCoreN大小的B矩阵相乘得到SingleCoreM * SingleCoreN大小的C矩阵,即为单核上输出的C矩阵大小。

比如,下图中共有8个核参与计算,将A矩阵沿着M轴划分为4块,将B矩阵沿着N轴切分为两块,单核上仅处理某一分块(比如图中绿色部分为core5上参与计算的数据):SingleCoreM * K大小的A矩阵分块和SingleCoreN * K大小的B矩阵分块相乘得到SingleCoreM * SingleCoreN大小的C矩阵分块。

切分M、N、K轴的策略如下图所示:

  • 对于A矩阵,沿着M轴进行切分,切分成多份的singleCoreM,沿着K轴切分,切分成多份的singleCoreK,单核上处理singleCoreM * singleCoreK大小的数据。
  • 对于B矩阵,沿着K轴进行切分,切分成多份的singleCoreK,沿着N轴进行切分,切分成多份的singleCoreN,单核上处理singleCoreK * singleCoreN大小的数据。
  • 对于C矩阵,singleCoreM * singleCoreK大小的A矩阵与singleCoreK * singleCoreN大小的B矩阵相乘并累加得到singleCoreM * singleCoreN大小的C矩阵分块。

比如下图中,C矩阵中的R矩阵块,是通过a*a+b*b+c*c累加得到的,其中,a*a、b*b、c*c可在多个核上并行计算。

上述的切分策略会在Tiling参数中体现,比如SingleCoreM、SingleCoreN、SingleCoreK,开发者在host侧通过调用API自动获取Tiling参数,与单核场景的不同的是,多核Tiling需要使用MultiCoreMatmulTiling构造多核Tiling对象,并通过SetDim接口设置Matmul计算所用的核数。注意:这里设置的核数为Matmul计算所用的核数,仅在多核场景下设置,用于计算tiling参数;SetBlockDim为整个算子计算所用核数,是实际会加载的核数,是必须设置的。SetBlockDim的设置规则请参考Host侧tiling实现。SetDim的设置规则如下:
  • CUBE_ONLY(只有矩阵计算)场景,本节内容以CUBE_ONLY模式举例。

    SetBlockDim加载的核数就是Matmul API实际计算会用到的核数,SetDim和SetBlockDim设置的值是一样的。

  • MIX模式(包含矩阵计算和矢量计算)的设置规则请参考MIX场景核数设置规则

Matmul多核场景的完整样例请参考LINK

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 构造多核Tiling对象
auto ascendcPlatform = platform_ascendc::PlatformAscendC(context->GetPlatformInfo());
matmul_tiling::MultiCoreMatmulTiling tiling(ascendcPlatform); 
// 设置可参与矩阵乘运算的核数
tiling.SetDim(5);
tiling.SetAType(AscendC::TPosition::GM, CubeFormat::ND, matmul_tiling::DataType::DT_FLOAT16);
...
optiling::TCubeTiling tilingData;  
// 获取Tiling参数 
int ret = tiling.GetTiling(tilingData);    // if ret = -1, gen tiling failed 

非对齐场景尾块处理

多核场景,对矩阵进行切分时,若M、N、K无法整除singleCoreM 、singleCoreN、 singleCoreK时,就会出现尾块,如下图矩阵A、B的最后一行和最后一列的矩阵块:

此时,C矩阵中的R矩阵块,依然是通过a*a+b*b+c*c+d*d累加得到的,处理a*a、b*b、c*c、d*d等尾块时,需在kernel侧设置尾块大小,在不改变原有tiling的情况下,重新设置本次计算的singleCoreM/singleCoreN/singleCoreK,在处理尾块的时候按照设置的值也就是tailM/tailN/tailK进行搬运和计算。如下的示例中,当tailM < singleCoreM被认为需要处理尾块,此时可以调用SetTail接口进行设置。SetTail接口需要在Iterate/IterateAll之前调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template<typename aType, typename bType, typename cType, typename biasType
__aicore__ inline void MatmulKernel<aType, bType, cType, biasType>::CalcOffset(int32_t blockIdx, const TCubeTiling& tiling, int32_t& offsetA, int32_t& offsetB, int32_t& offsetC, int32_t& offsetBias, bool isAtrans, bool isBtrans){
    auto mSingleBlocks = Ceil(tiling.M, tiling.singleCoreM);
    auto mCoreIndx = blockIdx % mSingleBlocks;
    auto nCoreIndx = blockIdx / mSingleBlocks;

    ...
    // 处理尾块
    int tailM = tiling.M - mCoreIndx * tiling.singleCoreM;
    tailM = tailM < tiling.singleCoreM ? tailM : tiling.singleCoreM;
    int tailN = tiling.N - nCoreIndx * tiling.singleCoreN;
    tailN = tailN < tiling.singleCoreN ? tailN : tiling.singleCoreN;
    if (tailM < tiling.singleCoreM || tailN < tiling.singleCoreN) {
        matmulObj.SetTail(tailM, tailN);
    }
}