AWS GPU インスタンスにおける ChainerMN の分散効率に対する評価

EDIT(2018/05/26): ChainerMN 1.2.0/13.0 において実装された double buffering および FP16 サポートの機能により AWS 環境でも高い分散効率を出せるようになったようです。See [ChainerMNのクラウド環境向け新機能とAWSにおける性能評価 | Preferred Research](https://research.preferred.jp/2018/05/chainermn%E3%81%AE%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E7%92%B0%E5%A2%83%E5%90%91%E3%81%91%E6%96%B0%E6%A9%9F%E8%83%BD%E3%81%A8aws%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%80%A7%E8%83%BD%E8%A9%95/)

AWS EC2 インスタンスにおける ChainerMN の分散効率に対する評価を実施した。ChainerMN のバージョンは 1.0.0。

AWS の p3 (v100) インスタンスもつい最近ローンチされたので、p2 と p3 インスタンスで評価を実施する。

# バージョンなど

* AWS p2 (k80) および p3 (v100) インスタンス
* Ubuntu 16.04
* Python 3.5.2 (`/usr/bin/python3` of Ubuntu 16.04)
* CUDA 9.0
* cuDNN 7.0.3
* Chainer 4.0.0a1
* CuPy 3.0.0a1
* Open MPI v3.0.0
* ChainerMN 1.0.0

# 実行したコマンド

hostfile を用意して、chainermn にある mnist, reset50 examples を利用

```
mpiexec --hostfile hostfile python3 chainermn/examples/mnist/train_mnist.py --gpu
```

```
mpiexec --hostfile hostfile python3 chainermn/examples/imagenet/train_imagenet.py --arch resnet50 --epoch 100 --batchsize 32 2048 2048
```

hostfile は例えば p2.8xlarge の場合は、１ホストで8プロセス走らせたいので `cpu=8` と指定する。

```txt:hostfile
host01 cpu=8
host02 cpu=8
```

なお、[分散数 * batchsize が総バッチサイズになる](https://research.preferred.jp/2017/02/chainermn-benchmark-results/)

## resnet50 へのパッチ

resnet50 に対しては、データを用意するのが面倒だったので、パッチをあててランダムにデータを生成して計測した。精度は全くあてにならなくなるが、分散効率の評価には十分使える。

chainermn/examples/imagenet/train_imagenet.py にあてたパッチ

```diff
--- a/examples/imagenet/train_imagenet.py
+++ b/examples/imagenet/train_imagenet.py
@@ -43,16 +43,21 @@ if major <= 2 or (major == 3 and minor < 4):

 class PreprocessedDataset(chainer.dataset.DatasetMixin):

-    def __init__(self, path, root, mean, crop_size, random=True):
-        self.base = chainer.datasets.LabeledImageDataset(path, root)
+    def __init__(self, length, root, mean, crop_size, random=True):
+        # self.base = chainer.datasets.LabeledImageDataset(path, root)
+        self.length = int(length)
         self.mean = mean.astype('f')
         self.crop_size = crop_size
         self.random = random

     def __len__(self):
-        return len(self.base)
+        return self.length # len(self.base)

     def get_example(self, i):
+        image = np.random.rand(3, self.crop_size, self.crop_size).astype('f')
+        label = np.random.randint(0, 9, 1, 'int32')[0]
+        return image, label
+
         # It reads the i-th image/label pair and return a preprocessed image.
         # It applies following preprocesses:
         #     - Cropping (random or center rectangular)
@@ -160,7 +165,7 @@ def main():

     # Split and distribute the dataset. Only worker 0 loads the whole dataset.
     # Datasets of worker 0 are evenly split and distributed to all workers.
-    mean = np.load(args.mean)
+    mean = np.random.rand(3, model.insize, model.insize).astype('f') #  np.load(args.mean)
     if comm.rank == 0:
         train = PreprocessedDataset(args.train, args.root, mean, model.insize)
         val = PreprocessedDataset(
@@ -175,7 +180,7 @@ def main():
     # using InfiniBand and MultiprocessIterator. This is because processes
     # often crash when calling fork if they are using Infiniband.
     # (c.f., https://www.open-mpi.org/faq/?category=tuning#fork-warning )
-    multiprocessing.set_start_method('forkserver')
+    # multiprocessing.set_start_method('forkserver')
     train_iter = chainer.iterators.MultiprocessIterator(
         train, args.batchsize, n_processes=args.loaderjob)
     val_iter = chainer.iterators.MultiprocessIterator(
```

# メトリクス計測方法

## 速度

実行時に出てくる iters/sec を取得して計測。ChainerMN のメインプロセスでのみ表示されるので、iters/sec/process と捉えて、分散数倍する。

```
     total [######............................................] 13.28%
this epoch [##############....................................] 28.12%
       850 iter, 13 epoch / 100 epochs
    5.3518 iters/sec. Estimated time to finish: 0:17:17.042087.
```

## GPU Usages

nvidia-smi で取れる。この時に重要なのが、 `Volatile GPU-Util` だけではなく、 `Persistence-M Pwr:Usage/Cap` も見ること。NCCLによる通信待ちの状態でも GPU-Util は 100% になるので、区別するには使用電力を見る必要がある。

最初は GPU-Util だけ見て「100% 使ってるのに遅いなぁ、おかしい」などと言っていた。要注意。

```
$ nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.90                 Driver Version: 384.90                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   45C    P0    90W / 300W |   4194MiB / 16152MiB |     55%      Default |
+-------------------------------+----------------------+----------------------+
```

## CPU Usages

top -c のあと 1 を打って、コアごとの Usages をみること。

network traffic が増えると sys (kernel) に使われる CPU 使用率が上がる傾向が見られた。

```
$ top -c
%Cpu0  : 36.1 us, 31.8 sy,  0.0 ni, 32.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 40.4 us,  1.7 sy,  0.0 ni, 55.7 id,  0.0 wa,  0.0 hi,  1.7 si,  0.3 st
%Cpu2  : 49.8 us, 18.7 sy,  0.0 ni, 31.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 68.9 us, 10.7 sy,  0.0 ni, 15.7 id,  0.0 wa,  0.0 hi,  4.3 si,  0.3 st
%Cpu4  : 69.5 us,  0.3 sy,  0.0 ni, 28.9 id,  0.0 wa,  0.0 hi,  1.3 si,  0.0 st
%Cpu5  : 89.1 us,  0.0 sy,  0.0 ni, 10.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  : 33.0 us, 20.5 sy,  0.0 ni, 41.7 id,  0.0 wa,  0.0 hi,  4.9 si,  0.0 st
%Cpu7  : 39.3 us, 10.2 sy,  0.0 ni, 49.2 id,  0.0 wa,  0.0 hi,  1.4 si,  0.0 st
```

## Network bandwidth

ChainerMN は現状、計算→通信→計算→通信 という動きをする。このワークロードではバーストトラフィックを見なければならない。

最初は `iotop` や `ifstat` などで 1s あたりの平均ネットワーク帯域を見ていて、「ネットワーク帯域が余っているのに遅いなぁ、おかしい」などと言っていた。要注意。

以下の python スクリプトで 1ms 辺りのネットワーク帯域を観測した。`/proc/net/dev` を 1ms ごとに観測して、ネットワーク帯域を出力している。

ネットワークデバイス(ドライバ)によっては 1ms の解像度で観測できないらしいが、幸いなことに p2.16xlarge よび p3.x16xlarge の環境ではこのスクリプトで 1ms の解像度で観測できた。実際はもっと細かい解像度で観測する必要があると思われるため、この値はあくまで参考値とした。

```python:net.py
import re
import time
import os

SLEEP_SEC =  0.001
if os.system('ifconfig eth0 > /dev/null 2>&1') == 0:
    INTERFACE = 'eth0'
elif os.system('ifconfig ens3 > /dev/null 2>&1') == 0:
    INTERFACE = 'ens3'
else:
    raise('interface is not found')

while True:
    b_time = time.clock_gettime(time.CLOCK_MONOTONIC)
    with open('/proc/net/dev') as f:
        b_lines = f.readlines()
    time.sleep(SLEEP_SEC)
    with open('/proc/net/dev') as f:
        a_lines = f.readlines()
    a_time = time.clock_gettime(time.CLOCK_MONOTONIC)

    for line in b_lines:
        if line.find(INTERFACE) >= 0:
            b_rx_bytes = int(line.split()[1])
            b_tx_bytes = int(line.split()[9])
            break
    for line in a_lines:
        if line.find(INTERFACE) >= 0:
            a_rx_bytes = int(line.split()[1])
            a_tx_bytes = int(line.split()[9])
            break
    d_rx_bytes = a_rx_bytes - b_rx_bytes
    d_tx_bytes = a_tx_bytes - b_tx_bytes
    d_time = a_time - b_time
    print("%d bps(in), %d bps(out), %.5f sec" % (d_rx_bytes / d_time * 8, d_tx_bytes / d_time * 8, d_time))
```

```
$ python3 net.py | tee /tmp/net.out
3528738 bps(in), 1354050 bps(out), 0.00120 sec
...
...
$ expr $(cat /tmp/net.out  | awk '{print $1}' | sort -nr | head -1) / 1024 / 1024 / 1024
14 # Gbps
```

## Disk IO やメモリなど

さっと見た感じ余裕で余っていたので観測は飛ばした。

# 計測結果

* p2 インスタンスは k80、p3 インスタンスは v100 を装備
* k80 一枚あたり \$0.9/h (oregon)、v100 一枚あたり \$3.06/h (oregon) でコスト換算できる

Spreadsheet で見たい方はこちら。計測したメトリクスについては Spreadsheet にしか書いてない。
https://docs.google.com/spreadsheets/d/14s1YQpU1x6zmHQOC2wCwQWWez6jw6lQrC4h93dJN6ME/edit?usp=sharing

## resnet50

|  Machine Spec |  |  |  | Result |  |  |  |
|  ------ | ------ | ------ | ------ | ------: | ------ | ------ | ------ |
|  Machine type | # of Machines | # of GPUs | Measured Network<br/>bandwidth by iperf | iters/sec/process | Speed-up | Speed-up<br/> (vs p2) | Parallel Efficiency<br/>(1 is great) |
|  p2.xlarge | 1 | 1 | 1.44Gbps | 1.2071 | 1 |  | 1 |
|   | 2 | 2 | 1.44Gbps | 1.0223 | 1.6938 |  | 0.847 |
|   | 3 | 3 | 1.44Gbps | 0.99681 | 2.4774 |  | 0.8258 |
|   | 4 | 4 | 1.44Gbps | 0.95933 | 3.179 |  | 0.7948 |
|  p2.8xlarge | 1 | 8 | 10Gbps | 1.1848 | 7.8522 |  | 0.9816 |
|   | 2 | 16 | 10Gbps | 0.97573 | 12.9332 |  | 0.8084 |
|   | 3 | 24 | 10Gbps | 0.93625 | 18.6149 |  | 0.7757 |
|   | 4 | 32 | 10Gbps | 0.90176 | 23.9055 |  | 0.7471 |
|  p2.16xlarge | 1 | 16 | 24.5Gbps | 1.0875 | 14.4147 |  | 0.901 |
|   | 2 | 32 | 24.5Gbps | 0.87994 | 23.327 |  | 0.729 |
|   | 3 | 48 | 24.5Gbps | 0.79586 | 31.6472 |  | 0.6594 |
|   | 4 | 64 | 24.5Gbps | 0.76187 | 40.3941 |  | 0.6312 |
|  p3.2xlarge | 1 | 1 | 10Gbps | 5.3518 | 1 | 4.4336 | 1 |
|   | 2 | 2 | 10Gbps | 2.4848 | 0.9286 | 4.117 | 0.4643 |
|   | 3 | 3 | 10Gbps | 2.1693 | 1.216 | 5.3914 | 0.4054 |
|   | 4 | 4 | 10Gbps | 2.1558 | 1.6113 | 7.1437 | 0.4029 |
|  p3.8xlarge | 1 | 4 | 10Gbps | 5.2089 | 3.8932 | 17.2609 | 0.9733 |
|   | 2 | 8 | 10Gbps | 3.1953 | 4.7764 | 21.1767 | 0.5971 |
|   | 3 | 12 | 10Gbps | 2.1676 | 4.8603 | 21.5485 | 0.4051 |
|   | 4 | 16 | 10Gbps | 2.587 | 7.7342 | 34.2904 | 0.4834 |
|  p3.16xlarge | 1 | 8 | 24.5Gbps | 5.0137 | 7.4946 | 33.2281 | 0.9369 |
|   | 2 | 16 | 24.5Gbps | 2.4339 | 7.2765 | 32.2611 | 0.4548 |
|   | 3 | 24 | 24.5Gbps | 2.1206 | 9.5098 | 42.1625 | 0.3963 |
|   | 4 | 32 | 24.5Gbps | 1.9216 | 11.4898 | 50.9413 | 0.3591 |

## mnist

|  Machine Spec |  |  |  | Result |  |  |  |
|  ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ |
|  Machine type | # of Machines | # of GPUs | Measured Network<br/>bandwidth by iperf | Seconds for<br/>1 epoch | Speed-up | Speed-up<br/>(vs p2) | Parallel Efficiency<br/>(1 is great) |
|  p2.xlarge | 1 | 1 | 1.44Gbps | 5.4 | 1 |  | 1 |
|   | 2 | 2 | 1.44Gbps | 13.9 | 0.3885 |  | 0.1942 |
|   | 4 | 4 | 1.44Gbps | 10.5 | 0.5143 |  | 0.1286 |
|   | 8 | 8 | 1.44Gbps | 6.1 | 0.8852 |  | 0.1107 |
|  p2.8xlarge | 1 | 8 | 10Gbps | 0.72 | 7.5 |  | 0.9375 |
|   | 2 | 16 | 10Gbps | 0.74 | 7.2973 |  | 0.4561 |
|   | 3 | 24 | 10Gbps | 0.65 | 8.3077 |  | 0.3462 |
|   | 4 | 32 | 10Gbps | 0.47 | 11.4894 |  | 0.359 |
|  p2.16xlarge | 1 | 16 | 24.5Gbps | 0.39 | 13.8462 |  | 0.8654 |
|   | 2 | 32 | 24.5Gbps | 0.46 | 11.7391 |  | 0.3668 |
|   | 3 | 48 | 24.5Gbps | 0.39 | 13.8462 |  | 0.2885 |
|   | 4 | 64 | 24.5Gbps | 0.31 | 17.4194 |  | 0.2722 |
|  p3.2xlarge | 1 | 1 | 10Gbps | 4.8 | 1 | 1.125 | 1 |
|   | 2 | 2 | 10Gbps | 4.8 | 1 | 1.125 | 0.5 |
|   | 3 | 3 | 10Gbps | 4 | 1.2 | 1.35 | 0.4 |
|   | 4 | 4 | 10Gbps | 3.6 | 1.3333 | 1.5 | 0.3333 |
|  p3.8xlarge | 1 | 4 | 10Gbps | 1.24 | 3.871 | 4.3548 | 0.9677 |
|   | 2 | 8 | 10Gbps | 1.2 | 4 | 4.5 | 0.5 |
|   | 3 | 12 | 10Gbps | 0.9 | 5.3333 | 6 | 0.4444 |
|   | 4 | 16 | 10Gbps | 0.8 | 6 | 6.75 | 0.375 |
|  p3.16xlarge | 1 | 8 | 24.5Gbps | 0.62 | 7.7419 | 8.7097 | 0.9677 |
|   | 2 | 16 | 24.5Gbps | 0.66 | 7.2727 | 8.1818 | 0.4839 |
|   | 3 | 24 | 24.5Gbps | 0.6 | 8 | 9 | 0.303 |
|   | 4 | 32 | 24.5Gbps | 0.58 | 8.2759 | 9.3103 | 0.2586 |


# 考察

## 分散効率について

### resnet50

p2.8xlarge ４台構成で分散効率 75%、p2.16xlarge ４台構成で分散効率 63% というまずまずの結果が出た。p2.8xlarge においては GPU の消費電力も平均して 100% 近く発揮できていた。

しかし、p3.8xlarge および p3.16xlarge になると、消費電力が平均して 80W/300W ほどに落ちてしまった。v100 になり GPU の計算能力が上がった結果、GPU の計算能力が余ってしまったものと思われる。結果として、分散効率も p3.8xlarge で 48%、p3.16xlarge で 35% に落ち込んでしまった。

### mnist

p2.16xlarge 4台構成で分散効率 27%、p3.16xlarge ４台構成でも分散効率 25% と低い結果になった。

mnist に関しては、1 GPUの時点でGPUの利用率が50%程度しかなく、分散することによってさらに GPUの計算能力が余ってしまい、良い結果にならなかった。

## 精度について

分散数を増やすほど、**同エポック**での精度は下がる傾向にあるようだ。最終的には精度は同程度に落ち着くというのが[ChainerMNの主張](https://research.preferred.jp/wp-content/uploads/2017/02/dlsummit_02_learning_curve.png)なので、分散数を増やした時は、エポック数も気持ち多めに指定する必要がありそう。

※ そういう意味で 1 epoch 辺りの速度を比較しても片手落ちではあるのだよなぁ。

補足: これについては、ChainerMN というよりは、巨大バッチサイズでの学習効率の話になるようで、単純にエポック数を増やせば良いという話ではないようだ。Slow-start Learning rate とか warm up とか[1][2][3]。

[1] [Priya Goyal, Piotr Dollár, Ross Girshick, Pieter Noordhuis, Lukasz Wesolowski, Aapo Kyrola, Andrew Tulloch, Yangqing Jia, Kaiming He, Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour](https://arxiv.org/abs/1706.02677)
[2] [Samuel L. Smith, Pieter-Jan Kindermans, Quoc V. Le, Don't Decay the Learning Rate, Increase the Batch Size](https://arxiv.org/abs/1711.00489)
[3] [Extremely Large Minibatch SGD: Training ResNet-50 on ImageNet in 15 Minutes, Takuya Akiba, Shuji Suzuki, Keisuke Fukuda](https://arxiv.org/abs/1711.04325)

## p2 vs p3

mnist においては、p2.16xlarge １台 (k80 16枚) と p3.16xlarge １台 (v100 8枚)で速度を比較すると、 GPUの枚数が多いせいか p2.16xlarge 1台のほうが 1.7倍速いという結果が出た。さらに p2.16xlarge のほうが1.7倍安い。

resnet50 においては、p2.16xlarge 1台に比べて p3.16xlarge １台のほうが **5倍** 速いという結果が出た。1.7倍高いが5倍速いなら割にあう。これは以前、別件で調べたのであるが、cuDNN が Convolution2D の計算において、少なくとも pascal 以上の世代では、3x3 kernel において、winograd アルゴリズムを使うようになっているためであると思われる。winograd アルゴリズムが使われると Convolution2D の演算において、winograd が使われない k80 に比べて 10 倍ほど高速化される。

p2.16xlarge １台 (k80 16枚) と p3.16xlarge １台 (v100 8枚)で分散効率を比較すると、わずかであるが p3.16xlarge のほうが高くなっていた。これは単純に枚数が少ないのと、ひょっとしたら p3 が NVLink を使用しているのが影響しているかもしれない。

# まとめ

[ChainerMN の論文](https://arxiv.org/abs/1710.11351)では、InfiniBand 装備の 4GPU 4台構成で 83.88%、32台構成で 79.16% の分散効率が出ているが、p2.16xlarge および p3.16xlarge ではネットワーク帯域が 25Gbps 程度であるため、そこまでの分散効率は得られなかった。平均としてはネットワーク帯域が余っているので、ネットワーク転送と演算を並列化するなどできれば、AWS環境でもより効率をあげられる可能性はありそう。

ニューラルネットワークのモデルによって、分散効率が高いものや低いものがあることがわかった。例えば resnet50 はある程度分散効率が高いが、mnist は分散させても計算リソースが余ってしまうだけであまり効果がない。また、その resnet50 においても、p2.8xlarge 4台では 74% の分散効率が出ているが、p3.8xlarge 4台では 48% 程度になるなど、最新世代である v100 では計算リソースが余ってしまう。

AWSの費用を考えると、２台に増やして速度が上がらない(分散効率が50%より低い)モデルの場合は、複数台は諦めて１台構成で学習させるのが割が良さそう。それでも、どうしても札束で速度を買いたい場合は、割に合わない事を理解した上で台数を増やしても良いとは思うが。。。。

構成選択フローチャート

* (1) Conv2D を多用している？
  * YES: p3.16xlarge を選択
  * NO: p2.16xlarge を選択
* (2) １台構成と２台構成を比較し、2台構成のほうが速度が速い？
  * YES: 複数台構成を選択
  * NO: 1台構成で我慢しましょう

# おまけ: トラブルシューティング

## docker0

あとで nvidia-docker を試そうと思って、docker を入れたホストで実験していたところ、Open MPI および NCCL2 が docker が作る docker0 仮想インターフェースを見にいってしまうようで通信エラーになってしまった。

```
$ mpiexec --hostfile hostfile python3 chainermn/examples/mnist/train_mnist.py --gpu

WARNING: Open MPI accepted a TCP connection from what appears to be a
another Open MPI process but cannot find a corresponding process
entry for that peer.

This attempted connection will be ignored; your MPI job may or may not
continue properly.
```

とか

```
$ mpiexec --hostfile hostfile python3 chainermn/examples/mnist/train_mnist.py --gpu

At least one pair of MPI processes are unable to reach each other for
MPI communications.  This means that no Open MPI device has indicated
that it can be used to communicate between these processes.  This is
an error; Open MPI requires that all MPI processes be able to reach
each other.  This error can sometimes be the result of forgetting to
specify the "self" BTL.

  Process 1 ([[38512,1],0]) is on host: host01
  Process 2 ([[38512,1],1]) is on host: unknown!
  BTLs attempted: self
```

とか。

```
$ sudo ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:71:5a:8c:a1
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

ens3      Link encap:Ethernet  HWaddr 06:1b:83:2c:ef:8c
          inet addr:xx.xx.xx.xx  Bcast:xx.xx.xx.xx  Mask:255.255.255.128
          inet6 addr: xx::xx:xx:xx:xx/64 Scope:Link
```

docker をアンインストールして、

```
sudo ip link set dev docker0 down
sudo ip link delete docker0
```

として docker0 を削除するか、以下のように mpi および nccl が docker0 を見ないように指定することで実行できた。

```
mpiexec --mca btl_tcp_if_exclude docker0,lo --hostfile hostfile env NCCL_SOCKET_IFNAME=^docker0 python3 chainermn/examples/mnist/train_mnist.py --gpu
```

(btl_tcp_if_exclude に docker0 だけでなく lo も指定しないといけなかったのが謎い... :thinking: )

ref. https://www.open-mpi.org/faq/?category=tcp#tcp-selection
ref. http://tabisurubiker.hatenadiary.jp/entry/2017/10/02/092952

## 環境変数

mpiexec は `ssh ホスト名 コマンド` と同様の動きをする。

ログインシェルが起動しないので、`/etc/profile` なり `/etc/profile.d/*` なり `~/.bash_profile` なりのファイルは読み込まれない。インタラクティブシェルが起動するわけでもないので `/etc/bash.bashrc` なり `~/.bashrc` なりのファイルも読み込まれない。(see also man 1 bash)

この場合に環境変数を渡す方法は以下の３つが考えられる。個人的には (2) bash -lc が楽と思っている。

(1) 実行時に env を渡す

```
mpiexec --hostfile hostfile env FOO=1 python print_rank.py
```

(2) bash -lc 経由で実行する

`-l` オプションをつけて bash をログインシェルとして起動しつつ、コマンドを実行する。`/etc/profile` なりが読み込まれる。

```
mpiexec --hostfile hostfile bash -lc 'python print_rank.py'
```

(3) ~/.ssh/environment に記述する

sshd の設定で、PermitUserEnvironment を yes にして、その機能が読み込んでくれる `.ssh/environment` に環境変数設定を記述する。


```diff:/etc/ssh/sshd_config
-# PermitUserEnvironment=no
+PermitUserEnvironment=yes
```

```
$ sudo /etc/init.d/ssh restart
```

```txt:.ssh/environment
FOO=1
```

確認

```
mpiexec --hostfile hostfile env
```

## OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable

１台構成でプロセス数を複数にして mpiexec 経由で train_imagenet.py を実行したところ以下のようなエラーが出た。

```
Traceback (most recent call last):
  File "chainermn/examples/imagenet/train_imagenet.py", line 226, in <module>
    main()
  File "chainermn/examples/imagenet/train_imagenet.py", line 222, in main
    trainer.run()
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/training/trainer.py", line 313, in run
    six.reraise(*sys.exc_info())
  File "/home/anasys/.local/lib/python3.5/site-packages/six.py", line 693, in reraise
    raise value
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/training/trainer.py", line 299, in run
    update()
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/training/updater.py", line 223, in update
    self.update_core()
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/training/updater.py", line 227, in update_core
    batch = self._iterators['main'].next()
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/iterators/multiprocess_iterator.py", line 86, in __next__
    self._thread = self._prefetch_loop.launch_thread()
  File "/home/anasys/.local/lib/python3.5/site-packages/chainer/iterators/multiprocess_iterator.py", line 314, in launch_thread
    initargs=(self.dataset, self.mem_size, self.mem_bulk))
  File "/usr/lib/python3.5/multiprocessing/context.py", line 118, in Pool
    context=self.get_context())
  File "/usr/lib/python3.5/multiprocessing/pool.py", line 168, in __init__
    self._repopulate_pool()
  File "/usr/lib/python3.5/multiprocessing/pool.py", line 233, in _repopulate_pool
    w.start()
  File "/usr/lib/python3.5/multiprocessing/process.py", line 105, in start
    self._popen = self._Popen(self)
  File "/usr/lib/python3.5/multiprocessing/context.py", line 281, in _Popen
    return Popen(process_obj)
  File "/usr/lib/python3.5/multiprocessing/popen_forkserver.py", line 36, in __init__
    super().__init__(process_obj)
  File "/usr/lib/python3.5/multiprocessing/popen_fork.py", line 20, in __init__
    self._launch(process_obj)
  File "/usr/lib/python3.5/multiprocessing/popen_forkserver.py", line 55, in _launch
    f.write(buf.getbuffer())
BrokenPipeError: [Errno 32] Broken pipe
SystemError: initialization of multiarray raised unreported exception
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 2950938 current, 2950938 max
Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/forkserver.py", line 178, in main
    _serve_one(s, listener, alive_r, handler)
  File "/usr/lib/python3.5/multiprocessing/forkserver.py", line 212, in _serve_one
    code = spawn._main(child_r)
  File "/usr/lib/python3.5/multiprocessing/spawn.py", line 115, in _main
    prepare(preparation_data)
  File "/usr/lib/python3.5/multiprocessing/spawn.py", line 226, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "/usr/lib/python3.5/multiprocessing/spawn.py", line 278, in _fixup_main_from_path
    run_name="__mp_main__")
  File "/usr/lib/python3.5/runpy.py", line 254, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "/usr/lib/python3.5/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/anasys/chainermn/examples/imagenet/train_imagenet.py", line 9, in <module>
    import numpy as np
  File "/home/anasys/.local/lib/python3.5/site-packages/numpy/__init__.py", line 142, in <module>
    from . import add_newdocs
  File "/home/anasys/.local/lib/python3.5/site-packages/numpy/add_newdocs.py", line 13, in <module>
    from numpy.lib import add_newdoc
  File "/home/anasys/.local/lib/python3.5/site-packages/numpy/lib/__init__.py", line 8, in <module>
    from .type_check import *
  File "/home/anasys/.local/lib/python3.5/site-packages/numpy/lib/type_check.py", line 11, in <module>
    import numpy.core.numeric as _nx
  File "/home/anasys/.local/lib/python3.5/site-packages/numpy/core/__init__.py", line 16, in <module>
    from . import multiarray
SystemError: initialization of multiarray raised unreported exception
```

ulimit -u を増やしたところで解決はしなかった。yak shaving が過ぎるので深く追ってないが無限ループしている？

AWSの環境では、chainermn/examples/imagenet/train_imagenet.py の下記の箇所をコメントアウトして、`forkserver` 方式を止めた所動いた。

```diff
     # We need to change the start method of multiprocessing module if we are
     # using InfiniBand and MultiprocessIterator. This is because processes
     # often crash when calling fork if they are using Infiniband.
     # (c.f., https://www.open-mpi.org/faq/?category=tuning#fork-warning )
-    multiprocessing.set_start_method('forkserver')
+    # multiprocessing.set_start_method('forkserver')
     train_iter = chainer.iterators.MultiprocessIterator(
         train, args.batchsize, n_processes=args.loaderjob)
     val_iter = chainer.iterators.MultiprocessIterator(
         val, args.val_batchsize, repeat=False, n_processes=args.loaderjob)
```

python 3.5.2 の multiprocessing module の問題かもしれないが深く追ってない。