第456回 ではUbuntu 16.04 LTS上にCUDA 8.0をインストールする方法を紹介しました。これをもっと簡単に環境構築できるよう、今回は第458回 で紹介したDockerを使ってみましょう。
NVIDIAのDocker
第458回 で紹介しているようにDocker はカーネルのコンテナ技術などを利用して、アプリケーションをサンドボックス環境の中で動かす仕組みです。Dockerではカーネルの名前空間機能を用いてプロセスなどのリソースをホストの他のリソースから隔離することで、独立したサンドボックス環境を構築しています。KVMやVirtualBoxといった仮想マシンともっとも異なるのは、「 ホストとコンテナのカーネルは共通である 」ことでしょう。ホストもコンテナも同じカーネルインスタンスの上で動いています。つまりコンテナの操作によってカーネルがハングアップしてしまったとしたら、当然のことながらホストもハングアップしてしまいます。
カーネルが共通であるということは、カーネルモジュールも同じであるということです。そのためNVIDIAのデバイスをDocker上で使いたければ、ホスト上にNVIDIAのドライバーモジュールをインストールしなくてはなりません[1] 。非特権コンテナであれば、コンテナ内部でのデバイスファイルの読み書きは明示的にホスト側で許可しない限りは行えません。
[1] CUDAのインストーラーはNVIDIAのドライバーもインストールしてくれます。ただし今回はコンテナの中でCUDAをインストールするため、ホストでも別途NVIDIAドライバーをインストールしておく必要があります。これが第456回 のCUDAの紹介とは独立した記事として、第454回 においてドライバーのインストール方法を紹介した理由です。
つまりDockerコンテナの中でCUDAのようなデバイスファイルの操作を行いたい場合、次の作業が必要になります 。
CUDAを動かしたいだけであれば、必要なのは「/dev/nvidia0
」などの読み書きのみとなります。よってcapabilitiesの設定は不要です。しかしながら指定しなければならないデバイスファイルが複数存在し、しかもnvidia-uvm
のようにCUDAの利用によって動的にロードされうるモジュールも存在するため、生のDockerをそのまま使うのは若干手間がかかります。そこで出てくるのが「NVIDIA Docker」です。
NVIDIA Docker はCUDA入りのDockerイメージを構築・利用するためのツールです。もともとDocker Hubには、CUDAとcuDNN入りのイメージ が公開されています。これ自体は普通のDockerツールでも利用はできます。NVIDIA Dockerは上記で説明したようなコンテナの中でCUDAを使うための諸々の作業をラッピングして、普通のDockerコンテナと同じように使えるようにしたツールです[2] 。
[2] Dockerコマンドを完全に置き換えるコマンドラインツールであるnvidia-docker
コマンドと、Dockerのプラグインとして作られたnvidia-docker-plugin
の2種類が存在します。後者はデーモンとして常駐し、GPUとnvidia-docker
コマンドとの連携を行うためのAPIを提供します。前者はdocker
コマンドの代わりに使います。主にコンテナの中にホストのデバイスファイルを追加するなどの処理を行っています。NVIDIA Dockerのドキュメント によると、将来的に--device
オプションを肩代わりする仕組みがDockerプラグインシステムに実装されたら、nvidia-docker
コマンドは不要になるようです。
CUDAをDocker上で使う最大のメリットは、複数のバージョンを同じホストで共存できることでしょう。また一度DockerとNVIDIA Dockerをインストールさえすれば、CUDAのインストールやアップグレードが簡単になります。必要なライブラリはすべてコンテナの中にインストールしてしまうので、ホスト側はNVIDIA製のドライバーを除いて、シンプルに保てるのも人によってはうれしいポイントかもしれません。またcuDNN入りのコンテナも用意されています。本来CUDAを利用したDeep Neural Network LibraryであるcuDNN は、ダウンロードするためにNVIDIAのサイトで開発者登録が必要です。今回紹介するNVIDIAのDockerイメージを使えば、最初からcuDNNがインストールされたイメージをデプロイすることが可能なのです。
このようにメリットの多いNVIDIA Dockerですが、2017年の1月19日に待望の1.0.0がリリースされました 。1.0.0でDocker 1.13を、最近リリースされた1.0.1ではDocker 17.03にも対応しています。なお、NVIDIA Dockerをインストールするためには公式のDockerパッケージ(docker-engineパッケージ)が必要です。
NVIDIA Dockerのインストール
あらかじめ第458回 に従って、Docker公式のdocker-engineパッケージをインストールしておいてください。
NVIDIA Dockerもこれまでと同様に、インストールにはいくつかの方法が存在します。
リリース版のDebファイルをダウンロードしてインストールする
リリース版のソースコードをダウンロードしてビルド&インストールする
Gitリポジトリの最新版ををcloneしてビルド&インストールする
Ubuntuの場合は、単純にDebファイルをダウンロードしてインストールする方法が簡単です。
$ wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb
$ sudo apt install /tmp/nvidia-docker*.deb && rm /tmp/nvidia-docker*.deb
$ ps -fe | grep nvidia
nvidia-+ 1259 1 3 20:06 ? 00:00:00 /usr/bin/nvidia-docker-plugin -s /var/lib/nvidia-docker
root 1293 2 0 20:06 ? 00:00:00 [irq/132-nvidia]
root 1294 2 0 20:06 ? 00:00:00 [nvidia]
shibata 1653 22617 0 20:06 pts/2 00:00:00 grep --color=auto nvidia
$ systemctl is-enabled nvidia-docker
enabled
これでインストールは完了です。ちなみに前述のとおり、NVIDIA DockerはDockerのラッパースクリプトです。Docker HubなどへのアクセスはDocker本体のほうが行います。よってプロキシなどのネットワーク関連の設定もDocker側のみ行っておけば大丈夫でしょう。
CUDAコンテナの起動
次はCUDAコンテナを起動してみましょう。まずはNVIDIA Dockerが正しく動くかどうかの確認がてら、コンテナを検索してみます。
$ nvidia-docker search nvidia/cuda
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nvidia/cuda CUDA and cuDNN images from gitlab.com/nvid... 94
(以下略)
いくつも候補が出てくると思いますが先頭にあるnvidia/cuda がNVIDIAの公式イメージです。Docker Hubのサイトを確認するとCUDAのバージョンごとにタグが割り振られていることがわかります。2017年3月時点でのタグ未指定時に使われる「latest」は、Ubuntu 16.04 LTSのコンテナ上にCUDA 8.0とその開発用のファイル一式が入った「8.0-devel-ubuntu16.04」です。
まずはlatestタグのコンテナでnvidia-smi
コマンドを使ってみましょう。
$ nvidia-docker run --rm nvidia/cuda nvidia-smi
Using default tag: latest
latest: Pulling from nvidia/cuda
d54efb8db41d: Pull complete
f8b845f45a87: Pull complete
e8db7bf7c39f: Pull complete
9654c40e9079: Pull complete
6d9ef359eaaa: Pull complete
cdfa70f89c10: Pull complete
3208f69d3a8f: Pull complete
eac0f0483475: Pull complete
4580f9c5bac3: Pull complete
6ee6617c19de: Pull complete
Digest: sha256:2b7443eb37da8c403756fb7d183e0611f97f648ed8c3e346fdf9484433ca32b8
Status: Downloaded newer image for nvidia/cuda:latest
Fri Mar 4 08:26:58 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 Off | N/A |
| 0% 33C P8 35W / 72W | 0MiB / 4038MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
コンテナの中からでもGPUデバイスにアクセスできていることがわかりますね。
普通のDockerとNVIDIA Dockerの違い
nvidia-docker
コマンドとdocker
コマンドを比べてみると、どちらも同じイメージファイルが見えています。
$ nvidia-docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nvidia/cuda latest 1cce8839a2c5 36 hours ago 1.62 GB
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nvidia/cuda latest 1cce8839a2c5 36 hours ago 1.62 GB
ではdocker
コマンドからnvidia-smi
を実行するとどうなるのでしょうか。
$ docker run --rm nvidia/cuda nvidia-smi
container_linux.go:247: starting container process caused "exec: \"nvidia-smi\": executable file not found in $PATH"
docker: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "exec: \"nvidia-smi\": executable file not found in $PATH".
nvidia-smi
が見つからなかったようです。nvidia-docker
のほうでコマンドの位置を確認してみましょう。
$ nvidia-docker run --rm nvidia/cuda which nvidia-smi
/usr/local/nvidia/bin/nvidia-smi
$ docker run --rm nvidia/cuda ls /usr/local/nvidia
ls: cannot access '/usr/local/nvidia': No such file or directory
nvidia-docker
コマンド経由だと/usr/local/nvidia/bin
以下に存在するにもかかわらず、docker
コマンド経由だとディレクトリ自体が存在しないようです。つまり「nvidia/cuda」コンテナのイメージにはこのディレクトリが存在しないことになります。
実はDockerには「Volume」というホストの一部の領域をコンテナからもアクセスできるようにする仕組みが存在します。/usr/local/nvidia
はこのVolumeとしてマウントされた領域です。実際にコンテナの中でmount
コマンドを実行してみると、それがわかります。
$ nvidia-docker run --rm nvidia/cuda mount | grep nvidia
/dev/sda2 on /usr/local/nvidia type btrfs (ro,relatime,ssd,space_cache,subvolid=257,subvol=/@/var/lib/nvidia-docker/volumes/nvidia_driver/375.39)
どうやらホストの「/var/lib/nvidia-docker/volumes/nvidia_driver/375.39
」がコンテナ内部の「/usr/local/nvidia
」になっているようですね[3] 。Volumeの状態はdocker
のvolume
サブコマンドを使うと確認できます。ちなみにこのVolumeはnvidia-docker-plugin
が作成しています 。
$ docker volume ls
DRIVER VOLUME NAME
nvidia-docker nvidia_driver_375.39
$ docker volume inspect nvidia_driver_375.39
[
{
"Driver": "nvidia-docker",
"Labels": null,
"Mountpoint": "/var/lib/nvidia-docker/volumes/nvidia_driver/375.39",
"Name": "nvidia_driver_375.39",
"Options": {},
"Scope": "local"
}
]
$ ls /var/lib/nvidia-docker/volumes/nvidia_driver/375.39/
bin lib lib64
$ ls /var/lib/nvidia-docker/volumes/nvidia_driver/375.39/bin/
nvidia-cuda-mps-control nvidia-cuda-mps-server nvidia-debugdump nvidia-persistenced nvidia-smi
NVIDIAのカーネルドライバーはたんなるカーネルモジュールだけでなく、カーネルモジュールとユーザーランドとの橋渡しを行うライブラリも提供しています。CUDAは原則としてこのライブラリを経由してGPUを操作するのです。よってこれらのライブラリは、コンテナの中から見える必要があります。ところがこれらのライブラリそのものは、カーネルモジュールのバージョンに紐付いています。つまりカーネルドライバーのバージョンがあがると、これらのライブラリも同じバージョンとして更新する必要があるということです。
もしこれらのライブラリをコンテナイメージの中に同梱してしまうと、ホストのカーネルドライバーの更新のたびにコンテナイメージを再構築する必要がでてきます。これではDockerの有りがたみが薄れてしまいます。そこでNVIDIA Dockerでは、カーネルモジュールに紐付いているライブラリや実行バイナリは、DockerのVolumeを使ってコンテナの中からもアクセスできるようにしているわけです。
なおデバイスファイルについても、前述したとおりnvidia-docker
経由でのみアクセスできます。
$ nvidia-docker run --rm nvidia/cuda sh -c 'ls -l /dev/nvidia*'
crw-rw-rw- 1 root root 241, 0 Mar 3 09:54 /dev/nvidia-uvm
crw-rw-rw- 1 root root 241, 1 Mar 3 09:54 /dev/nvidia-uvm-tools
crw-rw-rw- 1 root root 195, 0 Mar 3 09:54 /dev/nvidia0
crw-rw-rw- 1 root root 195, 255 Mar 3 09:54 /dev/nvidiactl
$ docker run --rm nvidia/cuda sh -c 'ls -l /dev/nvidia*'
ls: cannot access '/dev/nvidia*': No such file or directory
サンプルの実行
最後のデモとして、第456回 と同様にサンプルプログラムをDocker上でビルド&実行してみましょう。NVIDIA Dockerのリポジトリにはサンプルプログラム用のDockerfile があるので、これを使ってみます。
最初に必要なパッケージのインストールとNVIDIA Dockerのコードのチェックアウトです。
$ sudo apt install git
$ git clone https://github.com/NVIDIA/nvidia-docker.git
$ cd nvidia-docker/
前回と同じくdeviceQuery
プログラムから試してみましょう。
$ docker build -t sample:deviceQuery samples/ubuntu-16.04/deviceQuery
(中略)
Successfully built f771d146a5a1
$ docker images sample:deviceQuery
REPOSITORY TAG IMAGE ID CREATED SIZE
sample deviceQuery f771d146a5a1 2 minutes ago 1.93 GB
$ nvidia-docker run --rm sample:deviceQuery
./deviceQuery Starting...
CUDA Device Query (Runtime API) version (CUDART static linking)
Detected 1 CUDA Capable device(s)
Device 0: "GeForce GTX 1050 Ti"
CUDA Driver Version / Runtime Version 8.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 4039 MBytes (4235001856 bytes)
( 6) Multiprocessors, (128) CUDA Cores/MP: 768 CUDA Cores
GPU Max Clock rate: 1392 MHz (1.39 GHz)
Memory Clock rate: 3504 Mhz
Memory Bus Width: 128-bit
L2 Cache Size: 1048576 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 1 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 8.0, CUDA Runtime Version = 8.0, NumDevs = 1, Device0 = GeForce GTX 1050 Ti
Result = PASS
次にnbody
も試してみましょう。サンプルのDockerfileは/usr/local/cuda/samples/
の個々のサンプルディレクトリをWORKDIR
に設定した上でmake
し、CMD
フィールドでサンプルを実行しています。よって「./サンプルプログラム オプション
」を渡たすことで、任意の引数でサンプルプログラムを実行可能です。
$ docker build -t sample:nbody samples/ubuntu-16.04/nbody
(中略)
Successfully built 32144285f8f4
$ docker images sample:nbody
REPOSITORY TAG IMAGE ID CREATED SIZE
sample nbody 32144285f8f4 About a minute ago 1.93 GB
$ nvidia-docker run --rm sample:nbody
(中略)
6144 bodies, total time for 10 iterations: 6.136 ms
= 61.522 billion interactions per second
= 1230.441 single-precision GFLOP/s at 20 flops per interaction
$ nvidia-docker run --rm sample:nbody ./nbody -benchmark -numbodies=8192
(中略)
number of bodies = 8192
8192 bodies, total time for 10 iterations: 12.298 ms
= 54.568 billion interactions per second
= 1091.357 single-precision GFLOP/s at 20 flops per interaction
$ nvidia-docker run --rm sample:nbody ./nbody -benchmark -numbodies=65536
(中略)
number of bodies = 65536
65536 bodies, total time for 10 iterations: 636.416 ms
= 67.487 billion interactions per second
= 1349.736 single-precision GFLOP/s at 20 flops per interaction
$ nvidia-docker run --rm sample:nbody ./nbody -benchmark -numbodies=8192 -cpu
(中略)
> Simulation with CPU
number of bodies = 8192
8192 bodies, total time for 10 iterations: 27903.188 ms
= 0.024 billion interactions per second
= 0.481 single-precision GFLOP/s at 20 flops per interaction
コンテナ上であっても、ホスト上で直接実行した第456回 と遜色ない速度が出ていることがわかりますね。
仮想マシンでのGPGPU
コンテナではなくKVMのような仮想マシン上でもGPGPUは利用可能です。
たとえばIOMMU(IntelのVT-dやAMDのAMD-Vi)に対応したチップセットであれば、「 GPUパススルー」という形で仮想マシン上でGPUを利用できます。この場合、GPUを利用できるのはひとつのゲストだけです。そのゲストがGPUを使っている間は、ホストや他のゲストはそのGPUを利用できません。この点、コンテナであれば複数のインスタンスでGPUを共有できます。
たとえばIntelのGVT-g のように、複数の仮想マシンでひとつのGPUを共有できる仕組みも作られています。ただしUbuntu上でいくつか設定するだけで使えるようになるまでには、まだもう少し時間がかかるでしょう。
PCIデバイスとして考えた場合、SR-IOVに対応したチップセットとPCIデバイスであれば複数の仮想マシンでそのPCIデバイスを共有できます。NICにはSR-IOV対応製品がありますが、GPUの場合はごく一部です。ちなみに『うぶんちゅ! まがじん ざっぱ~ん♪ Vol.5』 には、SR-IOVに対応したIntelのNICをUbuntuサーバー上に構築した複数の仮想マシンで共有する記事が載っています。
そもそもCUDA 8.0のリリースノートでは「Unified memory is not currently supported with IOMMU. The workaround is to disable IOMMU in the BIOS.」と言及されています 。つまり仮想マシン上でGPGPUを使いたい場合、CPU/GPUのメモリをシームレスに利用できるUnified Memory機能が使えないということです。
以上のようにいろいろな制約があることから、少なくとも現時点では、仮想マシンではなくコンテナを使って環境を隔離するほうが現実的と言えるでしょう。
コラム:今週末は「オープンソースカンファレンス2017 Tokyo/Spring」
Ubuntu Weekly Topics でも改めて告知する予定ですが、今週末の3月10日(金) 、11日(土)に、オープンソースカンファレンス2017 Tokyo/Spring が、行政区画上は東京都ということになっている明星大学日野キャンパスで開催されます。
Ubuntu Japanese Teamも両日参加予定で、ブースではいつものようにUbuntu PhoneやUbuntu Tablet、UbuntuのいずれかのフレーバーをインストールしたRaspberry Pi2などの実機を来場者が自由に触っていただける形で展示します。また土曜日14時からは「Skylake世代以降のIntel HDAドライバ」と題したセミナー も開催します。皆様お誘い合わせの上、お越しください。
セミナーの時間以外は、ブースに誰かが常駐している予定です。Ubuntuに関して質問がある場合はブースに座っている人に気軽にお声がけください。うまく人があいているタイミングをつかめない時は、他の来場者と話しているところに割り込んでもらっても大丈夫です。きっとなんとかします。ちなみに今回のブースの位置は、いつもの5Fと異なり2Fだそうです。
ちなみに会場の最寄り駅の隣の駅には多摩動物公園があります。ただし、サーバル舎の塗装補修のため、3月10日(金)を含む前後の平日はサーバルの公開を中止している とのことですのでご注意ください。