Intel Chainerを使ってみる

# Intel Chainerを使ってみる

**Disclaimer**: まだまだExperimentalな状態のものを勝手に使っているだけなので、ちゃんとすべての性能をひきだせていない可能性があります。また、導入手順も変更がある可能性が高いです。

## Intel Chainerとは

Intelが公式でChainerから[fork](https://github.com/intel/chainer)した、Intel CPU向けの最適化を施したChainerです。最近Chainer公式などでも[言及](https://www.slideshare.net/unnonouno/chainer-85416154)されるようになりました。

## Intel Chainerをインストールする

### Prerequisite

今回は[2018/1/1当時のmaster_v3ブランチ](https://github.com/intel/chainer/commit/537484c0113c790fd09affbcd97fa20e9bf6da77)を使います。

環境はUbuntu 16.04、gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5)で、Python 3.5.3[^1]で新しいpyenv virtualenv環境を使っています。

[^1]: 公式のRequirementにある3.6.0+をぶっちした感じになりますが、動いてるっぽいのでOK

CPUは`Intel(R) Core(TM) i5-4258U CPU @ 2.40GHz`で、ぎりぎりMKL-DNNの対象内です。（なお、MKL-DNNはXeon/Xeon Phiを主ターゲットとしているため、この条件ではMKL-DNNを活かしきれていない可能性があります。)

### インストール手順

必要ライブラリをインストールします。
`setup.py`から直接必要なライブラリをインストールする形式になっているので、インストールするライブラリは最小限ですみます。

```
sudo apt install swig3.0
pip install protobuf numpy six filelock
```

Intel Chainerをダウンロードします。

```
git clone https://github.com/intel/chainer.git intel-chainer
cd intel-chainer
git checkout 537484c0113c790fd09affbcd97fa20e9bf6da77
```
gcc5.4と、現時点でIntel Chainer同梱のMKL-DNNのだと[バグ](https://github.com/01org/mkl-dnn/issues/157)を踏むため、異なるバージョンのMKL-DNNを指定します。

```diff:dnn_setup.py
diff --git a/dnn_setup.py b/dnn_setup.py
index 0805379..79f9139 100644
--- a/dnn_setup.py
+++ b/dnn_setup.py
@@ -9,7 +9,7 @@ subdir = 'mkldnn'
 # Sepcify prefix under which you put ipl_mkldnn
 # prefix = '/usr/local'
 mkldnn_root = external.mkldnn.root()
-mkldnn_version = 'ba482eca9459e3b9a8256ab07f9afa41dba34b9e'
+mkldnn_version = 'v0.12'
 
 
 def prepare_mkldnn():
```

`python setup.py install`を実行してインストールします。指定のインストール場所他、`~/.chainer/lib`と`~/.chainer/include`に関連ライブラリがインストールされます。

## 試してみる

### MNIST

まずはhello worldというということでmnistです。`examples/mnist`にて`python train_mnist.py`を実行します。本例はLinear/ReLUから構成されています。

|  | Time (s)[^2] | Validation loss |
|:--|:---------|:---------------|
| Intel Chainer | 547.4 | 0.109 |
| Normal Chainer | 671.7 | 0.0982 |

[^2]: 20 epoch終了時点

うーーーーーん、微妙です。
MKL-DNNの詳細はしらないのですが、通常RNN/CNNのほうが工夫しやすいので、RNN/CNNのほうが差がでるかも？

### CIFAR

`examples/cifar`にて`python train_mnist.py -g -1`を実行します。
本例はCNN/BN/max pooling/ReLUから構成されます。

|  | Time (s)[^3] | Validation loss |
|:--|:---------|:---------------|
| Intel Chainer | 1413 | 2.08 |
| Normal Chainer | 3966 | 2.15 |

[^3]: 1 epoch終了時点

このくらいになってくれると気持ちがよいですね。実に**0.36%**の時間で済んでいます。これくらい差があれば、導入するメリットがありそうですね。

### PTB

`examples/ptb`にて`python train_ptb.py`を実行します。本例はEmbedID/LSTM/Linearから構成されます。

|  | Time (s)[^4] | Train loss |
|:--|:---------|:---------------|
| Intel Chainer | 2598 | 6.54 |
| Normal Chainer | 2312 | 6.53 |

[^4]: 500 iterations終了時点

あれれ・・・むしろ遅くなっていますね。適当にググってみたところ、MKL-DNNでは現状LSTMはサポートしてなさそうなので、当然Intel Chainerで速くなるわけない、といったところでしょうか。

なお、`EmbedID`がそのままでは動かなかったので、次のような変更を加えています。

```diff:chainer/functions/connection/embed_id.py
--- a/chainer/functions/connection/embed_id.py                           
+++ b/home/koreyou/.pyenv/versions/intel-chainer/lib/python3.5/site-packages/chainer/functions/connection/embed_id.py                             
@@ -72,8 +72,11 @@ class EmbedIDGrad(function_node.FunctionNode):        
         if xp is numpy:                                                 
             # It is equivalent to `numpy.add.at(gW, x, gy)` but ufunc.at is                                                                      
             # too slow.                                                 
-            for ix, igy in six.moves.zip(x.ravel(),                     
-                                         gy.reshape(x.size, -1)):       
+            x_ravel = x.ravel()                                         
+            gy_reshape = gy.reshape(x.size, -1)                         
+            for i in six.moves.xrange(len(x_ravel)):                    
+                ix = x_ravel[i]                                         
+                igy = gy_reshape[i]                                     
                 if ix == self.ignore_label:                             
                     continue                                            
                 gW[ix] += igy                                           
```