クーの自由研究

マスターのかえるのクーは、弟子達の召喚術により新たな依り代を得てⅡ世として復活しました。

pythonでMPI (mpi4py) を使って並列化プロセス間通信をしてみる

めざせ!「富岳」(もしくは「MN-3」)

井戸ごもりして、MPIについて勉強してきた、かえるのクーの助手の「井戸中 聖」(いとなか あきら)です。

f:id:AssistantOfKoo:20200711190422p:plain f:id:AssistantOfKoo:20200712002429p:plain f:id:AssistantOfKoo:20200711184034j:plain f:id:AssistantOfKoo:20200711184025j:plain

(イメージはあいかわらず本編と全く関係ありません)

MPIはとてもハードルが高いとおもっていましたが、なんとか理解できるレベルではないかと思ってきました。(関係ないですが、IntelのDPC++*1はまったく理解できず、完全敗北でした。もともとc++アレルギーなので仕方ありません。アーキテクチャは素敵なのに。。。)

動作する環境調整に結構苦労しました。

今回の本題(通信やってみた)とは違うので、MPIが動作する環境構築(ただしWindows)は別の記事ページにします。(従来ならこれも含めて1週間~1か月で1つの記事にしてたところです)

MPI (めっせぇじぱっしんぐいんたぁふぇぃす)とは

マルチプロセスで起動したプロセス間を通信する標準化されたしくみです。スーパーコンピュータをはじめとしたHPC(はいぱふぉぉまんすこんぴゅうたぁ)でよく使われます。プログラミング言語やOSとは独立した通信プロトコルを用い、各種の通信方法(1対多とか、共有の全部/一部だけ同期とか)をサポートします。

それではいつものように先輩先生、よろしくお願いします

 f:id:AssistantOfKoo:20200711224531j:plain f:id:AssistantOfKoo:20200711230221p:plain

理化学研究所の方の講義

「MPIのプログラムする方」向けかな?(高度情報科学技術研究機構)

概要ならこちらがカラフルでわかりやすいかも(これも高度情報科学技術研究機構)

一週間でなれる!スパコンプログラマ(結構高度:これがすらっと1週間で読めるレベルになるまで、3年かかる気がする)

IntelはいってなくてもOK(Intel MPI for Windowsマニュアル)*2

なるほど、ほんのすこしだけわかったような気がしてきました。

それではpythonのサンプルコードを動かしてみましょう。

なお、OSはWindows10Pythonはintelチューニング版3.7.2MPIはintel MPI for Windowsでやってみてます。(このニッチな組み合わせこそが、このページの存在意義です) Intelチューニング版python*ディストリビューション, intel MPI Library for Windowsフリー版があります。(ただしダウンロード時に氏名etcの登録が必要です)

もちろん、フツーのpythonとMS MPIでいいのですが、わたくしがやってみたかっただけです。

PythonMPIのサンプルコード (MPI_TEST01.py)

#MPIするときは必ずmpi4py importします。標準で起動するPythonのライブラリとして登録しておく必要があります。
from mpi4py import MPI

'''#プログラムが起動したときには、その時点ですでにMPIインスタンスはそのプロセスについての基本情報を保持しています。'''
comm = MPI.COMM_WORLD #COMM_WORLDは全体
size = comm.Get_size() #サイズ(指定されたプロセス(全体)数)
rank = comm.Get_rank() #ランク(何番目のプロセスか。プロセスID
name = MPI.Get_processor_name() #プロセスが動いているノードのホスト名

my_self = MPI.COMM_SELF #SELFは自分のプロセスの情報を保持しています。
'''#試しに取得してみました。本当は自ノード内のランク・サイズがほしいのですが、どこにあるか/ないのかわかりませんでした。'''
my_size = my_self.group.Get_size()
my_rank = my_self.group.Get_rank()

if rank == 0:
''' #ランクゼロのプロセスは全体を統括する役割を持たせることが多いようです。他のプロセスからの情報を受信してみます。
まずは自プロセスの情報を出力します。'''
print ("From the New World: rank %d of %d running on %s (SELF rank %d of %d)"
% (rank, size, name, my_rank, my_size), end = ' ')
print (" :I am The Beast that Shouted Love at the Heart of the World") #ジョークです。

'''#1番目から最後までの情報を受信するために回します。'''
for i in range(1, size):
'''#受信します'''
world_rank, world_size, your_name, your_rank, your_size = comm.recv(source=i, tag=1)
'''#受信したデータを表示します'''
print ("From the New World: rank %d of %d running on %s (SELF rank %d of %d)" %
(world_rank, world_size, your_name, your_rank, your_size))
else:
''' #ランクゼロ以外の場合
#自分プロセスの情報をランクゼロのプロセスへ向けて送信します。'''
comm.send((rank, size, name, my_rank, my_size), dest=0, tag=1)

'''#マニュアルを読むと後始末がいる気がするのですが、私がみたほとんどのmpi4pyサンプルには記載がないので微妙です。(勉強中)'''
MPI.Finalize()

コメントが多いので少し読みにくいですが、非常にシンプルでストレートです。

MPI実行してみた

f:id:AssistantOfKoo:20200712101432p:plain


自分のマシン(自ノード):Casperで3つ、他マシン(別ノード):Melchiorで2つ、プロセスを起動して動作させました。※Casper, Melchiorマシン名です

M:\MPI_01>mpiexec -n 3 -host localhost python MPI_TEST01.py : -n 2 -host melchior python \\casper\MPI_Experiment\MPI_01\MPI_TEST01.py
From the New World: rank 0 of 5 running on CASPER (SELF rank 0 of 1) :I am The Beast that Shouted Love at the Heart of the World
From the New World: rank 1 of 5 running on CASPER (SELF rank 0 of 1)
From the New World: rank 2 of 5 running on CASPER (SELF rank 0 of 1)
From the New World: rank 3 of 5 running on MELCHIOR (SELF rank 0 of 1)
From the New World: rank 4 of 5 running on MELCHIOR (SELF rank 0 of 1)

M:\MPI_01>

指定したとおり、Casperで3つ、Melchiorで2つプロセスが起動していることがわかります。開始から表示まで30~40秒もかかっているのですが、これは原因を確認中です。

単体で実行するとすぐに完了するので、mpiexecでのプロセス準備や後始末にほとんどの時間がかかっている感じです。

ソースはコメント書いたとおりですが、Rank=ゼロが「マスター」で、それ以外が「子分」として動作をしています。すべて同列のプロセスなどで別に違いはないのですが、「とりまとめを行うプロセス」をきめて処理を行うことが多いようです。

ちなみに内部では自ノード(Casper)内では「shm:共有メモリ」、他ノード(Melchior)とは「tcp:TCP/IP」で通信しているはずです。(標準は他ノードとの通信はたぶんOFIですがtcpに変えています)

マルチプロセスでバッチも起動できます

サンプルのバッチ (a.bat)です。

@echo off
IF "%PMI_RANK%" == "0" (
set message=:I am The Beast that Shouted Love at the Heart of the World
) ELSE IF "%MPI_LOCALRANKID%" == "0" (
set message=:I am The Owl that Whispered Love at the Bottom of the World
) ELSE (
set message=
)
echo From the New World: rank %PMI_RANK% of %PMI_SIZE% running on %COMPUTERNAME% (NODE rank %MPI_LOCALRANKID% of %MPI_LOCALNRANKS%) %message%

バッチをマルチプロセス実行してみます

M:\MPI_01>mpiexec -n 3 -host localhost a.bat : -n 2 -host melchior a.bat
From the New World: rank 0 of 5 running on CASPER (NODE rank 0 of 3) :I am The Beast that Shouted Love at the Heart of the World
From the New World: rank 1 of 5 running on CASPER (NODE rank 1 of 3)
From the New World: rank 3 of 5 running on MELCHIOR (NODE rank 0 of 2) :I am The Owl that Whispered Love at the Bottom of the World
From the New World: rank 2 of 5 running on CASPER (NODE rank 2 of 3)
From the New World: rank 4 of 5 running on MELCHIOR (NODE rank 1 of 2)

こちらは各ノード内のサイズとランクを表示できています。動作プロセスの環境変数から情報を取得できることがわかります。

バッチは御覧のとおり、プロセス間では通信しておらず、標準出力をmpiexecが回収して実行ターミナルに表示しているだけです。

MPIのほうでも自ノード内でのサイズとランクがある(取得できるはず)とは思うのでうが、情報がなくわかりません。

実験環境を整えるのには少し苦労しましたので、次はその話題にしようと思います。

(つづく)

並列コンピューティングは間違いなく「沼」です

すこしやっただけでも、「EPYC」64コアマシンを32台とInfinibandスイッチ(36ポート)×2台と、ConnectX-6 VPI(200GBpsHDR2ポート版) が32枚と、 NVIDIA RTX2080Tiが64枚(NVLink SLIで各マシン2枚づつ装備)と電源設備・空調設備付き防音室(自宅マシン室)くらいが欲しくなります。

宝くじ/ロト6を当てる方法についても自由研究の課題になりそうです。(乞うご期待)*3

Infinibandスイッチは(宝くじがあたらなくても、マジ冗談ではなく:ただしおこずかいの範疇で!)中古購入を検討中です。自宅Infiniband普及委員会がInfinibandスイッチも持っていないようでは、恰好がつかないのでwww

とりあえず、今月のおこずかいは今日すべて「並列計算」の書籍をクリックしてなくなりました。

 

 

*1:どんなCPUでもGPUでもみーんなまとめてつなげちゃえな新言語

*2:このマニュアルのわかりやすさは別次元。他のマニュアルつくる中の人はぜひ参考にしてください。:前言撤回:なかの人はちゃんとレベルダウン情報をもらって、リリースにあわせた内容のマニュアルを作成/更新してください。動かない(実装もたぶんされてない)オプション多数あり。所詮インテルも残念ながら普通の会社という理解。さらに前言撤回:確認していたマニュアルのバージョンがずれていました。英語版では該当オプションは「削除」されてました。manページ内容が古くて合致してませんでした。

*3:ロト6予想サイトはいろいろありますが、なかなか高額当選しないものですね。未来予知は人工知能にも人間にも難しそうです。ロト6は仮に予想が当たっても、事前に情報公開している以上、高額当選とならないジレンマには目をつぶりましょう