CUDAが動く Julia の深層学習フレームワーク Flux.jl の環境構築をDockerで行う．

# 本日は

- Julia製の深層学習フレームワーク Flux.jl のアドベントカレンダー１日目の記事として執筆したものです．


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Flux.jl Advent Calendar 2019 を作成しました。<a href="https://twitter.com/hashtag/Julia%E8%A8%80%E8%AA%9E?src=hash&amp;ref_src=twsrc%5Etfw">#Julia言語</a> <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a> <a href="https://t.co/BLsZ7asCgB">https://t.co/BLsZ7asCgB</a></p>&mdash; abap🌒⁦(ᐡ ᐧ ﻌ ᐧ ᐡ) (@abap34) <a href="https://twitter.com/abap34/status/1200970574098419712?ref_src=twsrc%5Etfw">December 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- Flux.jl ってみんな使ってるのかな？ :thinking: 空いてるところは abap 先輩が書くと信じて１日目は環境構築の方法を紹介します．
- ちなみに [アドベントカレンダー参加方法はこちらを見るとやり方がわかります](https://help.qiita.com/ja/articles/qiita-adcal-join)
- Julia全般のアドベントカレンダーは別個にあるらしいので我こそはーと思う方は是非書いてくださいな．

## 一年前を振り返る（ポエム）

- 私が Flux.jl を知ることになったのは１年前なんですが，レイヤーがCPUで動くんだけれどGPUで動かなかったりつらぽよ感がありましたが BatchNormレイヤーがGPUで動くようになったみたいなのでこれから始める方はそこまでストレスなくいけるんじゃないかなーと思います :innocent: .
- まぁ，まだバージョンが0.10らしいのでこれからですよ．これから.


# 本題

- 環境構築をDockerで行うことを考えます．かつ NVIDIA の GPU が動く環境を構築しましょう．
- Dockerで. 「Docker CUDA」という言葉でぐぐると nvidia-docker っていう言葉/コマンドが出てくると思いますが，
[NVIDIA Docker って今どうなってるの？ (19.11版)](https://qiita.com/ksasaki/items/b20a785e1a0f610efa08) にもあるように名前がコロコロ変わって，NVIDIA Container Toolkit という名前になってるようです．(まぁ nvidia docker って言葉がけっこうシミわかってるので通じると思います)
- まっさらなホスト環境（これからDockerを入れていこうというマシーン環境を指す．ここでは NVIDIAの GPU 付きUbuntuマシーンを想定）から環境構築を始めるときは 同記事のセクション [Docker 19.03 以降の環境で前だけを見て生きる場合](https://qiita.com/ksasaki/items/b20a785e1a0f610efa08#docker-1903-%E4%BB%A5%E9%99%8D%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%A7%E5%89%8D%E3%81%A0%E3%81%91%E3%82%92%E8%A6%8B%E3%81%A6%E7%94%9F%E3%81%8D%E3%82%8B%E5%A0%B4%E5%90%88)に従えばOKです．つまり
  - ホスト環境にNVIDIAのドライバーを入れる
  - Dockerを入れる
  - https://github.com/NVIDIA/nvidia-docker　から導入

というステップを踏めばOKです．

# Dockerfile

- [nvidia/cuda](https://hub.docker.com/r/nvidia/cuda/) の DockerHub を探して適当なtagを探します．ここでは `10.0-cudnn7-devel-ubuntu18.04` としておきます．ドライバーが古いと怒られてしまう可能性があるので `nvidia-smi` などを見て対応するCUDAバージョンを確認するかかくじバージョンアップをしてください．


```Dockerfile
FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu18.04

MAINTAINER SATOSHI TERASAKI

RUN apt-get update && apt-get install -y \
    build-essential \
    libatomic1 \
    python \
    gfortran \
    perl \
    wget \
    m4 \
    cmake \
    pkg-config \
    git

ARG JL_VERSION="v1.3.0"
ARG WDIR="/root"
ARG JL_BUILD_DIR=$WDIR/build
WORKDIR $JL_BUILD_DIR

RUN echo "\
CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0\n\
prefix=/usr/local/julia-$JL_VERSION\n\
USE_BINARYBUILDER = 0\n\
" > Make.user \
    && cat Make.user \
    && git clone --depth=1 -b $JL_VERSION https://github.com/JuliaLang/julia.git julia-$JL_VERSION\
    && cp Make.user $JL_BUILD_DIR/julia-$JL_VERSION \
    && cd julia-$JL_VERSION \
    && make -j $(nproc) \
    && make install

RUN rm -r $JL_BUILD_DIR
ENV PATH=/usr/local/julia-$JL_VERSION/bin:$PATH
# runtime test
RUN julia -e "using InteractiveUtils; versioninfo()"
RUN julia -e 'using Pkg; Pkg.add(["Flux","CuArrays"]); using Flux'

CMD ["julia"]
```

- Juliaを無駄にソースからビルドしてるのは私の趣味なので公式ページのバイナリーのリンクを持ってきてコピーするでいいと思います．
- 例えば [docker-library/julia](https://github.com/docker-library/julia/blob/master/1.3/buster/Dockerfile) を参照

# 使い方

ホスト環境にログインして次を実行します．


```console
$ ls
Dockerfile
$ docker build -t fluxgpu .
$ docker run --gpus all --rm -it fluxgpu
sudo docker run --gpus all --rm -it fluxgpu
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.3.0 (2019-11-26)
 _/ |\__'_|_|_|\__'_|  |
|__/                   |

julia> ; # to jump shell-mode
shell> nvidia-smi
Sun Dec  1 11:37:39 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.48                 Driver Version: 410.48                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1080    Off  | 00000000:03:00.0 Off |                  N/A |
|  0%   45C    P5    19W / 270W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 1080    Off  | 00000000:04:00.0 Off |                  N/A |
| 37%   46C    P5    19W / 230W |      0MiB /  8118MiB |      4%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

```

上記のように `nvidia-smi` コマンドの出力が出ればOKです．

一度 REPL モードに戻って次を実行してみましょう：

```julia
julia> using Flux
julia> d=Dense(100,1000)|>gpu
Dense(100, 1000)

julia> x=rand(Float32,100)|>gpu;

julia> typeof(x)
CuArray{Float32,1,Nothing}

julia> d(x)|>typeof
CuArray{Float32,1,Nothing}

julia> d(x) |> size
(1000,)
```

- これは Chainerでいえば `chainer.links.Linear` に相当するレイヤーですね．`|> gpu` とすることで `Dense` レイヤーと データ `x` をGPUに移しています．
- 畳み込みのレイヤーを担当する `Conv` への入力インターフェースは注意が必要です．Chainerですと `NCHW` というデータレイアウトを採用していますが．Flux.jlでは逆で `WHCN` とすることになります．例えば `x=rand(Float32,224,224,3,1)|>gpu` というデータをJuliaでは投げることになります．

# まとめ

- GPU 付きの環境をDockerで構築し Flux.jl を１年ぶりに動かしました．BatchNorm のforwardが動くのでまともなレイヤーが組めそうですね．
- 今回はとくにFluxのコンテキストはあまりないのでGPGPUをしたい人も各自でカスタマイズして活用できると思います．