Edited at
PyTorchDay 3

PytorchでMulti-GPUを試す

Pytorch Advent Calender 2018 3日目の記事です。


はじめに

学生に"Pytorchのmulti-GPUはめっちゃ簡単に出来るから試してみ"と言われて重い腰を上げた。

複数GPU環境はあったのだが、これまでsingle GPUしか学習時に使ってこなかった。

試しに2x GPUでCIFAR10を学習しどれくらい速度向上が得られるか実験。

またpitfallなどあったら報告する。


環境

GPU: TitanXp *4

OS: Ubuntu 16.04

Pytorch: 0.4.0

Python: 3.5

Network: Resnet 18

codes:

https://github.com/kentaroy47/pytorch-mgpu-cifar10

GPUコマンドあれこれについて:

https://qiita.com/arutema47/items/3e2118ac95029a43b45c


MultiGPUにするには。。

Code的には超単純.

モデル(nn)にtorch.nn.DataParallelを適応するだけでmultiGPUが使用可能となってしまう。。恐るべし。

Pytorchマニュアル。

https://pytorch.org/tutorials/beginner/former_torchies/parallelism_tutorial.html

device = 'cuda' if torch.cuda.is_available() else 'cpu'

# ネットワーク宣言
net = ResNet18()
# cuda or cpu?
net = net.to(device)
# 複数GPU使用宣言
if device == 'cuda':
net = torch.nn.DataParallel(net) # make parallel
cudnn.benchmark = True


single GPUで学習.

まずはシングルGPUで学習する。

使用GPU切り替えはターミナルにて


export CUDA_VISIBLE_DEVICES=0
python train_cifar10.py --net res18

とGPUが一つしか見えないように宣言すれば良い。

gpu1.JPG

1epochあたり21秒かかっている。


multi GPUで学習

ここでは2つのGPUを使って学習してみる。

どれくらい早くなるかな?理想的には2倍だがCIFAR10は小さいのでどうか。

シェルでGPUが2つ見えるように切り替えておく。


export CUDA_VISIBLE_DEVICES=2,3
python train_cifar10.py --net res18

2gpus.JPG

学習を開始し、正常に同じIDのプロセスがGPU2,3に入っている事を確認。


あれ?

2gpu_batch128.JPG

1 epochあたり15秒。

40%ほどしか高速化していなくておかしい。。


batch数を増加させる。

https://pytorch.org/tutorials/beginner/former_torchies/parallelism_tutorial.html


One can wrap a Module in DataParallel and it will be parallelized over multiple GPUs in the batch dimension.


チュートリアルによるとBatchレベルで並列化しているよとある。

Batchが2個あったら一つをGPU1,もうひとつをGPU2に渡しているということだ。

このスクリプトだとBatch数=128でDataloadしているが、GPU1にBatch=64 GPU2にBatch=64が渡されることになりSingle GPU時に比べBatch数が少なくなっている。

つまりGPUを2つ使う場合はDataloaderのBatch数も2倍にしなければならない。

trainloader = torch.utils.data.DataLoader(trainset, batch_size=256, shuffle=True, num_workers=8)

dataloaderのバッチ数を2倍の256に設定。

2gpus_res18_batch256.JPG

すると1 epochあたり13秒に改善。

2GPU化により60%早くなったのでまあまあかな。データセットが小さいのがまだ足をひっぱている気がする。まだ遅い。

Batch=378まで大きくしてみたがあまり変わらず。


Resnet50 + 101で実験

ネットワークを大きくしてみれば恩恵が大きくなるかと思い実験。

Res50では速度改善は77%!理想に近づいてきた。

ImageNetサイズまでスケールアップすると更に良くなりそう。

ネットワークサイズが小さいとマルチGPUにより計算時間はスケールできる。がgradientをreduce,computeする部分はシリアルのためボトルネックとなってしまう。

Res18
Res50
Res101

SingleGPU
21s
72s
126s

MultiGPU(*2)
13s
44s
77s

Improvement
60%
77%
63%

Res101では学習速度の向上は50ほど良くない。

速度コンペでRes50を皆使うのは一番速度がいい感じで出るからなのだろうか(笑)


singleGPUとMultiGPUのロスを比較

普通バッチ数を2倍にすると学習係数を2倍にしないと学習結果が変わってしまう。

MultiGPU時はどう考えればいいんだっけ?

→ざっと検索してもわからなかったので試してみた。

image.png

single lr=0.1, batch=128と等価なのはmulti(2GPU) lr=0.2, batch=256であった。multi(2GPU) lr=0.1, batch=256は学習の進みが異なる。


マルチGPUのデータの流れはどうなってる?

演算としてはBatch[0:255]のデータを

GPU1 Batch[0:127]

GPU2 Batch[128:255]

のようにアサインする。するとbackpropによりバッチ数分のgradientが生成される。

GPU1 grad[0:127]

GPU2 grad[128:255]

となり、これらは集約する必要がある。

# parameter update

Weights += -sum(grad[0:255]) * learnrate

のような演算を行うため、パラメータアップデート時には何個のGPUに分散したかどうかは関係ない。


結論 (or main takeaways)

1. PytorchでmultiGPUを使う際はBatchsizeも使用するGPU数に比例させ増加させなければ十分な学習速度向上は得られない。

2. Batch数を増やしたらその分学習係数も増やさなければ学習結果は変わってしまう。またはパラメータアップデート時に何個のGPUに分散したかどうかは関係ない。

3. ネットワークサイズはある程度ないとマルチGPUの恩恵は受けられない

gradientをbatch要素毎に計算→CPUに戻しbackprop時に学習率を適応するのでGPU数を増やしても学習率に影響はないと予想..


さらなる高速化のために

PytorchでFP16学習を試す。

https://qiita.com/arutema47/items/d9e097f00b0b4934d07a