A64FXプログラミング講習会
入門編
2023.10.31
富士通株式会社

1© 2023 Fujitsu Limited目次
13:00 13:30

スパコンの使い方など

13:30 14:20

A64FXアーキテクチャと並列プログラミング（座学）

14:30 15:40

OpenMPを活用した並列プログラミング演習
3重ループを含むプログラムで、OpenMPの指示
文挿入によるスレッド並列やSIMD演算を活用するコードを取り扱います。
また、 Intrinsic（ACLE）を使ってSIMD化する例も取り上げます。

15:50 17:00

プロファイラを活用したチューニング演習2© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
A64FXアーキテクチャ

3© 2023 Fujitsu Limited

A64FXの概要
A64FX CPU⚫ 富士通が2019年にリリースした

メニーコアCPU⚫ 48 (+アシスタント)コア、コアあたり512bits SIMD演算器 x2⚫ 高バンド幅のメモリを搭載⚫ 2023年6月第2位の「富岳」に使われるCPU⚫ 2019年にはGPUのシステムを抑えてGreen500で1位を取るなど、非常に電力効率がよい

https://www.fujitsu.com/jp/about/resources/publications/technicalre
view/2020-03/article03.html
4© 2023 Fujitsu Limited

A64FXのハードウェアの特徴⚫ 汎用CPUとしては非常に高い電力性能⚫ 高い理論演算性能⚫ 高いメモリバンド幅⚫ 一方で使いづらいところがある⚫ メモリ容量が小さい（32GiB）⚫ 命令レイテンシが長い（FMAは9cycles）⚫ ラストレベルキャッシュメモリサイズが小さい⚫ すべてのコアとSIMD演算器を使わないと高性能は達成できない5

コア数

48 + アシスタントコア2~4

周波数

2.0GHz (or 2.2GHz)

理論演算性能3.4 TFLOPS

メモリ容量

32 GiB

メモリバンド幅

1024GB/s

L1 Cache

64 KiB/core (Inst/Data)

L2 Cache

8 MiB / CMG© 2023 Fujitsu Limited

A64FXのソフトウェアから見た特徴⚫ 得意なプログラムをチューニングすれば非常に高性能⚫ 例1: 最適化されたライブラリが主体のプログラム⚫ 例2: メモリバンド幅ネックかつ並列性が十分にあるプログラム⚫ 例3: 演算ネックで、並列性があり、レイテンシを隠蔽できるプログラム⚫ チューニングなしでは性能が出にくい⚫ 既存CPU向けに記述されたコードでは性能が活かせないことが多い（コアとSIMDの並列性の抽出が必須。演算ネックのプログラムだとレイテンシの隠蔽も必要）⚫ アルゴリズムとデータ構造の見直しも必要かもしれない⚫ 苦手なプログラムは諦めたほうがいい（こともある）⚫ 演算間の依存関係が強いループボディの大きいプログラムは性能が出しづらい
6© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
A64FXの性能の引き出し方

7© 2023 Fujitsu Limited

A64FXに最適化された計算ライブラリの利用⚫ A64FXのHW性能を十分引き出した計算ライブラリが公開されている⚫ まずは計算ライブラリの活用を検討する⚫ 有名な計算ライブラリ⚫ SSL2：富士通が最適化したHPC向けライブラリ（行列演算系などがある）。マルチコアで使用するときは、富士通のOpenMPライブラリを使う必要がある⚫ OneDNN: Intel発のDeep learning向けライブラリ。主に、富士通とサイボウズ・ラボの光成さんでA64FX向け最適化を行い、本家OSSにアップストリームした⚫ その他⚫ BLAS, LAPACK, FFTなど：A64FX向けに最適化されていないものでも実行効率30-50%程

度はあると思うので活用したほうが良いと思います8© 2023 Fujitsu Limited

並列処理は必須⚫ 現代のプロセッサは、並列性を活かさなければ性能がでない
複数コアの並列処理
コア コア
コア コア

コア内の並列処理

SIMD演算器

複数のデータをまとめて処理できる演
算器

SIMD演算器⚫ 複数コアの並列処理とSIMD演算器の並列処理は、コンパイラによる最適化があまり期待できないので、プログラマが記述しなければならない⚫ プログラムから、依存関係のない（＝並列処理可能）処理を抽出する必要がある

9© 2023 Fujitsu Limited

データ並列とスレッド並列⚫ データ並列処理⚫ 最もよく使われる並列性

コア⚫ 異なるデータに対して同じ処理を行う

メモリ⚫ コア間の並列処理でもSIMD演算器の0 1 2 3 4 5 6 7 8 9

並列処理でも利用可能

0 2 4 0 0 0 0 0 0 0

シングルコア処理⚫ スレッド並列⚫ コア間の並列処理に使われる並列性

処理（SIMD演算器では利用できない）⚫ コア間で同じコードを処理させる

コア1

処理
コア2

メモリ

⚫ タスク並列

0 1 2 3 4 5 6 7 8 9⚫ ややこしいのでなるべく考えない

0 2 4 0 0 101214 0 0

マルチコア処理10© 2023 Fujitsu Limited

SIMD演算器での並列処理の記述
SIMD演算器⚫ メモリ上で連続したデータをまとめて

A A A A

演算する

SIMD演算器⚫ 最も内側のループで、連続している

データに対して処理するように記述されていればコンパイラがSIMD演算器を活用するコードを生成してくれる

B B B BC C C C

SIMD化されるコード 多分SIMD化されないコード
for (i = 0; i<N; i++)c[i] = a[i] + b[i];11for (i = 0; i<N; i++)c[i] = a[2*i] + b[2*i];© 2023 Fujitsu Limited

並列性の抽出⚫ すべてのコアを使う（A64FXはコア数が多いので重要）⚫ ループにOpenMPディレクティブを挿入⚫ ループの回転数を確認して足りない場合は工夫する⚫ OpenMPのcollapse節を使って、多重ループに対して並列化を行う⚫ 処理方法を見直す⚫ プロセス並列やタスク並列の導入を検討する（今回の演習の対象外）⚫ SIMD演算器を活用する（A64FXではメモリバンド幅ネックのプログラムでもSIMD演

算器の活用は必須）⚫ 最も内側のループで以下を満たせるようにする⚫ 連続データに対して処理できるようにする⚫ 十分な回転数があり、なるべく512bitsの倍数でデータが処理できるようにする⚫ if文はなるべくループの外に追い出す⚫ 関数呼び出しは避ける（コンパイラによる最適化が難しくなる）⚫ もしくは、アセンブリコードレベルで記述する
12© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
すべてのコアを使う

13© 2023 Fujitsu Limited複数コアを使った並列処理の記述方法⚫ OpenMPで記述⚫ サーバ内（共有メモリ）での並列処理を記述するときに使う⚫ 逐次プログラムにディレクティブと呼ばれる指示文を入れることで並列化する⚫ 共有メモリが前提のため、明示的なデータのやり取りは記述しなくて良い⚫ 記述が簡単で、コードの可読性があまり落ちないのでよく使われる

本講習の対象⚫ MPIで記述⚫ 主に複数サーバ（分散メモリ）での並列処理を記述するときに使う⚫ 共有メモリがない前提で、明示的にデータのやりとりを記述する⚫ 複数サーバでの並列処理を使うときは、ほぼ必須なのでOpenMPを使わずにMPIだけ使うコードもある⚫ 他の言語で記述⚫ Pthread, OpenCL, SYCLなどがある⚫ 興味のある方は調べてみると良いと思います14© 2023 Fujitsu Limited

OpenMPとは⚫ 共有メモリ型の計算機において並列処理を可能とするAPI⚫ 逐次プログラムにディレクティブという指示文を挿入することで並列化可能⚫ OpenMPディレクティブは、コンパイルオプションを指定しない限り、無視されるため同じコードで逐次プログラムと並列プログラムを生成できる15

共有メモリ型計算機=同じデータにアクセス可能
コア コア … コア
メモリ
OpenMPプログラム例#pragma omp parallel for
for(i=0;i<M;i++){for(j=0;j<N;j++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}© 2023 Fujitsu Limited

OpenMPでよく記述される並列モデル
マスタースレッド// 逐次処理…
#pragma omp parallel{// 並列処理A…
}// 逐次処理…
#pragma omp parallel{// 並列処理B…
}// 逐次処理…

スレッド生成(fork)
スレッド
スレッド消滅(join)

スレッド生成スレッド消滅16© 2023 Fujitsu Limited

OpenMPでよく使われる並列化⚫ ループを対象とした並列処理
逐次コード
for(i=0;i<N;i++){C[i]=a[i]+b[i];}並列化コード#pragma omp parallel for
for(i=0;i<N;i++){C[i]=a[i]+b[i];}⚫ タスク並列なども記述できるが、あまり使わない17© 2023 Fujitsu Limited

コアの並列処理効率の確認
1. ループの回転数が十分あれば、複数コアの並列処理の効率は高いことが多

いため、まずはループの回転数が十分かを確認する⚫ printf文をソースコード中に挿入するなどして確認

2. キャッシュメモリなどの共有資源での競合で性能が向上しないこともあるため、並列化させるコア数を変更して性能を確認⚫ デフォルトの設定のOpenMPのスレッド生成数＝コア数⚫ スレッド生成数は、環境変数 OMP_NUM_THREADSや

omp_set_num_threads(int num)関数で変更可能

18© 2023 Fujitsu Limited

コアの並列処理効率の向上⚫ 並列化対象のループの回転数を増やす⚫ 多重ループであれば複数のループを対象にする（collapse節を挿入）⚫ 対象となるループを変更する#pragma omp parallel for collapse (2)for(i=0;i<M;i++){for(j=0;j<N;j++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}⚫ データ構造とアルゴリズムレベルで見直す⚫ 細かい最適化をする⚫ 立ち上げるスレッド数を減らす⚫ omp parallel節の範囲をなるべく広げて、スレッド生成コストを下げる⚫ 自動で挿入されるバリア同期を削る（nowait節を挿入）

19© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
SIMD

20

© 2023 Fujitsu Limited

Single Instruction Multiple Data (SIMD)⚫ 1命令を複数のデータに同時に適用する機構⚫ 複数データを一度に処理することで演算性能を向上⚫ 近年のCPUの多くはSIMD機構を搭載⚫ x86: SSE, AVX2, AVX512⚫ ARM: Neon, Scalable Vector Extensions(SVE)

スカラー演算for(i=0;i<N;i++)B[i] = 2 * A[i];* * * *

2 * 2 → 4A[0]2 2 2 2

A[0]

B[0]

0 1 2 3 4 5 6 7 8 9

メモリ

0 1 2 30 2 4 0 0 0 0 0 0 0

SIMD演算0 2 4 6B[0]0 1 2 3 4 5 6 7 8 921

0 2 4 6 0 0 0 0 0 0

メモリ© 2023 Fujitsu Limited

Scalable Vector Extensions (SVE)⚫ ARMアーキテクチャの拡張SIMD命令セット⚫ Vector Length Agnostic (VLA)⚫ SVE命令はベクトル長として128-bit ~ 2048-bitまで128-bitの倍数をサポート⚫ ハードウェアは任意のベクトル長を実装することが可能⚫ ベクトル長に依存しないプログラムを記述可能→ 汎用性がある高速なプログラムの作成を可能に

Grace CPU128-bit SVE

Graviton3
256-bit SVE
22

A64FX
512-bit SVE© 2023 Fujitsu Limited

SVEの特徴⚫ SVEレジスタの長さ(要素数)は実行時に取得可能

cntd x1;8(=512/64)⚫ cnt[b,h,w,d]命令を使用⚫ b: 8-bit, h: 16-bit, w:32-bit, d:64-bit要素換算⚫ プレディケーション

for (i=0;i<N;i++)⚫ SVEレジスタの要素ごとに命令の適用の有無を変えられる⚫ 多くのSVE命令がプレディケートレジスタ付きの命令

whilelt i,N N-1
predicateregister⚫ 利点→ 任意長のループのSIMD化が容易 (端数処理)→ if文を含む処理のSIMD化が可能
C/C++コードでの使用方法は後述
23

predicateregisterN

N+1 N+2

1

0

0

0

3

1

-5

4

2

2

2

2

1

0

1

1

5

１

-3

6

© 2023 Fujitsu Limited

A64FXのSVE演算部⚫ A64FXの各コアに、2本のSVE演算パイプライン⚫ SVE幅: 512-bit⚫ FP64: 8要素分

⚫ FP32: 16要素分⚫ FP16: 32要素分

1度にまとめて処理可能

4命令/cycle⚫ 命令発行部⚫ 最大4命令発行/cycle

アプリ性能を高めるためには2本のSVE演算パイプライン

SVE命令x2/cycleを目指して
SVE命令の割合が高いプログラムを作る

A64FXのコア内のプロセッシングステージ
24© 2023 Fujitsu Limitedコンパイラ最適化: SVE命令が使われるようにする⚫ コンパイラ最適化の確認: 最適化メッセージ出力オプションを追加⚫ fcc/FCCのメッセージ出力オプション: -Koptmsg=2⚫ 最適化されない → コンパイラ最適化が効きやすいようにコードを修正⚫ 最適化できない原因は多種多様 (例: ループ間での依存関係がある)
43 void ReLU(dtype *inout, int num_elements) {44
45
for (int i = 0; i < num_elements; i++)46
inout[i] = (inout[i] >= 0) ? inout[i] : 0.0;
47 }

最適化
メッセージ

FCC main.cpp ¥-Kfast -Koptmsg=2

jwd6001s-i "main.cpp", line 45: SIMD conversion is applied to thisloop with the loop variable 'i'.
jwd8204o-i "main.cpp", line 45: This loop is software pipelined.
jwd8205o-i "main.cpp", line 45: The software-pipelined loop is chosen
at run time when the iteration count is greater than or equal to 64.
25© 2023 Fujitsu Limited

指示文: SVE命令が使われるようにする⚫ 最適化でSIMD化されないコードでも、指示文でSIMD化できる可能性あり⚫ SIMD化を適用するforループにOpenMP指示文を挿入⚫ 指示文: #pragma omp simd
36
37
38
39

for (int m = 0; m < ic; m++)for (int l = 0; l < oc; l++) {Out(i,j,k,l) += W(l,m) * In(i,j,k,m);}

36 for (int m = 0; m < ic; m++)37 #pragma omp simd38
for (int l = 0; l < oc; l++) {39
Out(i,j,k,l) += W(l,m) * In(i,j,k,m);
40}

FCC main.cpp -Kfast -Koptmsg=2

FCC main.cpp -Kfast,openmp -Koptmsg=2
jwd6208s-i "main.cpp", line 38: SIMD
conversion is not applied to this loop
because the uncertain order of thedefinition and reference to variable 'out'
may cause different results from serial
execution.

jwd6501s-i "main.cpp", line 38: Thisloop with variable 'l' is vectorizedby OpenMP SIMD.
26© 2023 Fujitsu Limited

SVE命令の使用状況確認 1/3⚫ コンパイラの最適化メッセージ⚫ Fujitsuコンパイラのオプション”-Nlst=t”と”-Koptmsg=2”を追加⚫ “-Nlst=t”: ソースコードに最適化情報を付与した*.lstファイルを生成⚫ “-Koptmsg”: SIMD化等の最適化機能が動作したことをメッセージ出力••

アンローリング:2
SIMD化
38
39
40

p

2v
2v
2v

直後のfor文に適用した最適化情報<<< Loop-information Start >>><<< [OPTIMIZATION]• 16要素のSIMD化• ハードウェアにプリフェッチ利用推奨<<<
SIMD(VL: 16)<<<
PREFETCH(HARD) Expected by compiler :<<<(unknown)<<< Loop-information End >>>for (int l = 0; l < oc; l++) {ConvOut(i, j, k, l) += ConvWei(l, m) * ConvIn(i, j, k, m);}*.lstファイルの例
27© 2023 Fujitsu Limited

SVE命令の使用状況確認 2/3⚫ fippプロファイラによる確認
1. プロファイルデータの生成: fipp –C –d <dir name> -Icall ./a.out
2. プロファイルデータの解析: fipppx –A –Icpupa –d <dir name>⚫ 最適なプロファイルオプションはドキュメント参照
Execution
Floating-point Mem throughput Mem throughputtime(s)GFLOPS
peak ratio(%)(GB/s)
peak ratio(%)-------------------------------------------------------------------------------26.5010
1.93770.22940.19400.0189-------------------------------------------------------------------------------26.5010
1.93770.22940.19400.0189Application
Process

SIMD命令に占める
Effective
Floating-point SIMD inst.
SVE operation
instruction
operationrate(%)
rate(%) SVE命令の割合-------------------------------------------------------------------------------315855961257
51350899939
0.2046
7.1878
Application-------------------------------------------------------------------------------315855961257
51350899939
0.2046
7.1878Process
28

解析出力例

0

0

© 2023 Fujitsu Limited

SVE命令の使用状況確認 3/3⚫ 逆アセンブルによるアセンブリ命令列の確認⚫ コマンド: objdump –d <ファイル>⚫ ベクトルレジスタの確認: “z”+数字⚫ zで始まるレジスタが使われていない場合、SVE命令ではない
864:
868:
86c:
870:
874:
878:
87c:
880:

ldr
fmul
ldr
fadd
str
add
cmp
b.ne

d0, [x20, x1]
d0, d0, d0
d1, [x19, x1]
d0, d1, d0
d0, [x19, x1]
x1, x1, #0x8
x1, #0x320
864 <main+0x50>

7b0:
7b4:
7b8:
7bc:
7c0:
7c4:
7c8:

ld1d
{z1.d}, p0/z, [x19, x1, lsl #3]
ld1d{z0.d}, p0/z, [x20, x1, lsl #3]
fmad
z0.d, p1/m, z0.d, z1.d
st1d
{z0.d}, p0, [x19, x1, lsl #3]
add
x1, x1, x2
whilelo p0.d, w1, w0
b.ne
7b0 <main+0xb0> // b.any

SVE命令を含む命令列

スカラー命令のみの命令列
29© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
行列積を例とした並列プログラムの記述方法の説明

33© 2023 Fujitsu Limited例題：密行列積⚫ 密行列と密行列の積: C=A x B
N

K

行列B

K
M

行列A

行列C

単純に記述した行列積
for(i=0;i<M;i++){for(j=0;j<N;j++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}

メモリ上のデータの並び

34© 2023 Fujitsu Limited複数コアの並列処理の記述⚫ OpenMPで記述することが多い⚫ 並列化したいループの直前にOpenMPの指示文を挿入⚫ parallel節：スレッドを生成する⚫ for節：直後のfor文を対象に並列処理#pragma omp parallel for
for(i=0;i<M;i++){privateやsharedで変数の特徴を指定した
for(j=0;j<N;j++){ほうがよい（ループ変数ぐらいであれば不要）
for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}for(i=0;i<M;i++){for(j=0;j<N;j++){for(i=0;i<M;i++){for(k=0;k<K;k++){#pragma omp parallel for
C[i][j]+=a[i][k]*b[k][j];for(j=0;j<N;j++){}}}for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];
}}}
35

•

M>>Nのときは、コア間の並列処理の効率が高いからこちらがよい•N>>MのときはNのループを対象にしたほうがよい
omp parallel節はループ内部に無い方がよいので外に追い出したほうがよい（次ページ）•© 2023 Fujitsu Limited複数コアの並列処理の記述
修正例1:parallel節を外に出す 修正例2: ループの順番の入れ替え
for(i=0;i<M;i++){#pragma omp parallel for
for(j=0;j<N;j++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}#pragma omp parallel
for(i=0;i<M;i++){#pragma omp for
for(j=0;j<N;j++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}#pragma omp parallel for
for(j=0;j<N;j++){for(i=0;i<M;i++){for(k=0;k<K;k++){C[i][j]+=a[i][k]*b[k][j];}}}⚫ A64FXではスレッド生成やバリア同期のコストが大きいため、ループ内部に

parallel節やfor節はないほうがよい⚫ 今回の例では、修正例1のようにparallel節を外に出すだけのではなく、ループの順序を入れ替えるほうがよい

36© 2023 Fujitsu Limited

SIMD演算器の活用⚫ 最も内側のループでメモリ上の連続したデータに対して処理したい#pragma omp parallel for
for(i=0;i<M;i++){for(j=0;j<N;j++){for(k=0;k<K;k++){c[i][j]+=a[i][k]*b[k][j];}}}• 配列aはk方向で連続アクセスとなっているのでOK• 配列b,cはk方向で連続アクセスとなっていないのNG⚫ j方向のループを一番内側にする#pragma omp parallel for
for(i=0;i<M;i++){for(k=0;k<K;k++){for(j=0;j<N;j++){c[i][j]+=a[i][k]*b[k][j];}}}• 配列b,cはj方向で連続となったためOK• 配列aは最も内側のループでは同じデータを処理し続ける（おそらくSIMD化はされない）

37© 2023 Fujitsu Limited

(参考）前ページのコードの問題と改善
c[0][0]c[0][1]c[0][2]a[0][0]+=a[0][0]a[0][0]b[0][0]xb[0][1]b[0][2]…

…

• aを8要素複製する形でレジ
スタ上にロードしたい• ソースコード上で記述すると、メモリ上でのデータの複製になるので遅くなる→ ACLEやインラインアセン
ブラで書いたほうがよいはず…

#pragma omp parallel for
for(i=0;i<M;i++){for(k=0;k<K;k++){for(j=0;j<N;j++){c[i][j]+=a[i][k]*b[k][j];}}}c[0][7]a[0][0]b[0][7]

さらに高速な行列積を記述したいときは、配列a, b, cをキャッシュヒットするように適切なサイズでブロック化して、最内ループをある程度ループ展開して記述する
38© 2023 Fujitsu Limited

A64FXアーキテクチャと並列プログラミング
プロファイラを使ったチューニング手順39© 2023 Fujitsu Limitedチューニングの流れ
1. プログラムのホットスポット（実行時間の割合が大きい箇所）を見つける
2. 対象ホットスポットにおいて、期待している性能がでているかを確認する
3. 性能が出ていなければ、性能が出ていない理由を分析する
4. 性能向上方法を考えて実装して、性能を測定する
5. 十分な性能が出ていなければ3に戻り、性能に満足できていれば次に進む

6. 全体性能が満足できる値となっていなければ、次のホットスポットを対象とし、2に戻る
7. 終わり40© 2023 Fujitsu Limited

1. ホットスポットを見つける⚫ プロセッサ内にあるHardware counterを使うプロファイリングツールを使う⚫ 富士通コンパイラを使っているなら、fipp, fapp
⚫ そうでないなら、Linux Perfがおすすめ⚫ ホットスポット解析のコマンドを使って関数ごとの実行時間を集計する

41© 2023 Fujitsu Limited

2. 期待している性能がでているか確認する⚫ 方法1：他のCPUで実行時間を計測して比較する⚫ 同一コードを他のCPUで実行する⚫ コンパイラ最適化の影響が出にくいように同じコンパイラと最適化オプションを使うほうがよい⚫ 方法2：理論性能値と比較する⚫ 演算回数とデータ転送量を人手で計算する⚫ 実行時間を取得し、時間あたりの演算数と時間あたりのデータ転送量を算出→ どちらかの実行効率が50%出ていれば十分42

理論演算性能（倍精度）

3.4 TFLOPS

理論演算性能（単精度）

6.8 TFLOPS

メモリバンド幅

1024 GiB/s© 2023 Fujitsu Limited

3. 性能が出ていない理由を分析する⚫ プロファイラで詳細を調べる前に以下を確認する⚫ コア間の並列化ができているか（=コア数を増やしたときの性能向上効果は高いか）⚫ SIMD化ができているか（=SIMD命令が発行されているかを確認する）⚫ プロファイラで詳細を確認⚫ （FAPPによる詳細プロファイルの取り方を調べて書く）

43© 2023 Fujitsu Limitedチューニングの流れ
1. プログラムのホットスポット（実行時間の割合が大きい箇所）を見つける
2. 対象ホットスポットにおいて、期待している性能がでているかを確認する
3. 性能が出ていなければ、性能が出ていない理由を分析する
4. 性能向上方法を考えて実装して、性能を測定する
5. 十分な性能が出ていなければ3に戻り、性能に満足できていれば次に進む

6. 全体性能が満足できる値となっていなければ、次のホットスポットを対象とし、2に戻る
7. 終わり44© 2023 Fujitsu Limited目次
13:00 13:30

スパコンの使い方など

13:30 14:20

A64FXアーキテクチャと並列プログラミング（座学）

14:30 15:40

OpenMPを活用した並列プログラミング演習
3重ループを含むプログラムで、OpenMPの指示
文挿入によるスレッド並列やSIMD演算を活用するコードを取り扱います。
また、 Intrinsic（ACLE）を使ってSIMD化する例も取り上げます。

15:50 17:00

プロファイラを活用したチューニング演習

45© 2023 Fujitsu Limited

プログラミング演習
複数コアの活用
SIMD演算器の活用

46© 2023 Fujitsu Limited

演習前の準備⚫ 下記ファイルをコピー

cp /work/gt00/share/lecture_a64fx.tgz /work/gt00/{アカウント
名}/

⚫ 解凍

cd /work/gt00/{アカウント名}/
tar zxvf lecture_a64fx.tgz

47© 2023 Fujitsu Limited

演習概要⚫ 演習1: OpenMP 基本 （15分程度）⚫ 演習2: OpenMPディレクティブ挿入（25分程度）⚫ 演習3: ACLEを活用したSIMD化 (30分程度）

48© 2023 Fujitsu Limited

演習1の概要⚫ OpenMPディレクティブを挿入して、プログラムを高速化する⚫ 対象プログラム：単純なサンプルプログラム
sum = 0;#define LOOP_SIZE 1280
for ( i = 0; i < LOOP_SIZE ; i++ ) {a[i] = b[i] + c[i];}for ( i = 0; i < LOOP_SIZE ; i++ ) {d[i] = b[i] + c[i];}for ( i = 0; i < LOOP_SIZE ; i++ ) {sum += b[i] + c[i];}49© 2023 Fujitsu Limited

演習1 実行方法⚫ 作業ディレクトリ
⚫ /work/gt00/{アカウント名}/omp1/asis⚫ 対象ファイル: main.c (55行 ~ 68行目）⚫ コンパイル⚫ pjsub -g gt00 make.sh⚫ 実行⚫ pjsub -g gt00 submit.sh⚫ 実行結果Validation check is OK
total time:
50

63.029 [msec]© 2023 Fujitsu Limited

演習1の内容⚫ 演習1-1: 対象コードにOpenMPディレクティブを挿入して結果検証をクリアする⚫ 3つ目のループはリダクション演算なので注意（並列化しないか、reductionの指示文を使う。

使い方は検索してみてください）⚫ 演習1-2: OpenMPディレクティブを工夫して高速化する⚫ 目標実行時間：30 msec

14:30 - 13:45 演習1 (13:42に解説を始めます）
14:45 - 15:10 演習2 (15:03に解説を始める予定です）
51© 2023 Fujitsu Limited

演習1-1 回答例#pragma omp parallel forfor ( i = 0; i < LOOP_SIZE ; i++ ) {a[i] = b[i] + c[i];}#pragma omp parallel forfor ( i = 0; i < LOOP_SIZE ; i++ ) {d[i] = b[i] + c[i];}#pragma omp parallel for reduction(+:sum)for ( i = 0; i < LOOP_SIZE ; i++ ) {sum += b[i] + c[i];}63 msec#pragma omp parallel{#pragma omp forfor ( i = 0; i < LOOP_SIZE ; i++ ) {a[i] = b[i] + c[i];}#pragma omp forfor ( i = 0; i < LOOP_SIZE ; i++ ) {d[i] = b[i] + c[i];}#pragma omp for reduction (+:sum)for ( i = 0; i < LOOP_SIZE ; i++ ) {sum += b[i] + c[i];}}38 msec

スレッド生成回数が少ない右のコードのほうが高速
52© 2023 Fujitsu Limited

演習1-2回答例⚫ nowait節をつけることで暗黙のバリ#pragma omp parallel{

ア同期を削れる#pragma omp for nowaitfor ( i = 0; i < LOOP_SIZE ; i++ ) {a[i] = b[i] + c[i];}#pragma omp for nowaitfor ( i = 0; i < LOOP_SIZE ; i++ ) {d[i] = b[i] + c[i];}#pragma omp for reduction (+:sum) nowaitfor ( i = 0; i < LOOP_SIZE ; i++ ) {sum += b[i] + c[i];}⚫ 38msec → 25msecへと改善}
53© 2023 Fujitsu Limited

演習概要⚫ 演習1: OpenMP TIPS （10分程度）⚫ 演習2: OpenMPディレクティブ挿入（20分程度）⚫ 演習3: ACLEを活用したSIMD化 (30分程度）

54© 2023 Fujitsu Limited

演習2の概要⚫ OpenMPディレクティブを挿入して、行列積を高速化する⚫ 対象プログラム：密行列と密行列の積: C=A x B
NK#define a(J,I) A[J*lda + I]
#define b(J,I) B[J*ldb + I]
#define c(J,I) C[J*ldc + I]// 90行目あたり
for ( i = 0; i < M; i++ ) {for ( j = 0; j < N; j++ ) {for ( k = 0; k < K; k++) {c(i,j) += a(i,k) * b(k,j);
}}}

行列B

K
M

行列A

行列C

メモリ上のデータの並び
55© 2023 Fujitsu Limited

演習 実行方法⚫ 作業ディレクトリ
⚫ /work/gt00/{アカウント名}/omp2/asis⚫ 対象ファイル: main.c (90行 ~ 97行目）⚫ コンパイル⚫ pjsub -g gt00 make.sh⚫ 実行⚫ pjsub -g gt00 submit.sh⚫ 実行結果Validation check is OK
matrix size, elapsed time, GFLOPS
M=10 N=4096 K=4096, 14.322, 0.023
56© 2023 Fujitsu Limited

演習内容⚫ 演習2-1: 対象コードにOpenMPディ

レクティブを挿入して高速化する⚫ 目標性能: 0.7GFLOPS⚫ M, N, Kの値に注意⚫ 演習2-2: 自由にコードを変形して高速化する (Mの値を大きくしてもよい）#define a(J,I) A[J*lda + I]
#define b(J,I) B[J*ldb + I]
#define c(J,I) C[J*ldc + I]// 90行目あたり
for ( i = 0; i < M; i++ ) {for ( j = 0; j < N; j++ ) {for ( k = 0; k < K; k++) {c(i,j) += a(i,k) * b(k,j);}}}⚫ 目標性能：3GFLOPS

14:45 - 15:10 演習2 (15:03に解説を始める予定です）
57© 2023 Fujitsu Limited

演習2-1: 解答例⚫ iループの回転数が少ないため、#pragma omp parallel for collapse(2)for ( i = 0; i < M; i++ ) {for ( j = 0; j < N; j++ ) {for ( k = 0; k < K; k++) {c(i,j) += a(i,k) * b(k,j);}}}collapse節を挿入して並列化対象
ループを増やすmatrix size, elapsed time, GFLOPS
M=10 N=4096 K=4096, 0.461, 0.728
58© 2023 Fujitsu Limited

演習2-2: 解答例
N方向をブロック化して最内ループにすることで同時に実行できる命令数を増やす

ライブラリをコールする
cblas_dgemm (CblasRowMajor,
CblasNoTrans,
CblasNoTrans,
M, N, K,
alpha,
A, lda,B, ldb,beta,
C, ldc );#pragma omp parallel for collapse(2)for ( i = 0; i < M; i++ ) {for ( jj = 0; jj < N; jj+=N_BLK) {for ( k = 0; k < K; k++) {for ( j = jj; j < jj+N_BLK; j++ ) {c(i,j) += a(i,k) * b(k,j);
}
}
}

}

matrix size, elapsed time, GFLOPS
M=10 N=4096 K=4096, 0.006, 56.290

matrix size, elapsed time, GFLOPS
M=10 N=4096 K=4096, 0.121, 2.779
59© 2023 Fujitsu Limited

演習2-2: 解答例2
Mを4096にしてライブラリをコールする(その2)cblas_dgemm (CblasRowMajor,
CblasNoTrans,
CblasNoTrans,
M, N, K,
alpha,
A, lda,B, ldb,beta,
C, ldc );Validation check is OK
matrix size, elapsed time, GFLOPS
M=4096 N=4096 K=4096, 0.073, 1894.299
60© 2023 Fujitsu Limited

演習概要⚫ 演習1: OpenMP TIPS （10分程度）⚫ 演習2: OpenMPディレクティブ挿入（20分程度）⚫ 演習3: ACLEを活用したSIMD化 (30分程度）

61© 2023 Fujitsu Limited

演習用プログラムの格納先⚫ /work/gt00/share/ACLE⚫ memcpy⚫ H-gate⚫ PConvReLUBNorm62© 2023 Fujitsu LimitedACLE⚫ C/C++で組み込み関数を用いてSVE命令を使用可能にする拡張機能⚫ VLA(Vector Length Agnostic)プログラミング⚫ SVEは128-bit ~ 2048-bitのベクトル長をサポートする命令体系⚫ 可変のベクトル長を意識したプログラミングが可能⚫ 同一コードで、異なるベクトル長のSVEをサポートするプロセッサで動作可能#include <arm_sve.h>for(int i=0; i < N; i++){float val = array[i];val += val*val;
array[i] =val;}

for(int i=0; i < N; i+=svcntw()){
svbool_t pg = svwhilelt_b32(i, N);svfloat32_t sv_val = svld1(pg, &array[i]);svmla_x(pg, sv_val, sv_val, sv_val);
svst1(pg, &array[i], sv_val);}
任意のベクトル長の63

SVE搭載プロセッサで実行可能© 2023 Fujitsu Limited

ACLEを使うための事前準備⚫ ヘッダファイルの追加⚫ arm_sve.h ← SVE data typeとintrinsicの追加⚫ コンパイラオプションの追加⚫ ターゲットアーキをSVEサポートのアーキに⚫ -march=armv8.2-a+sveなど#include <arm_sve.h> //ヘッダファイル追加

コンパイル例
gcc –O3 –march=armv8.2-a+sve main.cpp

for(int i=0; i < N; i+=svcntw()){
svbool_t pg = svwhilelt_b32(i, N);svfloat32_t sv_val = svld1(pg, &array[i]);svmla_x(pg, sv_val, sv_val, sv_val);
svst1(pg, &array[i], sv_val);}
64© 2023 Fujitsu Limited

ACLE: SVEレジスタの要素数の取得⚫ svcnt[b, h, w, d]()関数⚫ 8-, 16-, 32-, 64-bitデータ型それぞれに対応する関数がある⚫ svcntw(): 32-bitデータ換算の要素数を取得関数

Grace CPU Graviton3

A64FX#include <arm_sve.h>svcntb()

16

32

64

for(int i=0; i < N; i+=svcntw()){//処理}

svcnth()

8

16

32

svcntw()4816

ループ変数の増加量に使用する例

svcntd()

2

4

8

65

© 2023 Fujitsu Limited

ACLEでのプレディケート変数⚫ データ型: svbool_t

val1:

3

1

-5

4

⚫ 命令を適用するSVEレジスタの要素を指定

val2:

2

2

2

2

1

0

1

1

5

0

-3

6

するためのフラグ

predicate register“pg”⚫ SVE命令セットと同じでACLEの多くの組み込み関数の引数となっている⚫ フラグがfalseのときの要素の扱いによってACLE組
み込み関数のプレフィックスが異なる⚫ “_x”: falseの要素の値を保証しない⚫ “_z”: falseの要素は0が代入される

res:

res = svadd_z(pg, val1, val2);

val1:

3

1

-5

4

val2:

2

2

2

2

1

0

1

1

5

?

-3

6

predicate register⚫ “_m”: 入力要素が代入されるなど…

“pg”→今後使用しない要素の場合は”_x”がおすすめ

res:

res = svadd_x(pg, val1, val2);
66© 2023 Fujitsu Limited

ACLE: プレディケート変数の設定⚫ 全要素分のフラグをtrueに設定

predicate register“pg” 1 1 1 1 1 1 1 1⚫ 関数名: svptrue_bX()⚫ “_bX”: X-bitの全要素分のフラグがtrue⚫ 特定パターンのプレディケートの設定

svbool_t pg = svptrue_b64();

predicate register⚫ 関数名: svptrue_pat_b64(pattern);“pg” 1 1 1 1 0 0 0 0

svbool_t pg =svptrue_pat_b64(SV_VL4);⚫ パターン: SV_VL[1,2,4,8,16,32]など⚫ SV_VL2 → 下位2要素分がtrueとなるのプレディケートレジスタ設定⚫ 動的なプレディケートレジスタの設定⚫ オペランド比較でプレディケートレジスタを設定⚫ 関数例⚫ svwhile[le, lt, ge, gt]⚫ svcmp[eq, ne, le, lt, ge, gt]

4 5 6 7 8 9 10 11< < < < < < < <N: N N N N N N N N
predicate register“pg” 1 1 1 1 1 1 0 0

svbool_t pg = svwhilelt(4, N);67© 2023 Fujitsu Limited

ACLE: SVEレジスタへのデータ読み込み⚫ float64_tの配列からSVEレジスタにデータを読み込む命令
svfloat64_t svld1(svbool_t pg, float64_t *base);⚫ 戻り値 svfloat64_t: SVEレジスタ用のデータ型⚫ 引数1 svbool_t : 使用するプレディケートレジスタ⚫ 引数2 float64_t*: 読み込みデータの先頭アドレス⚫ 使用例: svfloat64_t val = svld1(svptrue_b64(), &A[1]);⚫ svptrue_b64() : すべての要素がactiveのプレディケートレジスタを返す関数

float64_t *A
0

1

2

3

svfloat64_t val
4

5

メモリ上

6

7

8

1

9

68

2

3

4

5

6

7

8

512-bit SVEレジスタ© 2023 Fujitsu Limited

ACLE: SVEレジスタからのデータ書き込み⚫ SVEレジスタからfloat64_tにデータを書き込む命令
svst1(svbool_t pg, float64_t *base, svfloat64_t val);⚫ 引数1 svbool_t : 使用するプレディケートレジスタ⚫ 引数2 float64_t*: 読み込みデータの先頭アドレス⚫ 引数3 svfloat64_t: 書き込むデータを保持しているSVEレジスタ⚫ 使用例: svst1(svptrue_pat_b64(SV_VL2), &A[1], val);⚫ svptrue_pat_b64() : 特定パターンの要素がactiveのプレディケートレジスタを返す関数⚫ SV_VL2: 2要素目までがactiveになるプレディケートパターン

float64_t *A

svfloat64_t val
100 200

3

4

5

6

7

0

8

512-bit SVEレジスタ69

100 200

3

4

5

メモリ上

6

7

8

9

© 2023 Fujitsu Limited

ACLE: スカラーレジスタからSVEレジスタへブロードキャスト⚫ スカラー値をsvfloat64_tにブロードキャスト
svfloat64_t svdup_f64(float64_t op);
⚫ 戻り値 svfloat64_t : SVEレジスタ用のデータ型⚫ 引数 float64_t: ブロードキャストするオペランド⚫ 使用例: svfloat64_t val = svdup_f64(op);float64_t op

svfloat64_t val

1.1

1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1

スカラーレジスタ

512-bit SVEレジスタ70© 2023 Fujitsu Limited

ACLE: 加・減・乗算/乗加算⚫ svfloat64_tに対するACLE関数を示す⚫ 異なる型でも同様に記載可能
処理

ACLE
プレフィックス
関数

加算

svadd

op1 + op2

減算

svsub

op1 – op2

乗算

計算結果
svmul _[x, z, m] svfloat64_t

除算

svdiv

乗加算 svmla

戻り値

引数

計算内容

svbool_t pg, svfloat64_t op1, svfloat64_t op2
op1 * op2
op1 / op2

svbool_t pg, svfloat64_t op1, svfloat64_t op2, svfloat64_t op3 op1 + op2 * op371© 2023 Fujitsu Limited

ACLE演習: 配列コピー⚫ 内容: 配列Aから配列Bへデータをコピー⚫ 配列の各要素はcomplex<double>型: 64-bit x 2⚫ 注意事項⚫ ACLE化では、complexのload/storeがないためdoubleポインタへ変換が必要→ double *ptr = (double *)in.data();void memcpy(vector<complex<double>> &in,vector<complex<double>> &out) {for (int i = 0; i < out.size(); i++)out[i] = in[i];}AsIsコード72

Step1
ACLEによるSIMD化

Step2
OpenMPによるスレッド並列© 2023 Fujitsu Limited

ACLE演習: 配列コピー 実行方法
⚫ 作業ディレクトリ

⚫ 演習⚫ /work/gt00/{アカウント⚫ Step1: ACLEによるSIMD化名}/ACLE/memcpy/asis⚫ 対象ファイル: memcpy.cpp→ 目標: 約 40 GB/sec⚫ Step2: +OpenMPによるスレッド並列→ 目標(12コア, 1CMG): 約125 GB/sec⚫ コンパイル⚫ pjsub -g gt00 make.sh⚫ 注意事項⚫ 実行⚫ ACLE化では、complexのload/storeがない

ため、doubleポインタへ変換が必要⚫ pjsub -g gt00 submit.sh⚫ double *ptr = (double *)in.data();⚫ 実行結果 == Memcpy benchmark ==⚫ double型でアクセスする場合は要素数が2倍

Array size[GB]: 2
memcpy exection time[msec]: XXXX
Bandwidth [GB/sec]: XXXX
Validation: PASSED⚫ スレッド数設定⚫ export OMP_NUM_THREADS=1273© 2023 Fujitsu Limited

ACLE演習: 配列コピー ACLE+OpenMP例
void memcpy(vector<complex<double>>& in, vector<complex<double>>& out){// double型のポインタ作成
double *ptr_in = (double *)in.data();double *ptr_out = (double *)out.data();⚫ double型ポインタの配列の見え方⚫ ptr_inからは2倍の要素数の配列にみえる// double型換算の要素数算出
size_t num = out.size() * 2;// double型換算の要素数でforループ実行#pragma omp parallel forfor (int i = 0; i < num; i += svcntd()) {// [i, i+1, …, i+7]がnum未満でtrueとなる// プレディケートレジスタを作成
svbool_t pg = svwhilelt_b64(i, num);in[0] in[1]1.0 1.5 3.0 2.0 -3.0 1.1 -0.3 4.2 0.8 -1.7

ptr_in[0] ptr_in[1]⚫ val = svld1(pg, &ptr_in[0])8要素

svfloat64_t val;// double型で連続(最大)8要素の読み込み
val = svld1(pg, &ptr_in[i]);// double型で連続(最大)8要素の書き込み
svst1(pg, &ptr_out[i], val);
}}

実部 虚部

val: 1.0 1.5 3.0 2.0 -3.0 1.1 -0.3 4.2
1.0 1.5 3.0 2.0 -3.0 1.1 -0.3 4.2 0.8 -1.7
74

Complex<double>4要素分© 2023 Fujitsu Limited

ACLE演習2:量子ゲート操作 (H-gate)⚫ State Vector方式の量子シミュレーション⚫ 全ての量子状態を保持し量子ゲート操作による更新を行う⚫ n量子ビットのシミュレーション→ 𝟐𝒏 要素の複素配列に対する演算⚫ Hゲート(アダマールゲート)操作⚫ 重ね合わせ状態を作るゲート⚫ 配列の2要素間の演算を全ペアに対して実行

観測すると必ず”00”
00

01
10
11

2qubit状態 初期

1qubit目
Hゲート
適用

観測すると
50%で”00”か”01”
00

01

疑似コード
mask = (1 << target)
mask_l = mask - 1
target: 対象qubit
mask_h = ~mask_l
for i=0 to 2𝑛−1
idx0 ← (i & mask_l) + ((i & mask_h)<<1)
idx1 ← idx0 + mask
val0 ← state[idx0]; val1 ← state[idx1]
state[idx0] ←10state[idx1] ←end for1175

val0+val12
val0−val1
2© 2023 Fujitsu Limited

ACLE演習2: Hゲート処理
H-gate
対象: 3qubit目
初期状態
0

000

1+0i

1

001

0+0i

2

010

0+0i

3

011

0+0i

4

100

0+0i

5

101

0+0i

6

110

0+0i

7

111

0+0i

全要素が更新されるまで繰り返す
1. 要素番号の3bit目のみ
異なる2要素を読み込む• 例: “000”と”100”
2. Hゲート適用後、配列に書
き戻す
要素番号の
3bit目が0の要素
val0+val1
,
2

要素番号の
3bit目が1の要素
val0−val1
2

H-gate
適用後の状態
0.707+0i
0+0i
0+0i
0+0i
0.707+0i

0+0i
0+0i
0+0i

番号の3bit目が0 番号の3bit目が1
に書き込む
に書き込む
76

© 2023 Fujitsu Limited

ACLE演習2: 実行条件
⚫ 配列サイズ: 227 = 128M要素⚫ 配列データ型: Complex128 (実部, 虚部ともにfloat64_t)⚫ Hゲート対象qubit: 20qubit目⚫ 要素番号で20bit目が異なる要素がペアとなり、Hゲート操作を適用⚫ SVEベクトル長内での複雑なデータ移動は必要ない⚫ 注意事項⚫ ACLE化では、complexのload/storeがないため、doubleポインタへ変換が必要⚫ double *ptr = (double *)in.data();⚫ スレッド数設定⚫ export OMP_NUM_THREADS=1277© 2023 Fujitsu Limited

ACLE演習2: H_gate 実行方法
⚫ 作業ディレクトリ: asis

⚫ 演習

⚫ /work/gt00/{アカウント⚫ AsIs⚫ 性能: 約 25 sec名}/ACLE/H-gate/asis⚫ 対象ファイル: hgate.cpp⚫ Step1: ACLEによるSIMD化→ 目標: 約 1 sec以下⚫ Step2: +OpenMPによるスレッド並列→ 目標(12コア, 1CMG): 約 0.3 sec以下⚫ コンパイル⚫ pjsub -g gt00 make.sh⚫ 実行⚫ pjsub -g gt00 submit.sh⚫ 実行結果 == H-gate benchmark ==Array size[GB]: 2
Target qubit: 20
H-gate exection time[msec]: XXXX
Validation: PASSED78© 2023 Fujitsu Limited

使用するACLE命令一覧⚫ SVEレジスタの要素数取得⚫ svcntd(): 64-bit換算での要素数を取得⚫ プレディケートレジスタの設定⚫ svbool_t pg = svptrue_b64()⚫ データ読み込み⚫ svfloat64_t sv_val = svld1(svbool_t, float64_t *base)⚫ データ書き込み⚫ svst1(svbool_t, float64_t *base, svfloat64_t val)
⚫ SVEレジスタの各要素にスカラー値をブロードキャスト⚫ svfloat64_t sv_val = svdup_f64(float64_t val)
⚫ 浮動小数点演算⚫ 加算: svfloat64_t sv_val3 = svadd_[x, z, m](svbool_t pg, svfloat64_t sv_val1, svfloat64_t sv_val2)⚫ 減算: svfloat64_t sv_val3 = svsub_[x, z, m](svbool_t pg, svfloat64_t sv_val1, svfloat64_t sv_val2)⚫ 乗算: svfloat64_t sv_val3 = svmul_[x, z, m](svbool_t pg, svfloat64_t sv_val1, svfloat64_t sv_val2)⚫ 除算: svfloat64_t sv_val3 = svdiv_[x, z, m](svbool_t pg, svfloat64_t sv_val1, svfloat64_t sv_val2)
79© 2023 Fujitsu Limited

ACLE演習2: H-gate ACLE+OpenMP例
void H_gate(vector<complex<double>>& array, int target_qubit){…
const int vec_len = svcntd();
svbool_t pg = svptrue_b64();
svfloat64_t factor = svdup_f64(sqrt2inv);①SVEレジスタの要素数取得(64-bitで8要素)②全要素分trueのプレディケート作成
1③全要素が 2のSVEレジスタ作成#pragma omp parallel forfor (int i = 0; i < loop_dim; i += (vec_len >> 1)) {svfloat64_t input0, input1, output0, output1;
int basis_index_0 =(i & mask_low) + ((i & mask_high) << 1);
int basis_index_1 = basis_index_0 + mask;
input0 = svld1(pg, &ptr[basis_index_0*2]);
input1 = svld1(pg, &ptr[basis_index_1*2]);
output0 = svadd_x(pg, input0, input1);
output0 = svmul_x(pg, output0, factor);
output1 = svsub_x(pg, input0, input1);
output1 = svmul_x(pg, output1, factor);
svst1(pg, &ptr[basis_index_0*2], output0);
svst1(pg, &ptr[basis_index_1*2], output1);}}

SVE load命令(A64FX:8要素まとめて読み込み)

input0 + input1

2
input0 − input1
2

SVE store命令(A64FX:8要素まとめて書き込み)80© 2023 Fujitsu Limited目次
13:00 13:30

スパコンの使い方など

13:30 14:20

A64FXアーキテクチャと並列プログラミング（座学）

14:30 15:40

OpenMPを活用した並列プログラミング演習
3重ループを含むプログラムで、OpenMPの指示
文挿入によるスレッド並列やSIMD演算を活用するコードを取り扱います。
また、 Intrinsic（ACLE）を使ってSIMD化する例も取り上げます。

15:50 17:00

プロファイラを活用したチューニング演習

81© 2023 Fujitsu Limitedプロファイラを活用したチューニング演習82© 2023 Fujitsu Limited演習内容⚫ 目的: プロファイラによる高コスト関数の特定＆チューニング
⚫ 演習コード: 深層学習の複数種類のレイヤ処理⚫ Point-wise Convolution → ReLU → Batch Normalization⚫ Point-wise Convolution: チャネル方向のみの畳み込み⚫ ReLU: 活性化関数の１つ(要素ごとの操作)⚫ Batch Normalization: バッチ方向の正規化
ReLU
要素ごとの操作
𝑥 if 𝑥 ≥ 0
𝑦=ቊ
0 if 𝑥 < 0

Point-wise Conv

バッチ方向 チャネル方向畳込み

チャネル方向フィルタ=出力チャネル数

平均・分散を算出し正規化

y

…

高
さ

BNorm
バッチ方向の正規化

x83© 2023 Fujitsu Limited

演習 実行方法⚫ 作業ディレクトリ:⚫ /work/gt00/{アカウント名}/ACLE/

PConvReLUBNorm/asis⚫ 対象ファイル: layers.cpp⚫ 演習⚫ Step1: 高コスト関数の特定⚫ PointConv, ReLU, BNorm⚫ Step2: SVE+OpenMPチューニング

⚫ コンパイル⚫ pjsub -g gt00 make.sh⚫ 実行⚫ pjsub -g gt00 submit.sh⚫ 実行結果== PointConv+ReLU+BNorm layers benchmark ==Exection time[msec]: 8115.26-- Validation -ref time[msec]: 876.264
validation time[msec]: 8.352***Validation: PASSED
84© 2023 Fujitsu Limitedプロファイラの使用方法
1. プロファイルデータの取得⚫fipp –C –d <dir name> -Icall ./ConvReLUBNorm

2. プロファイルデータの解析⚫ fipppx –A –Icpupa –Sregion –d <dir name> > result.txt⚫ -Icpupa:スレッド単位のメモリースループット、演算数などCPU動作状況を出力⚫ -Sregion: fipp_start/stop区間のみの解析

3. “result.txt”の確認
fipp_start();main.cpp
for (int i = 0; i < Loop; i++) {PointConv(conv_in, conv_out, weight, batch, conv_in_h, conv_in_w, conv_in_c,
conv_out_h, conv_out_w, conv_out_c);
ReLU(conv_out, batch * conv_out_h * conv_out_w * conv_out_c);
BNorm(conv_out, batch, conv_out_h * conv_out_w * conv_out_c);}© 2023 Fujitsu Limited
85
fipp_stop();AsIsコードのプロファイル結果⚫ 最もコストが高い関数: PointConv⚫ 処理時間の60%以上を占める → 2倍高速化すると、処理時間が30%短縮⚫ 次にコストが高い関数: BNorm⚫ 処理時間の20%を占める → 2倍高速化すると、処理時間は10%短縮*************************************************************************************
Application - procedures
Application and Process outputs the total value of the cost of each thread.
Procedure outputs the total value of the procedure cost of each thread.
*************************************************************************************
Cost% Operation (s)Barrier%StartEnd------------------------------------------------------------------------------------97
100.0000
9.840314
14.4330--------------------------------------------------------------------------------------61
62.8866
6.188300.00003
152020.6186
2.028900.000023
47
86Application
PointConv(…
BNorm(…
© 2023 Fujitsu Limited

PointConv関数のスレッド並列化例⚫ 5重のforループの外側3重分は依存関係がない⚫ for (int m = 0; m < ic; m++)は書き込み先が重複するため並列化できない⚫ “parallel for” + collapseでスレッド並列化== PointConv+ReLU+BNorm layers benchmark ==Exection time[msec]: 8115.26-- Validation -12スレッド
ref time[msec]: 876.264== PointConv+ReLU+BNorm layers benchmark ==#pragma omp parallel for collapse(3)validation time[msec]: 8.352
Exection time[msec]: 2846.09
for (int i = 0; i < b; i++)***Validation: PASSED-- Validation -for (int j = 0; j < oh; j++)ref time[msec]: 878.331
for (int k = 0; k < ow; k++)validation time[msec]: 8.352
for (int m = 0; m < ic; m++)***Validation: PASSED
for (int l = 0; l < oc; l++)out[((i * oh + j) * ow + k) * oc + l] =out[((i * oh + j) * ow + k) * oc + l] +wei[l + m * oc] * in[((i * ih + j) * iw + k) * ic + m];
87© 2023 Fujitsu Limited

PointConv並列化版のプロファイル結果
スレッド並列プログラムの場合、ス
スレッド並列では、実行時間はスレッド間同期待ちコストが該当
レッド並列数の合計値→Bnorm待機コスト
Cost% Operation (s)Barrier%Start
End→実際の処理時間とは異なる------------------------------------------------------------------------------------→スレッド毎の処理コストを確認
337
100.0000
33.8582
225
66.7656--Application------------------------------------------------------------------------------------225
66.7656
22.6056225
66.7656--85
25.2226
8.539900.0000616
216.2315
2.109900.000024
48__?unknown
PointConv
BNorm*************************************************************************************
Thread0 - procedures*************************************************************************************
Cost% Operation (s)Barrier%
Start
End
BNorm関数が最もコストが高い------------------------------------------------------------------------------------29
100.0000
2.913600.0000--Thread------------------------------------------------------------------------------------21
72.4138
2.109900.0000
24
48
BNorm
8
27.58620.803800.0000
6
16
PointConv
88

0© 2023 Fujitsu Limited

BNorm関数のスレッド並列化の例⚫ 外側ループにループ間の依存関係はない⚫外側ループにparallel for指示文を挿入しスレッド並列化可能#pragma omp parallel forfor (int i = 0; i <num_elements_inbatch; i++)== PointConv+ReLU+BNorm layers benchmark ==Exection time[msec]: 2846.09
12スレッド-- Validation -ref time[msec]: 878.331== PointConv+ReLU+BNorm layers benchmark ==validation time[msec]: 8.352
Exection time[msec]: 865.35***Validation: PASSED-- Validation -ref time[msec]: 877.017
validation time[msec]: 8.352***Validation: PASSED
89© 2023 Fujitsu Limited

BNorm関数並列化版のプロファイル結果⚫ BNorm関数が高コスト関数ではなくなる⚫ 高コスト関数は再びPointConv関数に⚫ これまで高コスト関数ではなかったReLU関数が全体の10％を占める*************************************************************************************
Thread0 - procedures*************************************************************************************
Cost% Operation (s)Barrier%StartEnd------------------------------------------------------------------------------------9
100.0000
0.915300.0000--------------------------------------------------------------------------------------8
88.8889
0.813600.00006
16
1
11.11110.101700.000018
2290

Thread0

PointConv
ReLU© 2023 Fujitsu Limited