Chainer でマルチGPUを試してみる

## やりたいこと
せっかくPCに2枚GPUがあるので、マルチGPUでDeepLearningしてみたい！
ということで、Chainerでやってみました。

## 環境
実行環境は下記の通りです。
- OS: Windows 10 Pro
- CPU: Intel Xeon E3-1240v3 3.40GHz
- メインメモリ: 24GB(4GBx2, 8GBx2)
- GPU 0: NVIDIA GeForce GTX 1050 Ti 4GB
- GPU 1: NVIDIA Quadro K2000 2GB
- Disk: Samsung SSD 860 EVO 500GB
- python=3.6.8
- chainer=5.3.0 (condaで入れた)
- cupy-cuda100=5.4.0 (pipで入れた)

OSライセンス付、GPU(K2000)付の中古PCを約3.2万円で購入し、モニタ1万円未満、GPU(GTX1050Ti) 2万円未満、SSD 1万円未満、メモリ16GB 1万円未満の総額約8万円です。
なぜかcondaではcupy=4.1.0しか入らなかったので、アンインストールしてpipで入れなおしました。

## 実行
GitHubからChainerのbranch v5をクローンし、/examples/mnist/ を試してみます。

```
> git clone -b v5 https://github.com/chainer/chainer.git
> cd chainer/examples/mnist
```

### CPU: Intel Xeon E3-1240v3 3.40GHz

```
> python train_mnist.py
GPU: -1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz...
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.188287    0.0964475             0.942783       0.9711                    48.1507
2           0.0740424   0.092968              0.976533       0.9704                    84.4993
3           0.0471156   0.0895041             0.9851         0.9737                    120.279
4           0.0368677   0.0782165             0.988          0.9766                    154.746
5           0.0272672   0.0831235             0.991333       0.977                     188.724
6           0.02559     0.0846559             0.99175        0.9786                    224.771
7           0.0204407   0.0845285             0.99305        0.9796                    258.854
8           0.0201815   0.0869562             0.9936         0.978                     293.897
9           0.016543    0.0926294             0.994783       0.9777                    328.331
10          0.0141709   0.0804844             0.995367       0.9821                    362.436
11          0.0118971   0.080963              0.99615        0.9823                    396.654
12          0.012708    0.084998              0.9962         0.9809                    430.591
13          0.0135935   0.0925437             0.995783       0.9803                    467.608
14          0.0108333   0.098339              0.996567       0.9804                    503.071
15          0.0098921   0.123784              0.996867       0.9795                    537.966
16          0.0143277   0.0788444             0.9964         0.9831                    572.556
17          0.00864772  0.090874              0.997117       0.984                     604.493
18          0.011913    0.0743239             0.996917       0.9843                    635.828
19          0.00436262  0.107345              0.99865        0.983                     667.157
20          0.00707226  0.0903141             0.998217       0.9837                    698.653
```

### GPU 0: NVIDIA GeForce GTX 1050 Ti 4GB

```
> python train_mnist.py --gpu 0
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.189976    0.0892177             0.942833       0.9735                    12.0189
2           0.0752845   0.0832106             0.976366       0.9741                    16.1882
3           0.0471824   0.0665943             0.984682       0.98                      19.9812
4           0.034498    0.0805068             0.989182       0.977                     23.9403
5           0.0285627   0.0817467             0.990881       0.9791                    27.7564
6           0.0246776   0.0762155             0.991915       0.9808                    31.5498
7           0.0209579   0.0818291             0.993148       0.9803                    35.3837
8           0.0173544   0.0905734             0.994382       0.977                     39.4301
9           0.0153213   0.0809816             0.995099       0.9824                    43.419
10          0.0141218   0.081298              0.995582       0.9817                    47.2647
11          0.0122369   0.0851226             0.995982       0.9811                    51.1495
12          0.0152793   0.0824623             0.995365       0.9828                    55.0064
13          0.0127705   0.087196              0.996265       0.9811                    58.7889
14          0.01036     0.0989774             0.996733       0.9814                    62.6396
15          0.00783721  0.0930669             0.997566       0.9805                    66.3864
16          0.0120825   0.0977217             0.996332       0.9799                    70.1693
17          0.00943458  0.121491              0.997449       0.9799                    74.0379
18          0.0117653   0.0909474             0.996516       0.9825                    77.9659
19          0.00977425  0.0968582             0.997549       0.9829                    81.7787
20          0.0117059   0.113914              0.996666       0.9794                    85.7096
```

### GPU 1: NVIDIA Quadro K2000 2GB

```
> python train_mnist.py --gpu 1
GPU: 1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.19171     0.100066              0.94255        0.9696                    11.4576
2           0.0754682   0.0875764             0.976649       0.9738                    16.7115
3           0.0471999   0.0760614             0.984499       0.9785                    21.9246
4           0.0335111   0.0781458             0.989282       0.9759                    27.1607
5           0.0279633   0.0822519             0.990799       0.9778                    32.3802
6           0.025783    0.0711645             0.991965       0.9816                    37.601
7           0.0176336   0.0900763             0.994332       0.9792                    42.7692
8           0.0175025   0.0811717             0.994532       0.9809                    47.9583
9           0.0163443   0.104123              0.994765       0.9786                    53.2008
10          0.016325    0.0901095             0.994698       0.9805                    58.4468
11          0.0148685   0.0930068             0.995565       0.9808                    63.6781
12          0.0125574   0.0930329             0.996132       0.9812                    68.9363
13          0.0129305   0.095227              0.995966       0.9823                    74.184
14          0.0088038   0.118182              0.996949       0.9803                    79.4221
15          0.013182    0.101077              0.995799       0.9811                    84.649
16          0.0113774   0.090629              0.996616       0.9816                    89.8784
17          0.00734026  0.0970724             0.997716       0.9832                    95.1036
18          0.0116805   0.0985688             0.996632       0.9832                    100.348
19          0.00937403  0.102181              0.997116       0.9816                    105.602
20          0.00310589  0.116704              0.99895        0.9825                    110.866
```

### マルチGPU (NVIDIA GeForce GTX 1050 Ti 4GB + NVIDIA Quadro K2000 2GB)

```
> python train_mnist_data_parallel.py
GPU: 0, 1
# unit: 1000
# Minibatch-size: 400
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.279714    0.120431              0.919166       0.9613                    6.57181
2           0.0852208   0.0769835             0.974567       0.9749                    10.1082
3           0.0517447   0.0801815             0.983967       0.9737                    13.6456
4           0.03252     0.0904261             0.989734       0.9727                    17.1756
5           0.0225429   0.0669389             0.993167       0.98                      20.7171
6           0.0181462   0.0622369             0.9942         0.9822                    24.2495
7           0.0133678   0.0835941             0.995733       0.9765                    27.785
8           0.014366    0.0858466             0.9953         0.978                     31.3237
9           0.00786593  0.0903438             0.997534       0.9795                    34.8555
10          0.0089393   0.0749918             0.9968         0.9821                    38.3886
11          0.0072351   0.0788488             0.997867       0.9823                    41.9232
12          0.00581059  0.0780663             0.9983         0.9826                    45.4561
13          0.0120998   0.0941829             0.995334       0.9785                    48.9994
14          0.0116896   0.0939382             0.995967       0.9792                    52.5245
15          0.00721718  0.0831597             0.997733       0.9822                    56.0741
16          0.00422235  0.0846977             0.9986         0.9832                    59.6151
17          0.00593313  0.0860614             0.998133       0.9827                    63.1654
18          0.00420095  0.0804558             0.998767       0.9836                    66.7144
19          0.00655401  0.0929561             0.997733       0.982                     70.2485
20          0.00847227  0.0957024             0.997434       0.9821                    73.7951
```

## 結果まとめ

モデルは3層のMLPで、validation/main/lossが数epochで上がり出す過学習ですが、GPU 0単体に比べ約14%学習時間を短縮できました。
GPUメモリを合わせて6GB使えればGTX 1060並みなのでは？

|  xPU  | elapsed_time |
|:------|-------------:|
| CPU   |   698.653    |
| GPU 0 |    85.710    |
| GPU 1 |   110.866    |
| 2GPUs |    73.795    |


## マルチGPUの指定方法
下記の通り、updater を StandardUpdater から ParallelUpdater にして、 devices= で指定するだけで、マルチGPUにできるようです。これは簡単！

### シングルGPU [train_mnist.py](https://github.com/chainer/chainer/blob/v5/examples/mnist/train_mnist.py)

``` python
    updater = training.updaters.StandardUpdater(
        train_iter, optimizer, device=args.gpu)
```

### マルチGPU [train_mnist_data_parallel.py](https://github.com/chainer/chainer/blob/v5/examples/mnist/train_mnist_data_parallel.py)

``` python
    updater = training.updaters.ParallelUpdater(
        train_iter,
        optimizer,
        # The device of the name 'main' is used as a "master", while others are
        # used as slaves. Names other than 'main' are arbitrary.
        devices={'main': args.gpu0, 'second': args.gpu1},
    )
```