Raspberry Pi 4BでOpenFOAMによる4ノード並列計算をするまで

初投稿です．

前回，前々回と，Raspberry PiにOpenFOAMを導入する方法について書きました．
今回はいよいよ，Raspberry PIで4ノード並列計算を実現していこうと思います．

環境は前回と変わりません．
マシン：Raspberry Pi 4B 8GB × 4機
OS：Raspberry Pi OS (64bit) 2023-02-21
クライアント：MobaXterm Portable v23.0

クラスタリングの設定を行う流れ的には，[こちらに書かれている方法が分かりやすかったです](https://note.com/rasen/n/n57dd963a7a40/ "RaspberryPiですぱこん！その②（いよいよクラスター化！）")．一方で，本サイトで紹介されている方法を辿るだけだとOpenFOAMでの並列計算は上手くいかないため，そのあたりの方法も交えながら記載していきます．

## MPI環境構築
既にマシンの構築は終わっているものとして話を進めます．
以下の手順を，クラスタを組むラズパイ各機に対して行っていってください．
### ライブラリの導入
まず最初に，MPI関係に必要なライブラリ群を導入します．
```
sudo apt-get update
sudo apt upgrade
sudo apt install build-essential manpages-dev gfortran nfs-common nfs-kernel-server vim openmpi-bin libopenmpi-dev openmpi-doc keychain nmap
```
同時に，OpenFOAMの環境構築も，[前回の記事を基に行います](https://qiita.com/NxNyNz/items/67b347d120c4f277f16a "Raspberry Pi 4BでOpenFOAM (その2)")．

### ネットワークの設定
クラスタを組むにあたって必要なネットワーク設定をラズパイ上で行います．基本的には分かりやすいようにホスト名を設定し，IPを固定する作業です．これらの方法はググればめちゃくちゃ出てきますので端折りますが，次のように設定していけばいい感じです．

```:/etc/dhcpcd.conf
interface wlan0
static ip_address=[固定したいIPアドレス]/24
static routers=[デフォルトゲートウェイのIPアドレス]
static domain_name_servers=[DNSサーバのIPアドレス]

interface eth0
static ip_address=[固定したいIPアドレス]/24
static routers=[デフォルトゲートウェイのIPアドレス]
static domain_name_servers=[DNSサーバのIPアドレス]
```
```:/etc/hosts
#127.0.1.1       rpi01
192.168.1.101   rpi01
192.168.1.102   rpi02
192.168.1.103   rpi03
192.168.1.104   rpi04
```

### 認証鍵の設定
マスターノード(ここではrpi01)上で，次のコマンドからSSH認証鍵を作成します．この時に，認証鍵を保存するディレクトリはデフォルトで良いか，認証鍵のパスフレーズを何にするか聞いてきますので，それぞれ設定してください．
```
ssh-keygen -t rsa
```
認証鍵を作成したら，その鍵をスレーブノードに送信します．今回はrpi02に送ります．途中で送信を続けていいか，スレーブノードのパスワード(鍵のパスフレーズではない)を聞いてきますので，入力していきます．
```
ssh-copy-id ユーザ名@rpi02
```
転送が出来たら，`ssh rpi02`でマスターノード(rpi01)から接続・操作が出来るようになります．ここで認証鍵のパスフレーズを聞かれますので入力します．
```
ssh rpi02
```
基本的にはこのSSH認証鍵を使って，ノード間通信を行い，並列計算を実現することになります．一方で，この状態でMPIによる計算を始めてしまうと，全ノード分のパスフレーズをいちいち入力する必要が生じてしまい，非常に煩雑です．そこで，`~/.bashrc`に以下の通り追記してください．
```:.bashrc
# Logic for Keychain
/usr/bin/keychain $HOME/.ssh/id_rsa
source $HOME/.keychain/$HOSTNAME-sh
```
追記したら，，`source .bashrc`を実行してください．その時に，またパスフレーズを聞かれますので入力します．これを行う事で，以降は逐一パスフレーズを入力しなくて済むようになります．

### 共有ディレクトリの作成
ノード間での並列計算を行うにあたって，同一ディレクトリ上で作業できる仕組みが必要です．そこで，`/working_MPI`というフォルダを各ノードに作り，各スレーブノードからマスターノードにマウントできるようにします．

まずマスターノードから以下の操作を行います．
```
sudo mkdir /working_MPI
sudo chown ユーザ名:ユーザ名 /working_MPI
```
スレーブノードから`/working_MPI`フォルダを操作するためには，対象のIPアドレスに対して，読み込み・書き込みの権限(rw)を与える必要があります．`sudo nano /etc/exports`から，以下の一文を追記してください．
```:/etc/exports
/working_MPI 192.168.1.0/24(rw,sync)
```
書き換え次第，NFSサーバの再起動を行い，以上の設定を適用します．
```
sudo service nfs-kernel-server restart
```
OS起動時に自動実行する内容を記述する`/etc/rc.local`の`exit 0`の前に，以上の一文を入れておくと，次回起動時から自動で適用されるようになります．

続けて，スレーブノード側で以下の操作を行うと，マスターノードの`/working_MPI`がマウントされ，中身が共有できるようになります．
```
sudo mkdir /working_MPI
sudo chown ユーザ名:ユーザ名 /working_MPI
sudo mount rpi01:/working_MPI /working_MPI
```
実際に共有できているかは，'touch'コマンド等使って確認すると良いでしょう．

また，スレーブノード側では，次回起動時にも自動でマウントされるように設定を行っておきましょう．まず，ファイルのマウントに関する記述を行う`/etc/fstab`に次の一文を追加します．
```:/etc/fstab
ユーザ名:/working_MPI     /working_MPI nfs     noauto,x-systemd.automount,rw,exec      0       0
```
続いて，マスターノード起動後にマウントを確実に実行できるよう，マウントするタイミングを5秒遅らせる設定を`/etc/rc.local`の`exit 0`の前に記述します．
```:/etc/rc.local
sleep 5
mount -a
```
これでMPIを実行する環境は出来ました．ここまででの導入結果のチェックをされたい場合は，[先ほど紹介したページ](https://note.com/rasen/n/n57dd963a7a40/ "RaspberryPiですぱこん！その②（いよいよクラスター化！）")や，或いは[ラズパイスパコンの参考書](https://www.kyoritsu-pub.co.jp/book/b10003954.html "Raspberry Piでスーパーコンピュータをつくろう！")付属のコードでチェックしてみてください．

## OpenFOAMにおけるMPI環境の構築
いよいよここから，OpenFOAM実行時に必要な環境構築について紹介します．この辺りは明確に方法を示してくださっていたサイトが無く，かなり手探りだったので，同様に試みている方々の参考になればと思います．

実は以上までの段階で，さぁ早速OpenFOAMでノード間計算だ～と`time mpirun --hostfile machines -np 16 pimpleFoam -parallel`とか実行すると以下のエラーが出ます．
```
--------------------------------------------------------------------------
mpirun was unable to find the specified executable file, and therefore
did not launch the job.  This error was first reported for process
rank 4; it may have occurred for other processes as well.

NOTE: A common cause for this error is misspelling a mpirun command
      line parameter option (remember that mpirun interprets the first
      unrecognized command line token as the executable).

Node:       rpi02
Executable: /home/user_name/OpenFOAM/OpenFOAM-v2006/platforms/linuxARM64GccDPInt32Opt/bin/pimpleFoam
--------------------------------------------------------------------------
```
pimpleFoamが見つかんね－よ！ってことなんですが，rpi02を覗くと普通にあるんですよね．rpi02上のみで`pimpleFoam`を実行する分には普通に計算が進みますので，インストールに問題があったとも思えません．

その原因は，SSH接続時の環境変数にありました．
まずマスターノード側(rpi01)で，環境変数の一覧を`env`から取得すると，普通にOpenFOAM関係の環境変数が確認できます．
```:env
CGAL_ARCH_PATH=/home/user_name/OpenFOAM/ThirdParty-v2006/platforms/linuxARM64Gcc/CGAL-4.12.2
FOAM_MPI=openmpi-system
WM_PROJECT_USER_DIR=/home/user_name/OpenFOAM/user_name-v2006
FOAM_RUN=/home/user_name/OpenFOAM/user_name-v2006/run
FOAM_EXT_LIBBIN=/home/user_name/OpenFOAM/ThirdParty-v2006/platforms/linuxARM64GccDPInt32/lib
FFTW_ARCH_PATH=/home/user_name/OpenFOAM/ThirdParty-v2006/platforms/linuxARM64Gcc/fftw-3.3.7
FOAM_TUTORIALS=/home/user_name/OpenFOAM/OpenFOAM-v2006/tutorials
WM_PROJECT=……(以下略)
```
これは，スレーブノード側(rpi02)でも`env`を打っても全く同じです．

一方で，マスターノード(rpi01)からスレーブノードにSSH接続し，環境変数を確認(`ssh rpi02 env`)してみると，これらの環境変数が確認出来ないのです．すなわち，スレーブノードで環境変数が設定済みでも，SSH接続でマスターノード側から除くと，その環境変数は存在しないことになってしまうのです．

この対策として，マスターノードからSSHでつなぐとき，環境変数も同時に渡してやるように設定してやります．

### 環境変数の引き継ぎ設定
まずスレーブノード側では，環境変数の受け入れ許可を行います．
`/etc/ssh/sshd_config`を開き，`PermitUserEnvironment`を許可するように書き換えてください．
```:/etc/ssh/sshd_config
#PermitUserEnvironment no
PermitUserEnvironment yes
```
また，同一ファイル上の末尾に以下の文を追加します．これで，スレーブノード側でOpenFOAM関連の環境変数受け入れが可能になります．
```:/etc/ssh/sshd_config
AcceptEnv FOAM_APP FOAM_EXT_LIBBIN FOAM_SRC FOAM_LIBBIN FOAM_MPI FOAM_SOLVERS FOAM_USER_APPBIN FOAM_APPBIN FOAM_INST_DIR FOAM_USER_LIBBIN FOAM_ETC FOAM_LIBBIN FOAM_TUTORIALS FOAM_UTILITIES FOAM_*
```
以上の設定が終わったら，`sudo service sshd restart`で設定を適用します．

<br>

続けて，マスターノード側の設定です．`$HOME/.ssh/config`を作成し，以下のように記述してください．
```:$HOME/.ssh/config
Host *
  SendEnv FOAM_APP FOAM_EXT_LIBBIN FOAM_SRC FOAM_LIBBIN FOAM_MPI FOAM_SOLVERS FOAM_USER_APPBIN FOAM_APPBIN FOAM_INST_DIR FOAM_USER_LIBBIN FOAM_ETC FOAM_LIBBIN FOAM_TUTORIALS FOAM_UTILITIES
```
これでマスターノード側から送信する環境変数を設定できました．

最後に，マスターノード・スレーブノード全てで，`$HOME/.bashrc`の編集を行います．まず最初，.bashrcの冒頭付近のcase文をコメントアウトします．環境変数を読まない原因として，SSH接続時にこのcase文を抜けてしまい，それ以降のコードを読まないことが理由にあるそうです．
```$HOME/.bashrc
# If not running interactively, don't do anything
#case $- in
#    *i*) ;;
#      *) return;;
#esac
```
同じ.bashrc上，末尾に以下の一文を足します．
```
#OpenFOAM
. ~/OpenFOAM/OpenFOAM-v2006/etc/bashrc
```
最後に`source .bashrc`を行えば，環境変数の問題はクリアできるかと思います．

### 実際にMPI実行するときは
MPI実行の前準備として，decomposeParDictを設定してやり，`decomposePar`を実行して計算領域を分割します．[このへんの話はこちらをご参照ください](https://ss1.xrea.com/penguinitis.g1.xrea.com/study/OpenFOAM/partition/partition.html "領域分割 - PENGUINITIS")．

`decomposePar`を実行したら以下のコマンドで計算を開始できます．見れば分かる通りなのですが，計算ノードの名前を使うコア数分だけ並べていき，合計のコア数を`-np`で記述，最後に並列計算であることを示す`-parallel`を書きます．
```
mpirun -H rpi01,rpi01,rpi01,rpi01,rpi02,rpi02,rpi02,rpi02,rpi03,rpi03,rpi03,rpi03,rpi03,rpi04,rpi04,rpi04,rpi04 -np 16 pimpleFoam -parallel
```

いちいちホスト名を列挙するのは煩雑なので，ケースディレクトリ内にそこに使用ノードと使用コア数を記載した`machines`を作ってから呼び出す方法もあります．
```:machines
rpi01 cpu=4
rpi02 cpu=4
rpi03 cpu=4
rpi04 cpu=4
```
```
mpirun --hostfile machines -np 16 pimpleFoam -parallel
```

## 緒言
本記事では，Raspberry Pi 4Bによるクラスタ設定から，OpenFOAMによるMPI実行までの操作について説明しました．ちなみにLESによる壁乱流チュートリアルであるchannel395での計算時間を以下に示します．
|   | real | user | sys |
|:-:|:-:|:-:|:-:|
|1node 4core|81m0.468s|299m7.207s|11m18.900s|
|2node 8core|66m47.498s|156m13.197s|101m37.000s|
|4node 8core|34m59.999s|43m57.109s|25m43.479s|
|4node 16core|56m26.276s|102m42.795s|116m28.655s|
|(参考) Ryzen7 3700X 8core|4m33.570s|20m35.703s|0m45.188s|

意外にも4node 16coreよりも4node 8coreの方が計算時間が早かったです．思いつく原因としては，ラズパイに用いているUSBの転送速度があまり早いものではない(50MB/s前後)ので，そこがボトルネックになってしまっているかなぁと思ったりしています．一方で，2node 8coreと比較すると計算時間がかなり短縮されているので，その点ではノード間計算による効果は出ている形です．

「MPIで数値計算」っていうと富岳やら不老やらAWSやら出てくるイメージもあり，なんとなくハードルも高い気がしますが，Raspberry PIであれば低コスト・コンパクトであり，スーパーコンピュータの仕組みや実装を自宅の机上で体験できるのは非常にいい点です．幸い，日本国内で稼働するスパコンは殆どLinuxで稼働していますし，ここで勉強した知識が，いつか本当にスパコンを触る時に生きるかもしれませんから，興味のあるうちにこうしたモノで慣れておくというのも良いのかもしれません．


##### 引用一覧
https://note.com/rasen/n/n57dd963a7a40/ (RaspberryPiですぱこん！その②（いよいよクラスター化！）)
https://note.com/rasen/n/n793feb3d6424 (RaspberryPiですぱこん！その① （1台でもスーパー！）)
https://nissy-lab.com/blogs/inherit-env-variables-over-ssh/ (SSH接続で環境変数を引き継ぎたい | にっしーラボ)
https://www.cfd-online.com/Forums/openfoam-solving/62627-parallel-runs-across-network-broken-openmpi-ssh-issue-no-bashrc.html (Parallel runs across a network broken with OpenMPI)
https://ss1.xrea.com/penguinitis.g1.xrea.com/study/OpenFOAM/partition/partition.html (領域分割 - PENGUINITIS)