Oracle Cloud のベアメタル GPU マシンで HPCG を動かす

# はじめに
HPCG という HPC ベンチマークを GPU で計測する必要があったのでセットアップから計測までをメモしておきたいと思います。

# 目的
HPCG を複数のGPUノードで実行・計測する

# 環境
Oracle Cloud の GPU ベアメタルマシンを3台利用します。
BM.GPU2.2 というインスタンスで、CPU が Xeon 8167M x2 でメモリが 192GB のベアメタルマシンに NVIDIA Tesla P100 が2個搭載されています。
詳細はこちら → https://cloud.oracle.com/compute/gpu/features


# HPCG について
HPCG はソースとバイナリの両方が提供されています。
今回はバイナリを利用したいと思います。

http://www.hpcg-benchmark.org/software/index.html に GPU 向けのバイナリがありますので、HPCG 3.1 Binary for NVIDIA GPUs Including Volta(2017-10-16) を利用します。

このバイナリは以下の環境でコンパイルされているので、これに近い環境をセットアップしていきます。
CUDA: ver 9.0.176
OpenMPI: 1.10.2 


# セットアップ
ここでは Oracle Cloud 側のセットアップ(ネットワーク、GPU インスタンスの作成)については省略します。
OS は CentOS を選ぶこともできますが、Oracle Linux 7.5 を利用します。また、CentOS でも同じ流れでセットアップできると思いますが、一部インストールするパッケージ名がことなるので注意が必要です。

## GPU の確認 
まずは GPU が認識されているかを確認しておきます。

```
lspci | grep -i nvidia
```

以下のように P100 が2つ搭載されていることが確認できます。

```
5e:00.0 3D controller: NVIDIA Corporation GP100GL [Tesla P100 SXM2 16GB] (rev a1)
86:00.0 3D controller: NVIDIA Corporation GP100GL [Tesla P100 SXM2 16GB] (rev a1)
```

## Kernel devel と header をインストール 
CUDA のインストールで DKMS を使ってドライバが Build されるようなので、これらをインストールしておきます。
※このへんは CentOS では違うので注意

```
sudo yum install -y kernel-uek-devel-`uname -r` kernel-headers
```

## OpenMPI のインストール
複数ノードで実行するので OpenMPI をインストールします。

```
sudo yum install -y openmpi.x86_64 openmpi-devel.x86_64
```

## CUDA のインストール
CUDA を rpm でインストールをしていきます。
ここでは HPCG がコンパイルされた環境に合わせるため、CUDA 9.0 を指定してインストールしていきます。

```
sudo yum install -y http://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/cuda-repo-rhel7-9.0.176-1.x86_64.rpm
sudo yum install -y cuda-9-0.x86_64
```

以上が正常に完了したら一度 Reboot します。

```
sudo reboot
```

ここで /dev/nvidia0, /dev/nvidia1 などが認識されているか確認しますが、認識されていなかったので以下のスクリプトを実行して作成します。reboot しても有効になるように /etc/rc.local などに設定しておくとよいでしょう。

```
cat startup_nvidia.sh

#!/bin/bash

/sbin/modprobe nvidia

if [ "$?" -eq 0 ]; then
  # Count the number of NVIDIA controllers found.
  NVDEVS=`lspci | grep -i NVIDIA`
  N3D=`echo "$NVDEVS" | grep "3D controller" | wc -l`
  NVGA=`echo "$NVDEVS" | grep "VGA compatible controller" | wc -l`

  N=`expr $N3D + $NVGA - 1`
  for i in `seq 0 $N`; do
    mknod -m 666 /dev/nvidia$i c 195 $i
  done

  mknod -m 666 /dev/nvidiactl c 195 255

else
  exit 1
fi

/sbin/modprobe nvidia-uvm

if [ "$?" -eq 0 ]; then
  # Find out the major device number used by the nvidia-uvm driver
  D=`grep nvidia-uvm /proc/devices | awk '{print $1}'`

  mknod -m 666 /dev/nvidia-uvm c $D 0
else
  exit 1
fi
```

root で実行します。

```
sudo ./startup_nvidia.sh
```

これにより /dev 配下に以下のデバイスが作成されたことを確認します。

```
$ ls /dev/nvidia*
/dev/nvidia0  /dev/nvidia1  /dev/nvidiactl  /dev/nvidia-uvm  /dev/nvidia-uvm-tools
```

## 環境変数の設定
OpenMPI と CUDA の PATH と LD_LIBRARY_PATH を HPCG を実行するユーザの .bashrc と .bash_profile に追加します。

```
PATH=/usr/lib64/openmpi/bin:$PATH
LD_LIBRARY_PATH=/usr/lib64/openmpi/lib:/usr/local/cuda-9.0/targets/x86_64-linux/lib

export PATH LD_LIBRARY_PATH
```

## NFS Server の設定
複数ノードで HPCG を実行するにあたり、OpenMPI 経由で並列実行します。
OpenMPI から複数ノードで実行する際は、バイナリが同一ディレクトリにある必要があるので NFS を利用し実現します。

Oracle Cloud には NFS as a Service があるためこれを利用します。詳細は省きますが、Cloud の画面から簡単に NFS サービスをデプロイできます。
https://blogs.oracle.com/cloud-infrastructure/introducing-oracle-cloud-infrastructure-file-storage-service
上記で作成した share を /mnt/hpcg にマウントしました。
また、こちらも reboot しても大丈夫なように /etc/fstab に追加しておきます。


# HPCG
以上で、HPCGを動かす環境が整いましたので、HPCG の実行をしてみます。

## HPCG のダウンロードと展開
マウントした NFS Share 配下にダウンロードします。

```
cd /mnt/hpcg
wget http://www.hpcg-benchmark.org/downloads/hpcg-3.1_cuda9_ompi1.10.2_gcc485_sm_35_sm_50_sm_60_sm_70_ver_10_8_17.tgz

tar xvf hpcg-3.1_cuda9_ompi1.10.2_gcc485_sm_35_sm_50_sm_60_sm_70_ver_10_8_17.tgz
```

## 実行(シングルノード)
まずはシングルノードで実行してみます。
オプションには以下の2つを指定。
Infiniband ではなく Ethernet で実行するので --mca btl tcp,sm,self オプションをつけます。
また -np 2 を指定して P100 x2個の両方で実行します。

※今回はテスト実行なので input file はデフォルトの hpcg.dat (実行時間が60秒)を利用します。正式なベンチマークを取得する際には1時間で実行しましょう。

```
cd hpcg-3.1_cuda9_ompi1.10.2_gcc485_sm_35_sm_50_sm_60_sm_70_ver_10_8_17
mpirun -np 2 --mca btl tcp,sm,self ./xhpcg-3.1_gcc_485_cuda90176_ompi_1_10_2_sm_35_sm_50_sm_60_sm_70_ver_10_8_17
```

nvidia-smi -l でGPUで処理しているプロセスが表示されることを確認します。

```
nvidia-smi -l

Tue Aug 14 15:38:10 2018       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 396.44                 Driver Version: 396.44                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla P100-SXM2...  Off  | 00000000:5E:00.0 Off |                    0 |
| N/A   41C    P0    41W / 300W |   8403MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla P100-SXM2...  Off  | 00000000:86:00.0 Off |                    0 |
| N/A   41C    P0    43W / 300W |   8403MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      6995      C   ...0_2_sm_35_sm_50_sm_60_sm_70_ver_10_8_17  8391MiB |
|    1      6996      C   ...0_2_sm_35_sm_50_sm_60_sm_70_ver_10_8_17  8391MiB |
+-----------------------------------------------------------------------------+
```

うまくいくと以下のように処理が進みます。

```
start of application (2 OMP threads)...
2018-08-13 15:34:06.375

Problem setup...
Setup time: 0.377932 sec

GPU: 'Tesla P100-SXM2-16GB'
Memory use: 8315 MB / 16280 MB
2x1x1 process grid
256x256x256 local domain

Reference SpMV+MG...

Reference CG...
Initial Residual: 7.303536e+03 Max_err: 1.000000e+00 tot_err: 5.792619e+03
REF  Iter = 1 Scaled Residual: 1.875554e-01 Max error: 1.000000e+00 tot_error: 9.748362e-01
REF  Iter = 2 Scaled Residual: 1.031535e-01 Max error: 1.000000e+00 tot_error: 9.515092e-01
REF  Iter = 3 Scaled Residual: 7.120013e-02 Max error: 1.000000e+00 tot_error: 9.286195e-01

～ 省略 ～

Number of CG sets:      20
Iterations per set:     52
scaled res mean:        1.701928e-03
scaled res variance:    0.000000e+00

Total Time: 6.146707e+01 sec
Setup        Overhead: 1.21%
Optimization Overhead: 0.54%
Convergence  Overhead: 3.85%

2x1x1 process grid
256x256x256 local domain
SpMV  =  xxx.x GF (xxxx.x GB/s Effective)   xx.x GF_per ( xxx.x GB/s Effective)
SymGS =  xxx.x GF (xxxx.x GB/s Effective)  xxx.x GF_per ( xxx.x GB/s Effective)
total =  xxx.x GF (xxxx.x GB/s Effective)  xxx.x GF_per ( xxx.x GB/s Effective)
final =  xxx.x GF (xxxx.x GB/s Effective)   xx.x GF_per ( xxx.x GB/s Effective)

end of application...
2018-08-13 15:39:17.102
```

最後の方に結果が出力されます。※ここでは諸事情により数値は xxx.x に変えています。

同一ディレクトリ内に readme に P100 x2 のベンチマーク結果が載っていて、これとほぼほぼ同じなのでうまく動いているようです。

```
2 x P100

2x1x1 process grid
256x256x256 local domain
SpMV  =  167.0 GF (1051.8 GB/s Effective)   83.5 GF_per ( 525.9 GB/s Effective)
SymGS =  231.4 GF (1786.3 GB/s Effective)  115.7 GF_per ( 893.1 GB/s Effective)
total =  212.9 GF (1614.7 GB/s Effective)  106.5 GF_per ( 807.3 GB/s Effective)
final =  201.2 GF (1525.7 GB/s Effective)  100.6 GF_per ( 762.9 GB/s Effective)
```

## 実行(マルチノード)

マルチノード実行の設定ポイントとしては以下がありました。

+ 作成した NFS の Share をすべてのノードでマウントする
+ すべてのノードに .bashrc に PATH, LD_LIBRARY_PATH を入れる
+ 実行する Master から他のノードへは Non Password で SSH ログインできるようにする
+ Firewalld のポートは不明だったので、一旦Stopさせる
+ SELinux は有効な状態で動いた


## ノードの作成
並列で動かすサーバを作成します。
今回は Cloud なので、シングルノードのイメージを複製します。こちらも詳細は省きます。
これによりポイントの 1, 2 については省略することができます。
シングルノードのイメージから2台、GPU ベアメタルマシンを作成し、合計3台になりました。
以降、元々のノードをマスターノード、複製した2台をスレーブノードと書いていきます。

## ssh Non-Password Login 設定
マスターノードで ssh key を作成し、public key をスレーブノードの ~/.ssh/authorized_keys に追加します。 

```
ssh-keygen  -N '' -t rsa -b 2048
```

## firewalld の停止
OpenMPI が利用するポート調べるとある程度の範囲のポートを開ける必要がある、とのことでした。
今回は一時的な利用なので、ポートを開放するのではなく、firewalld を停止させることにしました。
全ノードで以下を実行します。

```
sudo systemctl stop firewalld 
```

## マルチノードで HPCG を実行
まずは確認としてマスターとスレーブの2台で実行してみます。
シングルノードで動かしたときのコマンドに --host master,slave01 を追加して実行します。
これで指定したノードの GPU x1 で HPCG が走ります。
※今回はテスト実行なので input file はデフォルトの hpcg.dat (実行時間が60秒)を利用します。正式なベンチマークを取得する際には1時間で実行しましょう。

```
mpirun --mca btl tcp,sm,self -np 2 --host master,slave01 ./xhpcg-3.1_gcc_485_cuda90176_ompi_1_10_2_sm_35_sm_50_sm_60_sm_70_ver_10_8_17
```

nvidia-smi -l で指定したノードでプロセスが実行されていることを確認します。

続いて3台のすべての GPU を利用して動作させます。
host ファイルを作成し以下のように hostname slots=GPU数 を記述します。
ここで書くhostname は /etc/hosts または DNS で解決できる必要があります。

```
master slots=2
slave01 slots=2
slave02 slots=2
```

今度は -hostfile オプションで上記作成した host ファイルを指定します。
これで、3ノードにまたがって合計6GPU でHPCG を走らすことができます。

```
mpirun --mca btl tcp,sm,self -np 6 -hostfile host ./xhpcg-3.1_gcc_485_cuda90176_ompi_1_10_2_sm_35_sm_50_sm_60_sm_70_ver_10_8_17
```


# 最後に
GPU の利用や OpenMPI など初めてなことばかりでしたが、なんとか動かす事ができました。
主なポイントはおさえて書けていると思いますが、細かいところで抜けがあるかもしれません。
近々、NVIDIA Tesla V100 で同じことを行いますので、不備がありましたらそこで修正していきたいと思います。