美添 ⼀樹 (Kazuki Yoshizoe) - 北海道大学...並列探索ライブラリの提案 美添 樹...
Transcript of 美添 ⼀樹 (Kazuki Yoshizoe) - 北海道大学...並列探索ライブラリの提案 美添 樹...
並列探索ライブラリの提案
美添 ⼀樹 (Kazuki Yoshizoe)
基盤 (S) 離散構造処理系プロジェクトセミナー 2017年2⽉21⽇
6
5
33 3
5
7
6
5
6
4 5
6
5 5
5
⾃⼰紹介• 過去にはコンピュータ囲碁の研究に取り組む • 今は主に並列探索アルゴリズムの研究に取り組む • 過去の研究
– 探索いろいろ (AND-OR⽊探索、MCTSなど) – 並列計算 (キャッシュ同期プロトコルによる投機実⾏) – 情報セキュリティ (静脈認証の脆弱性評価) – デジタル無線通信 (Adaptive Array Antenna等)
• 1⽉1⽇付けで理研AIPセンター@⽇本橋に – 「探索と並列計算ユニット」のリーダーに着任 – 研究以外に計算機の選定なども担当
2
並列探索の概要
各種オーバーヘッドを低く保つ 並列化⼿法が必要
探索⽊、探索グラフは かなり Unbalanced なので 単純な並列化は困難
ルールで⽣成されるグラフ ゲーム パズル
SAT
NCSP 数値制約充⾜問題
組合せ 最適化
頻出パターン マイニング
探索の (我々の) ターゲット
1, 同期オーバーヘッド 正確に計算時間をそろえる (困難) ⼜は⾮同期アルゴリズムに変形する
3, 通信オーバーヘッド 送信や受信にかかる時間
2, 探索オーバーヘッド 閾値などの情報の伝達が遅れ、 枝刈りが遅れるオーバーヘッド
トレードオフ
並列化⼿法の種別と既存の成果
深さ優先探索 (DFS) stack を使う様に変形し work stealing を⾏う ハッシュ表を使う探索 例、IDA*, モンテカルロ⽊探索 Distributed Hash Tableを⽤いる
Priority Queue を使う探索 例、Dijkstra, A*探索 Hash distributed queue を使う
SATソルバ やや特殊
work stealing に少し⼯夫を追加
ハッシュ関数を⽤いて節点を分散 「都合の良い」ハッシュ関数と 通信の集中を回避する⼯夫が必要
Portfolio⽅式が主流
(今のところSATソルバは対象外)[Yoshizoe, Kishimoto, Kaneko, Yoshimoto, Ishikawa 2011]
[Yoshizoe, Terada, Tsuda. 2017?]
[Ishii, Yoshizoe, Suzumura. 2014]
並列モンテカルロ⽊探索 4,800コアで約3,200倍
数値制約充⾜問題 900コアで約750倍
統計的パターンマイニング1,200コア1,175倍
並列探索ライブラリの⽬標• 並列探索の難しさの回避
– ハードウェアに適したアルゴリズムの作成 – デバッグ
• ⼤きな⽬標 – シングルコアの探索を実装できる⼈なら並列版も実
装できるように • 分散メモリ環境で動作させる
– できるだけ⼀般的なツールを⽤いる • c++ と MPIライブラリ
• まだ未完成。今後数年の研究⽬標 – まず⼀番簡単な深さ優先探索 (DFS) について説明
5
遺伝データから 統計的に有意な変異の 組合せを発⾒する
(P-Value が⼩さい組合せを全列挙) アルツハイマー / ⽇本⼈
1,200コアを⽤いて最⼤1,175倍⾼速化
(東⼯⼤TSUBAME CPUのみ)
概要・元は lattice な探索空間・reverse search で⽊に変換・P-value の枝刈りを⽤いて 閾値付き深さ優先探索に・低遅延閾値伝播+work stealing (細かいテクニックはいくつかあり)
…GTCTAAAACATGATT…0…GTCTGAATCATGATT…1…GTCTGAAACATGATT…0…GTCTGAATCATCATT…1
頻出パターンマイニング の並列化でもある cf. ビールおむつ問題
Genome Wide Association Studiespos. neg.
変異 (SNP) が⾚ 各⾏が被験者
[美添, 寺⽥, 津⽥] arXiv:1510.07787 [cs.DC]並列DFS 応⽤例
101
102
103
104
0 200 400 600 800 1000 1200 0 100 200 300 400 500 600 700 800 900 1000 1100 1200
Tim
e (
s)
Sp
ee
du
p
Nu. Process
time (s)speedup
48285
41.1
数万〜数⼗万の組合せ ナイーブにやると 2数⼗万通り
数値制約充⾜問題に適⽤
特徴• 不平衡な探索⽊の分散• 不規則かつ⾼頻度な負荷分散• 短時間 (数秒以下) の求解でも
⾼速化• 閾値無し DFS
P1
P2
x1
x2
p0
p0
B&P
B&P B&P
p1
p2 p1 p3
. . .
. . .
. . .
. . . . . .
. . . . . . . . . . . .
nb boxes
Initial domain
不等式制約で記述される実数領域を求める問題
応⽤例:ロボットの可動範囲
(精度保証付き) 数値計算+ 領域分割 (⽊探索)
0 2 4 6
0
2
4
6
600コアを⽤いて最⼤約500倍⾼速化
NCSP
深さ優先探索 Depth First Search8
DFS() { Recur(r) } Recur(node n) { foreach (child c of n) { // do something for c Recur(c) } }
Back tracking DFS
r
a
d
f g
e
b
i
k
j
h l
c
n o
m
再帰呼び出しで⾃然と back tracking を実装可能
経路上の節点だけ覚えれば良い メモリ消費 O(d)�
⽊の節点を全て辿る 単純な動作
閾値付き Depth First Search9
DFS() { Recur(r) } Recur(node n) { foreach (child c of n) { // do something for c if (c is within threshold) Recur(c) UpdateThreshold() } }
DFS with threshold
r
a
d
f g
e
b
i
k
j
h l
c
n o
m
閾値を動的に更新して枝刈り 実⽤例が多い
探索の本体は⻘字の部分 ⼦節点の作り⽅によって 頻出パターンマイニングにも 統計的パターンマイニングにも 数値制約充⾜問題にもなる
並列DFSの準備10
DFS() { Recur(r) } Recur(node n) { foreach (child c of n) { // do something for c if (c is within threshold) Recur(c) UpdateThreshold() } }
r
a
d
f g
e
b
i
k
j
h l
c
n o
m
利点:O(d) メモリ ⽋点:並列化が困難
StackDFS() { push(r) Loop() } Loop() { while(stack not empty) { pop n from stack foreach (child c of n) { // do something for c if (c is within threshold) push(c) UpdateThreshold() } } }
⽋点:O(d b) メモリ 利点:並列化可能
深さd, 分岐数b の⽊とすると
再帰呼び出しを スタック + ループに変換
11
再帰呼び出しを スタック + ループに変換
r
a
d
f g
e
b
i
k
j
h l
c
n o
m
ba
cb
cbed
cbe
cbegf
cbeg
cbe
cb
cb
ha
r
df
g
cb
e
逆順にスタックに積むとほぼ同じ探索順序
DFS() { Recur(r) } Recur(node n) { foreach (child c of n) { // do something for c if (c is within threshold) Recur(c) UpdateThreshold() } }
StackDFS() { push(r) Loop() } Loop() { while(stack not empty) { pop n from stack foreach (child c of n) { // do something for c if (c is within threshold) push(c) UpdateThreshold() } } }
foreach in reverse order
注意: キュー (FIFO)にするとメモリ消費が激増する
h
r c
12
requ
est
reje
ct
request
give
あとは Work Stealing をすれば並列化完了
Work stealing スタックが空になったプロセスが ジョブを持つプロセスを探し request する (receiver initiated)
ユーザから以下を隠蔽 ・request 対象の選択⽅法 実際にはネットワークの能⼒に 応じた⽅法が必要 通信の集中を避ける ・正しい終了検知 全てのスタックが空になった保証 があって、初めて終了する Distributed Termination Detection (DTD) は意外と難しい
並列DFSライブラリ13
DFS() { Recur(r) } Recur(node n) { foreach (child c of n) { // do something for c if (c is within threshold) Recur(c) UpdateThreshold() } }
Back tracking DFSStackDFS() { push(r) Loop() } Loop() { while(stack not empty) { pop n from stack foreach (child c of n) { // do something for c if (c is within threshold) push(c) UpdateThreshold() } } }
Stack DFSこの変換はユーザにやってもらう
並列DFS with stack14
ParallelDFS() { if (proc_id == 0) stack.Push(r) Loop() } Loop() { while(not AllStackIsEmpty()) { if (stack.Count() > 0) { stack.Process() Probe() } send one steal REQUEST Probe() } }
Probe() { while (message received) { switch (message:TYPE) { case REQUEST: if (stack not enough) send REJECT else stack.Split() and send GIVE case REJECT: Send one steal REQUEST case GIVE:received_stack stack.Merge() } } if (proc_id == 0) Start DTD (後で説明) }
以下の関数を持つ stack があれば良い。 process が探索の本体 process / split / merge / count
抽象クラスを継承して実装15
class DFSStack { public: virtual void Process() = 0; virtual DFSStack * Split() = 0; virtual void Merge(int *, int) = 0; virtual int Count() = 0; }
class MyStack : public DFSStack { public: virtual void Process() { 探索の本体 通常は節点を pop し ⼦節点を push する } virtual MyStack * Split() { stack を半分に分割し、配列に置いて ポインタを返す } virtual void Merge(MyStack *) { 送られてきた stack をマージ } virtual int Count() { 要素の数を返す } }
⾃前の stack を作れば (閾値無しの) 深さ優先探索が動く
[Zhang,Tardieu, Grove, Herta, Kamada, Saraswat, Takeuchi 2014]
参考: 並列⾔語X10を⽤いた 類似の実装が存在
まず⽬指すもの• 以下の⼿順で並列化可能なライブラリ
– ユーザが Process / Split / Merge 等を持つ stack クラスを実装
• 実際に stack かどうかはどうでも良い • split した結果が連続メモリ領域にある必要あり
– そうでないと send しにくい
– 逐次 stack DFS で stack のデバッグ • stackの情報だけを元に探索する • (逐次では無意味だが)split と merge も使う
– 逐次で動くならそのまま並列でも動くように • ここが難しいかもしれない
16
さらに閾値伝播を追加• Spanning Tree 上で通信
– 終了検知と同時にやる • ユーザの実装が必要な物
– reduce 関数 / bcast 関数 がある threshold クラス
• 実体は何でも良い • 何らかの reduce 関数
– 探索は stack と threshold のみで動作する必要
17
ParallelDFS() { if (proc_id == 0) push(r) Loop() } Loop() { while(not AllStackIsEmpty()) { if (stack.Count() > 0) { stack.Process() Probe() } send one steal REQUEST Probe() } }
終了検知 Distributed Termination Detection
18分散アルゴリズムでは意外な事が難しい
スタックが全て空なら終了で良いのでは?
send と recv の総数を数えて⼀致していれば良いのでは?
さらに数えている途中に recv したら数え直すようにする
スタックが空でも通信の途中かもしれない
数えるタイミングによっては send と recv を1個ずつ (N個ずつ) ⾒逃すかも
これで正しく検知可能
ring を2周する: Dijkstra Scholten アルゴリズム (有名) ring 1周で良い改良: 4 counter algorithm など Spanning tree 上で集計: [Mattern 1990] 決定版、⾼速 (何故か知られていない) これと同時に閾値伝播を⾏う
メッセージ種類追加で実現19
ParallelDFS() { if (proc_id == 0) push(r) Loop() } Loop() { while(not AllStackIsEmpty()) { if (stack.count() > 0) { stack.process() Probe() } send one steal REQUEST Probe() } }
Probe() { while (message received) { switch (message:TYPE) { case REQUEST: if (stack not enough) send REJECT else split and send GIVE case REJECT: Send one steal REQUEST case GIVE: merge to local queue case BROADCAST: UpdateThreshold() send BROADCAST to neighbor or send REDUCE to parent case REDUCE: if (at root) send BROADCAST else send REDUCE if needed } } if (proc_id == 0) Start DTD }
ユーザ定義による任意の メッセージを追加しても良いが…
ユーザから隠蔽したい物• MPIライブラリの難しさ
– MPIメジャーなライブラリだがワナは⾊々 – 特に数値計算以外の使い⽅をすると難しい
• 通信パターンの問題 – 通信パターンに気を遣わないと何が起きるか
• 終了検知 – 分散メモリ環境での正しい終了検知
20
21
MPI Library (Message Passing Interface)
⼀番メジャーな並列プログラムツール
MPI規格にそった実装が多数存在
MPI_Send
MPI_Recv
main() { int buffer1[100], buffer2[100]; … if (rank==0) { MPI_Send(buffer1, 100, MPI_INT,1, ...); MPI_Recv(buffer2, 100, MPI_INT,1, ...); } else { // rank==1 MPI_Recv(buffer2, 100, MPI_INT,0, ...); MPI_Send(buffer1, 100, MPI_INT,0, ...); } }
Process 0
main() { int buffer1[100], buffer2[100]; … if (rank==0) { MPI_Send(buffer1, 100, MPI_INT,1, ...); MPI_Recv(buffer2, 100, MPI_INT,1, ...); } else { // rank==1 MPI_Recv(buffer2, 100, MPI_INT,0, ...); MPI_Send(buffer1, 100, MPI_INT,0, ...); } }
Process 1
MPI はプロセス (ランク) 単位で動作
各プロセスは rank (0,…,N-1) を持つ
冗⻑なメモリコピーを許容すれば 共有メモリ環境でも普通に動作する (単に通信をメモリコピーに置き換える)
rank 0 rank 1
MPI初⼼者の良くやるミス22
main() { int buffer1[100], buffer2[100]; … if (rank==0) { MPI_Send(buffer1, 100, MPI_INT,1, ...); MPI_Recv(buffer2, 100, MPI_INT,1, ...); } else { // rank==1 MPI_Send(buffer1, 100, MPI_INT,0, ...); MPI_Recv(buffer2, 100, MPI_INT,0, ...); } }
Process 0
main() { int buffer1[100], buffer2[100]; … if (rank==0) { MPI_Send(buffer1, 100, MPI_INT,1, ...); MPI_Recv(buffer2, 100, MPI_INT,1, ...); } else { // rank==1 MPI_Send(buffer1, 100, MPI_INT,0, ...); MPI_Recv(buffer2, 100, MPI_INT,0, ...); } }
Process 1このコードはデッドロック
MPI_Send, MPI_Recv はブロッキング通信なので この書き⽅をするとそこでデッドロックする 実は Send / Recv は何種類かあって適切な物を選ぶ必要 Send, Isend, Bsend, IBsend
MPI_Send は MPI_Recv されるまで待つ
探索での定⽯とワナ23
Loop() { while(not AllStackIsEmpty()) { // do work stealing Probe() } } Probe() { while (MPI_Iprobe()) { MPI_Recv(…) switch (message_tag) { // ここでメッセージに応じた処理 } } }
探索ではメッセージの タイミング、サイズ、送り先 の全てが不定
こういう⾏儀の悪いアプリは MPIはあまり想定していない 以下を使うのが⼀応の定⽯
MPI_Iprobe: メッセージが届いていたら trueを返す。 次の MPI_Recv はブロックしない
MPI_Bsend: バッファを⽤いた Send 1回バッファにコピーしてから送信
MPIの動作は何らかのMPI関数が呼ばれないと進まない (実際は実装依存だが、いくつか試したが実際⽌まる) 良くあるミス 無駄なポーリングを避けようとして途中で⽌まる
「刺さる」24
プロ⽤語? 何かが何かに刺さって不具合が起きる 良く原因が分からない場合に⽤いる ⽤例「〜〜したら刺さって40秒固まったぞ」
⼤規模並列 ⾃分のせいと⾔えない不具合は⽇常茶飯事
類義語 「秘孔を突く」
うっかり何かを「刺さない」 うっかり「秘孔を突かない」
並列プログラムは 気をつけないと良く刺さる
普通のプログラム 動かないのはほぼ100%⾃分のせい
良くある「刺さり」⽅25
前のプロセスがうまく終了せず、 後続のプロセスが影響を受けて死ぬ
通信がときどき 数秒〜10秒程度⽌まる
分かりやすいケース プロセスの未終了、ゾンビ化
原因が良く分からないケースも多い (プロにも良く分からないことも)
全く動かない (これは良い死に⽅)
しかも数時間待ったら勝⼿に治る(ことも)
最初から遅い (これもまだ良い)
しばらくすると遅くなる (こういうのが困る)
死に⽅いろいろ
簡単なツバメの刺し⽅26
1, ランダムな相⼿と通信 (数秒以上)
2, 特定の1ノードから 残り全てへ通信3, もう1回、特定の1ノード から残り全てへ通信
簡単なツバメの刺し⽅27
1, ランダムな相⼿と通信 (数秒以上)
2, 特定の1ノードから 残り全てへ通信3, もう1回、特定の1ノード から残り全てへ通信
2, 特定の1ノードから 残り全てへ通信
簡単なツバメの刺し⽅28
⼤ざっぱな回避策としては、通信の集中を避けること send/recv ペアに枝を張って作るグラフが ・最⼤次数が低く・直径が⼩さく ・”hot-spot” がない
1, ランダムな相⼿と通信 (数秒以上)
3, もう1回、特定の1ノード から残り全てへ通信
ここで⽌まる 数秒〜10秒
これには 数ミリ秒
≒ betweenness centrality が⾼い場所がない
通信が⽌まる現象29
HPC分野の専⾨家もあまり知らない そもそも MPI_Iprobe を⾃分で呼ぶ⼈が少ない 普通は MPI_Recv などの内部で使われている
現象としては MPI_Iprobe が数秒ブロックする (通常は 2〜3マイクロ秒で終了する) さらに同時に malloc がブロックすることも
Infiniband の RDMA (Remote Direct Memory Access) に伴う何らかの thrashing と推測される
推測される原因と背景
RDMA のために Infiniband カードは独⾃のメモリ変換 テーブルを持つ。実体はホストメモリ上にある また、malloc も独⾃の物に置き換える
詳細はドライバを読む?専⾨家に協⼒を依頼中
今後の課題• ハッシュ関数で分散
– ハッシュ関数は問題依存 • 性能のためにはここに介⼊したい
• DFSほど⾃由がない – DFS では work stealing の対象は⾃由 – Hash distributed A* などではそうは⾏かない
• 理想的には – 適切なハッシュ関数の⾃動⽣成、動的な変更
• しかしこれはかなりチャレンジング • アイデア
– 「刺さない」ために余分な通信を⾏うなど • 2-hop, 3-hop しても良いことにするなど
30
分散ハッシュを利⽤する⽅法もライブラリ化したい
より⼤規模に
より⼤規模な環境を⽬指す ・3D torus, TOFU (京) 等・Many-core アーキテクチャ
ある程度は成功 DFS 1,175倍 (1,200コア) MCTS 3,200倍 (4,800コア)ただし、まだ⼩規模 ・ネットワークが速い (full bisection) ・普通の CPU (Xeon)
実際のトポロジを無視し、 通信パターンのグラフだけ ⼯夫すれば⼗分な規模
実際のトポロジに即した 通信パターンを⾃動的に⽣成
HPCの専⾨家の 知⾒が求められる
より⼤規模な探索問題を解く 物性科学や⽣命科学の組合せ最適化
例、熱伝導率最⼤化 / RNA構造発⾒ 数値制約充⾜問題による精度保証求解
アルゴリズムに都合が良く 実現可能なトポロジの提案
(ランダムエッジを少数追加など)
ハードウェア、ミドルウェアに 負担をかけないアルゴリズム
たとえば
耐故障性のある探索
おまけ: 並列探索ハッカソン• 並列統計的パターンマイニングを github で公開中
– MP-LAMP https://github.com/tsudalab/mp-lamp • これを元に並列DFSをやるための解説会&ハッカ
ソン的なことをやりたいと思っています – 以下の様な⼈歓迎
• 頻出パターンマイニング界の⼈ • 探索の⼈
– 私のメリット • 意⾒収集
– 参加者の⽅に提供できるメリット • 並列探索の理解?
32