第100回お試しアカウント付き並列プログラミング講習会「REEDBUSH スパコンを用いたGPUディープラーニング入門」

ハンズオン#2:
マルチGPUによる高速化体験
山崎和博
NVIDIA, ディープラーニング ソリューションアーキテクト

ハンズオン#2のテーマ: 分散学習

AGENDA

タスク#1: マルチGPUでの学習
タスク#2: マルチノードでの学習ハンズオン#2のゴール
複数のノードを利用できるようになる• Reedbushの複数ノードを利用してジョブを流す方法を把握する• マルチGPU/マルチノードでの学習方法を理解する3

ハンズオン#2のテーマ: 分散学習

4

分散学習
モデル/データの大規模化モデルの複雑化
LeNet
5layers

GoogLeNet
24layers

ResNet152layers?

データの拡大?

ImageNet
256x256, 1M imgs

MNIST
28x28,60K imgs
5

分散学習
マルチGPU/マルチノードで分散して学習

データ並列（data parallelism）

モデル並列（model parallelism）
Large Scale Distributed Deep Networks より 6

分散学習
マルチGPU/マルチノードで分散して学習• 複数GPUで同じネットワーク（パラメータ）を共有• データを分割してそれぞれ勾配計算し、パラメータを更新→ 学習時間の短縮が期待できる
データ並列（data parallelism）
Large Scale Distributed Deep Networks より

7

分散学習
マルチGPU/マルチノードで分散して学習• ネットワーク（パラメータ）を複数GPUで分散して保持• 同じデータに対し、各GPUが協調してパラメータを更新→ 1GPUに乗り切らない大規模なネットワークを処理できる可能性
モデル並列（model parallelism）
Large Scale Distributed Deep Networks より 8

分散学習における注意点
バッチサイズと精度の関係
バッチサイズによって、同じ学習回数で達成できる精度が変化する

分散学習すると、トータルのバッチサイズは大きくなりがち→ 問題になる
可能性あり

https://arxiv.org/abs/1705.08741 より

9分散学習における注意点
バッチサイズと精度の関係
バッチサイズによって、分散学習すると、学習率やバッチサイズを調整することで、精度低下を回避する研究が多数
同じ学習回数でトータルのバッチサイズ• Train longer, generalize
better:closingthegeneralization
gap
達成できる精度がは大きくなりがちin large batch trainingofneuralnetworks
変化する→ 1問題になる• Accurate, Large Minibatch SGD: Training ImageNet inHour
可能性あり• LARGE BATCH TRAINING OF CONVOLUTIONAL NETWORKS• Don't Decay the Learning Rate, Increase the Batch Size……が、今日は細かいところに踏み込みません

https://arxiv.org/abs/1705.08741 より

10

タスク#1: マルチGPUでの学習11

マルチGPUで学習ジョブを流す
簡単な例で、ジョブの流し方を把握する

以下の作業を完了させる。

1. train_cifar_multi_gpu.pyに「PUT YOUR CODE」という箇所があるので、書き換えてプログラムを完成させる
2. スクリプトの修正完了したら実行
1. qsub –j oe run_multi_gpu_training.sh

3. 結果を確認
12

スクリプトの変更箇所
マルチGPU向けにデータ用iteratorを変更

メインのGPUとして使うものを指定

マルチGPU用の更新ロジックを指定

デバイス(=GPU)のリストを作成
テストで使うGPUを指定13

CHAINERのAPI
学習の実行• ネットワークの更新ロジックを指定•

chainer.training.updaters.MultiprocessParallelUp
dater(iterators, optimizer, devices)•

基本はシングルGPUのクラスと同じ使い方••

GPUへのモデル転送は明示的に実施する必要なし

学習データのイテレータをGPU数と同じ数準備する•

デバイス名をキーとする辞書(dict)を作成•

メインのGPUのみデバイス名を「main」にする必要あり14

メインのGPU？
ChainerのMultiprocessParallelUpdaterにおけるData Parallelism
シングルGPUの場合Forward

Backward

マルチGPUの場合
Forward計算した勾配を全GPUで共有してモデル更新

UpdateBackward
UpdateForwardBackward
1iteration15

メインのGPU？
ChainerのMultiprocessParallelUpdaterにおけるData Parallelism
シングルGPUの場合
ForwardBackward

マルチGPUの場合
Forward

ForwardUpdate

集約した勾配を使ってモデル更新

各GPUで計算した
Backward
勾配をメインに集約
Backward※ ChainerMNとは、異なる計算モデルを使っているようです

各GPUへ更新済み
モデルパラメータを配布

1iteration
16

CHAINERのAPI
データの分割• chainer.datasets.split_dataset_n_random(dataset, n)•

学習データのイテレータ作成時に使用•データをランダムにGPU数分割

17

データ分割？
各GPUへの分配
シングルGPUの場合

マルチGPUの場合

特に何もしないと、1epochで処理されるデータ量がGPU数倍に

18

データ分割？
各GPUへの分配
シングルGPUの場合

マルチGPUの場合各GPUで分担してトータルのデータ量を統一

19スクリプトの修正を試みましょう

コンソールに戻ってください！

20

スクリプトの変更箇所(答え; 1/2)
train_iter=[chainer.iterators.SerialIterator(sub_train, args.batchsize) \for sub_train \in chainer.datasets.split_dataset_n_random(train, args.n_gpus)]

master_gpu_id=0.get_device_from_id(master_gpu_id).

21スクリプトの変更箇所(答え; 2/2)
devices_list={'main': master_gpu_id}
devices_list.update({'gpu{}'.format(i):i for i in range(1, args.n_gpus)})device=0
updater=training.updaters.MultiprocessParallelUpdater(

train_iter, optimizer, devices=devices_list)
22

マルチGPUでの学習実行結果
Start a training script using single GPU.
# Minibatch-size: 32# epoch: 4

Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz
epoch
main/loss
validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1
3.56997
9.09890.107595
0.141016
50.0904(中略)
4
2.37658
2.14666
0.1991190.227344
193.16
Throughput: 51.76725472300107 [images/sec.] (10000 / 193.17230657697655)
Start a training script using multiple GPUs.
# GPUs: 2# Minibatch-size: 32# epoch: 4

シングルGPUだと
51.8image/sec.

Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz{'main': 0, 'gpu1': 1}/lustre/app/acc/cuda9/chainermn/ofed4.2/1.2.0/lib/python3.6/sitepackages/chainer/training/updaters/multiprocess_parallel_updater.py:141: UserWarning: optimizer.lr is changed to 0.025 byMultiprocessParallelUpdater for new batch size.
format(optimizer.lr))
epochmain/lossvalidation/main/loss main/accuracy validation/main/accuracy elapsed_time
1
3.03633
2.348110.107813
0.106641
37.0069(中略)
42.831942.33467
0.09375
0.100781
114.008
23
Throughput: 87.71081415935855 [images/sec.] (10000 / 114.011026985012)

同条件で2GPU使うと87.7image/sec.と高速に

マルチGPUでの学習実行結果
Start a training script using single GPU.
# Minibatch-size: 32# epoch: 4

Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz
epoch
main/lossvalidation/main/loss main/accuracy validation/main/accuracy elapsed_time
13.56997
9.09890.107595
0.141016
50.0904(中略)
4
2.37658
2.14666
0.1991190.227344
193.16
Throughput: 51.76725472300107 [images/sec.] (10000 / 193.17230657697655)
Start a training script using multiple GPUs.
# GPUs: 2# Minibatch-size: 32# epoch: 4

シングルGPUだと
51.8image/sec.

2GPUで約1.69倍？

Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz{'main': 0, 'gpu1': 1}/lustre/app/acc/cuda9/chainermn/ofed4.2/1.2.0/lib/python3.6/sitepackages/chainer/training/updaters/multiprocess_parallel_updater.py:141: UserWarning: optimizer.lr is changed to 0.025 byMultiprocessParallelUpdater for new batch size.
format(optimizer.lr))
epochmain/loss
validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1
3.03633
2.348110.107813
0.106641
37.0069(中略)
42.831942.33467
0.09375
0.100781
114.008
24
Throughput: 87.71081415935855 [images/sec.] (10000 / 114.011026985012)

同条件で2GPU使うと87.7image/sec.と高速に

マルチGPUによる高速化の詳細
高速化のキモは？
シングルGPUの場合
PreProc.

例: 200 iter.
F

B

U

F

B

F

例: 100 iter.

マルチGPUの場合
PreProc.

U

F

B

F

B C

U

F

C F

B

B C

U

C

F

B

F

B C

B

U

並列化によって総処理時間が短くなることを期待

U

C
time
25

マルチGPUによる高速化の詳細
高速化のキモは？
シングルGPUの場合
PreProc.

例: 20 iter.
F

B

U

F

B

前処理のような共通して時間を要する箇所の比率が大きくF

例: 10 iter.

マルチGPUの場合
PreProc.

U

F

B

F

B C

U

F

C F

B

B C

U

C

B

U

繰り返し回数が少ない間は並列化の効果が見えにくいF BUFB CCtime26

マルチGPUでの学習実行結果
Start a training script using single GPU.
# Minibatch-size: 32# epoch: 50
Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz
epoch
main/loss
validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1
3.69785
2.371160.111551
0.100781
50.3666(中略)
50
0.344069
1.946070.882612
0.417578
2394.26
Throughput: 52.20790861389455 [images/sec.] (125000 / 2394.2732685279916)
Start a training script using multiple GPUs.
# GPUs: 2# Minibatch-size: 32# epoch: 50

シングルGPUの場合は50epochでも変化なし

Using CIFAR10 dataset: /lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz{'main': 0, 'gpu1': 1}/lustre/app/acc/cuda9/chainermn/ofed4.2/1.2.0/lib/python3.6/sitepackages/chainer/training/updaters/multiprocess_parallel_updater.py:141: UserWarning: optimizer.lr is changed to 0.025 byMultiprocessParallelUpdater for new batch size.
format(optimizer.lr))
epochmain/lossvalidation/main/loss main/accuracy validation/main/accuracy elapsed_time
13.08727
4.656110.107031
0.0910156
37.061(中略)
50
0.338263
2.01606
0.888622
0.432031
1297.95
27
Throughput: 96.30540731984837 [images/sec.] (125000 / 1297.9541178290383)

50epoch回すと2GPUで96.3images/sec.の約1.84倍

タスク#2: マルチノードでの学習28

マルチノードで学習ジョブを流す
簡単な例で、ジョブの流し方を把握する

以下の作業を完了させる。

1. run_multi_node_training.shの実行環境指定を完成させる
2. train_cifar_multi_node.pyに「PUT YOUR CODE」という箇所があるので、書き換えてプログラムを完成させる
3. スクリプトの修正完了したら実行
1. qsub –j oe run_multi_node_training.sh

4. 結果を確認

29

ジョブスクリプト
マルチノード利用の条件指定#!/bin/bash#PBS -q (対象ジョブキュー名)#PBS -l (利用ノード数など)#PBS –W (利用グループ名)#PBS –l (実行時間制限)#PBS -N (ジョブ名)...

利用ノードなどのパラメータは、以下をコロン「:」区切りで指定• select ノード数
• mpiprocs mpiプロセス数/ノード• ompthreads スレッド数/プロセス
ノードごとに2GPUで2ノードなので……
例) –l select=2:mpiprocs=230

スクリプトの変更箇所(ジョブスクリプト)

ノード数などを指定

31

スクリプトの変更箇所(PYTHON; 1/2)
MPIプロセス情報を参照してどのGPUを割り当てるか指定
元データは親相当のプロセスのみロードするよう制御

各プロセスにデータを分配
32

スクリプトの変更箇所(PYTHON; 2/2)

マルチノード実行向けに最適化ロジックをラップ

マルチノード環境で適切に評価が動くようラップ
33

CHAINERMNのAPI
環境情報の取得• communicator.rank•

ワーカーID(プロセスID)を取得• communicator.intra_rank•

ノード内のプロセスIDを取得• ノード内2プロセスで、2ノード使用の場合の例•

ノード1: rank=0&intra_rank=0, rank=1&intra_rank=1•

ノード2: rank=2&intra_rank=0, rank=3&intra_rank=1
34

マルチノード実行時の状況
GPUとプロセス: ノード内2プロセス、2ノードの例
ノード#0

Rank=0Intra_rank=0

ノード#1

Rank=1
Intra_rank=1

Rank=2Intra_rank=0

各プロセスのintra_rankの数値を見て、どのGPUを使うかなどを決める

Rank=3Intra_rank=1
35

CHAINERMNでの分散ロジックAll-Reduceの利用
シングルGPUの場合
ForwardBackward

マルチGPUの場合
Forward

ForwardUpdate

共有された勾配でGPUごとにモデル更新

Backward

UpdateBackward

UpdateAll-ReduceによりGPU間で勾配を集約・共有

1iteration
36

CHAINERMNのAPI
分散学習用のAPI• chainermn.scatter_dataset(dataset, comm,shuffle)•

データセットを分割して、各プロセスに配布• chainermn.create_multi_node_optimizer(actual_optimizer, communicator)•

マルチノード環境で動作するよう、optimizerをラップ• chainermn.create_multi_node_evaluator(actual_evaluator, communicator)•

マルチノード環境で動作するよう、evaluatorをラップ

37

スクリプトの修正を試みましょう

コンソールに戻ってください！

38

スクリプトの変更箇所(答え; ジョブスクリプト)#PBS –l select=2:mpiprocs=2:ompthreads=1

ChainerMNはmpirunコマンドを使って起動39

スクリプトの変更箇所(答え; 1/2)
device = comm.intra_rank

if comm.rank == 0:

train=chainermn.scatter_dataset(train, comm, shuffle=True, ...)

test=chainermn.scatter_dataset(test, comm, shuffle=True, ...)

40

スクリプトの変更箇所(答え; 2/2)

optimizer=chainermn.create_multi_node_optimizer(chainer.optimizers.MomentumSGD(0.05), comm)evaluator=chainermn.create_multi_node_evaluator(evaluator, comm)41

マルチノードでの学習実行結果[1527482343.161277] [a080:11782:0]
sys.c:744 MXM WARN Conflicting CPU frequencies detected,using: 2600.39(中略)Start a training script using multiple nodes.
==========================================
Num process (COMM_WORLD): 4
Using GPUs
Using hierarchical communicator
Num Minibatch-size: 32
Num epoch: 4==========================================
Using CIFAR10 dataset:/lustre/gt00/share/lecture/20180531_dl_intro/dataset//cifar10_256px_5p.pickle.gz
epoch
main/loss
validation/main/loss main/accuracy validation/main/accuracy elapsed_time
2ノードx2GPUで173.4image/sec.
12.60504
142.057
0.115625
0.0856008
17.3478
22.73112.319620.129688
0.100822 (対1GPUで3.35倍)
31.013332.80991
2.372290.115132
0.104391
43.9895
42.87262.360620.126563
0.104391
57.6684
Throughput: 173.3976045844656 [images/sec.] (10000 / 57.670923563011456)
42

(OPTION) 追加で試す
早く終わった方のために• プロセス間通信のアルゴリズムを変更するとどうなるか比較してみる•

デフォルトはhierarchicalだが、ほかにも存在•

詳細は
http://chainermn.readthedocs.io/en/latest/reference/index.html#chainermn.create_communicator を参照• Chainer単体で2GPUを使った結果と、ChainerMNで1ノード2GPUを使った結果とを比較してみる•

違いがあるのかどうか検証
43

(FYI) 性能が思うように出ないときにプロファイラによる解析• 十分にGPUを使い切れているかどうかを確認する際、プロファイラが有効なことがある• コマンドはnvprof• 結果の可視化にはNVIDIA Visual Profilerを使う• 実行するには対象コマンドcmdに対し、nvprof -o ${resultfile} cmdとすればOK• オプション--profile-child-processesを追加すると子プロセスも追跡できる• 子プロセスを追うときは、出力ファイル名に%pを含める（ファイル名にプロセスIDを含めるため）44

(FYI) 性能が思うように出ないときにプロファイラによる解析

シングルGPUでの実行例

Nvprof -o ${filepath} cmd--profile-child-processes

マルチGPUでの実行例

45(FYI) 性能が思うように出ないときにプロファイラによる解析

PCIe接続でのGPU間通信の例

NVLinkでのGPU間通信の例

7msec.程度に短縮
25msec.程度
かかっている46

www.nvidia.com/dli