こんにちは。
アドテクスタジオ Central Infrastructure Agency の青山真也 (@amsy810) です。

アドテクスタジオでは「AI Lab」と呼ばれる、機械学習、計量経済学、コンピュータビジョン、自然言語処理、HAI/HRIなどを専門とする研究者がアドテクに関する技術の研究・開発を行う組織があります。 https://www.cyberagent.co.jp/techinfo/labo/ai/

adtech_logo

GPU を用いた研究も積極的に進めており、TensorFlow、Keras、PyTorch などを利用して GPU を用いた画像解析・クリエイティブの自動生成・大規模テキストデータ処理などを行っています。 GPU では画像との相性がよく、特に画像解析やクリエイティブ周りでの活用は顕著な性能差が現れます。 他にも大規模テキストデータ処理の場合には、Web 上のメディアのテキストについて文字レベルや単語レベルの埋め込みを獲得したり、カテゴリ分類するモデルの学習を可能にしています。 マルチ GPU 構成やマルチマシン構成での分散学習を用いることで、数百万ページ以上の規模のデータでモデルのパラメータ探索を行うことを実現しています。

NVIDIA 社が用意している NVIDIA Docker を利用することで研究開発の環境をすぐに用意することが可能ですが、NVIDIA Docker 単体での利用は GPU リソースのスケジューリングやジョブスケジューリング、運用面に課題があると考えています。
そこで Kubernetes と NVIDIA Docker を連携させて、雛形となる Manifest を事前に準備しておくことで利便性を高めることを検討しました。

 

要求環境と検証環境

現時点の NVIDIA Docker の最新である nvidia-docker の version 2 を使います。
nvidia-docker2 の要求環境は下記の通りです。

  • GNU/Linux x86_64 with kernel version > 3.10
  • Docker >= 1.12
  • NVIDIA GPU with Architecture > Fermi (2.1)
  • NVIDIA drivers ~= 361.93 (untested on older versions)

Kubernetes 1.8 以降、Device Plugins のサポートが入った他、GPU Kubernetes の成熟度も上がってきて利用がしやすくなりました。

今回弊社では下記の環境で検証を行いました。

  • Kubernetes: 1.9.2
  • Docker: 17.12.0-ce
  • nvidia-docker: 2.0.2+docker17.12.0-1
  • nvidia-driver: 390.25
  • GPU: GeForce 1080 Ti(1 ノードにつき 4 枚)

Kubernetes の Node は下記の 2 種類で構築しました。

  • 1 台目
    • OS: Ubuntu 17.10 artiful
    • Kernel: 4.13.0-32-generic
  • 2 台目
    • OS: Ubuntu 16.04.3 xenial
    • Kernel: 4.4.0-112-generic

Kubernetes Master は弊社で使用している AKE (Adtech Container Engine) で構築し、上記の Node を手動で cluster に組み込みました。
手軽に Kubernetes の環境を作ることができるとこういう検証の時に便利ですね。

ake

 

GPU を利用可能な Kubernetes Node の構築

GPU が利用可能な Kubernetes Node を構築するには下記の 5 Step を行って下さい。

  • Docker CE のインストール
  • NVIDIA Driver のインストール
  • nvidia-docker2 のインストール
  • kubelet の feature-gates DevicePlugins の有効化
  • DaemonSet nvidia-device-plugin の起動

 

Docker CE のインストール

Docker CE をインストールします。 Edge は利用できないようなので、Stable を使うようにして下さい。 以前のバージョンの Docker が入っている場合は削除します。

sudo apt-get remove docker docker-engine docker.io

sudo apt-get update

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

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

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

sudo apt-get update

sudo apt-get install docker-ce

NVIDIA Driver のインストール

NVIDIA のドライバをインストールします。 参考: http://www.nvidia.com/object/unix.html

sh NVIDIA-Linux-x86_64-390.25.run

nvidia-docker2 のインストール

nvidia-docker2 をインストールします。古いバージョン (v1) の nvidia-docker が入っている場合には事前に削除します。

参考: https://github.com/NVIDIA/nvidia-docker

# 既存のGPU コンテナの削除
docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f

# nvidia-docker v1 の削除
sudo apt-get purge -y nvidia-docker

# nvidia-docker リポジトリの追加 (Ubuntu 17.10 でも動作しました)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update

# nvidia-docker v2 のインストール
sudo apt-get install -y nvidia-docker2

 

Docker の設定を下記のように変更し、runc から nvidia-container-runtime を利用するように default runtime の設定を行います。

# Docker daemon の設定確認
cat /etc/docker/daemon.json
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

# Docker daemon の設定のリロード
sudo pkill -SIGHUP dockerd

kubelet の feature-gates DevicePlugins の有効化

GPU を内包したノードで起動する kubelet の起動オプションに Device Plugins を有効化する feature-gate を追加します。 DevicePlugins 機能が有効化することにより、次に起動する nvidia-device-plugin によって GPU リソースを認識することが可能になります。

--feature-gates=DevicePlugins=true

DaemonSet nvidia-device-plugin の起動

ここで一度 reboot します。 弊社の環境では Docker の default runtime に nvidia を指定しても nvidia-device-plugin が正常に動作しませんでした。 恐らく driver のロード周りの影響だと思われます。

$ kubectl -n kube-system logs nvidia-device-plugin-daemonset-2zhtm
2018/02/08 11:02:02 Loading NVML
2018/02/08 11:02:02 Failed to start nvml with error: could not load NVML library.
2018/02/08 11:02:02 If this is a GPU node, did you set the docker default runtime to `nvidia`?
2018/02/08 11:02:02 You can check the prerequisites at: https://github.com/NVIDIA/k8s-device-plugin#prerequisites
2018/02/08 11:02:02 You can learn how to set the runtime at: https://github.com/NVIDIA/k8s-device-plugin#quick-start

 

DaemonSets で動作する nvidia-device-plugin を起動します。

KUBERNETES_VERSION=v1.9

kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/${KUBERNETES_VERSION}/nvidia-device-plugin.yml

 

nvidia-device-plugin を起動することで、Kubernetes Node 上にある GPU リソースを検知してノードに登録を行います。 kubectl describe node すると nvidia.con/gpu がリソースとして登録されていることが確認できるかと思います。

$ kubectl describe node sandbox-003
Name:               sandbox-003
Roles:              node
...(省略)...
Capacity:
 cpu:             12
 memory:          131945888Ki
 nvidia.com/gpu:  4
 pods:            110
Allocatable:
 cpu:             12
 memory:          131843488Ki
 nvidia.com/gpu:  4
 pods:            110
...(省略)...

GPU Container の利用

GPU を割り当てられたコンテナを作成する場合には spec.containers[x].resources に nvidia.com/gpu を指定します。今回は nvidia-smi を使ってコア数を確認します

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-2-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: gpu-container
          image: nvidia/cuda
          command: ["sh", "-c", "nvidia-smi; sleep 36000"]
          resources:
            limits:
              nvidia.com/gpu: 2

 

実際にデプロイ後のコンテナのログを確認してみると、1 つのコンテナにつき 2 つの GPU が接続されていることが確認できます。

$ kubectl logs gpu-deploy-95f544577-mrqr6
Fri Feb  9 07:54:19 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.12                 Driver Version: 390.12                    |
|-------------------------------+----------------------+----------------------+
| 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 108...  Off  | 00000000:05:00.0 Off |                  N/A |
|  0%   33C    P8     9W / 250W |      0MiB / 11177MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 108...  Off  | 00000000:06:00.0 Off |                  N/A |
|  0%   36C    P8     9W / 250W |      0MiB / 11178MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

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

リソースの分割の検証

4 枚の GPU で構成されたノードが 2 台の環境を作ったため、先程の 1 コンテナ辺り 2 枚の GPU を割り当てる gpu-2-deploy のレプリカ数を 5 に変更してみましょう。

gpu_limit

 

$ kubectl scale deploy gpu-deploy --replicas 5
$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
gpu-deploy-95f544577-2xhr8   1/1       Running   0          26s
gpu-deploy-95f544577-4xjts   1/1       Running   0          26s
gpu-deploy-95f544577-d5c7v   0/1       Pending   0          26s
gpu-deploy-95f544577-mrqr6   1/1       Running   0          10h
gpu-deploy-95f544577-pp68t   1/1       Running   0          10h

 

割り当て可能な GPU は 8 枚分しかないので、当然 5 台目は Pending のままとなります。 当然ながら 1 コンテナ辺りの 3 枚の GPU を割り当てるコンテナを 2 台作った後に、1 コンテナ辺り 2 枚の GPU を割り当てるコンテナも作ることはできません。

GPU の失敗するスケジューリング

 

 

resources.requests と resources.limits の違い

Kubernetes のリソース制限には requests と limits の 2 種類が存在します。 limits の場合には、GPU がコンテナによって専有される挙動を取るため、limits だけを使う場合には確保した GPU が他のコンテナに使われるといった事態を防ぐことが可能です。

          resources:
            limits:
              nvidia.com/gpu: 2

GPU の limits の挙動

 

一方で requests の場合には少々挙動が違います。 例えば、下記のように requests:2 に設定した場合に、コンテナに割り当てられた GPU を確認してみます。

          resources:
            requests:
              nvidia.com/gpu: 2

GPU の requests の挙動

 

すると、コンテナ上には GPU の割り当て自体は 4 個割り当てられている状況となります。

$ kubectl logs -f  gpu-req-56b544d969-5mlf4
Fri Feb  9 08:30:52 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.12                 Driver Version: 390.12                    |
|-------------------------------+----------------------+----------------------+
| 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 108...  Off  | 00000000:05:00.0 Off |                  N/A |
|  0%   32C    P8     9W / 250W |      0MiB / 11177MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 108...  Off  | 00000000:06:00.0 Off |                  N/A |
|  0%   36C    P8    11W / 250W |      0MiB / 11178MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
|   2  GeForce GTX 108...  Off  | 00000000:09:00.0 Off |                  N/A |
|  0%   33C    P8     9W / 250W |      0MiB / 11178MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
|   3  GeForce GTX 108...  Off  | 00000000:0A:00.0 Off |                  N/A |
|  0%   30C    P8     9W / 250W |      0MiB / 11178MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

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

 

requests:2 の場合にはコンテナは最大で 4 つまで起動し、一見問題ないようにも思えますが、コンテナからは GPU が 4 枚見えている状態のため、実際は競合を起こしてしまいます。 nvidia-docker 単体で検証を行った際も、ジョブの中盤以降に競合を起こしてエラーで落ちてしまったため、コンテナ上で実行される全てのプログラムが共有で使えるような仕組みを有していない場合には推奨できません。

そのため、コンテナ環境で GPU を利用する場合には、基本的には limits を使ってリソース制限を行うことが望ましいようです。

 

まとめ

現在アドテクスタジオでは複数台の GPU マシンを使って研究を行っています。従来環境構築に手間がかかった GPU 環境もコンテナを利用することで利便性の高いプラットフォームを提供できると考えています。例えば、Tensorflow を利用する際には gcr.io/tensorflow/tensorflow:latest-gpu などのイメージを利用することで簡単に環境を用意することができるため、今後様々なフレームワークが出てきても柔軟かつ迅速に対応することが可能です。

また、Kubernetes や Docker の GPU 対応も成熟化してきたため、検討中の方は試してみると良いかと思います。

MasayaAoyama
2016 年新卒入社後、アドテク本部に所属。オンプレコンテナ基盤・プライベートクラウド基盤の構築に従事。Certified Kubernetes Administrator を全世界で 138 番目に取得する等、Kubernetes が趣味。