-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
65 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
# 实验三:稀疏矩阵-矩阵乘 | ||
|
||
负责助教:黄可钊 hkz20@mails.tsinghua.edu.cn | ||
负责助教:张齐颢 zqh23@mails.tsinghua.edu.cn | ||
|
||
在本实验中,你将通过实现 多线程 CPU 或 GPU 加速的稀疏矩阵-矩阵乘法(SpMM)进一步熟悉 OpenMP, CUDA 编程以及 CPU, GPU 体系结构。 | ||
在本实验中,你将通过实现 GPU 加速的稀疏矩阵-矩阵乘法(SpMM)进一步熟悉 OpenMP, CUDA 编程以及 CPU, GPU 体系结构。 | ||
|
||
## 实验任务 | ||
|
||
|
@@ -35,26 +35,22 @@ VAL = [ 10 20 30 40 50 60 70 80 ] | |
``` | ||
. | ||
|-- CMakeLists.txt | ||
|-- cmake | ||
|-- data | ||
|-- include | ||
| |-- args.hxx | ||
| |-- data.h | ||
| |-- dbg.h | ||
| |-- spmm_base.h | ||
| |-- spmm_cpu_opt.h | ||
| |-- spmm_cpu_ref.h | ||
| |-- spmm_cusparse.h | ||
| |-- spmm_opt.h | ||
| |-- spmm_ref.h | ||
| |-- util.h | ||
| `-- valid.h | ||
|-- script | ||
| |-- run_all_CPU.sh | ||
| `-- run_all.sh | ||
|-- src | ||
| |-- data.cu | ||
| |-- spmm_cpu_opt.cpp | ||
| |-- spmm_cpu_ref.cpp | ||
| |-- spmm_cusparse.cu | ||
| |-- spmm_opt.cu | ||
| |-- spmm_ref.cu | ||
|
@@ -63,85 +59,46 @@ VAL = [ 10 20 30 40 50 60 70 80 ] | |
|-- test | ||
| |-- CMakeLists.txt | ||
| |-- main.cpp | ||
| |-- test_spmm_cpu.cpp | ||
| `-- test_spmm.cu | ||
`-- third_party | ||
`-- googletest | ||
``` | ||
|
||
其中 `spmm_base.h` 是SpMM实现的基类,`spmm_ref.*` 和 `spmm_cpu_ref.*` 是效率很低的 GPU, CPU 参考实现,`spmm_cusparse.*` 是利用 NVIDIA 的稀疏计算库的 GPU 实现,`spmm_opt.*` 和 `spmm_cpu_opt.*` 是你需要实现的地方(请只修改 `spmm_opt.h`, `spmm_opt.cu`, `spmm_cpu_opt.h`, `spmm_cpu_opt.cpp`)。你需要实现的是 `preprocess` 和 `run` 函数。 | ||
其中 `spmm_base.h` 是SpMM实现的基类,`spmm_ref.*` 是效率很低的 GPU 参考实现,`spmm_cusparse.*` 是利用 NVIDIA 的稀疏计算库的 GPU 实现,`spmm_opt.*` 是你需要实现的地方(请只修改 `spmm_opt.h`, `spmm_opt.cu`)。你需要实现的是 `preprocess` 和 `run` 函数。 | ||
|
||
在 `test_spmm.cu` 和 `test_spmm_cpu.cpp` 中,使用 Googletest 实现了多个测试项,其中有验证正确性的以及测试性能的。请在先过了正确性验证之后再运行性能测试。 | ||
在 `test_spmm.cu` 中,使用 Googletest 实现了多个测试项,其中有验证正确性的以及测试性能的。请在先过了正确性验证之后再运行性能测试。 | ||
|
||
## 实验步骤 | ||
|
||
```bash | ||
spack load [email protected] | ||
spack load cuda | ||
spack load [email protected] | ||
cp -R /home/course/hpc/assignments/2024/PA3/ ~ | ||
cd ~/PA3/ | ||
mkdir build | ||
cd build | ||
cmake .. | ||
make -j4 | ||
# 运行单个数据点 | ||
srun -gpus 1 ./test/unit_tests --dataset <datasetname> --len 32 --datadir ~/PA3/data/ # 所有的数据集在 ~/PA3/data/ 中 | ||
srun -N 1 --gres=gpu:1 ./test/unit_tests --dataset <datasetname> --len 32 --datadir ~/PA3/data/ # 所有的数据集在 ~/PA3/data/ 中 | ||
# 运行全部 GPU 数据点 | ||
srun -gpus 1 ~/PA3/scripts/run_all.sh # 在 PA3/scripts 目录下 | ||
# 运行全部 CPU 数据点 | ||
srun -gpus 1 ~/PA3/scripts/run_all_CPU.sh # 在 PA3/scripts 目录下 | ||
srun -N 1 --gres=gpu:1 ~/PA3/script/run_all.sh # 在 PA3/scripts 目录下 | ||
# 改变环境变量,仅仅运行单个测试,例如验证正确性(Validation) | ||
GTEST_FILTER="SpMMTest.validation" srun --gpus 1 ./test/unit_tests --dataset <datasetname> --len 32 --datadir ~/PA3/data/ | ||
# 例如只运行 CPU 的相关测试 | ||
GTEST_FILTER="SpMMCPUTest.*" srun --gpus 1 ./test/unit_tests --dataset <datasetname> --len 32 --datadir ~/PA3/data/ | ||
GTEST_FILTER="SpMMTest.validation" # 运行全部 GPU 数据点 | ||
srun -N 1 --gres=gpu:1 ./test/unit_tests --dataset <datasetname> --len 32 --datadir ~/PA3/data/ | ||
``` | ||
|
||
其中 `dataset` 包含许多真实图数据(稀疏矩阵),稀疏矩阵的形状($M$,在代码中是 `kNumV`)和非零元($nnz$,在代码中是 `kNumE`)也被其决定。例如对于图数据 `a`,我们有两个文件,`a.config` 在同一行内存储了 $M$ 和 $nnz$,`a.graph` 第一行存储了 `PTR` 数组,第二行存储了 `IDX` 数组,`VAL` 数组在运行时随机初始化。 | ||
|
||
`--len` 决定了 $B$ 和 $C$ 的 $K$。数据在 `PA3/data` 中。可以自己造一个小的数据集来 debug,如下: | ||
|
||
```bash | ||
srun -gpus 1 ./test/unit_tests --dataset toy_graph --len 32 --datadir /path/to/your/data | ||
srun -N 1 --gres=gpu:1 ./test/unit_tests --dataset toy_graph --len 32 --datadir /path/to/your/data | ||
``` | ||
|
||
在测试时会测试 `len = 32, 256` 的情况。 | ||
|
||
## 实验提交 | ||
|
||
1. 实验代码: | ||
* 在截止日期之前将完成后的整个实验框架置于自己 home 目录下的 `PA3` 目录,如 `/home/course/hpc/users/2020000000/PA3`。 | ||
2. 实验报告: | ||
* 将 **PDF 文件** 提交至网络学堂。 | ||
* 包含以下内容: | ||
1. 介绍你的实现方法,可以包括如何解决 ref 实现中的 warp divergence 的问题,如何利用各级缓存,如何改善数据的局部性,如何解决 load imbalance 的问题等; | ||
2. 展示不同优化对性能产生的影响,可以以单个数据集为例子详细分析; | ||
3. 在 $len = 32, 256$ 时的运行时间,及相对于 cuSparse 实现的加速比。 | ||
|
||
## 优化 Hint | ||
|
||
* **GPU 访存**:在 SpMM 中,稀疏矩阵的一个元素代表了对于稠密矩阵的一行的访问,所以访存量很大,需要考虑到 GPU 的访存行为(coalesce memory access)来优化 | ||
* **Warp divergence**:稀疏矩阵的元素分布不规则,会导致 reference 实现中同一个 warp 内部的线程工作量差距很大,因为 warp divergence 导致一个 warp 的执行时间由最慢的线程决定。而在 reference 实现中,每个线程负责一整行稀疏矩阵相关计算;每行非零元相差大,所以有严重的 warp divergence 问题。可以通过改变并行维度,让同一个 warp 内线程处理相同的工作量; | ||
* **局部性**:稀疏结构带来不规则的数据访问,导致局部性很差,可以通过对图数据做预处理(在 `preprocess` 函数中处理,不占计算性能的运行时间;可以使用现有工具如 [METIS](https://github.com/KarypisLab/METIS),也可以自己实现),改变图数据结构,增加局部性; | ||
* **负载不均衡**:可以自己预处理图结构,来减少它的不规则性,优化计算过程中的 load imbalance 的问题。 | ||
|
||
## GPU 优化技巧 | ||
|
||
可以通过 GPU profiling 工具 `nvprof` 对程序进行 profile: | ||
|
||
```bash | ||
# 得到程序运行的总结,包括整个程序运行过程中,各个 kernel 以及 CUDA API 的执行时间和次数 | ||
srun --gpus 1 nvprof ./test/unit_tests xxxxxxxxxx | ||
# profile 单个 kernel 的执行情况,通过 --kernels 指定要 profile 的 kernel 的名称;通过 --metrics 指定要 profile 的是什么 metric,如 dram_read_bytes, achieved_occupancy 等,也可以指定为 all 来得到所有的 metric | ||
srun --gpus 1 nvprof --kernels "KERNEL1|KERNEL2" --metrics "METRIC1|METRIC2" ./test/unit_tests xxxxxxxxxx | ||
``` | ||
|
||
关于可以 profile 得到的性能指标以及 `nvprof` 更高级的使用方法可以参考 <https://docs.nvidia.com/cuda/profiler-users-guide/index.html>。 | ||
|
||
## 往届实现 | ||
|
||
![稀疏矩阵例子](./fig/pa3/performance.svg) | ||
|
||
图中显示了不同的实现在不同数据集上相对 cuSparse 的加速比($K = 32$):同学 A 通过改变线程映射的方式,基本消除了 warp divergence 的问题,其代码量相比于 naive 实现仅有小于 20 行不同;同学 B 除此之外,在 CPU 上预处理了稀疏矩阵,在一些不规则的数据集上,如 arxiv,取得了更多的提升。 | ||
|
||
## 评分 | ||
|
||
### 正确性 | ||
|
@@ -151,55 +108,38 @@ srun --gpus 1 nvprof --kernels "KERNEL1|KERNEL2" --metrics "METRIC1|METRIC2" ./t | |
|
||
为了避免直接提交下发的参考实现得分,我们还设置了加速比得分。GPU 的加速比得分为需要超过 cusparse 的性能; | ||
|
||
只要在 **≥20** 个数据集超过 cusparse 的性能且结果正确, 就可以获得 **全部** 加速比分; | ||
只要在 **≥20** 个测试中超过 cusparse 的性能且结果正确, 就可以获得 **全部** 加速比分; | ||
|
||
|
||
### 性能 | ||
|
||
性能得分共占 $30\%$, 针对 GPU 测试 13 个数据集 (`scripts/run_all.sh`中指定), 两种 $K$ 的长度; 针对 CPU 测试 4 个数据集 (`scripts/run_all_CPU.sh`中指定), 两种 $K$ 的长度; | ||
性能得分共占 $30\%$, 针对 GPU 测试 13 个数据集 (`scripts/run_all.sh`中指定), 两种 $K$ 的长度; | ||
|
||
* 对于每组测试用例,只有当你获得了正确性基础分后,才能得到性能分。每组测试用例的性能分数相同。 | ||
* 每组测试用例有一个性能线 (作业开始一周后公布),超过性能线的同学将得到满分。 | ||
* 每组测试用例有一个性能线,超过性能线的同学将得到满分。 | ||
* 未达到性能线的同学,根据测试性能在 **未达性能线同学** 的排名给出每组测试用例的分数:每组测试用例各自排名,性能排名前 $10 \%$ 的同学得到 $100 \%$ 的分数,排名 $10 \%$ - $20 \%$ 的同学得到 $90 \%$ 的分数,依此类推。对于任何测试用例,获得正确性分数的同学将至少获得 $10 \%$ 的性能分数。 | ||
|
||
### 更新 (2023.05.25) | ||
|
||
原要求: | ||
|
||
为了避免直接提交下发的参考实现得分,我们还设置了加速比得分。GPU 的加速比得分为需要超过 cusparse 的性能; | ||
|
||
对于每个测试用例,只有当你 **获得了正确性基础分且加速比合格**,方可获得该测试用例的加速比得分。 | ||
|
||
更新后的要求: | ||
|
||
为了避免直接提交下发的参考实现得分,我们还设置了加速比得分。GPU 的加速比得分为需要超过 cusparse 的性能; | ||
|
||
只要在 **≥20** 个数据集超过 cusparse 的性能且结果正确, 就可以获得 **全部** 加速比分; | ||
#### 性能线 | ||
|
||
|
||
|
||
### 性能线 | ||
|
||
为避免针对对于每个数据集进行微调导致不必要的工作量, 发布两种性能线, 一个是整体的, 一个是针对每个数据集的, 达到整体 **或者** 针对单个数据集的性能线, 即可获得全部性能分. | ||
为避免针对对于每个数据集进行微调导致不必要的工作量, 发布两种性能线:整体性能线和针对每个数据集的性能线, 达到整体 **或者** 针对单个数据集的性能线, 即可获得全部性能分. | ||
|
||
* 如果你满足了整体性能线, 则得到当前setting下对应的所有数据点的满分(即使有数据集没达线) | ||
* 如果你满足了某个数据集的性能线, 则得到这个数据集的所有性能分 | ||
* 性能线仅针对GPU测例, CPU不设性能线 | ||
<!-- * 性能线仅针对GPU测例, CPU不设性能线 --> | ||
|
||
#### 整体 | ||
#### 整体性能线 | ||
|
||
计算方法是 `avg(nnz/t)`, K不同, 分别计算 | ||
以吞吐量作为性能指标,其计算方法是 `avg(nnz/t)`。 | ||
|
||
对于 K=32, 平均吞吐量为 5e9 nnz/s | ||
对于 K=32, 平均吞吐量达到 5e9 nnz/s | ||
|
||
对于 K=256, 平均吞吐量为 6.5e8 nnz/s | ||
对于 K=256, 平均吞吐量达到 6.5e8 nnz/s | ||
|
||
|
||
#### 单个数据集性能线 | ||
|
||
|
||
#### 单个数据集 | ||
|
||
单位为us | ||
以运行时间作为性能指标,单位为us | ||
|
||
| dataset | k=32 | k=256 | | ||
|-----------------|----------|-----------| | ||
|
@@ -222,7 +162,45 @@ srun --gpus 1 nvprof --kernels "KERNEL1|KERNEL2" --metrics "METRIC1|METRIC2" ./t | |
|
||
### 实验报告 | ||
|
||
实验报告共占 $10\%$。 | ||
实验报告占 $10\%$。 | ||
|
||
## 实验提交 | ||
|
||
1. 实验代码: | ||
* 在截止日期之前将完成后的整个实验框架置于自己 home 目录下的 `PA3` 目录,如 `/home/course/hpc/users/2020000000/PA3`。 | ||
2. 实验报告: | ||
* 将 **PDF 文件** 提交至网络学堂。 | ||
* 包含以下内容: | ||
1. 介绍你的实现方法,可以包括如何解决 ref 实现中的 warp divergence 的问题,如何利用各级缓存,如何改善数据的局部性,如何解决 load imbalance 的问题等; | ||
2. 展示不同优化对性能产生的影响,可以以单个数据集为例子详细分析; | ||
3. 在 $len = 32, 256$ 时的运行时间,及相对于 cuSparse 实现的加速比。 | ||
|
||
## 优化 Hint | ||
|
||
* **GPU 访存**:在 SpMM 中,稀疏矩阵的一个元素代表了对于稠密矩阵的一行的访问,所以访存量很大,需要考虑到 GPU 的访存行为(coalesce memory access)来优化 | ||
* **Warp divergence**:稀疏矩阵的元素分布不规则,会导致 reference 实现中同一个 warp 内部的线程工作量差距很大,因为 warp divergence 导致一个 warp 的执行时间由最慢的线程决定。而在 reference 实现中,每个线程负责一整行稀疏矩阵相关计算;每行非零元相差大,所以有严重的 warp divergence 问题。可以通过改变并行维度,让同一个 warp 内线程处理相同的工作量; | ||
* **局部性**:稀疏结构带来不规则的数据访问,导致局部性很差,可以通过对图数据做预处理(在 `preprocess` 函数中处理,不占计算性能的运行时间;可以使用现有工具如 [METIS](https://github.com/KarypisLab/METIS),也可以自己实现),改变图数据结构,增加局部性; | ||
* **负载不均衡**:可以自己预处理图结构,来减少它的不规则性,优化计算过程中的 load imbalance 的问题。 | ||
|
||
## GPU 优化技巧 | ||
|
||
可以通过 GPU profiling 工具 `nvprof` 对程序进行 profile: | ||
|
||
```bash | ||
# 得到程序运行的总结,包括整个程序运行过程中,各个 kernel 以及 CUDA API 的执行时间和次数 | ||
srun --gpus 1 nvprof ./test/unit_tests xxxxxxxxxx | ||
# profile 单个 kernel 的执行情况,通过 --kernels 指定要 profile 的 kernel 的名称;通过 --metrics 指定要 profile 的是什么 metric,如 dram_read_bytes, achieved_occupancy 等,也可以指定为 all 来得到所有的 metric | ||
srun --gpus 1 nvprof --kernels "KERNEL1|KERNEL2" --metrics "METRIC1|METRIC2" ./test/unit_tests xxxxxxxxxx | ||
``` | ||
|
||
关于可以 profile 得到的性能指标以及 `nvprof` 更高级的使用方法可以参考 <https://docs.nvidia.com/cuda/profiler-users-guide/index.html>。 | ||
|
||
## 往届实现 | ||
|
||
![稀疏矩阵例子](./fig/pa3/performance.svg) | ||
|
||
图中显示了不同的实现在不同数据集上相对 cuSparse 的加速比($K = 32$):同学 A 通过改变线程映射的方式,基本消除了 warp divergence 的问题,其代码量相比于 naive 实现仅有小于 20 行不同;同学 B 除此之外,在 CPU 上预处理了稀疏矩阵,在一些不规则的数据集上,如 arxiv,取得了更多的提升。 | ||
|
||
|
||
## 注意事项 | ||
|
||
|
@@ -237,4 +215,4 @@ srun --gpus 1 nvprof --kernels "KERNEL1|KERNEL2" --metrics "METRIC1|METRIC2" ./t | |
|
||
关于本作业的问题可以在共享文档中反馈,助教会 check ,热心的同学也可以帮忙回答,共创良好讨论氛围。 | ||
|
||
【腾讯文档】高性能作业 PA3 反馈:<https://docs.qq.com/doc/DQURXTEF3dldBV0pC> | ||
<!-- 【腾讯文档】高性能作业 PA3 反馈:<https://docs.qq.com/doc/DQURXTEF3dldBV0pC> --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters