お試しアカウント付き
並列プログラミング講習会

MPI+OpenMPで並列化されたFortranプログラムのGPUへの移行手法
東京大学 情報基盤センター
担当：山崎一哉
kyamazaki(at)cc.u-tokyo.ac.jp（内容に関するご質問はこちらまで）
1

講習会スケジュール◼ 開催日時✓ 9月3日（水） 13:00 - 17:00◼ プログラム
13:00 - 13:50

OpenACC, OpenMP 5.x, do concurrent, CUDA
Fortran の特徴

14:00 - 14:50

OpenMP for CPUからOpenACC, OpenMP 5.x, doconcurrentへの移植手法

15:00 - 15:50

演習１：簡単なサンプル

16:00 - 16:50

演習２：有限要素法

2

講習会について◼ 本講習会は✓ OFP-IIに向け、MPI+OpenMPで記述されたFortranプログラムのGPU移植の手法を中心に扱います。
◼ その他の講習会

https://www.cc.u-tokyo.ac.jp/events/lectures/◼ スパコンイベント情報メール配信サービス

Youtubeにて過去の講習会を配信中！
https://www.youtube.com/channel/UC
2CHaGp1AO-vqRlV7wmU0w/videos?view=0&sort=p&flow=grid

https://regist.cc.u-tokyo.ac.jp/announce/✓ 講習会や研究会の案内、トライアルユースの実施のお知らせなどを配信しています。
3

講習会の進め方◼ Zoomを利用したオンライン講習会です✓ この講義は録画されています✓ 質問があるとき以外はミュートでお願いします✓ ビデオもオフを推奨します◼ slackを使って質問に対応します✓ slackはリンクを知っている人は誰でも使える設定になっています✓ slackのリンクをzoomのチャットに貼るので、未登録の場合は今のうちに登録

お願いします✓ スクリーンショットなどで画像を共有することで、質問対応します✓ Windows：Alt + PrtScn で作業中ウィンドウのスクショがクリップボードにコピーされます。

slackのチャット部分で貼り付け(Ctrl + V)することで画像をアップロードできます✓ Mac : command + shift + control + 4 の同時押し、その後撮りたいウィンドウ上でspaceを押すことで、スクリーンショットがクリップボードにコピーされます。
slackのチャット部分で貼り付け(command + V)することで画像をアップロードできます4Zoom: 「手を挙げる」方法
1.

Zoomメニュー中の「リアクション」をクリック

2.

ポップアップで表示された「手を挙げる」をクリック

5

Zoom: 手が挙がっていることの確認方法
1.

Zoomメニュー中の「参加者」をクリックして，参加者一覧を表示

2.

表示された参加者一覧の，自分のところを見ると手が挙がっている

6Zoom: 「手を降ろす」方法
1.

Zoomメニュー中の「リアクション」をクリック

2.

ポップアップで表示された「手を降ろす」をクリック

7

Slack: 質疑応答チャンネルへの移動◼ 左側のメニューバーのチャンネル一覧内に「第230回-

fortran2gpu」があるので，クリック◼ 表示されていない場合
1.
2.
3.

第230回

「チャンネルを追加する」をクリック「チャンネル一覧を確認する」をクリック「第230回-fortran2gpu」があるので，「参加する」をクリック

Slack: メッセージの入力方法◼ 最下部に入力欄があるので，質問内容を記載して Ctrl+Enter◼ 入力後に右下の「メッセージを送信する」をクリックしても同じ（メッセージ入力前には，「メッセージを送信する」は押せない）
メッセージの入力欄
コードブロックの生成

メッセージを送信する◼ コードを入力する際には，「コードブロック」がおすすめ◼ 枠が生成されるので，この中にコピペするのが簡単かつ見やすい◼ ```（JIS配列ならばShift+@を3連打）しても枠が生成される9

東大情報基盤センターのスパコン概要10

2001-2005
Hitachi SR8000
1,024 GF

2006-20102011-20152016-2020

Hitachi SR11000
J1, J2

Hitachi SR16K/M1
Yayoi

5.35 TF, 18.8 TF

54.9 TF

2021-2025

Hitachi
SR2201

Hitachi
SR8000/MPP

OBCX(Fujitsu)

307.2GF

2,073.6 GF

6.61 PF

Hitachi HA8000
T2K Todai

OakforestPACS (Fujitsu)

140 TF25.0 PF

Fujitsu FX10
Oakleaf-FX

東京大学情報基盤センターのスパコン
利用者2,600+名
55％は学外2026-2030

Miyabi (Fujitsu)
80.1 PFBDEC02

Fujitsu
33.1 PF

1.13 PF

ReedbushU/H/L (SGI-HPE)
3.36 PF

Ipomoea-01 25PB
Ipomoea-02?
11

2001-2005
Hitachi SR8000
1,024 GF

SR80002006-2010
Hitachi SR11000
J1, J2
5.35
18.8 TF
IBMTF,
Power5+

2011-20152016-2020

Hitachi SR16K/M1
Yayoi
54.9
TF
IBM
Power72021-2025

Hitachi
SR2201

Hitachi
SR8000/MPP

Intel CLX
OBCX(Fujitsu)

307.2GF

2,073.6 GF

6.61 PF

HARP-1E

SR8000

Hitachi HA8000
T2K Todai

OakforestPACS (Fujitsu)

140 TF25.0 PFAMD Opteron

疑似ベクトル
汎用CPU
加速装置付

Miyabi (Fujitsu)
80.1 PF

AcceleratorsIntel Xeon Phi
Fujitsu FX10
Oakleaf-FX

2026-2030

BDEC02

Fujitsu
33.1 PF

1.13 PFSPACR64 IXfx
ReedbushU/H/L (SGI-HPE)

A64FX,
Intel Icelake+
NVIDIA A100

Accelerators

3.36 PFIntel BDW +NVIDIA P100

Ipomoea-01 25PB
Ipomoea-02?
12

13

柏キャンパス
OFP，OBCX

柏IIキャンパス
Wisteria/BDEC-01，mdx
ABCI/ABCI-II

TX「柏の葉キャンパス」駅
13

Platform for Integration of (S+D+L)
Big Data & Extreme Computing

Oakforest-PACS (OFP) (Fujitsu, Intel Xeon Phi (KNL))

Simulation Nodes:
Odyssey• JCAHPC （筑波大CCS・東大ITC），2016年10月～2022年3月末• 25 PF, #39 in 58th TOP 500 (November 2021)

Fujitsu/Arm A64FX
25.9PF, 7.8 PB/s

Shared File
System
（SFS）
25.8 PB, 500 GB/s

2.0 TB/s

Data/Learning
Nodes: AquariusIntel Ice Lake + NVIDIA A100

Fast File
System（FFS）
1 PB, 1.0 TB/s

• 2019年7月～2023年6月末（予定）• 6.61 PF, #110 in 58th TOP500-June 2023 (Plan)

Wisteria/BDEC-01（Fujitsu）

7.20 PF, 578.2 TB/s

800 Gbps

External
Resources

Oakbridge-CX (OBCX) (Fujitsu, Intel Xeon CLX)

External
Resources
External Network• シミュレーションノード群（Odyssey）：A64FX (#17)• データ・学習ノード群（Aquarius）：Intel Icelake+NVIDIA A100) (#106)• 33.1 PF, #13 in 57th TOP 500，2021年5月14日運用開始• 「計算・データ・学習（S+D+L）」融合のためのプラットフォーム• 革新的ソフトウェア基盤「h3-Open-BDEC」• （科研費基盤（S） 2019年度～2023年度）

Miyabi (OFP-II) (Fujitsu) ※リンク先に詳細情報あり• 2025年1月運用開始予定• Miyabi-G: NVIDIA GH200, 78.8 PF• Miyabi-C: Intel Xeon Max 9480, 1.3PF

Simulation Nodes（Odyssey）

Data/Learning Nodes（Aquarius）

Oakforest-PACS

Oakbridge-CX 14

Aquariusの構成◼ Intel Xeon Platinum 8360Y (36c 2.4GHz) x 2ソケット, 512GBメモリ◼ ノード当たり8基のNVIDIA A100 GPU
DDR4
DDR4
DDR4
DDR4
DDR4
DDR4
DDR4
DDR4

x16

IBHDR

IB HDR:
200 Gbps x 4 link
(ノード当たり)x16

PCIe Gen4
x16

A100

UPI

CPU1
Intel Xeon Platinum
8360Y
(ICX)

CPU2
Intel Xeon Platinum
8360Y
(ICX)

x16

x16

PLX
IBHDR
x16

A100

x16

x16

x16

A100

A100

A100

DDR4
DDR4
DDR4
DDR4
DDR4
DDR4
DDR4
DDR4

x16

PLX

PLX

x16

メモリバンド幅：
409.6 GB/sec

PLX

x16

x16

A100

IBHDR

x16

A100

PCI Express:
Gen4 x16 = 16 GHz x 16 (128b130b)= 31.5 GB/s (片方向)
x16

IBHDR

PCIe Gen4
x16

A100

NVLink3:
GPU当たり 50 GHz x4x2 x6= 300 GB/s (片方向)NVSwitch
NVSwitch
NVSwitch
NVSwitch
NVSwitch
NVSwitch
15

Wisteria 利用上の注意（１）◼ ディレクトリについて（home と work)✓ ログイン時のディレクトリ（/home/gt00/txxxxx）にはログイン時に必要なファイルのみを置く✓ プログラム作成や実行などに必要なファイルは/work 以下のディレクトリ（/work/gt00/txxxxx）に置く✓ /home は計算ノードからは参照できない

16

Wisteria 利用上の注意（２）◼ コンパイルおよび実行のための環境準備✓コンパイルおよび実行のための環境を準備するために

module コマンドを使用する。これによって様々な環境を簡単に切り替えて使用できる。

$ module load <module_name>

モジュール名 <module_name> のモジュールをロードして環境を準備。環境変数PATHなどが設定される。
$ module avail

使用可能なモジュール一覧を表示する。
$ module list

使用中のモジュールを表示する。
17

コンパイラの種類と実行(Aquarius)◼ ログインノードとAquarius計算ノードとでは、CPUの命令セットが（ほぼ）同じ◼ ログインノード：命令セットアーキテクチャ Intel CascadeLake＋AVX512, x86_64◼ Aquarius計算ノード： 命令セットアーキテクチャ Intel IceLake＋AVX512, x86_64◼ 様々なコンパイラが利用可能: GPU向けには NVIDIAかgcc+CUDAを推奨$ module load nvidia nvmpi cuda または$ module load gcc cuda ompi-cuda
GNUコンパイラ

Intelコンパイラ

NVIDIA コンパイラ(旧PGI)

C

gcc

icc

nvc (pgcc)

C++

g++

icpc

nvc++(pgc++)

gfortran

ifort

言語

Fortran
OpenACC

CUDAコンパイラ
nvcc

nvfortran (pgfortran)○
18

Wisteriaでのプログラムの実行◼ ジョブスクリプト(〇〇.sh)を作成し、ジョブとして投入、実行する。
$ pjsub ./〇〇.sh◼ 投入されたジョブを確認する。（qstatではないので注意）$ pjstat◼ 実行が終了すると、以下のファイルが生成される。
〇〇.sh.??????.out〇〇.sh.??????.err （?????? はジョブID）◼ 上記の標準出力ファイルの中身を確認する。
$ cat 〇〇.sh.??????.out◼ 必要に応じて、上記のエラー出力ファイルの中身を確認する。
$ cat 〇〇.sh.??????.out

19

JOBスクリプトサンプルの説明（Aquarius, MPIなし）#!/bin/bash#PJM -L rscgrp=lecture-a#PJM -L gpu=2#PJM -L elapse=00:01:00#PJM -g gt00
module load nvidia./a.out

リソースグループ名：lecture-a
利用GPU数

実行時間制限：１分（講習会では最大10分）
利用グループ名：gt00
20

MPI+OPENMPからのGPUへの移行手法

21

MPI + “X”（Fortran向け）◼ 従来型のスパコン◼ MPI + OpenMP for CPU (どこのスパコンでもほぼ使える）◼ GPUスパコン◼ MPI + OpenACC (NVIDIA)◼ MPI + OpenMP for GPU (NVIDIA, AMD, Intel)◼ MPI + do concurrent (NVIDIA , Intel)◼ MPI + CUDA Fortran (NVIDIA)

従来型のスパコンではほとんどの場合”X=OpenMP”だったが、GPUスパコンでは”X”の選択肢が複数ある状態
22

GPUプログラムの実行イメージ
1スレッド

OpenMP
for CPU

OpenACC, OpenMP for GPU,
Do concurrent, CUDA

int main(){...
#このループを並列実行
for (i=0; i<n; i++) {

...

}
...
}

CPU

CPU

CPU

デバイス
(GPU)
23

GPUプログラミングの必須要素
1.

CPU-GPU間データ移動◼ CPUとGPUで独立のメモリを持つため◼ メモリ管理方針は“X”により異なる

バス (PCIe など)

CPU
OSが動いているGPU~40GB/s
相対的に遅い~1,600GB/s~200GB/s◼ CPU-GPU 変数独立型◼ 変数同一視型◼ Unified memory型（メモリアドレス空間ごと同一視）

2.

ループ並列化◼ いずれの”X”でも、プログラム中のループ構造を並列化することでGPUで高速実行する◼ “X”によって並列化できるループ、できないループがある

デバイスメ
モリ

メインメモリ

物理的に独立
世の中のすべてのループ
GPUで(≒CUDAで)高速化可能なループ
OpenACC, OpenMP for GPUで高速化可能なループ
Do concurrentで高速化可能なループ24

仕様の比較 (nvidia compiler 24.1 の場合)
OpenACC
kernels

OpenMP 4.x
target teams distributeparallel do

OpenMP 5.x
target loop

Fortrando concurrent

CUDA Fortran

変数同一視
Unified Memory ※1

変数同一視
Unified Memory ※1

変数同一視
Unified Memory ※1

Unified Memory

変数独立関数呼び出し✓✓✓N/A ※2✓

リダクション

✓

✓

✓

✓ ※3

✓

atomic演算

✓

✓

✓

N/A

✓

スレッド数の調整

✓ ※4

✓ ※4

N/A

N/A

✓

Streamの非同期
実行✓ (キュー指定可)

✓

✓

N/A

✓

GPU組み込み関数

N/A

N/A

N/A

N/A

✓

単一ソースでのCPU・GPU両対応

✓

△

✓

✓

N/A

CPU-GPUメモリ管理※１ Unified memoryはNVIDIA GPU, compilerの機能であり、OpenACC, OpenMPの仕様ではない。
※２ Fortranの仕様上は、pure functionであれば呼び出せるはずだが、pure function呼び出しもサポートされていない。
※３ NVIDIA compilerの独自拡張であり、Fortran do concurrentの仕様ではない。
※４ CUDA程自由ではない。32以上の２の冪乗数以外を指定しても無視されることが多い。OpenACCではkernelsではなくparallelを使うと自由度が少
し上がる。
25

CPU-GPUのメモリ管理◼

CPU-GPU変数独立型 (CUDA)
配列ごとのPinned memoryの設定など、細やかな指定ができる◼ Pinned memory: ページロックメモリ。CPU-GPU間のデータ転送速度が速い◼ CPU-GPUの変数の一貫性をプログラマが明示的に管理する必要があり、管理コストが大きい◼

◼

変数同一視型 (OpenACC, OpenMP)
CPU-GPUの変数の区別がプログラムの見かけ上はなくなるので、コードの見通しがいいが、CPU-GPU間の一貫性の管理は依然プログラマの仕事◼ 配列ごとのPinned memoryの設定など、細かな指定はできない◼

◼ NVIDIAコンパイラではオプションによる一括指定はできる◼

Unified Memory型 (do concurrent, OpenACC, OpenMP)◼

CPU-GPU間の一貫性の管理から解放されるため、圧倒的に楽◼ CPU-GPU間のデータ転送を書く必要がない◼ ポインタを含む構造体などもそのまま使える◼

速度的に不利◼ ページ単位（ページサイズの設定不可、サイズ非公開）で転送するため◼ GPU Direct RDMA通信が使えない◼

CPU-GPU間通信がバックグラウンドで実行されるため、高速化を目指す上でわかりづらい26

CPU-GPU間データ転送の書き方◼ 変数独立型
real(8), allocatable :: X_cpu(:)
real(8), allocatable, device :: X_gpu(:)
allocate(X_cpu(N))
allocate(X_gpu(N))
X_cpu(:) = 0
X_gpu(:) = X_cpu(:)
Call cuda_func<<<tb,th>>(X_gpu)
X_cpu(:) = X_gpu(:)

CPUメモリ
0x01◼ Unified Memory◼ 変数同一視型

GPUメモリ

X_cpu

real(8), allocatable :: X(:)
allocate(X(N))X(:) = 0!$acc data copy(X)Call openacc_func(X)!$acc end datareal(8), allocatable :: X(:)
allocate(X(N))X(:) = 0
Call do_concurrent_func(X)(X_cpu, X_gpu)のペアをソフトウェア的に変数Xとして同一視する

CPUメモリ
0x01

GPUメモリ

メモリアドレス空間を一致させることで、
X_cpu, X_gpuの区別を無くす
データ転送はページフォルトが起きた際にバックグラウンドで行われる

CPUメモリ

X

0x10
0x11

X_gpu

GPUメモリ

0x11

X

0x10

X

X
27

CPU-GPU間のデータ移動
OpenACC data指示文を使った場合
program main
implicit none
! 変数宣言allocate(a(n),b(n))
c = 2.0
do i = 1, na(i) = 10.0
end do!$acc data copyin(a) copyout(b)!$acc kernels!$acc loop independent
do i = 1, nb(i) = a(i) + c
end do!$acc end kernels!$acc end data
sum = 0.d0
do i = 1, n
sum = sum + b(i)end doprint *, sum/ndeallocate(a,b)
end program main

GPU

CPU

GPUのメモリ上にコピーを作る。
これは変数独立型、変数同一視型、Unified
Memory型のいずれの場合でも同じ。

a b

メモリ確保

GPUへ
copyin

a b

メモリ確保

カーネル
実行

解放
GPUから
copyout28

“X”に何を選ぶべきか（私見）◼ OpenACC（推奨）◼ NVIDIAのGPUで最も使い勝手が良く、コンパイラが成熟している◼ AMD, Intelがサポートすることはおそらくない◼ CUDA Fortranとも組み合わせやすい◼ OpenMP for GPU◼ NVIDIA, AMD, Intel全てがサポートするということが最大のメリット◼ OpenACCと仕様上の差はそれほど大きく無いが、細かいところで微妙に使いにくい◼ ループに対するスレッドマッピングの制限など◼ AMD・Intelのコンパイラでは、Fortran版は 動作しない or 非常に遅い ことが多々あり、Fortranを使うなら今は事実上NVIDIAしか選択肢がない◼ Do concurrent（Fortranの言語標準）◼ 言語標準であるということが最大のメリット◼ 仕様上の制約が非常に大きく、実アプリで使うならそれなりに書き換える必要がある◼ 書き換えを進めていき、do concurrentではどうしようもなくなって、OpenACCやCUDAを混ぜた瞬間、言語標準というメリットを失う
29

データ転送方式は？（私見）◼ OpenACC◼ ひとまずはUnified Memoryから始めることを推奨◼ ループの並列化に集中できる◼ 速度的な問題が出てきたら、データ指示文を追加すれば良い◼ OpenMP for GPU◼ ひとまずはUnified Memoryから始めることを推奨◼ メリットはOpenACCの場合と同じ◼ Unified MemoryはOpenMPやOpenACCの仕様に組み込まれているわけではないので、NVIDIA以外で動くかどうかはわからない◼ OpenACCはNVIDIAしかサポートしていない（GCCとかも名目上サポートしているが遅い模様）ので、その点はデメリットにならない◼ Do concurrent◼ Unified memoryしか選択肢がないため、速度的な問題が起きても解消できない

30

CUDAの立ち位置は？（私見）◼ ライブラリ的な利用方法を推奨◼ 単純なループにおいて、CUDA以外との性能差はほぼない◼ 指示文などではどうしようもない複雑なループや、少しでも速くしたいアプ

リケーションのメインのボトルネック部分などは、CUDAの利用を考えるべき◼ いずれの”X”でもCUDA Fortranと組み合わせられる◼ 少なくとも、アプリケーション全体をCUDAに置き換える積極的なモチベー

ションはもはや無い（特にコードが長い場合）◼ AMD, Intelは？
◼ AMD: hipと呼ばれるほぼCUDA C互換のものがある。よく出来ているが、

Fortran対応はない。Cメインのユーザであるなら、AMDは現時点でも強力な選択肢◼ Intel: DPC++なるものを推している。Fortran対応はない
31

OpenMP for CPUとの大きな違い◼ OpenACC, OpenMP for GPU, do concurrentでは、

明示的にスレッド番号を取得できない◼ omp_get_thread_num(), omp_get_num_threads() 相当のAPIはない◼ OpenMP for GPU でも使えない。

OpenMP for GPUとOpenACCは似ているが、OpenMP for GPUとOpenMP for CPUは別物。

◼ よって、allocate( array(m, n, omp_num_threads) ) みたいな使い方はできない◼ そのアルゴリズムはそもそもGPUに向いてない可能性が高い◼ CUDAではスレッド番号を取得できる◼ OpenACC, OpenMP for GPU, do concurrentでは、

明示的なスレッド間同期が取れない◼ $!omp barrier 相当の指示文がない◼ GPU化対象ループ終了時に暗黙に同期が取られる◼ CPUとGPUの同期を取る（GPUの終了を待つ）ための指示文は存在する◼ CUDAでは同一スレッドブロック内のスレッド間に限り同期を取れる◼ またCUDAではごく限定的なケースにおいて、GPU全体のスレッドで同期を取ることも一応できるが、ほとんど使うケースはない
32

ループ並列化の例：一重ループ
OpenMP for CPU

OpenACC

OpenMP 4.x
teams distribute parallel do

OpenMP 5.x
target loop

Fortran do concurrent
基本的に、このループは並列化可能ですよと宣言しているだけ。
CUDA含め比較しても、性能はほぼ変わらない。

33

ループ並列化の例：多重ループ
OpenMP for CPU

OpenMP 4.x
teams distribute parallel do

OpenACC

Fortran do concurrent

OpenMP 5.x
target loop

単純なループ構造であれば、基本的には各ループの並列可能性を示せばいい。
多重ループでは階層的な並列性を意識する必要性が出てくる。（特にOpenMP 4.x）34

ループ並列化の例：多重ループの一重化
OpenMP for CPU

OpenACC

OpenMP 4.x
teams distribute parallel do

OpenMP 5.x
target loop

Fortran do concurrent

Do concurrentでこのように書いた時、仕様上ループの順序についての決まりが無いため、どのループを最内として扱うかはコンパイラが決めることになる

Collapse節によって一重化できるなら、しておくとよい。
特にOpenMP 4.xでは並列性を確保するためにcollapseが重要。

35

GPUの階層構造 (1/2)◼ A100は108個のSM (Streaming Multiprocessor)を持つ

出典: NVIDIA A100 Tensor core GPUアーキテクチャ

36

GPUの階層構造 (2/2)◼ L2キャッシュはSM全体で共有◼ L3キャッシュはないので、L2がラストレベル◼ L1キャッシュはSM毎に独立◼ Shared memoryと共有で192 KB◼ キャッシュブロッキングを考えるなら

L1キャッシュ単位◼ 同期ができるのもSMの内部のみ◼ 限定的な条件下で全体同期もできるが、あまり使わない◼ SM内のスレッドは更にWarpと呼ばれる32スレッド単位で実行される

出典: NVIDIA A100 Tensor core GPUアーキテクチャ

37

GPUプログラミングの階層構造◼ CUDA, OpenACC, OpenMP for GPU も、GPUの階層構造に合わせて、階層的な構造を持つ

スレッドブロック（CUDA）
gang（OpenACC）
team（OpenMP）◼ CUDA: スレッドブロック、スレッド◼ OpenACC: gang, (worker, ) vector◼ OpenMP: team, thread◼ 一つのスレッドブロック/gang/teamに所属するスレッドは同じSMに割り当てられる

スレッド（CUDA）
vector（OpenACC）
スレッド（OpenMP）◼ つまりL1キャッシュが共有される◼ 1ブロックあたりのスレッド数は1〜1024◼ Warpを踏まえると、32の倍数、経験的に64,128, 256あたりがいい◼ CPU-GPU間のデータ移動や、リソースを使い切ることの方が重要なので、最初からそこまで意識する必要はない

cited from : http://cudaprogramming.blogspot.jp/2012/12/thread-hierarchy-in-cudaprogramming.html

38ループ並列化の例：リダクションループ
OpenMP for CPU

OpenACC

OpenMP for GPU

Fortran do concurrent

OpenACC, OpenMP for GPU の
リダクションの実装はOpenMP for CPUと大差ない

NVIDIAコンパイラでは、リダクションを勝手に判定してくれるが、Fortranの仕様上はまだリダクションはサポートされていない。
そのため上記コードが将来的に動くかどうかはわからない。
39

ループ並列化の例：atomic演算
OpenMP for CPU

OpenACC

OpenMP for GPU

Fortran do concurrent

Atomic計算は、OpenMPでもOpenACCでも書き方はほぼ同じ。
Fortran do concurretではサポートされていない。

N/A

40

ループ並列化の例：配列代入
OpenMP for CPU

OpenMP for GPU

N/A?

OpenACC

Fortran do concurrent

使えると便利な配列代入だが、今のところOpenACCでしか使えない。
OpenMP for GPUでは、コンパイルは通るものの並列化されない。

N/A

41

ループ並列化の例：関数呼び出しを含むループ
OpenMP for CPU

あまり認知されていないが、OpenMP for
CPUでもSIMDループ内で関数呼び出しをする際にはdeclare simdが必要。

OpenMP for GPU

Declare simd と同様の書き方

OpenACC

呼び出される関数側と、呼び出し元の関数側にroutine指示文を書く必要がある。
42

ライブラリの呼び方◼ 最も手っ取り早いのは、Unified memoryの使用の有無に関わらず、

data指示文を使う方法◼ OpenACC の指示文を使う場合◼ OpenMP のtarget data指示文でも可◼ Do concurrentでも、コンパイルオプションに-accをつけて、上記のように書くのが手っ取り早い

43

MPIとの繋ぎ◼

Unified Memoryを使う場合
コード上で変更することは特になし◼ GPUのメモリからCPUのメモリ（MPIで送受信するバッファ）への暗黙のコピーが発生するため、性能上不利になり得る◼

◼

Unified Memoryを使わない場合◼

CUDA-aware MPIを使うのが基本◼ MPI関数の引数に、GPU上のポインタを受け付けるMPI◼ OpenMPIからも提供されている◼

指示文を使って、GPU上のポインタを直接MPI関数の引数として渡す
OpenACC: host_data指示文◼ OpenMP: use_device_ptr指示節◼

44

MPIランクとGPU割り当て◼

OpenACC関数を呼ぶためヘッダーを追加
C: #include <openacc.h>
Fortran: use openacc◼

ノード上のGPU数の取得(acc_get_num_devices)
intrger ngpus
ngpus = acc_get_num_devices(acc_device_nvidia)

acc_device_nvidiaを指定することで、NVIDIA GPUを数える。
◼

あるプロセスに特定のGPUを割り当て(acc_set_device_num)
integer rank, ierrcall MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr);
call acc_set_device_num(mod(rank, ngpus), acc_device_nvidia)

device_num を rank % ngpus とすることで、ノード内のプロセスは異なるGPUを利用することとなる。これを呼ばないと、0番を使いに行く。
45

API関数の代わりに環境変数を使う方法◼ API関数を使わず、CUDA及びMPI処理系の環境変数を使うことで、GPUとMPIプ

ロセスの割付けを行う方法もある。
run.sh#!/bin/bash#PJM -L rscgrp=lecture-a#PJM -L gpu=4#PJM --mpi proc=4#PJM -L elapse=00:10:00#PJM -jwrapper.sh（chmodで実行権を与える必要あり）#!/bin/bash
export CUDA_VISIBLE_DEVICES=${OMPI_COMM_WORLD_LOCAL_RANK}$1

module purge
module load nvidia nvmpi

mpiexec -machinefile $PJM_O_NODEINF -n 4 ./wrapper.sh ./run

CUDA_VISIBLE_DEVICES： デフォルトでは0。APIによるGPU番号のセットがされていない場合、ここにセットされた
番号のGPUを使う。
OMPI_COMM_WORLD_LOCAL_RANK： MPIの処理系としてOpenMPIを使う場合、ノードローカルなランク番号がここに設定される。つまり上の場合、OMPI_COMM_WORLD_LOCAL_RANK番のGPUを使うことになる。
46

MPI通信に関する評価◼ GPUへの移行対象のアプリケーションの多くはMPI+OpenMPで並列化されている◼ Unified memoryは通信性能にどの程度影響を与えるのか？
◼ （細かい話として）GPUでの非同期のストリーム実行ができないが、性能への影響は？

• 拡散方程式カーネル✓メモリ律速なカーネル✓７点ステンシルと呼ばれ、よくベンチマークに用いられる✓Z軸一次元分割✓袖領域通信と計算のオー
バーラップ
47

拡散方程式カーネル◼

Strong scalingでは特に、Unified memory使用時の性能低下が大きい◼

do concurrentを使う以上はどうしようもない
Strong scaling

実装１

Unified Memory使用

実装２

Data指示文+GPU Direct RDMA使用

実装３

実装２に加えてasync節で袖領域計算の複数同時実行

Weak scaling （GPUあたりの性能）※問題サイズ 5123、倍精度※WisteriaのAquariusノード群を使用。1ノードあたりA100 GPUを8枚搭載※A100 コンパイルコマンド： nvfortran (version 22.5) (-stdpar=gpu -acc=gpu ) -Minfo=acc,stdpar -gpu=cc80(,managed)※OpenMPI 4.1.4 を使用。

48

有限要素法コードのGPU移植実習

49

Target: GeoFEM/Cube◼ Parallel FEM Code (& Benchmarks)◼ 3D-Static-Elastic-Linear (Solid Mechanics)◼ Performance of Parallel Preconditioned Iterative Solvers– 3D Tri-linear Hexa. Elements– SPD matrices: CG solver
U =0 @ y=Y– Fortran90+MPI+OpenMP– Distributed Data Structure(N -1) elements– Block Diagonal LU + CG
N nodes– Block CRS Format– MPI，OpenMP，OpenMP/MPI HybridyzUniform Distributed Force inz-direction @ z=Zmax

min

Ux=0 @ x=Xminzz• only OpenMP case(Ny-1) elements
Ny nodesy
Uz=0 @ z=Zmin(Nx-1) elements
Nx nodesx50

GeoFEM ICCGソルバー◼ 陰解法だが、隣接ノードとの通信は陽的に行う◼ 隣接ノード通信はisend, irecvにより実装されているが、通信隠蔽はアルゴリズム
上困難◼ スカラ要素のMPI_Allreduceが必要◼ カラーリング（CM-RCM法）により依存性は解消されており、並列化可能なループで構成される◼ リダクションが必要となるため、現在の do concurrent の正規仕様では並列化できない◼ NVIDIA compilerは独自拡張でリダクショ

ンに対応しているので、並列化可能◼ 今後他の環境で動くかどうかはわからな
い

29 61
45
22 46
16 62
11 47
7 63
4 48
2 64
1

29 22 16 11

7

4

2

1

53 36 19

13
37 29
49
30 14
23 30
51
17 15
12 31
53
8 16
55
5 32
3

37 30 23 17 12

8

5

3

7

54 37 20

44 57
41
38 42
31 58
24 43
18 59
13 44
9 60
6

44 38 31 24 18 13

9

6

25

8

9 25
33
50
45 10
39 26
35
32 11
25 27
37
19 12
14 28
39
10

50 45 39 32 25 19 14 10

43 26

5

52

55 53
37
51 38
46 54
40 39
33 55
26 40
20 56
15

55 51 46 40 33 26 20 1561 44 27 10 57 40 23

6

5 21
6 22
7 23
8 24
17
59
56 19
52
47 21
41
34 23
27
21

59 56 52 47 41 34 27 21

14 62 45 28 11 58 41 24

60 34
57 50
53 35
48 51
42 36
35 52
28
62 49
33

62 60 57 53 48 42 35 28

31 15 63 46 29 12 59 42

1 17
2
3
4
64
63 61
3 18
58 54
5 19
49 43
7 20
36

64 63 61 58 54 49 43 36

48 32 16 64 47 30 13 60

MC (Color#=4)
Multicoloring

RCM
Reverse Cuthill-Mckee

CM-RCM (Color#=4)
Cyclic MC + RCM

2

49 33 173

55 38 219

1

50 34 18
4

56 39 22

51 35

51

GeoFEM ICCGソルバー◼
◼
◼

ICCGソルバーの１ステップあたりの実行時間
収束回数には変化なし
MPI通信の占める割合が小さいため、差は大きくない
Strong scaling

実装１

Unified Memory使用

実装２

Data指示文+GPU Direct RDMA使用

Weak scaling※問題サイズ 1283、倍精度※WisteriaのAquariusノード群を使用。1ノードあたりA100 GPUを8枚搭載※A100 コンパイルコマンド： nvfortran (version 22.5) (-stdpar=gpu -acc=gpu -mp=gpu) -Minfo=accel,mp -gpu=cc80(,managed)※OpenMPI 4.1.4 を使用。

52

サンプルコードのダウンロード◼ 講習会で使うサンプルコードは以下にあります◼ /work/gt00/share/lecture_fortran2gpu_202409.tar.gz◼ ダウンロード手順1.

$ ssh tXXXXX@wisteria.cc.u-tokyo.ac.jp◼ wisteriaへのssh。tXXXXXはアカウント名$ cd /work/gt00/tXXXXX
3. $ cp /work/gt00/share/lecture_fortran2gpu_202409.tar.gz .
4. $ tar axf lecture_fortran2gpu_202409.tar.gz
5. $ cd lecture_fortran2gpu/
2.

53

実習１
◼ 有限要素法プログラムのCGソルバのGPU移行◼ サンプルコード： OpenMP_for_CPU/GeoFEM/01_naive◼ ステップ

CPUで実行して、参照となる結果を得ましょう
2. Makefileを書き換えましょう
3. まずはUnified Memoryを利用し、ループの並列化をしましょう
4. 指示文を使って、データ転送を実装しましょう
1.

54ステップ１：CPUの結果◼ 実行バイナリはOpenMP_for_CPU/GeoFEM/runに生成されます◼ 実行パラメータはmesh.inpで調整できます$ cat mesh.inp
96 96 96
1 1 1
1 1
2000
-8

並列化の際のカラーリング手法と色数。
今回は気にしなくて良い。

全プロセス合計の問題サイズ (NX*NY*NZ)MPI プロセス数
例えば 1 2 2 にすると、
96 * 96 * 96 の問題
を4プロセスで解くための問題になる講習会コードでは未使用。
今回は気にしなくて良い

96

96

96

1
1
1
1
-8
### NORMAL### CM-RCM
color number:
8color number:
8
2.267555E-02 coloring of elements
6.146324E+00 matrix assembling
100 1.985639E-02
200 6.754801E-05
収束履歴（多少ブレる）300 3.330452E-07
344 9.357312E-09
elapsed 344 1.953827E+02 1.953827E+02 1.953827E+02
iter
344 5.679730E-01 5.679730E-01 5.679730E-01
884736 -2.850000E+01 -2.850000E+01 9.500000E+01

CG法全体の時間(elapsed),
反復あたりの時間(iter)

収束後のある要素の値。
963 の時はこの値になる。
55

ステップ２：Makefileの書き換え◼ 書き換えの前に、workディレクトリを作ります◼ $ cd OpenMP_for_CPU/GeoFEM/◼ $ cp -r 01_naive 00_work◼ $ cd 00_work
F90 = mpifort
F90OPT = -Mpreprocess -Ofast -mp
LOPT = -Mpreprocess -Ofat -mp
TARGET = ../run/01_naive

OpenACCの場合

F90 = mpifort
F90OPT = -Mpreprocess -Ofast -acc -gpu=managed -Minfo=acc
LOPT = -Mpreprocess -Ofast -acc -gpu=managed -Minfo=acc
TARGET = ../run/00_work-acc : OpenACCのフラグ。-acc=multicoreとするとCPUで実行できる。
-mp=gpu : -accの代わりに使うと、OpenMP for GPUが有効化される。
-stdpar=gpu : -accの代わりに使うと、Do concurrentがGPUで実行される。
-gpu=managed : Unified Memoryモードで実行するときのオプション。データ指示文を使うときは外す。
-Minfo=acc/mp/stdpar : コンパイル時に並列化に関する情報を出力するオプション。

56

ステップ３：ICCGソルバの並列化◼ まずは solver_CG_3_SMP_novec.f を書き換えます◼ OpenMP 指示文のついたループを、OpenACCやOpenMP for GPUなどに書き換えてください◼ この時、結果に変動がないかどうかを実行して確認しながら進めると良いです◼ コンパイルのためには以下の環境設定が必要です◼ $ module load nvidia nvmpi

57

ステップ４：指示文によるデータ移動◼ Makefileを編集し、managedを削除します
F90 = mpifort
F90OPT = -Mpreprocess -Ofast -acc -gpu=managed -Minfo=acc
LOPT = -Mpreprocess -Ofast -acc -gpu=managed -Minfo=acc
TARGET = ../run/00_work◼ このコードはgoto文が含まれるため、通常のdata指示文が使えません。代わりにenter data, exit data指示文を使います◼ !$acc enter data copyin(a) create(b)◼ !$acc exit data delete(a) copyout(b)◼ enter dataやexit data指示文に与える変数名を間違えると、不可解な実行時エラーになることがあります。スペルミスに注意しましょう58

ステップ５：MPIへの対応◼ solver_SR_3.f の中身を書き換えて、MPIへ対応しましょう◼ OpenACC ならhost_data use_device指示文、OpenMPならtarget data

use_device_ptr 指示文を使います◼ MPIプロセスとGPU番号の対応は、ジョブスクリプトに記載済みです◼ wrapper.sh 内でOpenMPIとCUDAの環境変数を用いて設定しています◼ MPIのプロセス数を変更してみましょう◼ mesh.inpの2行目と、run_gpu.shのgpu数及びMPIプロセス数を変更する必要があります59チャレンジ：行列生成部の並列化◼ mat_ass_main.f を書き換えます◼ 単純に並列化するだけならあまり難しくありませんが、かなり複雑な構造をしているため、性能を出すためには色々工夫する余地があります◼ 実装をいくつか用意してあるので、興味があればみてください60

実装まとめ
実装言語

データ転
送方式

ループ分
割
fusionUnifiedMemorysplit

Doconcurrent(stdpar)fusionDirective
split
fusionUnifiedMemorysplit

OpenMP forGPU
fusionDirective
split

書き込み競
合

実装名

coloring

Std_U_f_c

atomicN/Acoloring

Std_U_s_c

atomicN/Acoloring

N/A

atomicN/Acoloring

N/A

atomicN/Acoloring

Omp_U_f_c

atomic

Omp_U_f_acoloring

Omp_U_s_c

atomic

Omp_U_s_acoloring-

atomic-coloring-

atomic-

ICCG部分は共通。実装例として参考にしてください。

実装言語

データ転
送方式

ループ分
割
fusionUnifiedMemorysplit
OpenACC
fusionDirective
split

fusionUnifiedMemorysplit
OpenACC +CUDA
fusionDirective
split

書き込み競
合

実装名

coloring

ACC_U_f_c

atomic

ADD_U_f_acoloring

ACC_U_s_c

atomicEcoloring

ACC_D_f_c

atomic

ACC_D_f_acoloring

ACC_D_s_c

atomicEcoloring-

atomicCUDA_U_f_acoloring-

atomiccoloring-

atomic-coloring-

atomic-

N/A：言語の制約で実装できない
E : 実装できるはずだが結果がおかしい- : 未実装61