CUBE SUGAR CONTAINER

技術系のこと書きます。

Ubuntu 16.04 LTS で NVIDIA Docker を使ってみる

(2019-09-22 追記) NVIDIA Docker は現在では非推奨 (Deprecated) な方法となっています。 代わりに NVIDIA Container Toolkit を使ってください。

blog.amedama.jp


以前、このブログで Keras/TensorFlow の学習を GPU (CUDA) で高速化する記事を書いた。 このときは、それぞれの環境の分離には Python の virtualenv を使っていた。

blog.amedama.jp

今回は、別の選択肢として NVIDIA Docker を使う方法を試してみる。 NVIDIA Docker というのは NVIDIA が公式で出している Docker から CUDA を使えるようにするユーティリティ群と Docker イメージ。 このやり方だと Docker ホストには NVIDIA Driver さえ入っていれば動作する。 そして、CUDA Toolkit と cuDNN は Docker コンテナの中に配備される。 ここら辺の概念図は GitHub の公式ページが分かりやすい。

github.com

この方法なら、それぞれのコンテナ毎に異なるバージョンの CUDA Toolkit と cuDNN を組み合わせて使うことができる。 例えば、あるコンテナでは CUDA Toolkit 7.5 + cuDNN 4.0 を、別のコンテナでは CUDA Toolkit 8.0 + cuDNN 5.1 を、といった具合に。

今回使った環境は次の通り。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
$ uname -r
4.4.0-71-generic

GPU については GTX 1050 Ti を使っている。

$ lspci | grep -i nvidia
01:00.0 VGA compatible controller: NVIDIA Corporation Device 1c82 (rev a1)
01:00.1 Audio device: NVIDIA Corporation Device 0fb9 (rev a1)

もくじ

NVIDIA Driver をインストールする

まずは NVIDIA Driver をインストールする。 ドライバを単体で入れても良いんだけど、ついでだから CUDA Toolkit ごと入れてしまう。 次の公式ページからインストール用のファイルを取得する。

developer.nvidia.com

今回はネットワークインストール用の deb ファイルを使った。 ボタンを Linux > x86_64 > Ubuntu > 16.04 > deb (network) のように操作する。

得られたリンクから deb ファイルを取得する。

$ wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.61-1_amd64.deb

インストールしてからリポジトリの内容を更新する。

$ sudo dpkg -i cuda-repo-ubuntu1604_8.0.61-1_amd64.deb
$ sudo apt-get update

これで CUDA Toolkit がインストールできる。 依存パッケージとして、ついでに NVIDIA Driver も入る。

$ sudo apt-get install cuda

インストールが終わったら、一旦再起動しておこう。

$ sudo shutdown -r now

Docker エンジンをインストールする

続いて NVIDIA Docker の動作に必要な Docker エンジンをインストールする。

まずは、必要な依存パッケージをインストールする。

$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common

続いて APT の認証鍵をインストールする。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Docker エンジンをインストールするための公式リポジトリを登録する。

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

リポジトリの内容を更新する。

$ sudo apt-get update

Docker エンジン (Community Edition) をインストールする。

$ sudo apt-get -y install docker-ce

これで、自動的に Docker のサービスが起動する。 docker version コマンドを実行してエラーにならなければ上手くいっている。

$ sudo docker version
Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:14:09 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:14:09 2017
 OS/Arch:      linux/amd64
 Experimental: false

NVIDIA Docker をインストールする

続いて、肝心の NVIDIA Docker をインストールする。 今のところ、配布パッケージはローカルインストールの deb ファイルしかないようだ。 もしパッケージが更新されても自動更新はされないはずなので、また入れ直す必要があるね。

github.com

ローカルインストール用の deb ファイルを取得する。

$ wget https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb

取得したファイルをインストールする。

$ sudo dpkg -i nvidia-docker_1.0.1-1_amd64.deb

これで、自動的に nvidia-docker サービスが起動する。

$ systemctl list-units --type=service | grep -i nvidia-docker
nvidia-docker.service                                 loaded active running NVIDIA Docker plugin

まずは、CUDA Toolkit 8.0 + cuDNN 5 がインストールされたコンテナをダウンロードしてこよう。

$ sudo docker pull nvidia/cuda:8.0-cudnn5-runtime

終わったら、動作確認として先ほどのコンテナで nvidia-smi コマンドを実行してみよう。 次のように、エラーにならず結果が出力されれば上手くいっている。

$ sudo nvidia-docker run --rm nvidia/cuda nvidia-smi
Mon Apr  3 12:49:17 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.39                 Driver Version: 375.39                    |
|-------------------------------+----------------------+----------------------+
| 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 105...  Off  | 0000:01:00.0      On |                  N/A |
| 35%   27C    P8    35W /  75W |     61MiB /  4038MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

これで Docker コンテナから GPU を使えるようになった。

カスタムイメージをビルドして使ってみる

先ほどは公式が配布しているバニラなイメージを使って動作確認をしてみた。 続いてはカスタムイメージをビルドして実行してみることにしよう。 サンプルとしては Keras と GPU 版 TensorFlow をインストールしてみることにした。

まずは適当に Dockerfile を書く。

$ cat << 'EOF' > Dockerfile
FROM nvidia/cuda:8.0-cudnn5-runtime

LABEL maintainer "example@example.jp"

RUN apt-get update
RUN apt-get -y install python3-pip curl
RUN pip3 install keras tensorflow-gpu
EOF

適当な名前でイメージをビルドする。

$ sudo docker build -t test/myimage .

ビルドしたイメージをインタラクティブモードで起動する。

$ sudo nvidia-docker run --rm -i -t test/myimage /bin/bash

Keras の CNN サンプルをダウンロードしてくる。

# curl -O https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py
# echo 'K.clear_session()' >> mnist_cnn.py

そして実行する。 MNIST データセットのダウンロードとか GPU デバイスのスピンアップに時間がかかるので、これもイメージのビルドに含めた方が良かったかも。

# python3 mnist_cnn.py
Using TensorFlow backend.
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcublas.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcufft.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcurand.so.8.0 locally
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
Train on 60000 samples, validate on 10000 samples
Epoch 1/12
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:910] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 1050 Ti
major: 6 minor: 1 memoryClockRate (GHz) 1.455
pciBusID 0000:01:00.0
Total memory: 3.94GiB
Free memory: 3.84GiB
I tensorflow/core/common_runtime/gpu/gpu_device.cc:906] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_device.cc:916] 0:   Y
I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 1050 Ti, pci bus id: 0000:01:00.0)
60000/60000 [==============================] - 106s - loss: 0.3181 - acc: 0.9041 - val_loss: 0.0792 - val_acc: 0.9739
Epoch 2/12
60000/60000 [==============================] - 9s - loss: 0.1105 - acc: 0.9674 - val_loss: 0.0572 - val_acc: 0.9811
Epoch 3/12
60000/60000 [==============================] - 9s - loss: 0.0851 - acc: 0.9743 - val_loss: 0.0440 - val_acc: 0.9858
Epoch 4/12
60000/60000 [==============================] - 9s - loss: 0.0700 - acc: 0.9793 - val_loss: 0.0387 - val_acc: 0.9869
Epoch 5/12
60000/60000 [==============================] - 9s - loss: 0.0610 - acc: 0.9818 - val_loss: 0.0380 - val_acc: 0.9873
Epoch 6/12
60000/60000 [==============================] - 9s - loss: 0.0554 - acc: 0.9833 - val_loss: 0.0324 - val_acc: 0.9901
Epoch 7/12
60000/60000 [==============================] - 9s - loss: 0.0512 - acc: 0.9849 - val_loss: 0.0311 - val_acc: 0.9892
Epoch 8/12
60000/60000 [==============================] - 9s - loss: 0.0475 - acc: 0.9861 - val_loss: 0.0331 - val_acc: 0.9894
Epoch 9/12
60000/60000 [==============================] - 9s - loss: 0.0432 - acc: 0.9871 - val_loss: 0.0304 - val_acc: 0.9902
Epoch 10/12
60000/60000 [==============================] - 9s - loss: 0.0421 - acc: 0.9874 - val_loss: 0.0313 - val_acc: 0.9892
Epoch 11/12
60000/60000 [==============================] - 9s - loss: 0.0400 - acc: 0.9880 - val_loss: 0.0305 - val_acc: 0.9902
Epoch 12/12
60000/60000 [==============================] - 9s - loss: 0.0377 - acc: 0.9887 - val_loss: 0.0290 - val_acc: 0.9910
Test loss: 0.0289570764096
Test accuracy: 0.991

良い感じ。