簡易mpiクラスター構成の手順

お疲れさまです。橋本です。

mpi並列に対応するプログラムを複数ノードで実行可能なクラスターを構築しましたので手順をナレッジします。

具体的な記事を拝見いただく前に、経験から得た格言を紹介いたします。
__1. ネットワーク系ってクッソ難しい！__
__2. 労力がハンパない__
__3. LANってこんなに遅かったのか・・・__
__4. ザコをそこそこ集めたところで、出来上がるのはザコ__
__5. CUDAコアってすげぇ__
以上を念頭にお進みください。

#1. 構成
構成を以下のように設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571701/5e455cfa-2f52-6ed2-0e46-8f6a7b6a8379.png)


計算の命令を流すノードを「ホストノード」、ホストノード以外のノードを「計算ノード」と名付けます。

OSはホストノードでGUI操作可能な「Ubuntu 20.04 LTS Desktop」を、計算ノードではCUI操作の「Ubuntu Server 20.04 LTS」を採用しました。

スイッチングハブのスイッチングファブリックは16Gbpsの大容量品を使用し、ハブ及びLANカードは1Gbps品で統一しています。

ホストノードには、2つのLANカードが装着されており、それぞれ、外につながる用「192.168.1.111(青)」と計算内部用「10.0.0.1(橙)」にIPアドレスが振られています。さらに、ホストノードは計算内部にIPアドレスを振る「DHCPサーバ」機能、計算内部で共有のストレージを担う「NFSサーバ」機能が設定されています。

計算内部と外部でネットワークを分けた理由は、他の固定IPの重複防止や将来的に拡張した際の扱いやすさからです。DHCPサーバを立てたのは1Gbpsのルーターが転がって無かった事、各MACアドレスに簡単に固定IPを振れるです。NFSサーバを立てた理由は、mpirunをする(並列計算を実行する)とき、「実行するプログラムが、それぞれのノードで、同じ階層のディレクトリに居る必要がある」からです。

インストールするコンパイラはGCCで、並列化にはOpenMPIを使用します。

※MACアドレスとは：ネットワークカードが持つ固有のアドレスです。
※DHCPサーバとは：IPアドレスを振る、仕分け師です。
※NFSサーバとは：任意のディレクトリにマウントできるネットワーク・ストレージ共有の方法です。
※ちなみに、ホストノードのみ、ストレージ機能をもたせ、ホストノードのHDDにあるOSを計算ノードから起動する方法もあります。=>PXEブート
※青のネットワークと橙のネットワークは分けられており、青のネットワークから橙の計算ノードにアクセスする事はできません。する方法もありますが。=>IPマスカレード
※ここではmpiを使用しますが、ジョブ管理ソフトを使用する方法もあります。=>OpenHPC(PBS pro)
※また、OpenMPIやGCCではなく、ICC、IntelMPIで構成する方法もあります。
※ちなみに、PXEブート及び、OpenHPC、ICC、IntelMPIの構成を挑戦したのですが、敗北しました。

#2. OS・ソフトウェアのインストール
##OSのインストール
パソコンが組み終わりましたら、OSをインストールします。
OSはUbuntu公式「https://jp.ubuntu.com/download 」からUbuntu DesktoptとUbuntu Serverを入手します。
ダウンロードしたisoファイルをDVDに焼いても良いですが、isoファイルをUSBに焼くにはRufus「https://rufus.ie/ja_JP.html 」が便利です。
作成したブートメディアを挿入し、UEFIBIOSからブートメディアを選択し、手順に従ってインストールを進めます。新規にインストールする場合は詰まる事はないと思います。
このとき、計算ノードにインストールするUbuntuServerのオプションにてOpenSSH Serverのインストールをしておくと、インストール後すぐにSSHが使用でき便利です。また、同じユーザー名にしておくと便利です。

##ホストノードにインストールするソフトウェア
インストールするソフトウェア一覧です。

```
sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get -y install vim
sudo apt-get -y install openssh-server
sudo apt-get -y install make build-essential g++ gfortran
sudo apt-get -y install openmpi-doc openmpi-bin libopenmpi-dev
sudo apt-get -y install libblas-dev liblapack-dev libscalapack-mpi-dev libfftw3-dev
sudo apt-get -y install nfs-kernel-server isc-dhcp-server vsftpd
```
インストールしたソフトは順番に、
Ubuntu desktopには簡易版のvimしか入ってないので、vimを入れる。
ssh接続のためにOpenSSH Serverを。
ビルドエッセンシャル関係。
OpenMPI関係。
科学計算ライブラリ。
nfsサーバー、dhcpサーバー、FTPサーバー
です。

ちなみに、確認なしで実行できるので、シェルで実行すれば楽だと思います。

##計算ノードにインストールするソフトウェア
インストールするソフトウェア一覧です。このとき、計算ノードは計算内部ネットワークではなく、インターネットに接続しておきましょう。

```
sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get -y install openssh-server
sudo apt-get -y install make build-essential g++ gfortran
sudo apt-get -y install openmpi-doc openmpi-bin libopenmpi-dev
sudo apt-get -y install libblas-dev liblapack-dev libscalapack-mpi-dev libfftw3-dev
sudo apt-get -y install nfs-common
```
インストールしたソフトは順番に、
ssh接続のためにOpenSSH Serverを。
ビルドエッセンシャル関係。
OpenMPI関係。
科学計算ライブラリ。
nfsクライアント
です。

ちなみに、確認なしで実行できるので、シェルで実行すれば楽だと思います。
インストールが完了したら、計算内部ネットワークにつなぎなおしましょう。

##DHCPサーバーの設定
まず、全ての計算ノードのMACアドレスを調べます。IPアドレスを調べるには```ip addre```コマンドを打ち、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571701/d472fcbc-a1e7-ee78-d451-03e9df4b2b46.png)
読み解きます。手順通りなら、上から2つ目のstate DOWNになっているアダプタだと思います。多分。

ホストノードでDHCPサーバーを構成します。
ホストノードで、```ip addre```コマンドを使用し、計算内部に接続しているLANアダプタ名をメモします。
DHCPサーバーを設定するアダプタ(計算内部に接続しているLANアダプタ)を指定します。任意のテキストエディタで編集します。

```
sudo vi /etc/default/isc-dhcp-server
```

```:isc-dhcp-server
INTERFACESv4 = "アダプタ名"
INTERFACESv6 = "アダプタ名"
```
次に各MACアドレスに指定したIPアドレスを振る、DHCPサーバーの根幹の設定を行って行きます。

```
sudo vi /etc/dhcp/dhcpd.conf
```

```:dhcpd.conf
option domain-name "example.org";
option domain-name-servers ns1.example.org, ns2.example.org;
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
authoritative;
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.1;
option subnet-mask 255.255.255.0;
option broadcast-address 10.0.0.255;
}
host node1{
    hardware ethernet 計算ノードのMACアドレス;
    fixed-address 振りたいIPアドレス;
}
host node2{
    hardware ethernet 04:20:9a:46:82:37;
    fixed-address 10.0.0.102;
}
host node3{
    hardware ethernet 05:60:9a:46:82:87;
    fixed-address 10.0.0.103;
}
```
多分、「netmask」と「option subnet-mask」は同じ 255.255.255.0を入力します。「option routers」がルーターのアドレスです。

はっきり言って、全然わかりません。ブロードキャストアドレスとかなんの役割をしているのか、サブネットとサブネットマスクの違いとか...
しかし、動いているので多分間違っては無いと思います。ネットワーク系は予備知識なしでは難しいです(泣)。

設定が完了したら以下のコマンドで設定を読み込み、DHCPサーバーを再起動します。

```
sudo systemctl enable isc-dhcp-server
sudo systemctl restart isc-dhcp-server
```
ホストノードのIPアドレスはDHCPサーバーのIPアドレスそのものなので、「設定」→「ネットワーク」→「歯車」
![Screenshot from 2021-01-22 05-00-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571701/cd472e2a-40e9-e47a-47fb-c653f34b7711.png)
![Screenshot from 2021-01-22 05-00-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571701/7c2179e8-8851-5b26-6afa-bb537e705315.png)
「IPv4」タブに移動し、「手動」を選択。
アドレス、ゲートウェイ、DNSに「option routers」のアドレスを。
ネットマスクに「subnet」のアドレスを入力します。
適用後、歯車の隣のスイッチをON-OFFしてください。

できたら、正常に動作しているか、各ノードでipアドレスを確認しましょう。

##SSHの設定
鍵交換にてSSH接続する設定を行います。

まず、ホストノード、各計算ノードでRSA暗号鍵の生成、ファイヤーウォールの無効化を行います。

```
ssh-keygen -t rsa
sudo ufw disable
```
色々出てきますが、Enterキーを連打しましょう。そして、生成された自分の鍵を認証された鍵一覧に登録します。

```
cd ~/.ssh && cat id_rsa.pub > authorized_keys
```
これで、自分の鍵「id_rsa.pub」が認証された鍵一覧「authorized_keys」に追記されました。この「authorized_keys」に接続先の鍵「id_rsa.pub」を互いに追記すれば、鍵交換によるSSH接続が可能になります。
また、鍵交換をする相手は、ホストノードと各計算ノードです。計算ノード同士で鍵交換する必要はありません。

やり方は任意ですが、例えば、ホストノードから、

```
ssh 計算ノードのIPアドレス
cd .ssh
vi authorized_keys
```
と打ちこみ、相手の鍵を自分の「authorized_keys」に追記し、自分の鍵を相手の「authorized_keys」に追記します。これを全ての計算ノードで実行します。

鍵交換が完了したら、ホストから各計算ノードに、各計算ノードからホストにパスワードなしでSSH接続できるか、確認しておきましょう。
さらに、ホストからホスト自身、各計算ノードから各計算ノード自身にも接続しておきましょう。

さらっと書きましたが、地味に面倒です。

##NFSの設定
まず、ホストノードでNFSサーバー側の設定をします。
たとえば、「/home/user/mpi」のフォルダーを共有するとしましょう。
NFSサーバーの設定を行います。

```
sudo vi /etc/exports
```

```:exports
/home/user/mpi 10.0.0.101(rw,no_subtree_check,sync)
/home/user/mpi 10.0.0.102(rw,no_subtree_check,sync)
/home/user/mpi 10.0.0.103(rw,no_subtree_check,sync)
```
並びは、
```共有するファイル　共有相手のIP(書き込み可能、一覧の一気取得を防止、共有の可)```
です。

※「IPアドレスをサブネットのアドレス？にすれば、一つ一つ設定しなくても良い」との記事もありましたが、うまくいきませんでした。

設定が完了したら設定を反映します。

```
sudo exportfs -ra
```

次に各計算ノードでホストノードのNFSをマウントします。
「/home/user/mpi」にフォルダーを作成し、以下のコマンドを各計算ノードで実行します。

```
sudo mount -t nfs 10.0.0.1:/home/user/mpi /home/user/mpi
```
できたら、適当な計算ノードから、「/home/user/mpi」にファイルを作って、それが他の計算ノードにも反映されているか確認しましょう。

#3. 実行
正常に動作するPCを全て組み立てるのに1日、うまくDHCPサーバーが機能せず四苦八苦して1日、そして3日目で完成が私のスケジュールでした。労力がハンパないです。
実行には、実行のプログラムとホストファイルをNFSファイル下に配置します。
ホストファイルは以下のような__改行コードLF__のテキストファイルです。
今回は「hosts」というファイル名で保存します。

```:hosts
10.0.0.1 cpu=6
10.0.0.101 cpu=6
10.0.0.102 cpu=6
10.0.0.103 cpu=6
```
ホストノードのIPとcpuコア数
各計算ノードのIPとcpuコア数
が記述されています。

ホストノードで、以下のコマンドで並列処理が開始します。

```
mpirun -hostfile hosts -np 24 ./プログラム名
```
24とあるところは並列化数(6x4)です。それぞれの環境に合わせて入力しましょう。

#4. 結果
さて、実行していかがでしょうか。おそらく「あれ？速くなったのかな？」と効果を疑問視されているはずです。
例えば、第一原理計算パッケージVASPにて290原子の電子状態最適化計算で単ノード実行の場合「2.6時間」かかっていたところ、4ノード並列で「66.3分」に短縮されました。およそ2.3倍程度高速化したわけですが、「4並列で2.3倍って(笑)」となりますよね。
原因は「並列化されない部分は高速化しない」も考えられますが、一番の要因は「1Gbpsの遅さ」です。
1Gbpsはおおよそ125MB/sの速度です。この速度はHDDのシーケンシャルリード・ライトの速度に同じです。CPUは主記憶装置とやり取りするので、ノードのCPU同士はそれぞれのRAMとデータ交換するのですが、RAMの転送速度から比べると125MB/sなんて亀です。例えばDDR3 PC3-12800なら12800MB/sです。LAN速度はこんなにも遅いのです。

では、「どうしたらよいか」なのですが、
・転送速度を向上させる
・ノードあたりの性能を最強にする
・ノードをありったけに増やす
この3通りの選択肢があります。
転送速度が向上すればより速くなるのは自明。ノードあたりの性能を最強にし、更に最強のノードを加えて、CPUタイムをできるだけ短くして高速化を測る方法。そして、ノードをありったけに増やして、1ノードあたりの転送量を少なくすれば高速になります。
ザコをそこそこ集めたところで、出来上がるのはザコで、最強のマシンをより最強にするか、ザコを蟻の如く集めるかの何れかの選択を取らないと高速化は難しいでしょう。

ところで、「ノード間の高速な通信が可能な、ザコを蟻の如く集めた計算ユニット」があります。GPUです。
例えば、CPU intel i9-9900Xは1クロックで32個の計算を行えますが、GPU Tesla-V100の演算ユニットは1クロックでたった2個の計算しかできません。しかし、i9-9900Xは10コアなのに、Tesla-V100は__2560コア__あります。すなわち、1クロックあたりの総計算数はi9-9900Xが320個、Tesla-V100は5120個と圧倒です。i9を10台並列してもTeslaたった1枚にかないません。CUDAコアってすげぇ。

これはあくまで並列数にこだわった話であって、単ノード性能が重要な場合はこの限りではありません。

#5. 参考
クラスタ制作の参考
https://www2.yukawa.kyoto-u.ac.jp/~koudai.sugimoto/dokuwiki/doku.php?id=%E8%87%AA%E4%BD%9C%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E8%A8%88%E7%AE%97%E6%A9%9F
後半のお話
https://vectory.work/flops/#toc6