アカデミアの大規模超並列クラスタ型スーパーコンピュータ上で、Rを並列実行する際の備忘録です。あまりスパコンに詳しくないので、用語を間違っていたら教えてください。僕が使っているスパコンは富士通のサーバでXeon Scalableプロセッサを積んでて、Intelのコンパイラがインストールされてて、pjsub
コマンドでjobをsubmitします。
並列実行するために、試した方法は次の3種類です。順に説明します。
- バルクジョブを使う方法
- Rのパッケージを利用してMPIで実行する方法
- インテルのMPIライブラリのコマンドラインを使う方法
バルクジョブを使う方法
まず実行したいRファイル(calc.R
)は以下のようなものとします。
.libPaths(c('/path/to/my_R_lib', .libPaths())) library(rstan) args <- commandArgs(trailingOnly=TRUE) paraID <- as.integer(args[1]) f_in <- sprintf('input/param.%d.csv', paraID) f_out <- sprintf('output/result.%d.RData', paraID) # ... resultの計算 ... save(result, file=f_out)
- 1行目: Rのパッケージをユーザ権限で
/home/
以下などにインストールした場合には、このようにそのRパッケージへのパスを.libPaths()
関数で加えておく必要があります。 - 4行目: コマンドラインの引数を
commandArgs
関数で受け取ります。 - 5~7, 9行目: 引数の使用例です。数値の文字列(
10
とか)を渡して、その数値に対応するファイルを読み込み、計算結果を格納しています。
次にRコードをキックするためのバッチファイルを作ります。
#!/bin/sh #PJM -g my_proj_name #PJM -L rscgrp=my_resource #PJM -L node=1 #PJM -L elapse=01:00:00 #PJM -j #PJM -X module load R/3.6.0 Rscript --vanilla /my_dir/calc.R $PJM_BULKNUM
- 3~8, 10行目: この部分はsubmitするときに指定するオプションとRのPATHの読み込みで、必要かどうかはスパコンの環境に依存します。説明しません。
- 11行目: ここで
/my_dir/calc.R
を実行します。$PJM_BULKNUM
はpjsub
で実行するときに渡されます。この値が先ほどのRコードのargs[1]
になります。
最後にjobをsubmitするときのコマンドです。バルクジョブで実行します。
pjsub --bulk --sparam 1-50 run.sh
実行すると1
~50
の数値が一つずつ$PJM_BULKNUM
に渡されて、サブジョブとしてRが実行されます。
しかしこの方法では、サブジョブ1つ1つが1ノードに割り振られるという扱いになるため、スパコンの使用チケットがノード×(サブ)ジョブ×計算時間
の合計で定められている場合には、非常に速いスピードでチケットを消費してしまいます。この状況を打破するためには、MPIを使って1ジョブでマルチプロセスを利用できると良いです。
Rのパッケージを利用してMPIで実行する方法
手っ取り早いのはMPIを利用するRパッケージを利用することです。{foreach}
, {doParallel}
, {snow}
, {Rmpi}
パッケージをインストールする必要があります。その後、以下のようなRコードを準備します。
.libPaths(c('/path/to/my_R_lib', .libPaths())) library(foreach) library(doParallel) library(snow) library(Rmpi) args <- commandArgs(trailingOnly=TRUE) paraID_vec <- seq(from=as.integer(args[1]), to=as.integer(args[2])) cl <- makeCluster(50, type='MPI') registerDoParallel(cl) memo <- foreach(paraID=paraID_vec, .packages='rstan', .export=ls(envir = globalenv())) %dopar% { f_in <- sprintf('input/param.%d.csv', paraID) f_out <- sprintf('output_mpi/result.%d.RData', paraID) # ... resultの計算 ... save(result, file=f_out) } stopImplicitCluster()
- 7~8行目:ここでは並列実行したい数値のはじまりと終わりを
args
変数に渡して、vector
を作っています。 - 10~11行目:ここでMPIで実行するための準備をしています。50プロセスを使う指定をしています。
{Rmpi}
パッケージがないとmakeCluster
関数でtype='MPI'
引数が使えません。 - 13行目:あとはいつもの
foreach
関数と%dopar%
演算子で並列実行します。
次にRコードをキックするためのバッチファイルを作ります。
#!/bin/sh #PJM -g my_proj_name #PJM -L rscgrp=my_resource #PJM -L node=1 #PJM -L elapse=01:00:00 #PJM --mpi proc=50 #PJM -j #PJM -X module load R/3.6.0 Rscript --vanilla /my_dir/calc-with-Rmpi.R 1 50
- 7行目: ここで
mpi
で50
プロセス使う指定をしています。Rコード内のプロセス数と合わせる必要があります。 - 12行目: 上のRコードを実行します。
しかし、この方法はRパッケージ、特に{Rmpi}
のインストールが環境によっては難しいという欠点があります。僕も失敗しました。install.packages
関数のconfigure.args
オプションを使って、用意されているMPIのincludeディレクトリやlibpathを指定したのですが、エラーを完全に取り除くことができませんでした。だから上のコードも未テストでうまくいくか分かりません。ごめんなさい。
MPIライブラリのコマンドラインを使う方法
triadsouさんに教えてもらいました(URL)。ありがとうございました!
僕のスパコンの環境ではmpirun
に相当するコマンドラインがIntelのmpiexec.hydra
でした。そのため、以下のようなバッチファイルになります。
#!/bin/sh #PJM -g my_proj_name #PJM -L rscgrp=my_resource #PJM -L node=1 #PJM -L elapse=01:00:00 #PJM --mpi proc=50 #PJM -j #PJM -X module load R/3.6.0 mpiexec.hydra \ -n 1 /path/to/bin/Rscript --vanilla /my_dir/calc.R 1 : \ -n 1 /path/to/bin/Rscript --vanilla /my_dir/calc.R 2 : \ -n 1 /path/to/bin/Rscript --vanilla /my_dir/calc.R 3 : \ # ... 50まで ... コマンド書くのが面倒なので別のプログラムで自動生成するとよい -n 1 /path/to/bin/Rscript --vanilla /my_dir/calc.R 49 : \ -n 1 /path/to/bin/Rscript --vanilla /my_dir/calc.R 50 :
- 12行目:
mpiexec.hydra
コマンドを使います。 - 13~18行目:
-n 1
は1プロセス分使うという意味です。これを50プロセス分だけコロンでつなげて書きます。なお僕の環境ではRscript
を絶対パスにしないと動きませんでした。
この方法で、無事MPIで50プロセスを使って並列実行することができました。
Enjoy!