本当は怖い情報科学

とあるHPC屋の趣味&実益ブログ

ChainerMNをMPIで実行中に、例外でプロセスが死んでも実行が止まらない問題

通常、MPIプログラムは、実行中のどれかのrankがエラー等で以上終了した場合(あるいはMPI_Finalizeを呼び出さずに終了した場合)は全プロセスが強制終了されることが期待されます。

が、ChainerMNを含む mpi4py を用いたプログラムを実行している場合、Pythonの例外によってプロセスの1つが異常終了しても、その他のプロセスが終了せずにハングしてしまうという問題があります。

# test.py
def func():
    import mpi4py.MPI
    mpi_comm = mpi4py.MPI.COMM_WORLD
    if mpi_comm.rank == 0:
        raise ValueError('failure!')

    mpi4py.MPI.COMM_WORLD.Barrier()

if __name__ == '__main__':
    func()

実行結果:

$ mpiexec -n 2 python test.py
Traceback (most recent call last):
  File "main.py", line 27, in <module>
    func()
  File "main.py", line 21, in func
    raise ValueError('failure!')
ValueError: failure!  # <----- このまま固まっていて、プロセスが終了しない

この時、オンプレミスのクラスタ環境等であれば、logを監視したりなどして手動で kill すればよいのですが、クラウド環境やジョブスケジューラ下ではなかなかそうもいきません。特にクラウド環境では、計算が進んでいないのに課金だけが進んでしまうという状態になるので非常にまずいですね。

この場合、Pythonの例外処理機構をフックして、処理されていない例外が発生した場合に MPI_Abort() を呼び出すことによってMPIプロセスを強制終了することができます。

import sys

# Global error handler
def global_except_hook(exctype, value, traceback):
    import sys
    from traceback import print_exception
    print_exception(exctype, value, traceback)
    sys.stderr.flush()

    import mpi4py.MPI
    mpi4py.MPI.COMM_WORLD.Abort(1)

sys.excepthook = global_except_hook


def func():
    import mpi4py.MPI
    mpi_comm = mpi4py.MPI.COMM_WORLD
    if mpi_comm.rank == 0:
        raise ValueError('failure!')

    mpi4py.MPI.COMM_WORLD.Barrier()


if __name__ == '__main__':
    func()

実行結果:

$ mpiexec -n 2 python main.py
Traceback (most recent call last):
  File "main.py", line 26, in <module>
    func()
  File "main.py", line 20, in func
    raise ValueError('failure!')
ValueError: failure!
--------------------------------------------------------------------------
MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD
with errorcode 1.

NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes.
You may or may not see output from other processes, depending on
exactly when Open MPI kills them.
--------------------------------------------------------------------------

Time to remove the openib btl ?

Open MPI devel メーリングリストで、「openibコンポーネントを削除する時が来たのでは?」という議論が始まっています。

Open MPIでInfinibandを使う場合、ながらく openibib という BTL コンポーネントが使われてきました。 BTL というのは、Byte Transfer Layer というOpen MPIのコンポーネント階層です。例えば、環境テスト等の目的でInfinibandの使用を強制したい場合、次のように mpiexec を起動すれば可能です(IBが使えなければ、TCPにフォールバックすることなくエラーとなる)。

$ mpiexec --mca btl openib ${PROGRAM}

mcaは、Module Component Architectureの略で、Open MPIのモジュールシステムの名前)

しかし、この ib コンポーネントは古くてバグが多いのが問題点でした。InfinibandのAPIであるibverbsは、仕様が曖昧で使いづらいところも多く、次世代のAPIへの以降が望まれている状態です。

現在、高性能インターコネクトの標準APIとして、(このブログでつらつらと書いているように)UCXが期待されています。Infinibandハードウェアの主要ベンダーであるMellanoxも、ibverbsではなくUCXをサポートするというのが方針のようです。

すぐに移行するには、まだiWARPなどのハードのサポートが十分ではないという意見もあるようですが、これは完全に時間の問題だと思われます。

UCXを試す(7)

勉強がてら作成中の UCXのC++ラッパーをGithubに上げました。たぶんライブラリ関数の網羅率は5%くらいだしUCXの使い方をほとんどわかっていないので、あえてREADMEも何も書いていません。少しずつ機能を試して実装しながらライブラリとしての形を整えていきたいと思います。

GitHub - keisukefukuda/ucxcpp

ところで、下記のコマンドを使って上記のリポジトリに含まれているプログラム hello_world を実行すると、以下のようなエラー(警告?)が表示されます。原因と対策を調査中。ソース読むしかないかなー。

$ cd ucxcpp
$ cmake ..                                                                                                                                                                                                                                            [~/ucxcpp/build]
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Found MPI_C: /home/kfukuda/mpi/openmpi-2.1.2/lib/libmpi.so
-- Found MPI_CXX: /home/kfukuda/mpi/openmpi-2.1.2/lib/libmpi.so
-- Found Git: /usr/bin/git (found version "1.9.1")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/xxxxxxx/ucxcpp/build
$ make

...

[100%] Built target hello_world
$ mpiexec -n 2 -mca btl tcp,self -host host1,host2 ./hello_world
    rc / mlx4_0:1
[1520851183.876675] [sakura171:12401:0]            sys.c:555  UCX  ERROR A new segment was to be created and size < SHMMIN or size > SHMMAX, or no new segment was to be created. A segment with given key existed, but size is greater than the size of that segment. Please check shared memory limits by 'ipcs -l'.
=================================
List of resources:
    self /  self / self
    tcp /  tcp / ib0
    tcp /  tcp / eth0
    tcp /  tcp / eth1
    ib /  rc / mlx4_0:1
    ib /  ud / mlx4_0:1
    cuda_cp /  cuda_copy / cudacopy0
    sysv /  mm / sysv
    posix /  mm / posix
    cma /  cma / cma
=================================

    rc / mlx4_0:1
[1520851183.886542] [sakura173:16109:0]            sys.c:555  UCX  ERROR A new segment was to be created and size < SHMMIN or size > SHMMAX, or no new segment was to be created. A segment with given key existed, but size is greater than the size of that segment. Please check shared memory limits by 'ipcs -l'.
sent string = IGUVOYG
Rank 0 Done.
Received: IGUVOYG
Rank 1 Done.

MPIを使っているのは、デバイスのアドレス交換(ランデブー)のためです。UCXに含まれているオリジナルのhello_world だと、ソケットを使っているのですが、それだとクライアントとサーバーの両方のプロセスを立ち上げるのが面倒なため、起動メカニズムとしてOpen MPIを使っています。MPIは通信には -mca btl tcp,self を指定しているので、TCPのみが使われます。

共有メモリ(memory mapped file)関係のエラーぽいですが、よくわからん。(エラーと言われているが動くし)

【広告】