富士通詳細プロファイラの利用事例
2022-12-14
17th HPC-Phys勉強会

R-CCSソフトウェア開発技術ユニット
似鳥啓吾

内容
• 詳細プロファイラfappの使い方• 雰囲気で使っているので非公式です、あくまでも１ユーザーの体験と感想として• Nek5000/RS axhelmカーネルのチューニング
• R-CCS辻さんの仕事の再現実験＋文書化• プロファイラを取ったのはチューニングの後• Bridge++ QXS Wilsonカーネルのチューニング
• 金森さんからの宿題• チューニング前にプロファイラを取った例

参考文献• 「富岳」もしくはFX1000ユーザー限定となります• ポータル->マニュアル->言語マニュアル• プロファイラ使用手引書• ポータル->プログラミングガイド• プログラミングガイド (チューニング編)• プログラミングガイド (プロセッサ編)

基本プロファイラと詳細プロファイラ• 基本プロファイラ: fipp: Fujitsu Instant Performance Profiler• 割り込みによるサンプリングベース• 関数ごとの消費時間など• 一部CPU動作状況（Gflopsやメモリスループット）• 詳細プロファイラ: fapp: Fujitsu Advanced Performance Profiler• ハードウェアカウンターベース• 最大17回同じプログラムを繰り返し実行• CSV形式に変換後Excelマクロで可視化できる• 共通事項• 実行時のDLL切り替えにより通常実行とプロファイリングを切り替え• ソースコードにstart/stopの埋め込みが必要だが、プロファイリング専用のbranchを作らなくても済む• ただし富士通以外の環境ともソースを共通化するにはマクロ関数でAPIを消せるようにする必要があるソースコードの編集 (fapp)#include <fj_tool/fapp.h>
fapp_start("eo_bulk", 1, 0);
BridgeQXS::mult_wilson_eo_bulk_dirac(v2, up, v1, xp, m_CKs, &m_boundary[0],m_Nsize, do_comm, &m_Leo[0], ieo, iflag);
fapp_stop("eo_bulk", 1, 0);• name, number, levelの順で指定• name+numberで区間を識別• levelは0なら常に計測、実行時にオプションで対象を絞れる• fapp_start/fapp_stopのネストも可能

実行スクリプトの例
for i in {1..17}
dofapp -C -d ./rep${i} -Hevent=pa${i},method=fast mpiexec ${BIN} ${Px} ${Py} ${Pz} ${Pt} ${Nx} ${Ny} ${Nz}${Nt} ${Nthreads} ${Iter}done
for i in {1..17}
dofapp -A -d ./rep${i} -Icpupa,nompi -tcsv -o pa${i}.csv
done• ホストでcsvファイルを作成するときはfapppxコマンド• -Hmethod=fastだとOSを経由しないのでオーバーヘッドが小さい• 後々結果をscpする用に/optからxlsmファイルへのリンクを作っておくとよい• 出力例：50インチぐらいの4K TVで見るとよいNek5000/RS axhelmカーネル• スペクトル要素方• 六面体要素の中に83=512個の点がある• 各軸の8点は選点（直行多項式の根）になっている• 各軸に8×8行列を掛けることで勾配が求まる（7次の多項式になっていると思えばわかる）• やっている計算• 勾配、幾何係数、勾配の転地の順に掛ける• 幾何係数：六面体の中の座標での微分を外の座標での微分に直す（＋ヤコビアンと密度で重み付け）• 勾配は演算ネック、幾何係数はメモリネック（6語読んで9乗算）

As-isとtune-1• 最内ループ変数mに対するSIMD化では不適切である• Gatherと水平加算の発生• 幾何係数の部分が一切SIMD化されない• ディレクティブでm-loopをアンロールするとひとつ外側のループ変数iに対してSIMD化される

Before/after• 効果は抜群だが、、、• 左側のPA情報からこのチューニングに到れるかどうかが問題• どのループ変数でSIMD化したかはコンパイラのメッセージからわかる• mでは不適切でiならよいと気付くことができるか？
• ディレクティブは知らないかもしれない• mとiのループ順序を入れ替えることでも対処は可能

Tune-2 (ストライドアクセスの回避）• 8×8の行列s_Dは要素に非依存• Gather/scatterの発生はプロファイラで検出可能• 関数の入り口で転置行列s_Tを作っておくTune-3 （ソフトウェアパイプラン）• mループではフルアンロール、iループはSIMD化• その外側にj、kとループがあるが長さが8でソフトウェアパイ
プライニングを適用するには短い• 現状ではjについてフルアンロールされている• k-loopとj-loopを手動でcollapseさせると長さが64になりswpが有効になる• OpenMPのcollapseは同時にスレッド並列化もするので使えない

Before/after• 待ち時間は減った？
• IPCが1.01→2.01、コアあたりGIPSが2.03→4.02• 答え：整数命令が増えた、実行時間の減少は少しだけプリフェッチ

幾何係数の部分のデータ構造
ggeo[Nelement][7][512]• 冗長なアドレス計算を手で取り除く• kとjを求めてからアドレス計算→kjから直接アドレス計算• 急に速くなった• アセンブリを見るとプリフェッチ命令が出るようになっていた• コンパイラからのメッセージはなし• PA情報からキャッシュミス情報を見ることでもわかったかも最終版 (swp+prefetch)• L1ビジーが大きい（59.33%）• 演算：ピークの19.1％• メモリスルー
プット：ピークの39.4%

axhelmまとめ• プロファイラでわかること• メモリ待ち時間• 演算待ち時間• Gather/Scatter命令• ソースコード上の場所までは示してくれない• （スレッド間インバランス）• チューニングの指針となったか？
• 消したい待ち時間はある• 「このスイッチをオンにしたら待ち時間が消えます」という便利なものはない（-Kfastオプションから始めるなら）• 結局は試行錯誤による発見的手法になる• Before/afterの比較が情報量に富むBridge++ QXS (格子QCD)• SIMDを2次元方向に使おうという実装• float16のSIMDなら例えばxy方向に4×4で• Even-odd法との組み合わせ• C++でtemplatを多用しておりコンパイルはClangモード• ACLE for SVEも多用• 「正しく計算できるけれどflops値がいまいちなので」と金森さんから調査と改善の依頼

格子QCDの計算• 格子点上にクォークのスピン（Dirac spinが3色で複素12語）• リンク変数にゲージ（グルーオン）
4方向に3×3ユニタリ行列• 9点（対角+8点）のステンシル計算
Naïveなデータ構造
real spin [NT][NZ][NY][NX][3][4][2];real gauge[NT][NZ][NY][NX][4][3][3][2];格子点あたり24語と72語、floatなら96 byteと144 byte

付録 A記法と く に断ら ない限り 、 各添字に意味と 範囲は以下の通り :site4γ行列自由度（次元格子座標） : x , y,... . コ ード の上では、 s など を 用いる 。

color 自由度: a, b,... . 範囲は 1–3. コ ード の上では i c など を 用いる 。
spin 自由度: α, β, ... . 範囲は 1–4. H opping 項では、 2 成分のこ と がある 。 コ ード の上では i s など用いる 。
• を(1±γμ)を掛けるとスピンの自由度が複素4語から複素2語に減5 次元目の自由度: i , j ,... . 範囲は 1–N s . (N s を L s と 書く こ と も ある ) 。
るのに注意

4 次元空間軸の方向: µ, ν,... . 範囲は x , y, z, t （ も し く は 1–4） , コ ード の上で は mu など を 用いる 。
spin 自由度に作用する γ-行列は、 以下を 用いる 。

γ1 =000−i

γ5 =00 −1 0000 −1−1 0000 −1 00

付録 B00−i00 ii 00 00 0

Bridge+ + の class、 関数群

便利なも のを 抜粋する 。

vout

γ2 =0 000 0 −10 −1 0
1 001000γ3 =00−i00 i00 0 −i0 0 0i 0 0

γ4 =10000 001 000 −1 00 0 −1(48)(49)

n1 + n2 + n3 + n4 =(odd)odd 格子点(even) even 格子点+n+n+n=(16)1234と する 。 H opping 項 H は最近接格子点への hopping なので、 H は必ず even 格子点(odd) odd 格子点列になる 。 残り の行列（ G, M˜ ） は格子点の偶奇を 変えない。 そこ で格子点の even-oEven-odd法

格子点への hopping なので、 H は必ず even 格子点とD oddD 格子点を結ぶ行M˜H̃
Even
D DW

Lexicalee=eoOddee=
D ooH̃ oe
3,0 3,2 3,4oe
は格子点の偶奇を 変えない。 そこ で格子点の even-oddD に着目して3,0oo と

3,23,3

3,43,53,63,70,02,10,12,20,2

3,1

3,33,53,73,6と 書く こ と ができ る 。 一般に、 D oo と D ee の逆が存在する と し て、 連立一次方程式

2,6 2,7D ee D eo2,3 2,4 2,5M˜ ee
H̃ eo
=
1,0
1,2 1,3 1,4 1,5 1,6 1,7˜
D oe1,1 DH̃ oe M oooo

2,0

D DW =

3,1eo

M˜ oo

0,30,40,50,60,72,0

2,22,41,11,3D
1,5ee 1,7D eo
D oe D oo

0,00,20,42,60,62,12,3x e 1,21,0=xo
0,1 0,32,52,7(17)be 1,61,4bo
0,5 0,7はD ee の逆が存在すると し て、 連立一次方程式

D eeD oeD eo
D ooxexo=bebo−1−1−1−1
1 − D eeD eo D oo
D oe x e = D eebe − D eo D oobo
≡D(18)−1x o = D oo(bo − D oe x e )と 2 つの方程式に分解で き る 。 こ の (19) 式を 反復法で 解く のがア プ リ の律速部分で• Doe: Even成分を読んでOdd成分に書き込み⇔
Deoがチュ ーニン−1−1− 1 グの対象である 。
−1D ee D eo D oo D oe x e = D ee be − D eo D oo bo(19)• Dee,Doo：Wilsonカーネルでは単なる対角行列
D oo も D ee も 座標によ ら ず、 (14) 式で与えら れる 。 color 自由度にも よ ら ないので
≡Dと spin の空間で求めればよ い。 こhopするので収束が早い
こ では、 LU 分解を 用いる 。
• 1回のmultの演算数は減らないが2−1x=D(bo − D oe x e )(20)ooo• CG法等で複数のベクトルを保持するメモリフットプリントが半分

QWSとQXS• QWS: QCD Wide SIMD
• 重点課題9のターゲットアプリ• 単精度16語のSIMDとeven-odd法の組み合わせにより、MPIプロセス当たりのx軸の長さが32の倍数である必要• 目標の1924格子には使えるが、、、• Bridge++ QXS• SIMDの軸を2次元化• x軸とy軸の4×4タイルをSIMDのレジスタに載せる• その分x方向とy方向のステンシルアクセスが非自明に例えばこんなレイアウトに
float spin_eo [NT][NZ][NY/4][NX/8] [3][4][2] [16];
float gauge_eo[NT][NZ][NY/4][NX/8] [4][3][3][2] [16];SIMD命令の複素数サポートについて• 一部のSIMD命令では偶奇のレーンを束ねての複素数演算をサポートしている• BlueGeneや京、FX100（倍精度限定）• x86はshuffl系の命令を多用する• 第1オペランドの偶数レーン複製• 第1オペランドの奇数レーン複製• 第2オペランドの偶奇スワップと符号変更• これがあると格子QCDの計算で嬉しい• 実効的にベクトル長が半分になる• 実効的にレジスタの本数が倍になる• スピンの12本をレジスタに置きっぱなしにできる• ゲージの9本をレジスタに置きっぱなしにできる (DWF)• SVEの命令セットには入ったがA64FXではマイクロコード実装

ステンシルアクセス: X3,-11,-1

z1:

0,00,20,40,63,13,33,53,73,0

3,23,43,62,02,22,42,62,12,32,52,71,11,3

1,5

1,71,01,2

1,4

1,60,00,2

0,40,60,10,30,50,7

1,1

1,3

1,52,42,63,13,3z2:
pg:
sel:
idx:
tbl:

2,02,2

3,5

1,-1

3,-1000000010000000

10,00,20,40,61,1

1,3

1,51,-1

2,02,22,42,63,1

3,33,53,-101237456891011151213

140,00,20,40,6

1,-1

1,1

1,3

1,52,02,22,42,63,-1

3,1

3,3

3,5

• Oddのひとつ左（X-）のevenからの読み込み（右にシフトして足す）• yが奇数の行だけ左にずれる• load, load, sel, tblで実現可能ステンシルアクセス: Y3,1

3,3

3,5

3,72,02,22,42,6

1,1

1,3

1,5

1,70,0

0,20,40,6

z1:-1,1 -1,3 -1,5 -1,7z2:

0,00,61,1

1,3

1,51,72,02,22,42,6ext-1,1 -1,3 -1,5 -1,70,00,2

0,40,6

1,1

1,3

1,51,70,20,4-1,1 -1,3 -1,5 -1,7• load, load, extでできる• ext: 2つのベクトルを連結して即値でシフト

2,02,22,42,6

通信バッファのgather/scatte回避
3,1

3,3

3,53,7z1:

2,02,22,42,6pg:

0

0

1,1

1,3

1,51,7compact:

1,7

3,70,0

0,20,40,6idx:

01tbl:

1,73,71,7000001

3,7000• 16語中2語だけ送信バッファに詰める (compact)• レジスタ内で連続にしscatterを回避• 受信バッファから元の場所に戻す (tbl)• Gatherを回避

00001

計測条件、チューニング
• A64FX 1ノード4プロセス、164=64K格子• CMGあたり8×8×16×16• SIMDとEOも含めると 8×8×4×2• カーネルとなる関数• eo1: 送信バッファの準備• 途中でスレッドの同期とMPI送信のキック• bulk: ローカルでできる計算• スレッドを同期してMPIの受信待ち• eo2: 受信バッファからの計算• コンパイラオプション• -Rpass-missed=inline -mllvm -inline-threshold=1000を追加• 8方向からのhopのカーネルで関数callがなくなるように

As-is状態• Bulk部分• L1アクセス待ち、L1ビジー率が高い• Gather/scatterが沢山出ていた• 実は対角の部分、最後にκを掛けてストアするだけの部分• Vsimd_tというSIMDエミュレーシ
ョン型• といっても単なる外側24、内側16の二重ループなのだがチューン（性能デバッグ）後• Bulk• 普通の見た目になっている• 180 Gflops→360
Gflops• 若干主記憶アクセスが発生しているのが気になる• 6MiB/CMGぐらいのはず• 若干のロードインバ
ランスロードバランス問題

eo1 (export)

eo2 (import)• 実行時間／演算数含め可視化してくれる• 格子点数均等割にしているので、袖に触る／触らないで負荷バランスが崩れるまとめ（プロファイラで何が見えたか）• 手を動かせば必ず絵が出てくるのが有難い• （意図せぬ）Gather/Scatter• ロードインバランス• 見ておくべきもの• ビジー率• ある程度のチューンができたコードは大抵複数のリソースが忙しい• 「実アプリだから主記憶帯域ネック」は本当？

• 見えにくいもの• ソースコードの何処でそれが起こっているか？
（区間の細分化には限度がある）• アセンブリ出力を確認することの代替にはならない