本当は怖い情報科学

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

MPI環境の切り替えをするmpienvというツールを作った

背景

多くの場合、スパコンやクラスタ上には複数のMPIがインストールされています。ユーザーは、これらを適切に切り替えて使わなければなりません。普通のユーザーであれば、普段使うMPIは一種類で固定であるケースが多いので、.bashrc等の設定ファイルに設定を書いておけば問題ありません。

ライブラリなどの開発をする場合、複数のMPIでテストを行う必要があるので、MPIを切り替えて使うことになります。基本的にはPATHLD_LIBRARY_PATHを切り替えればOKなのですが、プログラムをコンパイルしたときのMPIと、実行するときのMPIは合致していなければいけません。これらが食い違うと、正しく並列プログラムを実行できません。

基本的には2つの環境変数を切り替えるだけですので、シェルスクリプトでも書けば一瞬ですし、環境によっては実際に既に用意されていることも多いです。プラットフォーム固有の切り替えコマンドがある場合もあります(例えばCray系システムのmoduleコマンドなど)。

解決すべき問題

このような、複数のMPIの利用にはいくつか問題があります。

  • システムにどのようなMPIが用意されているのか忘れる
  • あるプログラムをどのMPIでコンパイルしたのか忘れる
  • MPIの切り替えを手動でやろうとして忘れる
  • 自分でインストールしたMPIの存在を忘れる

のような問題です。

さらに、PythonからMPIを使うライブラリであるmpi4pyを用いると、さらなる問題が発生します。mpi4pyはインストール時に使ったMPIにリンクされるため、PATHLD_LIBRARY_PATHを切り替えるだけでは駄目だということです。切替時には、mpi4pyを一旦アンインストールし、MPIを切り替えたあとに再度mpi4pyをインストールする必要があるということです。

pyenvなどのツールを使ってPython実装ごと切り替えるという手もありますが、それはかなり面倒ですし、どちらにしろpyenvによる切り替えを忘れると謎のエラーで苦しむ事になります。

解決策

これらを解決するために、mpienvというツールを作りました。これは、Pythonにおけるpyenvだと思っていただければ、ほぼ正しいです。

github.com

詳細はREADMEを見ていただきたいと思いますが、基本的にできることは以下です。

  • システムにインストールされているMPIを一覧・管理する
  • MPIを切り替える
  • mpi4pyも同時に切り替える
  • 開発中の機能
    • MPIを自動でインストールする
    • バイナリを指定した時に、それに適したMPI実装を自動で選ぶ
    • pyenvのように、ディレクトリごとに使うMPIを切り替える

などのことができます。手前味噌ですが、複数MPIを切り替えてのテストやデバッグが劇的に楽になりました。まだまだ開発中ですが、適当なタイミングで利用方法の説明を書きたいと思います。

Raspberry Pi3 Model Bを買った

ちょっと前から興味があったので、Raspberry Pi3 Model B を購入しました。

ARMアーキテクチャ上での数値演算の最適化とか、エミュレーター書いてみたいとかいろいろ興味があります。 CPUは4コアのARM Cortex-A53、クロックサイクルは1.2GHz。

ポスト京スーパーコンピューターもARMアーキテクチャですしね。

news.mynavi.jp

もちろんラズパイで数値演算しても 実用上は意味ないのですが、ARMアーキテクチャについていろいろ勉強してみたいところです。 SVE(Scalable Vector Extension)を理解する上でも、基本的なARMアーキテクチャについてわかってないとダメですし。

ということでしばらく遊んでみます。

数値微分(Juliaで)

Juliaで数値微分を書いてみます。

数値微分の基本

数値微分は、直感的には微小幅  h について関数の値を計算して差分を取れば、それが勾配になります。しかし、数学的にもう少しちゃんと根拠が示されています。

まず、微分を求めたい1変数関数を  f、点を x_0とします。そして、微小幅  h > 0 について、関数  f(x + h) x の周りでテイラー展開します。

 { \displaystyle
f(x + h) = f(x) + h f^{(1)}(x) + \frac{h^{2}}{2} f^{(2)}(x)  + \frac{h^{3}}{6} f^{(3)}(x) + \dots
}

ただし、 \dots は、 h^{4} 以上の項です。同様に、関数  f(x - h)xの周りでテイラー展開します。

 { \displaystyle
f(x - h) = f(x) - h f^{(1)}(x) + \frac{h^{2}}{2} f^{(2)}(x)  - \frac{h^{3}}{6} f^{(3)}(x) + \dots
}

ここで上2つの式を引き算すると、都合の良いことに  h^{2} の項が消えます。

 {\displaystyle
f(x + h) - f(x - h) = 2 h f^{(1)}(x) + \frac{h^{3}}{6} f^{(3)}(x) + \dots
}

 h^{3} 以上の項を無視することによって、

{\displaystyle
f^{(1)}(x)  = \frac{ f(x + h) - f(x - h)  }{2h}
}

という結果が得られました。これは中心差分の公式と呼ばれます。上の式変形からわかるように、 h^{1}の項まで考慮しているので(計算の途中で消えたけどw)、誤差のオーダーが  O(h^{2})(二次精度)となります。

Juliaで実装

上式をJuliaで実装してみます。Float32をベースとして使って、 h としてJuliaの組み込み定数であるeps(Float16)を使ってみます。

コード自体は非常に単純です。

function diff(f)
  h = eps(Float16)
  return (x -> (f(x+h) - f(x-h))/(2h))
end

受け取った関数  f の1次の導関数を返します。試しに、いくつか解析的に自明な関数の計算をしてみます。

julia> f = x -> x  # 恒等関数:微分係数は常に1
(anonymous function)

julia> df = diff(f)
(anonymous function)

julia> df(0)
1.0f0

julia> df(1000)
1.0f0

julia> g = x -> x^2 + 3x + 1   # g(x) = x^2 + 3x + 1
(anonymous function)

julia> dg = diff(g)  # 導関数は g'(x) = 2x + 3
(anonymous function)

julia> dg(0)
3.0f0

julia> dg(5)
13.0f0

julia> h = x -> sin(x)  # h(x) = sin(x)
(anonymous function)

julia> dh = diff(h)  # 導関数は h'(x) = cos(x)
(anonymous function)

julia> dh(0)   # cos(0) = 1
0.9999998f0

julia> dh(pi/2)    # cos(π / 2) = 0
0.0

なんとなく良さそうです。

【広告】