ChainerX移行ガイド


![chainerx_mod_summary2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25635/42bd7c78-18c5-c06d-e234-620fdbc7a776.png)

chainerx 対応：コード変換部分早見表


とうとうChainer version 6.0.0 がリリースされましたね。本記事ではこれまでの Chainer v5 以前までに書かれていたモデル訓練用コードを、 Chainer v6 で導入されたChainerXへ対応させるための変更点についてまとめます。
（上記コードの変換をしなくても今まで通り v5 のコードを v6で動作させることは可能です。）
本題に飛びたい方は [v5 のコードを v6 のChainerX対応コードに変換する](#v5-のコードを-v6-のChainerX対応コードに変換する) からご覧ください。

## ChainerX とは

先日Chainer version 6.0.0 が正式リリースされました。

 - [Released Chainer/CuPy v6.0.0](https://chainer.org/announcement/2019/05/16/released-v6.html)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/Chainer?src=hash&amp;ref_src=twsrc%5Etfw">#Chainer</a>/<a href="https://twitter.com/hashtag/CuPy?src=hash&amp;ref_src=twsrc%5Etfw">#CuPy</a> v6.0.0 をリリースしました。変更点の概要はブログをご覧ください。 <a href="https://t.co/1k7VXirOFC">https://t.co/1k7VXirOFC</a>　<a href="https://t.co/Rf7GxJCZyK">https://t.co/Rf7GxJCZyK</a><a href="https://t.co/leNCHR6bdU">https://t.co/leNCHR6bdU</a></p>&mdash; ChainerJP (@ChainerJP) <a href="https://twitter.com/ChainerJP/status/1128951245476061184?ref_src=twsrc%5Etfw">May 16, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

V6で追加された機能で注目したいのは何といってもChainerXです。
ChainerXはC++で実装されており、
 - 多次元配列の計算 Operators：numpy arrayのように使える
 - Device management　　　　：さまざまなDevice (CPU/GPUなど) を切り替えて使える
 - 自動微分機能 　　　　　　　：chainer Variableが行っていた、微分機能

の機能を合わせもつ Array を提供しています。

これまで numpy or cupy という CPUかGPUか、→他のDeviceは考えていないというコードを書いていましたが、ChainerXの導入により**将来新しく出てくるDeviceに対しても**アプリケーション側のコードは同じまま、計算を扱えるようになりました。

つまり、CPU だろうがGPUだろうが、理論的には "`Backend`さえ追加で実装されれば" (計算で使用する関数など)、 IOTに使用される格安チップで使われるFPGA、スマホなどで使われる Arm 専用などなどにも対応できる仕組みということです。

ChainerXに変えることによる大きなメリットは以下二つです
・**高速**：C++で書かれているため、python のOverheadを少なくすることができます。※
・**hetero device への対応**：Backend さえ実装すれば、組み込みなどへの対応も可能です！

※ ただし、現状はBLASなどの実装が最適化されていない関数もあり、絶対に numpy/cupy より速い！とは限らないようです。

より詳しい説明は以下のslideshareやブログをご覧ください
 - [ChainerX and How to Take Part](https://www.slideshare.net/HiroyukiVincentYamaz/chainerx-and-how-to-take-part)
 - [N次元配列の自動微分をC++で実装したChainerXをリリース。Chainer v6（β版）に統合し、計算パフォーマンスを向上](https://www.preferred-networks.jp/ja/news/pr20181203-1)


## インストール方法

 - https://docs.chainer.org/en/latest/chainerx/install/index.html

ChainerXは**DefaultではBuildされません**。
Linux では以下のように環境変数を設定してからChainerをインストールすることで、ChainerXもBuild されます。

    $ export CHAINER_BUILD_CHAINERX=1
    $ export CHAINERX_BUILD_CUDA=1    # Only when you have GPU and CUDA installed
    $ export MAKEFLAGS=-j8            # Using 8 parallel jobs.
    $ pip install chainer

Dockerfileを使いたい場合は、以下のように書くとよいでしょう。

    FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04
    ...

    # Install from source is expected, to align cudnn version between cupy & chainerx
    RUN pip install cupy==6.0.0

    # chainer & chainerx
    ENV CHAINER_BUILD_CHAINERX 1
    ENV CHAINERX_BUILD_CUDA 1
    ENV CUDNN_ROOT_DIR=/usr/include
    ENV MAKEFLAGS -j8
    RUN pip install chainer==6.0.0
    #RUN git clone https://github.com/chainer/chainer && pip install -e chainer

    # To use ChainerMN
    # RUN pip install mpi4py

GPUを使わない場合は `cupy` のインストールと、 `CHAINERX_BUILD_CUDA`, `CUDNN_ROOT_DIR` の設定の行を抜かしてください。


## v5 のコードを v6 のChainerX対応コードに変換する

いよいよ本題です。既存のChainerコードをChainerXに対応させるためにコードのどの部分を変換すればよいかを紹介します。

一番BasicなOfficial Example である `train_mnist.py` を、 [v5のコード](https://github.com/chainer/chainer/blob/v5/examples/mnist/train_mnist.py)と[v6のコード](https://github.com/chainer/chainer/blob/v6/examples/mnist/train_mnist.py) で比較してみればその違いはすぐに分かります。


一番の大きな違いは以下の部分です。

~v5: CPUかGPUかを、args.gpu として int で指定。 GPUの場合のみ Deviceへ送る処理を書く。

    parser.add_argument('--gpu', '-g', type=int, default=-1)
    ...

    # args.gpu は int. -1 がCPU、0以上が使用するGPU IDを表す。
    model = L.Classifier(MLP(args.unit, 10))
    if args.gpu >= 0:
        # Make a specified GPU current
        chainer.backends.cuda.get_device_from_id(args.gpu).use()
        model.to_gpu()  # Copy the model to the GPU



v6: どの deviceをつかうかを、args.device として str で指定。 任意の `device` へ送る処理を書く。

    parser.add_argument('--device', '-d', type=str, default='-1')
    ...

    # args.device は str となり、Deviceを文字列で表すように。
    device = chainer.get_device(args.device)
    model = L.Classifier(MLP(args.unit, 10))
    model.to_device(device)
    device.use()


**基本的にはこの部分を変更するだけでOKです。**
上記コードの変換をするとChainerXに対応した動作ができるようになるということなので、変換をしなくても今まで通り v5 のコードを v6で動作させることは可能です。

`device.use()` は必須ではなく省略可能のようです。
また有効範囲としては、thread local な機能なので、別スレッドでは効果がないということに注意してください。

以下、コード上は変化ありませんが、関数の引数が変化している部分です。
これまで `device` として -1 or 0 といった int を引数としていたところが、 chainerのDevice instance を受け取るように変化しています。

 - Updater
 - Evaluator

~v5

    device = args.gpu  # type: int
    updater = training.updaters.StandardUpdater(
            train_iter, optimizer, device=device)

    trainer.extend(extensions.Evaluator(
        test_iter, model, device=device))

v6

    device = chainer.get_device(args.device)  # type: chainer._backend.Device
    updater = training.updaters.StandardUpdater(
            train_iter, optimizer, device=device)

    trainer.extend(extensions.Evaluator(
        test_iter, model, device=device))



また、`converter` をカスタマイズして使っている方はその部分も変更が入っています。

### 実行時の option の指定方法

これまで、
`--gpu -1`　→ CPU, numpy
`--gpu 0` 　→ GPU, cupy

と int 型を指定してCPU or GPUを切り替えていたののが、

`--device -1`　    → CPU, numpy
`--device 0`　     → GPU, cupy
`--device native`  → CPU (native backend), chainerx
`--device cuda:0`  → GPU (CUDA backend), chainerx

というように `str` 型を渡すことで任意のDeviceを使えるような設計に変わっています。
'-1' や '0' といった int に変換できる文字列を渡した場合には これまでどおりの挙動と同じく numpy/cupy が使われます。

今後新しいDeviceに対しても Backend が実装されれば、アプリケーション側のコードは同じまま、
`--device custom_device:0` などと指定することで動かせることができるようになっています。

## まとめ
以上をまとめると冒頭に張った図のように変換するだけでChainerXを動かせるということになります。

![chainerx_mod_summary2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25635/42bd7c78-18c5-c06d-e234-620fdbc7a776.png)

chainerx 対応：コード変換部分早見表

時間がとれれば次は少しコードリーディングや device class/backend 機構について追記したいと思っています。

皆さんもぜひChainerXを使ってみてください。
[chainer レポジトリ](https://github.com/chainer/chainer)から、コードをpull して /examples/mnist で 
`python train_mnist.py --device cuda:0` 
などと実行してみれば動かせるはずです！