ECE408 Lecture 19 Parallel Sparse Methods
这次回顾ECE408 Lecture 19,这次介绍了并行稀疏方法。
课程主页:
搬运视频:
目标
- 学习在并行稀疏方法中压缩输入数据以减少内存带宽消耗的关键技术;
- 更好地利用片上存储器;
- 更少的字节传输到片上存储器;
- 更好地利用全局内存;
- 挑战:保持规律性;
稀疏矩阵
- 许多现实世界的系统本质上都是稀疏的;
- 描述为稀疏矩阵的线性系统;
- 求解稀疏线性系统
- 基于稀疏矩阵向量乘法的迭代共轭梯度求解器是一种常用方法;
- PDE系统的解可以表述为表示为稀疏矩阵向量乘法的线性运算;
分析和人工智能中的稀疏矩阵
稀疏矩阵-向量乘法(SpMV)
挑战
- 与密集矩阵向量乘法相比,SpMV;
- 不规则/非结构化;
- 从优化技巧、先前讨论的想法中获益甚微;
- 实现最佳性能的关键;
- 减少稀疏性(通过去除零);
- 最大化规律性(通过减少分歧和负载不平衡);
- 最大化DRAM突发利用率(布局安排);
简单的并行SpMV
每个线程处理一行。
压缩稀疏行(CSR)格式
记录:
- 数据值;
- 列索引;
- 行指针;
- 例如下图中表示col_index的第$[0, 2)$个元素是第1行,第$[2, 2)$个元素是第2行,第$[2, 5)$个元素是第3行;第$[5, 7)$个元素是第4行;
CSR数据布局
CSR Kernel设计
并行SpMV/CSR Kernel(CUDA)
__global__ void SpMV_CSR(int num_rows, float *data, int *col_index, int *row_ptr, float *x, float *y) {
int row = blockIdx.x * blockDim.x + threadIdx.x;
if (row < num_rows) {
float dot = 0;
int row_start = row_ptr[row];
int row_end = row_ptr[row+1];
for (int elem = row_start; elem < row_end; elem++)
dot += data[elem] * x[col_index[elem]];
y[row] = dot;
}
}
CSR内核负载不平衡
线程执行不同次数的迭代:
CSR内核内存发散(未合并访问)
- 相邻线程访问不相邻的内存位置;
- 灰色元素在第0次迭代中被所有线程访问;
使用ELL(PACK)格式规则化SpMV
- 将所有行填充到相同的长度;
- 如果某几行比其他行长得多,效率低下;
- 转置(Column Major)以提高DRAM效率;
- 数据和col_index都被填充/转置;
ELL Kernel设计
并行SpMV/ELL kernel
__global__ void SpMV_ELL(int num_rows, float *data, int *col_index, int num_elem, float *x, float *y) {
int row = blockIdx.x * blockDim.x + threadIdx.x;
if (row < num_rows) {
float dot = 0;
for (int i = 0; i < num_elem; i++)
dot += data[row+i*num_rows]*x[col_index[row+i*num_rows]];
y[row] = dot;
}
}
使用ELL进行内存合并
Coordinate(COO)格式
明确列出每个非零元素的列和行索引:
COO允许重新排序元素
实现SpMV/COO的顺序循环
for (int i = 0; i < num_elem; row++)
y[row_index[i]] += data[i] * x[col_index[i]];
COO内核设计
访问输入矩阵和向量
所有线程都可以访问矩阵数据和col_index以访问向量:
累加到输出向量
每个线程使用其元素的row_index累加到输出,需要原子操作:
Hybrid格式
- ELL处理典型条目;
- COO处理异常条目;
- 以分段减少的方式实施;
- 在实践中经常在顺序主机代码中实现;
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere