roof-line模型

理解计算平台的两个指标

算力 π \pi π:计算平台的性能上限,指的是一个计算平台倾尽全力每秒钟所能完成的浮点运算数。单位是 FLOPS or FLOP/s。

带宽 β \beta β:也即计算平台的带宽上限,指的是一个计算平台倾尽全力每秒所能完成的内存交换量。单位是Byte/s。

计算强度上限 I I I:两个指标相除即可得到计算平台的计算强度上限。它描述的是在这个计算平台上,单位内存交换最多用来进行多少次计算。单位是FLOPs/Byte。
I = π / β I = \pi / \beta I=π/β

这里所说的“内存”是广义上的内存。对于CPU计算平台而言指的就是真正的内存;而对于GPU计算平台指的则是显存。

神经网络模型的两个指标

  • 计算量:指的是输入单个样本(对于CNN而言就是一张图像),模型进行一次完整的前向传播所发生的浮点运算个数,也即模型的时间复杂度。单位是 FLOP or FLOPs。其中卷积层的计算量公式如下
    M 2 ˆ × K 2 × C i n × C o u t M\^{2} \times K^{2} \times C_{in} \times C_{out} M2ˆ×K2×Cin×Cout

  • 访存量:指的是输入单个样本,模型完成一次前向传播过程中所发生的内存交换总量,也即模型的空间复杂度。在理想情况下(即不考虑片上缓存),模型的访存量就是模型各层权重参数的内存占用(Kernel Mem)与每层所输出的特征图的内存占用(Output Mem)之和。单位是Byte。由于数据类型通常为float32(共占用了4字节) ,因此需要乘以四。
    KaTeX parse error: Undefined control sequence: \tiems at position 63: …imes C_{out} ) \̲t̲i̲e̲m̲s̲ ̲4

  • 模型的计算强度:由计算量除以访存量就可以得到模型的计算强度,它表示此模型在计算过程中,每Byte内存交换到底用于进行多少次浮点运算。单位是FLOPs/Byte。可以看到,模计算强度越大,其内存使用效率越高。

roof-line model

其实 Roof-line Model 说的是很简单的一件事:模型在一个计算平台的限制下,到底能达到多快的浮点计算速度。更具体的说,Roof-line Model 意义是 : 计算量为A且访存量为B的模型在算力为C且带宽为D的计算平台所能达到的理论性能上限E是多少。

Roof-line 的形态

所谓“Roof-line”,指的就是由计算平台的算力和带宽上限这两个参数所决定的“屋顶”形态,如下图所示。

算力决定“屋顶”的高度(绿色线段)
带宽决定“房檐”的斜率(红色线段)
在这里插入图片描述

Roof-line 划分出的两个瓶颈区域

在这里插入图片描述

计算瓶颈区域 Compute-Bound

不管模型的计算强度有多大,它的理论性能最大只能等于计算平台的算力 。当模型的计算强度大于计算平台的计算强度上限时,模型在当前计算平台处于 Compute-Bound 状态,即模型的理论性能受到计算平台算力的限制,无法与计算强度成正比。但这其实并不是一件坏事,因为从充分利用计算平台算力的角度上看,此时模型已经的利用了计算平台的全部算力。可见,计算平台的算力越高,模型进入计算瓶颈区域后的理论性能也就越大。

带宽瓶颈区域 Memory-Bound

当模型的计算强度小于计算平台的计算强度上限时,由于此时模型位于“房檐”区间,因此模型理论性能的大小完全由计算平台的带宽上限(房檐的斜率)以及模型自身的计算强度所决定,因此这时候就称模型处于 Memory-Bound 状态。可见,在模型处于带宽瓶颈区间的前提下,计算平台的带宽越大(房檐越陡),或者模型的计算强度越大,模型的理论性能可呈线性增长。

这一部分摘自知乎博主,Roofline Model与深度学习模型的性能分析

注意这副图和之前的差别
在这里插入图片描述
如下图所示,斜线区域表示的带宽瓶颈,水平线表示的是计算瓶颈。不同的机制会导致计算峰值下降(如SIMD等),该变化反映到水平线的下降;不同的硬件会导致带宽上线的下降(如软件预取、NUMA、L1 Cache),该变化会反应的截距上,即roolline模型的起点可能在原点上方或右方。

也很好理解,因为 I I I是峰值算力除以峰值带宽,即可也认为处理器的算力强度是不变的, π = β × I \pi=\beta \times I π=β×I,当使用峰值带宽的时候,算力自然大,但是带宽下降时,算力自然就减小,因此该变化反应到了截距上。

  • 不使用预先抓取数据,忽略非统一内存访问(NUMA)等将会降低可实现带宽;不同级别的内存有着不同的带宽(HBM 与DRAM)
  • 水平线的下移依次表示:只计算加法,没有乘法–>不使用SIMD->不使用ILP导致所实现的峰值计算率不断衰减;
  • 斜线的下移依次表示:不使用软件预抓取数据→在分布式系统中不使用NUMA使得所实现的带宽降低。
    在这里插入图片描述
    其实做成平行线没什么道理,可以理解成为了方便看图这样做的吧

mperf

介绍
git项目
教程
mperf绘制roof-line模型

mperf 使用

#include "opencv2/core.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"

#include "mperf/cpu_affinity.h"
#include "mperf/timer.h"
#include "mperf/tma/tma.h"


int main(){
    cv::Mat image = cv::imread("test.png");
    cv::Mat image1;

    size_t iter_num = 100;

    mperf::tma::MPFTMA mpf_tma(mperf::MPFXPUType::A55);

    mpf_tma.init({"Frontend_Bound",
                    "Fetch_Latency", 
                        "ICache_Misses",
                        "ITLB_Misses",
                        "Predecode_Error",
                    "Fetch_Bandwidth",
                "Bad_Speculation",
                    "Branch_Mispredicts",
                "Backend_Bound",
                    "Memory_Bound",
                        "Load_Bound",
                            "Load_DTLB",
                            "Load_Cache",
                        "Store_Bound",
                            "Store_TLB",
                            "Store_Buffer",
                    "Core_Bound",
                        "Interlock_Bound",
                            "Interlock_AGU",
                            "Interlock_FPU",
                        "Core_Bound_Others",
                "Retiring",
                    "LD_Retiring",
                    "ST_Retiring",
                    "DP_Retiring",
                    "ASE_Retiring",
                    "VFP_Retiring",
                    "PC_Write_Retiring",
                        "BR_IMMED_Retiring",
                        "BR_RETURN_Retiring",
                        "BR_INDIRECT_Retiring",
                "Metric_L1D_Miss_Ratio",	
                    "Metric_L1D_RD_Miss_Ratio",
                    "Metric_L1D_WR_Miss_Ratio",
                "Metric_L2D_Miss_Ratio",	
                    "Metric_L2D_RD_Miss_Ratio",
                    "Metric_L2D_WR_Miss_Ratio",
                "Metric_L3D_Miss_Ratio",	
                "Metric_L3D_RD_Miss_Ratio",
                "Metric_BR_Mispred_Ratio",
                "Metric_L1I_TLB_Miss_Ratio",
                "Metric_L1D_TLB_Miss_Ratio",
                "Metric_L2_TLB_Miss_Ratio",
                "Metric_ITLB_Table_Walk_Ratio",
                "Metric_DTLB_Table_Walk_Ratio",
                "Metric_Load_Port_Util",
                "Metric_Store_Port_Util",
                "Metric_FPU_Util",
                "Metric_GFLOPs_Use",
                "Metric_L3_BW_Use",
                "Metric_L2_BW_Use",
                "Metric_DRAM_BW_Use"
                });

    size_t gn = mpf_tma.group_num();
    size_t uncore_evt_num = mpf_tma.uncore_events_num();
    printf("the gn and uncore_evt_nums %zu, %zu\n", gn, uncore_evt_num);
    for (size_t i = 0; i < gn; ++i) {
        mpf_tma.start(i);

       for(size_t j = 0; j < iter_num; ++j){
            cv::GaussianBlur(image, image1, cv::Size(3, 3), 0.8);
       }

        mpf_tma.sample_and_stop(iter_num);
    }

    for (size_t i = 0; i < uncore_evt_num; ++i) {
        mpf_tma.start_uncore(i);

        for(size_t j = 0; j < iter_num; ++j){
            cv::GaussianBlur(image, image1, cv::Size(3, 3), 0.8);
            mpf_tma.sample(1);
        }
        mpf_tma.sample_and_stop(iter_num);
    }

    mpf_tma.deinit();

    return 0;
}

mperf 优化教程

矩阵乘法的Naive实现为三层循环计算,两个矩阵都是900 * 900:
测试平台:phantom X2 pro 5G
环境:0号核心。最高频

naive

//lda:number of columns of A 
//ldb:number of columns of B
//ldc:number of columns of C 
#define A(i, j) a[(i)*lda + (j)]
#define B(i, j) b[(i)*ldb + (j)]
#define C(i, j) c[(i)*ldc + (j)]
void my_matmul_naive(int m, int n, int k, float* a, int lda, float* b, int ldb,
              float* c, int ldc) {
    int i, j, p;
    for (i = 0; i < m; i++) {         
        for (j = 0; j < n; j++) {     
            for (p = 0; p < k; p++) { 
                C(i, j) = C(i, j) + A(i, p) * B(p, j);
            }
        }
    }
}

通过mperf测试的指标,分析可知,该段代码的GFPLOS只有0.4,而ARM A55的峰值计算量为14GFLOPs,有很大的优化空间。
在这里插入图片描述
观察到FPU_Util为0,因为此时没有进行向量化计算,说明了 Naive 实现未能充分利用处理器的SIMD单元。解决方法是循环展开。

unroll

void my_matmul_unroll(int m, int n, int k, float* a, int lda, float* b, int ldb,
              float* c, int ldc) {
    int i, j;
 
    for (j = 0; j < n;
         j += 12) {
        if (j + 12 > n)
            break;
        for (i = 0; i < m; i += 8) {
            if (i + 8 > m)
                break;
            AddDot8x12(k, &A(i, 0), lda, &B(0, j), ldb, &C(i, j), ldc);
        }
        if (i != m) {
            AddDot4x12(k, &A(i, 0), lda, &B(0, j), ldb, &C(i, j), ldc);
        }
    }
    if (j != n) {
        for (; j < n; j += 4) {
            for (i = 0; i < m; i += 8) {
                if (i + 8 > m)
                    break;
                AddDot8x4(k, &A(i, 0), lda, &B(0, j), ldb, &C(i, j), ldc);
            }
            if (i != m) {
                AddDot4x4(k, &A(i, 0), lda, &B(0, j), ldb, &C(i, j), ldc);
            }
        }
    }
}

分析mperf数据可知:FPU_util明显上升,GFLOPS有数量级的上升。
在这里插入图片描述
但是memory_bound很大,而Memory_Bound占比高主要是 Load_cache 造成的,接下来要进行访存优化。解决方法包括:分块和pack。
在这里插入图片描述

block

#define kc 256
#define nc 252
void my_mamtmul_block(int m, int n, int k, float *a, int lda, float *b, int ldb,
              float *c, int ldc) {
  int j, p, pb, ib;
  for (p = 0; p < k; p += kc) {
    pb = min(k - p, kc);
    for (j = 0; j < n; j += nc) {
      ib = min(n - j, nc);
      InnerKernel(m, ib, pb, &A(0, p), lda, &B(p, j), ldb, &C(0, j), ldc);
    }
  }
}

观察mperf测试数据可知,GFLOPS确实上升不少,FPU利用率也得到了提高,Load_Cache 也有所下降。说明分块确实减少了对 latency 非常大的系统主存的访问,接下来使用pack方法优化
在这里插入图片描述
在这里插入图片描述

pack

arm cpu引入了SIMD指令,支持VFP(向量浮点运算),通过 pack 成向量,使用 SIMD 单元

过程:
在这里插入图片描述

void InnerKernel(int m, int n, int k, float* a, int lda, float* b, int ldb,
                 float* c, int ldc) {
    int i, j;
    float packedA[m * k];
    float packedB[k * n];
 
    for (j = 0; j < n; j += 12) {
        if (j + 12 > n)
            break;
        PackMatrixB_12(k, &B(0, j), ldb, packedB + j * k);
        for (i = 0; i < m; i += 8) {
            if (i + 8 > m)
                break;
            if (0 == j) {
                PackMatrixA_8(k, &A(i, 0), lda, packedA + i * k);
            }
            AddDot8x12(k, packedA + i * k, k, packedB + j * k, 12, &C(i, j),
                       ldc);
        }
        if (i != m) {
            PackMatrixA_4(k, &A(i, 0), lda, packedA + i * k);
            AddDot4x12(k, packedA + i * k, k, packedB + j * k, 12, &C(i, j),
                       ldc);
        }
    }
    if (j != n) {
        for (; j < n; j += 4) {
            PackMatrixB_4(k, &B(0, j), ldb, packedB + j * k);
            for (i = 0; i < m; i += 8) {
                if (i + 8 > m)
                    break;
                AddDot8x4(k, packedA + i * k, k, packedB + j * k, 12, &C(i, j),
                        ldc);
            }
            if (i != m) {
                AddDot4x4(k, packedA + i * k, k, packedB + j * k, 12, &C(i, j),
                        ldc);
            }
        }
    }
}

优化后GFLOPS又有一定幅度的增长,但非常小,说明虚拟内存系统缺页处理非常完善,虚拟分页系统存在页面预取机制。Load_Cache明显下降。
在这里插入图片描述在这里插入图片描述
但是观察到 core_bound 占比非常大,说明寄存器单元的使用率不高,通过把内层计算逻辑替换为嵌入式汇编,依据架构硬件特性调整指令选择和指令排布,进一步减少pipeline上的依赖和冲突。
在这里插入图片描述

pipeline优化-嵌入汇编

ldq跟fmla不能双发射(注:ARM A55是双发射架构),这就验证了ldq会造成计算和访存指令无法双发射,并导致了PU_util数值的下降。进一步我们发现,ldr,ldr,ins三种指令都可以与fmla双发射,且发射都是1周期,而这三条指令可以组合出ldq等价的功能。因此可以想象,使用ldr,ldr,ins指令组合替换ldq指令,可以提高流水线的满载程度,进而提高性能。

asm volatile(
               "fmla v1.4s, v1.4s, v1.s[0]\n"
               "ldr d0, [%[b_ptr]]\n"
               "fmla v2.4s, v2.4s, v2.s[0]\n"
               "ldr x0, [%[b_ptr], #8]\n"
               "fmla v3.4s, v3.4s, v3.s[0]\n"
               "ins v0.d[1], x0\n"
               "fmla v4.4s, v4.4s, v4.s[0]\n"
               "ldr d7, [%[b_ptr]]\n"
               "fmla v5.4s, v5.4s, v5.s[0]\n"
               "ldr x0, [%[b_ptr], #8]\n"
               "fmla v6.4s, v6.4s, v6.s[0]\n"
               "ins v7.d[1], x0\n"
               : [b_ptr] "+r"(b_ptr)
               :
               : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7");

在这里插入图片描述
在这里插入图片描述
可以看到 core bound 有所降低,GFLOPs大幅提升。
mperf 优化教程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值