Ubuntu14.04.3でnvidia-docker使ってCaffeをインストールしてみた

  • 70
    いいね
  • 2
    コメント

機械学習ライブラリがいろいろあって目移りしますね。私もUbuntuにCaffe,Chainer,Tensorflowと一通り入れてみたのですが、それ以外にも欲張ってあれもこれもと入れているうちに、ライブラリの依存関係がおかしくなったり、入れ替えると戻らなくなったりして、5回ぐらいインストールし直していました。

さすがに辛くなってきたのでgoogle先生に尋ねたところ、docker内からGPU使えることがわかったので、備忘録を兼ねてご紹介します。NVIDIA Dockerを使います。

この記事のゴール

  • NVIDIA Dockerをインストールして
  • Caffe組み込み済みのイメージを作成して
  • コンテナの中に入ってMNIST実行するまで

NVIDIA Dockerとは何ぞい?

NVIDIA社の日本語説明ページが参考になりますが、ホストOS側にGPUのドライバがあって、dockerのコンテナ側にCUDA Toolkitを置いて、dockerの中からでもGPUにアクセスできるようにしているようです。

NVIDIA Dockerを使わずに、docker越しにCUDAを使うという記事(Chainer開発用のDocker環境を整備する)も参考になりました。

導入環境

  • OS: Ubuntu Server 14.04.3 LTS
  • CPU: Intel(R) Core(TM) i5-4670
  • GPU: GeForce GTX 980 Ti
  • Memory: 8Gbyte
  • HDD: 1Tbyte

準備

フォーマット済みのHDDに新規でubuntuインストールして、GPUのドライバインストールして、dockerインストールするまで。

  1. Ubuntu Server 14.04.3 LTSをPCにインストールします

  2. CUDA Toolkitをインストールします

    • GPUのドライバをインストールするのが目的です。CUDA Toolkitインストールするとドライバもインストールされます。
    • CUDA Toolkit 7.5からubuntu14.04用のバイナリをダウンロードします。
    • 上記リンクの下部に書いてある通りにインストールします
    • インストールが終わったら一度再起動します
  3. dockerをインストールします

$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ sudo echo 'deb https://apt.dockerproject.org/repo ubuntu-trusty main' > /etc/apt/sources.list.d/docker.list
$ sudo apt-get update
$ sudo apt-cache policy docker-engine
$ sudo apt-get install docker-engine
$ sudo service docker start

NVIDIA Dockerインストール

nvidia-dockerのインストールページ見ると、ソースコードからインストールしろとしか書いていないので、githubからとってきます。

$ git clone https://github.com/NVIDIA/nvidia-docker.git
$ cd nvidia-docker
$ sudo make install
$ sudo nvidia-docker volume setup

特に何も指定しなければ、/usr/bin/ディレクトリに、nvidia-dockernvidia-docker-pluginが作成されます。

またdockerのイメージとして、最終的に以下のラベルを持つイメージが作成されます。(同じIDのものは同じイメージです。IDは作成環境によって変わります)

REPOSITORY TAG IMAGE ID
cuda 7.5 242f4f109770
cuda latest 242f4f109770
cuda 7.5-devel 242f4f109770
cuda devel 242f4f109770
nvidia/cuda latest 242f4f109770
cuda 7.5-runtime 6199b491d4b3

最後の、sudo nvidia-docker volume setupの挙動ですが、このコマンド実行後、/var/lib/docker/volumesディレクトリに、nvidia_driver_352.63というファイルができているのを確認しました。

NVIDIA Dockerを使ってみる

$ sudo nvidia-docker run nvidia/cuda nvidia-smi

でnvidia-dockerを実行すると、dockerの中からGPU情報を見て結果を出力します。

+------------------------------------------------------+
| NVIDIA-SMI 352.63     Driver Version: 352.63         |
|-------------------------------+----------------------+----------------------+
| 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 980 Ti  Off  | 0000:01:00.0      On |                  N/A |
|  0%   50C    P8    23W / 275W |     99MiB /  6140MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

コンテナ自体はdockerからも扱えるのですが、runさせた際に/usr/local/nvidiaディレクトリが見えないため、nvidia-smiにアクセスできず、こんな結果になります。

$ sudo docker run nvidia/cuda nvidia-smi
exec: "nvidia-smi": executable file not found in $PATH

いろいろ試してみたところ、一度nvidia-dockerでrunさせた後、後からattachで入る時はdockerでもよいことがわかりました。

$ sudo nvidia-docker run -it --name "test1" nvidia/cuda /bin/bash
 (ctrl-p, ctrl-qで抜ける)
$ sudo docker attach test1
$ nvidia-smi
 -> 成功する

※ちなみに、nvidia-smiのProcessesについては、同一コンテナ内であってもGPUのプロセスが出てこないです。温度とファンの回り具合はわかります。ホストOS側のnvidia-smiには全て出てきます。

Caffeを入れてみる

さて、本来の目的のCaffeをdocker上に導入してみます。せっかくですので、Dockerfileを使ってインストールすることにします。

1. cuDNNを入れる

cuDNNについては、こちらの記事を参考にしてください。
GTC 2015 - Deep Learning用のCUDAライブラリ「cuDNN」

以下のコマンドで、イメージを作成します。Dockerfileの中を見ると、さきほどの「NVIDIA Dockerインストール」で作成したcuda:7.5-develのイメージを基にしていることがわかります。

nvidia-dockerディレクトリからの作業をを想定しています。buildの後の-tオプションはタグ名です。タグ名は手順4で出てくるDockerfileで使います。

$ sudo nvidia-docker build -t cuda:7.5_cudnn70 ./ubuntu/cuda/7.5/devel/cudnn3

以下2〜4の手順は、便宜的に同じディレクトリで作業する前提とします。

2. Anacondaをダウンロードしておく

buildの度に毎回ダウンロードするように、Dockerfileを書いてもいいのですが、やり直す度に毎回ダウンロードするのも時間がかかるので、事前に用意しておきます。

Anacondのダウンロードページから、Linux 64bit, Python2.7を選択してダウンロードします。

コマンドで一気にダウンロードしたい場合は以下になります。

$ curl "https://3230d63b5fc54e62148e-c95ac804525aac4b6dba79b00b39d1d3.ssl.cf1.rackcdn.com/Anaconda2-2.4.1-Linux-x86_64.sh" > Anaconda2-2.4.1-Linux-x86_64.sh

3. CaffeのMakefile.configを先に作っておく

Caffeのmake時に、Makefile.configというファイルが必要なのですが、これは事前に用意しておく必要があります。
雛形を取ってきて編集しましょう。

$ curl https://raw.githubusercontent.com/BVLC/caffe/master/Makefile.config.example -O
$ cp Makefile.config.example Makefile.config

Makefile.configはコメント行を除外しています。
cuDNN使うよというのと、pythonがAnadonda環境だよという点を修正しています。

Makefile.config
USE_CUDNN := 1
CUDA_DIR := /usr/local/cuda
CUDA_ARCH := -gencode arch=compute_20,code=sm_20 \
        -gencode arch=compute_20,code=sm_21 \
        -gencode arch=compute_30,code=sm_30 \
        -gencode arch=compute_35,code=sm_35 \
        -gencode arch=compute_50,code=sm_50 \
        -gencode arch=compute_50,code=compute_50
BLAS := atlas
ANACONDA_HOME := /opt/anaconda2
PYTHON_INCLUDE := $(ANACONDA_HOME)/include \
        # $(ANACONDA_HOME)/include/python2.7 \
        # $(ANACONDA_HOME)/lib/python2.7/site-packages/numpy/core/include \
PYTHON_LIB := $(ANACONDA_HOME)/lib

INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib

BUILD_DIR := build
DISTRIBUTE_DIR := distribute
TEST_GPUID := 0
Q ?= @

4. Dockerfileを作る

作業1で作ったイメージをベースにして、以下の作業を行うDockerfileを作成します。

  • Anaconda(64bit, Python2.7)のインストール
  • Caffe用パッケージのインストール
  • Caffeをgithubから取得
  • Caffeのmake

あと、build時にエラーが出る部分を修正しています。
AnacondaとCaffeは、/opt以下に作成しています。

以下をコピペして、Dockerfileというファイル名で保存します。

cuda7.5_cudnn7.0_anaconda2_caffe/Dockerfile
FROM cuda:7.5_cudnn70

COPY Anaconda2-2.4.1-Linux-x86_64.sh /opt

# caffe makefile:use anaconda2 / use cudnn3
COPY Makefile.config /opt

# install anaconda2
RUN echo 'export PATH=/opt/anaconda2/bin:$PATH' > /etc/profile.d/conda.sh && \
    cd /opt && \
    /bin/bash /opt/Anaconda2-2.4.1-Linux-x86_64.sh -b -p /opt/anaconda2 && \
    rm /opt/Anaconda2-2.4.1-Linux-x86_64.sh

# prepare caffe
RUN apt-get update && apt-get install -y \
    libatlas-base-dev libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libboost-all-dev libhdf5-serial-dev libgflags-dev libgoogle-glog-dev liblmdb-dev protobuf-compiler git wget && \
    rm -rf /var/lib/apt/lists/*

# solution "libdc1394 error: Failed to initialize libdc1394"
# ref) http://stackoverflow.com/questions/12689304/ctypes-error-libdc1394-error-failed-to-initialize-libdc1394
RUN ln -s /dev/null /dev/raw1394

# solution "Error loading shared library libhdf5_hl.so"
# ref) https://github.com/BVLC/caffe/issues/1463
RUN cp /opt/anaconda2/pkgs/hdf5-1.8.15.1-2/lib/libhdf5.so.10.0.1 /lib/x86_64-linux-gnu/ && \
    cp /opt/anaconda2/pkgs/hdf5-1.8.15.1-2/lib/libhdf5_hl.so.10.0.1 /lib/x86_64-linux-gnu/ && \
    ldconfig

# make caffe
RUN cd /opt && \
    git clone https://github.com/BVLC/caffe.git && \
    cd caffe && \
    cp /opt/Makefile.config .  && \
    make all -j4 && \
    make test -j4

# ref) https://hub.docker.com/r/eduwass/face-the-internet-worker/~/dockerfile/
CMD sh -c 'ln -s /dev/null /dev/raw1394'; bash

面倒な点がひとつありまして、ln -s /dev/null /dev/raw1394という処理が、イメージ作成時だけではなくて、コンテナを作成する度に必要になります。
参考) How to persist 'ln' in Docker with Ubuntu

そのため、コンテナ起動時に実行するコマンドとして設定しています。

5. Let's nvidia-docker build!

2〜4で作ったファイルを全て同じディレクトリ内に入れて確認します。3つのファイルがありますね。

$ ls 
 Anaconda2-2.4.1-Linux-x86_64.sh  Dockerfile  Makefile.config

そのディレクトリで以下のコマンドを実行します。

$ sudo nvidia-docker build -t ml_caffe:7570ana2 .

(たぶんこのタイミングでは、docker build -t ml_caffe:7570ana2 .でも同じだと思われますが、念のためnvidia-dockerを使っています)

自動的にbuildが始まります。ここではbuild後のイメージタグ名を、ml_caffe:7570ana2としています。

6. runtestで確認しましょう

makeがうまくいったのか、Caffeの動作環境は問題ないか確認しましょう。一番簡単な確認は、Caffeのmake runtestです。

※ 実は、Dockerfileの最後に、make runtestって書いたら、GPUが使えなくてエラーが出たので、build時はnvidia-dockerはGPU使えないっぽいです。

以下のコマンドでコンテナ内に入り、make runtestを実行します。

$ sudo nvidia-docker run -it --name "runtest" ml_caffe:7570ana2
root@e14ad9a3b2d6:/# cd /opt/caffe; make runtest

root@e14ad9a3b2d6:/#の部分は、コンテナの中に入った後のシェルプロンプトです。e14ad9a3b2d6はコンテナIDなので、毎回runする度に変わります。

ちゃんと[RUN][OK]が出力されていたらGPUは認識されています。全てのテストがPASSすることを確認しましょう。最後はこんな感じになります。

[----------] Global test environment tear-down
[==========] 1744 tests from 257 test cases ran. (241958 ms total)
[  PASSED  ] 1744 tests.

普通のdockerで実行するとどうなるかやってみましょう。

$ sudo docker run -it --name "runtest_docker" ml_caffe:7570ana2
root@b25a57ebfe81:/# cd /opt/caffe; make runtest
.build_release/tools/caffe
caffe: command line brew
usage: caffe <command> <args>
 :
 (中略)
 :
[----------] 1 test from HDF5OutputLayerTest/3, where TypeParam = caffe::GPUDevice<double>
[ RUN      ] HDF5OutputLayerTest/3.TestForward
F0118 12:02:51.914427   240 syncedmem.hpp:18] Check failed: error == cudaSuccess (35 vs. 0)  CUDA driver version is insufficient for CUDA runtime version
*** Check failure stack trace: ***
 :
 (後略)
 :

一瞬動いたかな?と思うのですが、すぐにCUDAのドライバのバージョンがおかしいというエラーが出て終了します。

7. MNIST動かしてみましょう

MNISTは機械学習におけるHello worldにあたります。参考)MNIST For ML Beginners

というわけで、以下のコマンドで試してみましょう。コンテナの中に入って作業します。
参考)Caffeで手書き数字(MNIST)の認識学習をする

$ sudo nvidia-docker run -it --name "mnist" ml_caffe:7570ana2
root@a99d3a442790:/# cd /opt/caffe/
root@a99d3a442790:/opt/caffe# ./data/mnist/get_mnist.sh
root@a99d3a442790:/opt/caffe# ./examples/mnist/create_mnist.sh
root@a99d3a442790:/opt/caffe# ./examples/mnist/train_lenet.sh

やっていることは、シンプルです。

  • ディレクトリ移動して(cd /opt/caffe/)
  • MNIST用データ取得して(./data/mnist/get_mnist.sh)
  • データを学習させやすい形に変換して(./examples/mnist/create_mnist.sh)
  • 学習させます(./examples/mnist/train_lenet.sh)

学習結果として、最後こんな出力が出てきたら完了です。

I0118 13:18:48.695695    34 solver.cpp:459] Snapshotting to binary proto file examples/mnist/lenet_iter_10000.caffemodel
I0118 13:18:48.700121    34 sgd_solver.cpp:273] Snapshotting solver state to binary proto file examples/mnist/lenet_iter_10000.solverstate
I0118 13:18:48.702507    34 solver.cpp:321] Iteration 10000, loss = 0.00265015
I0118 13:18:48.702540    34 solver.cpp:341] Iteration 10000, Testing net (#0)
I0118 13:18:48.765329    34 solver.cpp:409]     Test net output #0: accuracy = 0.9905
I0118 13:18:48.765362    34 solver.cpp:409]     Test net output #1: loss = 0.0263541 (* 1 = 0.0263541 loss)
I0118 13:18:48.765379    34 solver.cpp:326] Optimization Done.
I0118 13:18:48.765384    34 caffe.cpp:215] Optimization Done.

以上、nvidia-dockerでのCaffeの導入でした!

はまったところ

nvidia-dockerで1つはまったところがあったので共有しておきます。

コンテナを作った後でdocker rmなり、nvidia-docker rmでコンテナ削除した場合は問題ないのですが、nvidia-docker run --rmで起動してコンテナを自動削除した場合、`nvidia-docker volume setup'で作成したボリュームが勝手に消えることがあります。

突然、Error response from daemon: Error looking up volume plugin nvidia-docker: Plugin not foundというエラーが出てきたので、「何もしていないのにエラーが出た」という言葉が浮かんできました。

気を取り直して条件を探ったところ、コンテナが全く登録されていない時に、nvidia-docker run --rm 〜を実行すると消えます。

状況を並べるとこうなります。

$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ sudo nvidia-docker run -it --rm ml_caffe:7570ana2
root@34302aeabcfa:/# exit
exit
$ sudo nvidia-docker run -it --rm ml_caffe:7570ana2
Error response from daemon: Error looking up volume plugin nvidia-docker: Plugin not found

バグなのか仕様なのかよくわかってないので、とりあえずの対策としては、

  • --rmオプションは使わないようにする
  • sudo nvidia-docker volume setup; nvidia-docker run --rm 〜とコマンドを並べて回避
  • Error looking up volume plugin nvidia-docker: Plugin not foundが出たら、sudo nvidia-docker volume setupを実行する

となりますね。

まとめ

個人で使う分には、環境をdockerのイメージ毎に隔離させることができるので、非常に使い勝手がいいと思っています。

dockerのイメージが、自分のPCのグラフィックカード依存になっているような気がするので、dockerの趣旨に反しているのかもしれませんが、容量用法を守って正しく使えば大丈夫でしょう。

GPUでも楽しいdockerライフを!