神威-SpMM学习笔记备忘

2.27

  • 辨析:多核与众核

    Manycore: Large number of cores; Optimized for higher degree of parallelism; Higher throughput; Lower single thread performance.
    Multicore: Small number of cores; Optimized for both parallel and serial codes; Utilizing OOO, deeper pipelines, etc.; More emphasis on higher single thread performance.

    即:
    Manycore:core数量多,单线程的性能可能不高,为并行计算做了优化,高吞吐;
    Multicore:core数量较少,单线程性能高,为并行和串行计算都做了优化;
    一个 CPU 是一个处理器,但是一个处理器并不总是一个 CPU ——当核心数量增加时尤其如此。GPU、 DSP 和特殊用途的处理器通常已经有100或1000个内核。例如,低功耗的多核处理器,我们称之为网格处理器,这些处理器在视频编码、信号处理、密码学和神经网络等任务中表现非常好。

  • 定义:稀疏矩阵通常是指的非零元素占比不超过5%,即稀疏度大于95%。
    矩阵的稀疏度对于矩阵压缩比以及性能有很大的影响。

  • BLAS(Basic Linear Algebra Subprograms)是进行向量和矩阵等基本线性代数操作的事实上的标准,如NV的cuBLAS;

  • 当前的 NVIDIA GPU 中有 1~30 个包含完整前端的流多处理器 SM(Stream Multiprocessor),每个流多处理器可以看成是包含 8 个流处理器(SP)的 SIMD 处理器。

  • 矩阵乘(GEMM)算法是数学中最基本、最关键的算法,它的性能对许多科学计算、深度学习的问题解决效率至关重要。GEMM 是实现其他 Level-3 BLAS 例程的基础

PC平台主要SIMD扩展发展简史

2.28

  • 常用稀疏矩阵存储格式
    COO Coordinate (以坐标的形式进行表示)
    这是最简单的一种格式,每一个元素需要用一个三元组来表示,分别是(行号,列号,数值),对应上图右边的一列。这种方式简单,但是记录单信息多(行列),每个三元组自己可以定位,因此空间不是最优。
    Compressed Sparse Row (CSR) (以行压缩的形式进行表示)
    CSR是比较标准的一种,也需要三类数据来表达:数值,列号,以及行偏移。CSR不是三元组,而是整体的编码方式。数值和列号与COO一致,表示一个元素以及其列号,行偏移表示某一行的第一个元素在values里面的起始偏移位置。如上图中,第一行元素1是0偏移,第二行元素2是2偏移,第三行元素5是4偏移,第4行元素6是7偏移。在行偏移的最后补上矩阵总的元素个数,本例中是9。
    Compressed Sparse Column (CSC) (以列压缩的形式进行表示)
    对应的,CSC按列压缩。
    可以看到这个代码和CSR的版本非常相似,但是却又有本质区别。之前是间接访问x,现在成了间接访问y。这个区别大吗?非常大。
    因为对y的写入,而内存写入则会影响并行性分析(内容读入则不会影响)。所以显而易见对于上述代码我们无法直接并行化每一列的计算(因为对y的间接写入,静态不可知到底会写入那一列),而对于CSR的版本则可以直接并行每一行的计算。这样的一个非对称性其实是矩阵向量乘法的内存访问模式导致的。

3.1

  • 稀疏矩阵格式另一篇介绍

  • Block compressed sparse row (BCSR) Format
    BCSR是对CSR的一般化和改进,它和CSR的区别在于把原矩阵分成了大小相同的block,block中的空元素则用0填上,于是每一个block都是稠密的,所以val数组会变大一些(需要填充一些0),但是索引却简化了。

  • 申威26010pro属于SoC
    SOC(system on Chip),翻译过来是片上系统,又叫做系统芯片。在这一块芯片上可以运行一整套完整的系统,它是一个集合体。SOC的集成度会更高一些,它总共包括两类。一类是MPU微处理器构建的SOC,这类比较注重处理手机、音响等智能设备;另一类是MCU微控制器构建的SOC,注重的是控制像冰箱等嵌入式设备。

  • 矩阵乘(GEMM)算法是数学中最基本、最关键的算法,它的性能对许多科学计算、深度学习的问题解决效率至关重要。GEMM 是实现其他 Level-3 BLAS 例程的基础,也是LAPACK、ScaLAPACK 和高性能 LINPACK 基准等软件包的关键算法。
    BLAS库中函数根据运算对象的不同,分为三个类:
    Level 1 函数处理单一向量的线性运算以及两个向量的二元运算。Level 1 函数最初出现在1979年公布的BLAS库中。
    Level 2 函数处理 矩阵与向量的运算,同时也包含线性方程求解计算。 Level 2 函数公布于1988年。
    Level 3 函数包含矩阵与矩阵运算。Level 3 函数发表于1990年。

  • 申威26010
    神威太湖之光最新升级的处理器
    1.45GHz
    fp64
    3.168 TFlops/s
    内存带宽136.5GB/s
    4个运算控制核心和 256个运算核心构成
    4个DDR3 IMC
    pcie3.0
    256bit simd

  • 26010pro
    控制核心主频2.1G,运算核心2.25G
    6 个运算核组构成,每个核组包括 65 个核心:一个运算控制核心(MPE)以及由 64 个运算核心(CPE)组成的运算核心阵列(CPEs),这些部件通过环形网络进行连接。运算核心阵列以 8*8网格的方式构成,运算核心之间以及运算核心与外部交互通过阵列内网络进行互连。
    fp32 14.026 TFlops
    fp16 55.296TFlops
    访存带宽 307.2GB/s
    512bit simd
    sw26010

3.2

3.8

  • MPI
MPI_Comm_rank(MPI_COMM_WORLD, &rank)

是mpi的默认通信器(communicator),获得本进程的rank,即which process of a communicator;

MPI_Comm_size(MPI_COMM_WORLD, &size)

获得communicator的进程总数

MPI_Send() 
MPI_Recv()

最低级别的通信原语

MPI_Barrier(MPI_COMM_WORLD)

用于同步,所有线程到此处等待,直到全部都调用此

MPI_Scatter()

通过根进程向同一个通信域中的所有进程发送数据,将数据发送缓冲区的数据分割成长度相等的段,然后分段发送数据给每个进程,如果每段包含N个数据,则向进程i发送的段为 [ s e n d [ i ∗ N ] , i n t [ i ∗ N + N ] ) [send[i*N],int[i*N+N]) [send[iN],int[iN+N])

3.14
矩阵数据集
COO SpGEMM
MPI SpGEMM

  • MPI_Send() 、MPI_Recv()会阻塞直到信息收发完成

MPI运行参数

3.20
vtune中文教程

3.22

  • 遇到一个极其折磨的segfault:A * B的时候,B总会在遍历到某一行时爆掉,值突然改变(异常值),但找了许久都没发现B有甚么理由会爆。最后突然发现一个简单粗暴但疏忽掉的问题:稀疏 * 稀疏的结果可不一定是稀疏,于是检查C,果然是很稠密,所以C爆了,因此影响到了B,吃了一个烟雾弹。教训:算法竞赛很少遇到直接的内存操作和分配问题,在hpc中此类问题一定要完全周密的考量,即使debug部分比程序本身都长,也要坚持练习。
    实现了csr乘csr得到csr或稠密矩阵,在原来的5%随机数据上的结果(默认&O2):
    在这里插入图片描述
    在这里插入图片描述
    3.29
MPI_Scatterv()

其中,第i号进程接收的是发送地址自displs[i]到displs[i+cnt[i]]的数据。

4.16
神威平台 主从核并行模式:(区别在与从核计算时,主核在干什么)

  1. 主从加速并行(最简单,不易出错)
  2. 主从协同并行
  3. 主从异步并行
  4. 主从动态并行
    在这里插入图片描述“SW26010每个核组内的从核可以使用SunwayOpenACC或Athread实现并行执行。根据性能指标计算,SW26010上的主核浮点性能约为23.2 GFlops,而从核浮点性能达到了742.4 GFlops。由于这种浮点性能上的巨大差距,要提升计算密集型程序的运行效率,就需要尽可能充分地发挥从核的运算性能,充分发掘应用内部的并行性。”

athread是对DMA的封装
<slave.h> athread_get() athread_put()
从核代码中的从核全局变量要加__thread_local关键字,否则会创建在全局主存,产生很多gload、gstore(函数中的不用,自动在从核上)
每个从核只有64K的局存

  • mpiswg++

matrixMul例子改为athread版的各种疑问

  • “SW处理器主核上的SIMD需要满足以下一些要求: a)仅在内存中连续的数据可以被取入向量寄存器进行向量运算; b)数据在内存中要求32字节对界(64*4单精度浮点要求16字节对界),不对界的向量访存会引起异常,然后由操作系统模拟,性能上有很大的降低;” ——神威问海一号基础编译器SIMD-C语言

关注这里的矩阵数据

4.20
CPC2022

在这里插入图片描述
神威架构理解及编程:

  • 每个从核有一个256KB的局部存储(LDM),LDM以草稿本(Scratch Pad Memory, SPM)的方式组织,由软件管理控制,属于一种可编程存储器(cache由硬件控制,对程序员不可见);从核可以通过gld/gst离散访主存,也可以通过DMA (Direct Memory Access,直接内存访问)批量式访主存。
    在这里插入图片描述

4.25

  • 对于稀疏矩阵而言, 任务划分方法有两种:一维划分和二维划分.二维划分时, 多个从核会同时更新y向量的一部分, 需要加锁处理, 从而导致额外的开销.对规则稀疏矩阵而言, 每行的非零元个数较少, LDM可以容纳至少一行计算所需的元素, 所以我们采用一维的任务划分方法.如果矩阵一行的非零元太多, 导致LDM空间不能一次容纳一行的元素进行计算, 那么将采用主核进行计算.
    ——《面向国产申威26010众核处理器的SpMV实现与优化》

5.6
神威CRTS编程方法总结:

  • 主核程序要准备:从核程序所需的参数定义结构体打包进去,以创建一个对象用athread_spawn传递进去(可以是地址,从核直接gld/gst);
  • 从核程序要准备:对应上面主核中定义过的结构体,都复制进来;

5.7
问题
按教程复现,出现问题在这里插入图片描述

5.8
上述问题解决

5.9
在这里插入图片描述
x86访问主存延迟大约200时钟周期[CSAPP III],与神威gld/gst相仿[面向异构众核超级计算机的大规模稀疏计算性能优化研究]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答您的问题。 首先,神威太湖之光是一个拥有数百万核心的超级计算机,因此在其上运行程序需要考虑到并行性。二级并行是指同时使用MPI和OpenMP进行并行计算,MPI用于在不同节点之间通信,OpenMP用于在节点内的多个核心之间共享任务。 以下是一个可以在神威太湖之光上运行的二级并行复数矩阵相乘的示例代码: ```c++ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <mpi.h> #include <omp.h> #define N 1000 #define BLOCKSIZE 100 // 每个线程块的大小 int main(int argc, char **argv) { int rank, size, i, j, k, l, m, n, p, q, s, t, u, v, w; double *A, *B, *C, *tmp1, *tmp2; double start_time, end_time; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); // 每个进程分配的矩阵大小 m = n = N / size; p = q = N; // 为矩阵分配内存 A = (double*)malloc(m * p * sizeof(double)); B = (double*)malloc(p * q * sizeof(double)); C = (double*)malloc(m * q * sizeof(double)); tmp1 = (double*)malloc(m * p * sizeof(double)); tmp2 = (double*)malloc(p * q * sizeof(double)); // 初始化矩阵A和B srand(1); for (i = 0; i < m; i++) { for (j = 0; j < p; j++) { A[i * p + j] = rand() / (double)RAND_MAX; tmp1[i * p + j] = A[i * p + j]; } } for (i = 0; i < p; i++) { for (j = 0; j < q; j++) { B[i * q + j] = rand() / (double)RAND_MAX; tmp2[i * q + j] = B[i * q + j]; } } // 计算矩阵乘法 start_time = MPI_Wtime(); #pragma omp parallel for private(j, k, l, s, t, u, v, w) shared(A, tmp1, B, tmp2, C) schedule(static) collapse(2) for (i = 0; i < m; i += BLOCKSIZE) { for (j = 0; j < q; j += BLOCKSIZE) { for (k = 0; k < p; k += BLOCKSIZE) { for (l = i; l < i + BLOCKSIZE && l < m; l++) { for (s = j; s < j + BLOCKSIZE && s < q; s++) { C[l * q + s] = 0.0; for (t = k; t < k + BLOCKSIZE && t < p; t++) { C[l * q + s] += A[l * p + t] * B[t * q + s]; } } } } } } end_time = MPI_Wtime(); // 验证结果 if (rank == 0) { double max_error = 0.0, error; for (i = 0; i < m; i++) { for (j = 0; j < q; j++) { double c = 0.0; for (k = 0; k < p; k++) { c += tmp1[i * p + k] * tmp2[k * q + j]; } error = fabs(c - C[i * q + j]); if (error > max_error) { max_error = error; } } } printf("Max error: %lf\n", max_error); printf("Execution time: %lf seconds\n", end_time - start_time); } // 释放内存 free(A); free(B); free(C); free(tmp1); free(tmp2); MPI_Finalize(); return 0; } ``` 以上代码使用了OpenMP进行线程级别的并行计算,并使用MPI进行进程间的通信。每个进程负责计算一个大小为m x q的子矩阵,其中m和q是矩阵的行和列数,p是矩阵B的行数。在每个进程内部,使用OpenMP将计算任务分成若干个线程块,每个线程块计算一个大小为BLOCKSIZE x BLOCKSIZE的子矩阵。 在代码中,使用了collapse(2)来将两个for循环并行化,这样可以提高并行效率。在计算矩阵乘法时,使用了临时矩阵tmp1和tmp2来存储矩阵A和B,这是因为在并行计算中,每个进程只负责计算一个子矩阵,无法直接访问整个矩阵A和B。 请注意,以上示例代码仅供参考,实际应用中需要根据具体需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值