ipyparallelで複数ノードで並列計算機を作る

ipyparallelの概要

pythonコードを複数のサーバーで並列化して走らせることが出来るipython拡張機能。

詳細なアーキテクチャは少し複雑だが、全体を統括するコントローラーが走るノードが1台、engineが走る計算ノード的なノードが1台以上あると考えたらいい。

コントローラーノードがプログラムを各engineに分散させて実行する。各engineへの接続はSSHで行われる。なお、この記事では並列化手法にSSHを使用するが他にもMPIなども使用可能。

設定方法の概要

すべてDebian系OSを想定

コントローラーノードをhost0, それ以外の計算ソード(1台以上)をhostXとする。

  1. host0, hostXともにaptでipython3、python3-ipyparallelをインストール
  2. host0からhostXにパスワードなしSSHが出来るように公開鍵認証設定
  3. 新しくipythonのプロファイルを作成(host0のみ)
  4. 上記を書き換え、hostXのIPなどを記入する(host0のみ)
  5. ipclusterを実行しクラスタを起動(host0のみ)
  6. プログラムを実行(host0上で)

詳細な設定方法

1. ipython3、python3-ipyparallelのインストール

特に説明不要かと思う。Ubuntuであればどちらもaptでインストールできる。後者をpipでインストールする場合のパッケージ名はipyparallel。

2. 公開鍵認証設定

これも特に説明不要かと思う。パスワードなしログインできるか動作確認は行っておくこと。

(一応必要?)ホスト名で解決できるよう設定

hostXからhost0をホスト名で解決できるようにしておく。

/etc/hostsに(host0のIP) (host0のホスト名)を追記。

hostXに渡されるipcontroller-engine.jsonを読むと、なぜか接続先情報がホスト名しか記述されていない事があるため、必要かもしれない。

3. 新しくipythonのプロファイル作成

profile名は自由に決めてよい。以下では公式サンプル通りmyprofileという名前で進める。

ipython profile create --parallel --profile=myprofile

を実行して、ipythonの新しいプロファイルを作成する。プロファイルは~/.ipython以下に作成される。

4. コンフィグの書き換え

ipcluster-config.pyの該当の行のコメントアウトを外して、以下の値を設定する。

c.IPClusterEngines.engine_launcher_class = 'SSHEngineSetLauncher'
c.SSHEngineSetLauncher.engines = { 'host1.example.com' : 2, 'host2.example.com' : 5}

2つ目については、hostXのIP or ドメイン名と走らせるエンジン数を辞書型で指定する(そのマシンのコア数を指定すればよいと思う)。

なお、ここにlocalhostも加えることでコントローラーノード上でengineも走らせることができる(コントローラーノードでパスワード無しでssh localhsot出来るようにしておく必要あり)。

5. ipclusterを起動

プロファイル名、controllerへの接続を許可するIPを指定してクラスタを起動する。

ipcluster start --profile=myprofile --ip='*'

6. プログラムの実行

以下でクラスタでプログラムを実行できる。

ipython3 --profile=myprofile スクリプト名

以下はサンプルプログラム。ホスト名を表示する関数を50回実行する。関数の実行はクラスタ上のノードでで並列化して行われるので、クラスタ上のホスト名がいろいろ表示されれば正常(もちろん複数ホストでengineを動かす場合のみ)。

もしhost0以外のhostXを使用する設定にも関わらずhost0のホスト名しか表示されない場合、正常に動いていない。

from ipyparallel import Client

def smp(smparg):
    # 使用するライブラリのimport は関数内で必要
    import os
    # ホスト名を返す
    return os.uname()[1]

rc=Client()
dview = rc[:]

# smp関数を引数を変えて50回実効する。
result_list = dview.map_sync(smp, range(50))

# 実行結果の配列を表示
print(result_list)

関連するファイル

.ipython/profile_xxxxxの中身の各ファイルの役割。以下のもの以外はわからない。

security/以下のファイルは、ipcluster実行時に作成される。

  • ipcluster-config.py・・・ipcluster用の設定
  • ipengine-config.py・・・ipengine用の設定
  • ipcontroller-config.py・・・ipcontroller用の設定
  • security/ipcontroller-engine.json・・・ipengineを動かすノードがコントローラーノードへの接続するのに必要な情報
  • security/ipcontroller-client.json・・・実際にプログラムを動かすクライアントからコントローラーに接続するのに必要な情報(もしクライアントをコントローラーノードとは別マシンで動かさない限り触る必要はない)

上記のjsonファイルについては、host0ではipcluster実行時に作成され、終了時に削除される。hostXについては、ipcluster実行時にhost0から転送され、終了時には消えない。

詰まったところなど

host0を変えたりした場合、hostXの.ipython/profile_myprofile/security/以下に以前の接続情報が残ってしまうことがあるので、問題が起きたら中のjsonファイルを削除すると良いかも。

また、ノード間でPythonのバージョンが違うと実行時エラーが起きやすい。

パッケージはipython(ipython2)とipython3があるので、間違えないよう注意。

その他

ipclusterの引数について

以下のリンク先の公式ドキュメントではipcluster起動時に–ip=’*’のオプションを与えるのではなくipcontroller-config.pyに

c.IPController.ip = '*'

を追記する方法も記載されているが、検証時うまく動かなかったので、この記事では上記の通り起動時引数でオプションを与える方法で進めていく。

Starting the IPython controller and engines — ipyparallel 8.9.0.dev documentation

ただし、この設定はhost0もhostXも社内や自宅内など信頼できるネットワーク内に存在する場合のみ行うこと。

ノードで実行するエンジン数について

各ノードでは詳細手順4のc.SSHEngineSetLauncher.enginesで設定した数だけ関数が同時実行される。そのため、もしマルチコア対応していて全コアを使用するような関数を動かす場合、engine数は1を指定するとよい。

CPUが8コア、関数は4コアを使用する場合は各ノードについてengine数2を設定するとちょうど使い切れる。

余談だが、浮動小数点演算をゴリゴリ行う場合ハイパースレッディングは効果がないことが多いので、CPUが8コア16スレッドでも8コア8スレッドと考えたほうがよい。

コメント

タイトルとURLをコピーしました