ここではシミュレーターをOACISに登録する方法について解説します。
OACISから既存のシミュレーターを実行できるようにするには、OACISの実行方式にあうようにシミュレーターを設定する必要があります。 例えば、OACISはパラメータを引数またはJSONでシミュレーターに渡しますが、既存のシミュレーターをその形式に合わせるために小さなスクリプトを用意する必要があります。 ここではその設定方法について解説していきます。
まず、OACISに登録されたSimulatorがどのようにジョブが実行されるかについてより詳細に説明します。
OACISにSimulatorを登録する際には、実行プログラムそのものではなくコマンドの文字列を登録します。こうすることにより、OACISは任意の言語で書かれたプログラムを実行できるようにしていますが、実行プログラムは各ホストで事前にビルドしておく必要があります。
OACISはジョブ実行時にコマンドが埋め込まれたシェルスクリプト(以降、ジョブスクリプトと呼ぶことにします)を作成し、そのジョブスクリプトをリモートホストのジョブスケジューラ(例えばTorque)にSSH経由で投入します。
ジョブを投入する際には、各ジョブごとに一時ディレクトリ(以後、ワークディレクトリと呼びます)を作成します。
ワークディレクトリはHost登録時に”work base dir”という項目で指定したパス以下に作成されます。
ジョブスクリプトの中では、そのワークディレクトリにcd
してからコマンドを実行するようになっています。
時系列順により詳細にジョブ実行の流れを見ていきましょう。実行の流れは以下のようになっています。
OACIS-server | computational host | computation node
----------------------------------------------|-------------------------------------|---------------------------------------
---|--> SSH login |
| create a work directory |
| prepare _input.json |
| create a job script |
execute local preprocess | |
| copy output of local preprocess |
| execute preprocess |
| submit job script |
| | (when job script start)
| | execute print-version command
| | save execution logs to a file
| | execution of the simulation program
| | compress the work directory
| |
| (after the job finished) |
---|--> SSH login |
| download the compressed results |
extract the results | |
move the output files to specified directory | |
parse logs and save them in MongoDB | |
まずOACISはリモートホストにログインし、ワークディレクトリを作成します。その後、実行パラメータが書かれた_input.json
を配置します。(Simulator登録時にJSON形式を指定した場合)
ジョブスケジューラにジョブを投入する前に、ワークディレクトリで個別に実行する処理を定義することもできます。プリプロセスと呼んでいます。
詳細は以後説明しますが、例えばジョブの実行に必要なファイルなどをコピーしたりするのに利用します。
プリプロセスには2種類あり、OACISのあるサーバーで実行されるもの、計算ホストのログインノードで実行されるものを定義できます。(それぞれ”local pre-process”, “pre-process”と呼びます。)
_input.json
が作成された後、ジョブ投入前にこれらのプリプロセスが実行されます。
“local pre-process”についてはOACISサーバーで実行された後、出力ファイルが計算ホストのワークディレクトリに転送されます。
その後、ジョブがスケジューラに投入され、ジョブが実行待ちになります。ジョブがスケジューリングされるとジョブスクリプトが実行されることになります。
ジョブスクリプトでは、ジョブの実行を行うだけでなく、実行日時、実行ホスト、実行時間などの実行ログをファイルに書き出して保存する処理も行っています。
date
, hostname
, time
などの各種コマンドを使っています。
これらの情報は_status.json
というファイルに保存され、実行結果をOACISのサーバーに取り込む際に読み込まれます。
後述するようにSimulatorのバージョンを取得するコマンド(“print-version command”と呼びます)を登録することもできます。
その場合、print-version commandはシミュレーションプログラムの実行直前に実行され、その標準出力がシミュレーターのバージョンとして記録されます。
この情報は_version.txt
というファイル名で保存され、ジョブがOACISのDBに取り込まれる時に読み込まれます。
さらに、ジョブスクリプトの最後でワークディレクトリを圧縮して1ファイルにしています。 こうすることにより結果のダウンロードに要する時間を削減しています。
OACISで実行するプログラムは以下の要件を満たす必要があります。
~/path/to/simulator.out 100 3.0 12345
{"param1":100,"param2":3.0,"_seed":12345}
~/path/to/simulator.out
上記のように、OACISから実行プログラムにパラメータを渡す方法は引数またはJSONである必要があります。 これらのパラメータの渡し方に準拠していない既存のシミュレーションプログラムをOACISで実行したい時には、シミュレーターの実行をラップするスクリプト(以後、ラップスクリプトと呼ぶ)をRubyやPythonなどの言語で書くのが簡単です。
OACISにはこのラップスクリプトをSimulatorとして登録し、ラップスクリプトから実際のシミュレーションプログラムを起動します。
ここではそのサンプルを示します。
既存のシミュレーションプログラムが、オプション引数としてパラメータを渡す仕様だとしましょう。 パラメータが4つあり、それぞれオプション引数 “-l”, “-v”, “-t”, “–tmax” で渡すとします。 また乱数の種は “–seed” というオプションで渡せるとします。
例えば、
~/my_proj/my_simulator.out -l 8 -v 0.25 -t 1234 --tmax 2000 --seed 1234
という形で実行できるとします。
OACISで引数形式でパラメータを渡す場合、各パラメータが引数として順番に渡されるだけなので上記の形式には合致しません。 そこで、このプログラムをOACISのシミュレーターとして実行するために以下のようなシェルスクリプトを準備します。
#!/bin/bash
set -e
script_dir=$(cd $(dirname $BASH_SOURCE); pwd)
$script_dir/my_simulator.out -l $1 -v $2 -t $3 --tmax $4 --seed $5
このようなシェルスクリプトをmy_proj
に配置して実行することで、OACISから引数で与えられるパラメータを適切な形式にしてシミュレーションプログラムを実行できるようになります。
この際のポイントは
set -e
をスクリプト内で実行する。こうすることでmy_simulator.out
が異常終了(0以外のリターンコードで終了)した場合に、wrapper.sh
も異常終了するようになります。
wrapper.sh
のリターンコードを見て、Runが正常終了したか異常終了したかを判定します。set -e
が無いと、my_simulator.out
が異常終了しても、wrapper.sh
は正常終了するのでRun自体が正常終了したと判定され、OACIS上でのステータスが”finished”と誤判定されます。my_simulator.out
を実行する際に、パスは絶対パスで指定する必要があります。
別の例として、既存のシミュレーションプログラムがパラメータをXML形式で受け取る場合を考えましょう。
3つのパラメータ(“length”,”velocity”,”time”)と乱数の種をXMLで指定して、そのXMLファイルを実行コマンドの引数として指定するプログラムがあったとします。例えば、
<configuration>
<input>
<length value="8" />
<velocity value="25.0"/>
<time value="2000"/>
<seed value="1234"/>
</input>
</configuration>
というXMLを用意して、
~/my_proj/my_simulator.xml -c configuration.xml
という形で引数としてそのXMLファイルを指定して実行するとします。
このプログラムをOACISのシミュレーターとして実行するために、例えば以下のようなpythonスクリプトwrapper.py
を準備します。
pythonから扱いやすいように、OACISにシミュレーターを登録する際にはInput typeとしてJSONを選択したとしましょう。
またwrapper.py
は実行プログラムと同じディレクトリ(~/my_proj/
)に配置してあるものとします。
import os, sys, json, subprocess
# Load JSON file
fp = open( '_input.json' )
params = json.load( fp )
# Prepare input file
f = open('configuration.xml', 'w')
param_txt = """<configuration>
<input>
<length value="%d" />
<velocity value="%f"/>
<time value="%d"/>
<seed value="%d"/>
</input>
</configuration>
""" % (params['length'], params['velocity'], params['time'], params['_seed'])
f.write(param_txt)
f.flush()
# Execution of the simulator
simulator = os.path.abspath(os.path.dirname(__file__)) + "/my_simulator.out"
cmd = [simulator, '-c', 'configuration.xml']
sys.stderr.write("Running: %s\n" % cmd)
subprocess.check_call(cmd)
sys.stderr.write("Successfully finished\n")
configuration.xml
を出力する。
flush()
を呼んで、シミュレーター実行時に確実に内容が書き込まれているようにする。my_simulator.out
を実行する。
wrapper.py
)と同じディレクトリ上にmy_simulator.out
があるので、その絶対パスを使います。
my_simulator.out
のパスは絶対パスで指定します。wrapper.py
自体のリターンコードが0かどうかで、Runが失敗したかどうかの判定を行っている。外部プロセスでエラーが起きた場合には、スクリプト自体も異常終了させるとよい。subprocess.check_call
メソッドで実行すると、外部プロセスのリターンコードが0でない時に例外を送出する。OACISにSimulator登録する際に登録する項目の一覧を見ていきましょう。設定項目は以下の通りです。
フィールド | 説明 |
---|---|
Name * | シミュレータの名前。Ascii文字、数字、アンダースコアのみ使用可。空白不可。他のSimulatorとの重複は不可。 |
Definition of Parameters * | シミュレータの入力パラメータの定義。パラメータの名前、型(Integer, Float, String, Object)、デフォルト値、パラメータの説明(任意)を入力する。 |
Local Preprocess Script | ジョブの前にローカルホストで実行されるプリプロセスを記述するスクリプト。空の場合はプリプロセスは実行されない。 |
Preprocess Script | ジョブの前に計算ホストで実行されるプリプロセスを記述するスクリプト。空の場合はプリプロセスは実行されない。 |
Command * | シミュレータの実行コマンド。リモートホスト上でのパスを絶対パスかホームディレクトリからの相対パスで指定する。(例. ~/path/to/simulator.out) |
Pirnt version command | シミュレータのversionを標準出力に出力するコマンド。(例. ~/path/to/simulator.out –version ) |
Input type | パラメータを引数形式で渡すか、JSON形式で渡すか指定する。 |
Support mpi | シミュレータがMPIで実行されるか。チェックを入れた場合、Runの作成時にMPI並列数を指定することができる。 |
Support omp | シミュレータがOpenMPで並列化されているか。チェックを入れた場合、Runの作成時にOMP並列数を指定することができる。 |
Sequential seed | Runの作成時に指定されるseedをランダムな順番に与えるか、各ParameterSetごとに1から順番に与えるか指定することができる。 |
Description | シミュレータの説明を入力する。markdownフォーマット で入力できる。 |
Executable_on * | 実行可能Hostを指定する。ここで指定したホストがジョブ投入時に投入先ホストとして指定できる。 |
入力必須な項目は(*)で示されています。
Definition of Parameters の入力の際には、指定した型とデフォルト値の値が整合するように入力してください。 例えば、型がIntegerなのにデフォルト値として文字列を指定するとエラーになります。
New in v3.8.0 パラメータの型としてObject型が使えるようになりました。JSON形式で入力できます。
Local Preprocess Script または Preprocess Script を指定すると、ジョブ投入前に実行されるプリプロセスを指定することができます。 シミュレーターの入力を準備したり、ジョブ投入ノードでしか実行できない処理を指定するとよいでしょう。 詳細はプリプロセスの定義を参照してください。
Command で指定された文字列がシェルスクリプトに埋め込まれて実行されます。 OACISがジョブを実行する際には、各ジョブごとに一時的なディレクトリを作成し、その中でコマンドを実行します。 そのため、コマンドはフルパスで指定する必要があります。 様々なホストで同じように実行できるようにホームディレクトリからの相対パスで指定するとよいです。(例. ~/path/to/simulator.out )
Pirnt version command を指定すると、各Runの実行時にSimulatorのバージョンも記録されます。 ここで指定したコマンドがジョブ実行用シェルスクリプトの中に埋め込まれ実行されます。その標準出力として得られた文字列がバージョンとして記録されます。 バージョンを記録すると、指定のバージョンのシミュレーターで実行されたRunを一括で削除したり、実行し直したりできます。 詳細はシミュレーターのバージョンを記録するを参照してください。
Input type はパラメータの渡し方を指定します。 引数渡しかJSONか2種類から選択できます。
Simulator登録時に、 Suppot MPI, Support OMP のチェックを入れると、Runの作成時にプロセス数とスレッド数を指定するフィールドが表示されるようになります。
OpenMPのジョブのスレッド数を指定すると、ジョブスクリプトの中で OMP_NUM_THREADS の環境変数がセットされます。 つまりOpenMPで並列化しているシミュレータはOMP_NUM_THREADS環境変数を参照してスレッド数を決めるように実装されている必要があります。 ( プログラム内で omp_set_num_threads() 関数で別途指定している場合は、当然ながらここで指定したスレッド数は適用されません)
MPIで並列化して実行する場合、Runの作成時に指定したプロセス数は OACIS_MPI_PROCS の環境変数にセットされます。 Simulatorの実行コマンドとして、OACIS_MPI_PROCS環境変数を参照してmpiプロセスを起動するコマンドを指定する必要があります。 以下はコマンドの例です。
mpiexec -n $OACIS_MPI_PROCS ~/path/to/simulator.out
シミュレータによっては実際にシミュレーションジョブを開始する前に、入力ファイルを準備したりフォーマットを調整したりするプリプロセスが必要な場合がしばしばあります。 しかしプリプロセスを計算ジョブの中で行うのが難しい場合があります。 例えば
そこで、OACISにはジョブの実行前にプリプロセスを個別に実行する仕組みを用意しています。 このプリプロセスはジョブの投入前にローカルホストまたはログインノードで実行されるため上記の問題は起きません。 プリプロセスの中には2種類あり、OACISの動いているサーバーで実行される”local pre-process” (v2.12.0より利用可能), リモートホストでジョブの投入前に実行される”pre-process”の2種類があります。
プリプロセスはジョブの投入前にworkerによって実行されます。
“local pre-process”の実行手順は以下のようになる。
“pre-process”の実行手順は
これらの”local pre-process”, “pre-process” が終わったらジョブを投入します。 ただし、 Simulatorの local_pre_process_script または pre_process_script のフィールドが空の場合には、上記の手順は実行されません。
通常シミュレータが出力したファイル群はそのままファイルとしてサーバー上に保存されますが、結果をデータベース内に保存することもできます。 データベース内に保存されたデータはOACISのUI上からプロットをすることができるので、結果のスカラー値(例えば時系列データの平均値や分散)を保存しておくと便利です。
結果をDB内に保存するためには、保存したいデータをJSONフォーマットでシミュレータから出力すればよいです。 _output.json という名前でカレントディレクトリ直下にJSONファイルを作成すれば、データベースへの格納時にファイルがパースされDB内に保存されます。 (既存のプログラムがJSONを出力するようになっていない場合は、ラップスクリプトの中でJSON形式の出力に変換するのがよいでしょう。)
例えば、以下のような結果を保存しておくことができます。
{
"average": 0.25,
"variance": 0.02,
"hash_value": {"a": 0.7, "b": 0.4}
}
(注)ただしMongoDBの制限により、”.”を含むキーは使えません。ジョブがfailedになります。
格納された結果は各Runのページから確認することもできます。
プロットはParameterSetのページからPlotタブをクリックすると、プロットの表示画面に移動します。
プロットの種類と、横軸、縦軸や系列などを指定してください。 必要なParameterSetを集めて平均や標準誤差を計算してプロットします。
右下のマップをドラッグすることで一部分を拡大したり、ログスケールに表示を切り替えることもできます。 データ点をクリックすると対象となるParameterSetのページを表示することもできます。 画面右に表示されているURLを開くと、今表示しているプロットを再度開くことができます。
シミュレーションの実行時にどのバージョンのシミュレーターで実行したかOACISに記録をさせておくことができます。
例えば、シミュレーションを実行していくうちにシミュレーションコードにバグが見つかり、一部のシミュレーションを再実行したい場合などがあります。 RunとSimulatorのバージョンをひもづけて記録する事により、あるバージョンの実行結果を一括削除したり再実行したりすることができるようになります。 シミュレーターのソースコードを変更する可能性がある時は、バージョンを記録しておくと効率的にやり直しができるようになります。
バージョンを保存するには、Simulatorのバージョンを出力させるコマンドをOACISに登録します。 例えば
~/path/to/simulator.out --version
というコマンドでバージョン情報を出力されるシミュレーターがあるとします。 このコマンドをSimulator登録時に “Print version command” というフィールドに登録しておくと、ジョブ実行時にこのコマンドを実行し、その標準出力をバージョン情報として記録することができます。
Print version command の標準出力に出力された文字列がバージョンとして認識されるので、実行バイナリに引数を渡すだけでなく柔軟な指定が可能です。 例えば、ビルドログの一部をバージョン情報として記録したり、バージョン管理システムのコミットIDを出力するような利用方法も考えられます。
head -n 1 ~/path/to/build_log.txt
cd ~/path/to; git describe --always
Runの一括削除や一括置換はCommand Line Interface(CLI)から実行できます。 詳細はCLIのページを参照してください。