Condaパッケージの管理
カスタムデータやPythonを使った後処理解析のためにCondaパッケージ管理の機能を活用する方法はいくつもあります。このチュートリアルでは、Rescale上の様々なアプリケーションのためにConda環境をセットアップする方法を説明します。
お困りの方はRescaleサポートにご連絡ください。
Miniconda vs. Anaconda
Rescaleで利用できるConda環境には、プリインストールされたパッケージが多数含まれるもの(Anaconda)と、空の環境(Miniconda)の2つの「タイプ」が存在します。ユーザーのニーズに応じて、どちらを使うか異なる利点があります。Miniconda環境はクラスタにロードするスナップショットが非常に小さいため、スタートアップ時間が短くなります。このため、分析に必要なPythonパッケージが1つか2つだけであれば、理想的な選択肢となります。例えば、CFDシミュレーションに加えて、カスタムのPythonポストプロセッシングスクリプトを使用する場合は、Miniconda環境で十分です。解析に Anaconda 環境のプリインストールされたパッケージの多くが必要なことがわかっている場合は、クラスタが起動してからすべてのパッケージをインストールする必要があるため、追加のクラスタ起動時間はそれに値する可能性があります。どの環境を選択しても Conda コマンドは同じ動作をするので、ここにリストされたコマンドは Miniconda と Anaconda の両方で動作するはずです。
Rescale UIのコマンドウィンドウに、次の行を追加します。
conda install -y <package-name>
yフラグはインストールを完了させるために必要なので、必ず入れてください。また、コマンドでパッケージのバージョンを指定することもできます。
conda install -y <package-name>=<version-num>
Conda install instructions from Anaconda distributor
この方法でインストールされたすべてのパッケージは、そのクラスタ上で実行されるPythonスクリプトで呼び出すことができるようになりました。例えば
conda install -y numpy=1.15.4python myscript_with_numpy_calls.py
もし、追加のPythonパッケージを使用する後処理用のPythonスクリプトを実行したい場合、追加のソフトウェアとしてMinicondaを追加することができ、Condaは必要なパッケージをすべてインストールします。例えば、SU2の実行にmatplotlibを使った後処理の可視化を追加したい場合、「ソフトウェア設定」ページは以下のようになります。

SU2 タイルについては上記です。そして、

Miniconda コマンドに対しては上記です。
一般に、Pythonスクリプトの並列化はマルチノード構成には自動的に拡張されず、通常は同じノード上で複数のシリアルプロセスを起動することに限定されます。Rescaleで大規模なクラスタを動作させる機能を真に活用するためには、何らかの並列バックエンドを作成し、管理する必要があります。繰り返しになりますが、Condaはこのセットアップと必要なパッケージのインストールを支援することができます。Condaパッケージ管理システムを使用する主な利点の1つは、Pythonの外でパッケージをインストールし、設定することができることです。これは mpi4py のようなパッケージに必要な MPI を含む低レベルのライブラリとの互換性を必要とするパッケージをインストールする際に非常に有利になります。1つはipclusterを使用して並列化し、機械学習モデルの学習を高速化するもので、もう1つはmpi4pyを使用してより生のMPI呼び出しを行うものです。
Pythonの並列環境のセットアップを始める前に、Rescaleで複数ノードのクラスタをセットアップする方法について、いくつかの詳細を説明する必要があります。各ノードに必要なパッケージを個別にインストールするのではなく、NFSでマウントされた~/work/sharedディレクトリにConda envをcloneすることが推奨されます。これを行うには、以下のコマンドをジョブに追加します。
conda create -p ~/work/shared/uenv --clone uenvconda activate ~/work/shared/uenv... ... Run workflow ......rm -rf ~/work/shared/uenv
注:これは、クラスタに複数のノードを使用する場合にのみ必要です。
その後、conda install -p ~/work/shared/uenv -y を実行すると、そのパッケージはすべての計算ノードで利用可能になります。例えば
conda create -p ~/work/shared/uenv --clone uenvconda activate ~/work/shared/uenvconda install -p ~/work/shared/uenv -y <package-name> python myscript_with_package-name_calls.py rm -rf ~/work/shared/uenv
マルチノードクラスタにCondaがパッケージをインストールするとき、正しい環境にインストールされるように、プレフィックスフラグ -p ~/work/shared/uenv を追加する必要があることに注意してください。また、仮想環境を共有ディレクトリにクローンした場合、クラスタに対話的にssh’ingするときにそれを有効にする必要があります。ジョブが完了したら共有ディレクトリからConda環境を削除することが重要です、rm -rf ~/work/shared/uenv, そうでなければ多くの不要なファイルのアップロードが発生します。繰り返しになりますが、単一ノードで実行している場合は、プレフィックスフラグ -p ~/work/shared/uenv は必要ありません。
以下の説明では、ユーザがマルチノードクラスターで動作していることを想定しているので、Conda環境は~/work/shared/uenvにあります。mpi4py と ipython からインストールすることをお勧めします。
conda install -p ~/work/shared/uenv -y mpi4pyconda install -p ~/work/shared/uenv -y ipythonconda install -p ~/work/shared/uenv -y ipyparallelconda install -p ~/work/shared/uenv -y numpyconda install -p ~/work/shared/uenv -y joblib
まず、NFSドライブに新しいデフォルトのipythonディレクトリを作成し、並列設定ファイルをすべてのノードに貼り付けることができるようにします。
export IPYTHONDIR=~/work/shared/.ipython
ここでは、環境が ~/work/shared/uenv にクローンされていると仮定して、そこにある ipython の実行ファイルを呼び出します。
~/work/shared/uenv/bin/ipython profile create --parallel
コントローラを起動するには、ヘッドノードで次のコマンドを使用します。nフラグをクラスタで利用できるコアの総数と同じにして起動したくなりますが、ヘッドノード上のコアの総数に制限されるべきです。残りのコアは個々の「エンジン」として追加していきます。
ipcluster start -n $RESCALE_CORES_PER_NODE --engines=MPI --ip='*'
コントローラが立ち上がると、ワーカーノードでipengineを起動する必要があります。これが自動化されている場合、エンジンが起動する前に、スリープコマンドやコントローラの起動成功時のコールバックが必要になります。エンジンは、ipcontroller-engine.json ファイルで渡された情報を使ってコントローラに接続されます。IPYTHONDIR変数をエクスポートした主な理由の1つは、このファイルがデフォルトで全てのノードで利用できるようにするためでした。
実際には、各ワーカノードで各コアごとに個別のエンジンを起動する必要があります。前述の profile create コマンドでプロファイルを作成した場合、ipcontroller-engine.json ファイルは ~/work/shared/.ipython/profile_default/security/ に配置されるでしょう。個々のエンジンを起動するには、コマンドを使用することができます。
ipengine --mpi --file=~/work/shared/.ipython/profile_default/security/ipcontroller-engine.json
この部分は、すべてのワーカーノードでエンジンを自動起動するようにスクリプト化する必要があります。そのためのスクリプトを以下に示します。このスクリプトは、クラスタ上のマシンファイルを受け取り、各ワーカノードの各コアでエンジンを起動します。
#!/bin/bash headnode=$(head -n 1 $HOME/machinefile)for host in $(cat $HOME/machinefile); do if ! [ "$host" == "$headnode" ]; then for i in $(seq 1 $RESCALE_CORES_PER_NODE); do ssh "$host" "conda activate ~/work/shared/uenv; nohup ipengine --mpi --file=~/work/shared/.ipython/profile_default/security/ipcontroller-engine.json" >> "engine_output.log" & sleep 1 done fi done
It is important to activate the “shared” virtual environment even when passing the command over SSH. The addition of the nohup
command lets you background the process remotely.
Once the ipcluster
is up and running, connecting to it should be relatively simple. Here is a snippet that should demonstrate how to connect and run some basic operations.
from ipyparallel import Clientimport numpy as np # Connect to the default profile rc = Client() # Create a direct view dv = rc[:] # Add 100 sets of 3 numbers in parallel using all engines dv.map_sync(lambda x, y, z: x + y + z, range(100), range(100), range(100)) # Load balanced view bview = rc.load_balanced_view() # Can be used for jobs that take different amounts of time to complete bview.map_sync(lambda x: sum(x), np.random.random((10, 100000)))
裏側では、多くの機械学習アルゴリズムが非常に並列化されており、GPUはこれらの並列ワークロードを非常にうまく処理できるため、しばしば使用されます。また、GPUは、何百ものコアを持つHPCクラスタよりも、1つのGPUを利用する方が論理的に簡単であることも魅力的です。しかし、Rescaleを使えば、リソースの制約がなくなり、CPU並列化も可能になります。これは、CPU並列化が一般的に良い選択肢であると言うことではなく、GPUは確かに良い選択であることが多いですが、何が可能であるかを知っておくことは有用です。scikit-learnのツールセットには、CPU並列化のサポートが組み込まれた関数が多数あります。これにより、モデルのスケールアウトが非常に簡単になります…ただし、並列環境を正しく設定することが条件です。幸いなことに、上記のステップを踏んでいれば、数行のコードをプラグインするだけで、好きなだけ大規模なRescaleクラスタでモデルの実行を開始することができるはずです。これは次のようなものです。
... rc = Client() bview = rc.load_balanced_view() register_parallel_backend('ipyparallel', lambda: IPythonParallelBackend(view=bview)) with parallel_backend('ipyparallel'): search.fit(digits.data, digits.target) ...
サポートベクトル分類(SVC)アルゴリズムを古典的なMNISTの例に適用し、計算集約的な部分の大部分をipclusterを用いて実行した例を以下に示す。
from sklearn.externals.joblib import Parallel, parallel_backend, register_parallel_backend from sklearn.model_selection import RandomizedSearchCV from sklearn.datasets import load_digits from sklearn.svm import SVC import numpy as np from ipyparallel import Client from ipyparallel.joblib import IPythonParallelBackend digits = load_digits() rc = Client() bview = rc.load_balanced_view() register_parallel_backend('ipyparallel', lambda: IPythonParallelBackend(view=bview)) param_space = { 'C': np.logspace(-6, 6, 300), 'gamma': np.logspace(-8, 8, 300), 'tol': np.logspace(-4, -1, 300), 'class_weight': [None, 'balanced'], } model = SVC(kernel='rbf') search = RandomizedSearchCV(model, param_space, cv=10, n_iter=1000, verbose=1, n_jobs=-1) with parallel_backend('ipyparallel'): search.fit(digits.data, digits.target) rc.shutdown()
この問題では、フィット時間は、複数ノードにまたがる場合でも、ipclusterに使用されるCPUの数に対して驚くほどよくスケールします。
Number of Cores | Time to Fit |
---|---|
4 cores | 27.4 min |
9 cores | 11.7 min |
18 cores | 6.0 min |
36 cores* | 3.1 min |
*run on multiple nodes
ipcluster のセットアッププロセスをスクリプト化し、python スクリプトを別途呼び出すことをお勧めします。下記のリンク先のジョブ例では、プロセス全体がスクリプト化されている例を取り上げています。
mpi4py を使って Python スクリプトに初歩的な MPI 呼び出しを追加する
Rescaleで大規模なクラスタを起動する機能を利用するもう一つの方法は、mpi4pyの関数を使ってPythonのコードを構築することです。これは、最初からこれらの呼び出しでコードを構築する必要があります。ipclusterよりもプラグアンドプレイタイプのソリューションではありませんが、特定のシナリオでは良いオプションになります。繰り返しになりますが、複数のノードを持つクラスタに使用する場合は、ここにあるConda環境のクローンに関する説明に従う必要があります。
from mpi4py import MPI import numpy as np comm = MPI.COMM_WORLD rank = comm.Get_rank() # Point-to-Point Communication if rank == 0: data = np.arange(1000, dtype='i') comm.Send([data, MPI.INT], dest=1, tag=77) elif rank == 1: data = np.empty(1000, dtype='i') comm.Recv([data, MPI.INT], source=0, tag=77) # MPI Broadcast if rank == 0: data = np.arange(100, dtype='i') else: data = np.empty(100, dtype='i') comm.Bcast(data, root=0) for i in range(100): assert data[i] == i # Collective I/O with NumPy arrays amode = MPI.MODE_WRONLY|MPI.MODE_CREATE fh = MPI.File.Open(comm, "./datafile.contig", amode) buffer = np.empty(10, dtype=np.int) buffer[:] = comm.Get_rank() offset = comm.Get_rank()*buffer.nbytes fh.Write_at_all(offset, buffer) fh.Close()
ここでは基本的な例のみを紹介しますが、より詳しい情報は以下のサイトを参照してください: mpi4py docs and basic tutorials
スクリプトを実行するには、このように mpirun または mpiexec を呼び出す必要があります。
mpiexec -n 4 python script_with_mpi4py.py