Edited at

MPSを使ってシングルGPUで複数プロセスを効率的に並列実行する

Multi-Process Service (MPS) を試してみたので紹介します。


image.png

Multi-Process Service (MPS) より転載



TL;DR


  • シングルGPUで複数プロセスを同時に実行すると、例えリソースに余裕があってもプロセスが互いにロックし合って効率的な並列実行はされません。

  • NVIDIA謹製の Multi-Process Service (MPS) を使うとこの並列実行が改善されます(MPI以外でも)。

  • [例] シングルGPUで80秒かかる(軽い)ニューラルネットの学習を、同じシングルGPUで同時に10並列すると221秒かかりました。MPSを使うとこれが124秒に改善されます(5並列なら136秒から86秒に)。


Motivated example

今ここにMNISTをMLPで学習するコードがあるとします。

$ cat run_mnist.sh

#!/bin/bash
python mnist_mlp.py

これをシングルGPUで実行した結果、80秒 かかりました。

$ ./run_mnist.sh

[0] 2018-05-31 15:22:09: MNIST training starts!
[0] 2018-05-31 15:23:29: MNIST training ends!

80 sec

学習中GPUの使用率を見ているとリソースにはまだまだ余裕がありそうです。

$ gpustat

xxxxx Thu May 31 15:26:51 2018
[0] Tesla P100 | 38'C, 35 % | 326 / 12205 MB | user(324M)

では、次のように10個のプロセスを同時に実行すると全て終わるまで何秒かかるでしょうか?

$ cat run_mnist.sh

#!/bin/bash

python pytorch_mnist.py -n 0 &
python pytorch_mnist.py -n 1 &
python pytorch_mnist.py -n 2 &
python pytorch_mnist.py -n 3 &
python pytorch_mnist.py -n 4 &
python pytorch_mnist.py -n 5 &
python pytorch_mnist.py -n 6 &
python pytorch_mnist.py -n 7 &
python pytorch_mnist.py -n 8 &
python pytorch_mnist.py -n 9

実際に実行してみると 221 秒かかりました。

$ ./run_mnist.sh

[4] 2018-05-31 15:30:06: MNIST training starts!
[1] 2018-05-31 15:30:06: MNIST training starts!
[2] 2018-05-31 15:30:06: MNIST training starts!
[7] 2018-05-31 15:30:06: MNIST training starts!
[9] 2018-05-31 15:30:06: MNIST training starts!
[0] 2018-05-31 15:30:06: MNIST training starts!
[6] 2018-05-31 15:30:06: MNIST training starts!
[8] 2018-05-31 15:30:06: MNIST training starts!
[3] 2018-05-31 15:30:06: MNIST training starts!
[5] 2018-05-31 15:30:06: MNIST training starts!
[0] 2018-05-31 15:33:43: MNIST training ends!
[5] 2018-05-31 15:33:44: MNIST training ends!
[7] 2018-05-31 15:33:44: MNIST training ends!
[8] 2018-05-31 15:33:45: MNIST training ends!
[3] 2018-05-31 15:33:45: MNIST training ends!
[9] 2018-05-31 15:33:45: MNIST training ends!
[6] 2018-05-31 15:33:46: MNIST training ends!
[2] 2018-05-31 15:33:47: MNIST training ends!
[4] 2018-05-31 15:33:47: MNIST training ends!
[1] 2018-05-31 15:33:47: MNIST training ends!

221 sec

実行中のGPUの様子は次のようでした。

$ gpustat

xxxxx Thu May 31 15:33:06 2018
[0] Tesla P100 | 62'C, 79 % | 3301 / 12205 MB | user(329M) user(329M) user(329M) user(329M) user(329M) user(329M) user(329M) user(329M) user(329M) user(329M)

モデルが同時にGPUのメモリに載っていても、プロセス同士がロックし合って効率的に並列実行されていないことがわかります。

さて、この10個のプロセスの並列実行をより効率的に、早く終わらせるためにはどうすればいいでしょうか?

NVIDIA謹製の Multi-Process Service (MPS) を使えば解決できます。

$ export CUDA_VISIBLE_DEVICES=0

$ export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps
$ export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log
$ nvidia-cuda-mps-control -d
$ unset CUDA_VISIBLE_DEVICES
$./run_mnist.sh
[7] 2018-05-31 15:47:01: MNIST training starts!
[3] 2018-05-31 15:47:01: MNIST training starts!
[2] 2018-05-31 15:47:01: MNIST training starts!
[5] 2018-05-31 15:47:01: MNIST training starts!
[6] 2018-05-31 15:47:01: MNIST training starts!
[8] 2018-05-31 15:47:01: MNIST training starts!
[4] 2018-05-31 15:47:01: MNIST training starts!
[1] 2018-05-31 15:47:01: MNIST training starts!
[9] 2018-05-31 15:47:01: MNIST training starts!
[0] 2018-05-31 15:47:01: MNIST training starts!
[3] 2018-05-31 15:49:03: MNIST training ends!
[5] 2018-05-31 15:49:03: MNIST training ends!
[6] 2018-05-31 15:49:04: MNIST training ends!
[4] 2018-05-31 15:49:04: MNIST training ends!
[0] 2018-05-31 15:49:04: MNIST training ends!
[8] 2018-05-31 15:49:04: MNIST training ends!
[9] 2018-05-31 15:49:04: MNIST training ends!
[2] 2018-05-31 15:49:05: MNIST training ends!
[7] 2018-05-31 15:49:05: MNIST training ends!
[1] 2018-05-31 15:49:05: MNIST training ends!

124 sec

MPSを使うことで221秒からへと124秒へと改善しました。

5並列で試すと136秒から86秒に改善しました。ほぼプロセス一つだけの場合と変わらない時間が得られます。

ちなみにこの実験はPyTorchのExampleのMNISTの学習部分だけ(評価なし)でCUDA8.0を使って行いました。特に追加のインストール等は必要ありませんでした。


Multi-Process Service (MPS) の利用方法

MPSは nvidia-cuda-mps-control というコマンドをを通じて利用できます。

実行方法はいくつかありますが、おそらく最も単純なのはGPUを使うジョブを流すGPUと必要なテンポラリフォルダを指定して、MPSデーモンを起動するやり方です。

次のコマンドのあと実行したGPUを使ったプロセスは、GPU0に流れて、並列実行されます。

https://docs.nvidia.com/deploy/pdf/CUDA_Multi_Process_Service_Overview.pdf からそのまま転載)


$ export CUDA_VISIBLE_DEVICES=0 # Select GPU 0.
$ export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps # Select a location that’s
accessible to the given $UID
$ export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log # Select a location that’s
accessible to the given $UID
$ nvidia-cuda-mps-control -d # Start the daemon.

なお、プロセス実行時には CUDA_VISIBLE_DEVICES がセットされていない よう注意してください。デーモン立ち上げ時のみプロセスを流すGPUを指定します。

MPSデーモンを停止するには

$ echo quit | nvidia-cuda-mps-control

を実行してください。


MPSを試す

Stack Overflowに詳しく具体的な使用例が書いてありますので、まずこちらを試してみるとよいと思います。:

How do I use Nvidia Multi-process Service (MPS) to run multiple non-MPI CUDA applications?

ただしこちらは実行方法が少し違います。


関連コマンド



  • nvidia-cuda-mps-control -d: MPSデーモンをバックグラウンドで実行


  • echo quit | sudo nvidia-cuda-mps-control: MPSデーモンを停止


  • ps -aux | grep mps: MPSデーモンが立ち上がっているか確認

  • nvidia-smi -q -d compute: GPUデバイスのcomputeモードを確認


  • sudo nvidia-smi -i 0 -c DEFAULT: id=0のcomputeモードをデフォルトに


  • sudo nvidia-smi -i 1 -c EXCLUSIVE_PROCESS: id=0のcomputeモードを排他的に


参考文献


  • [stackoverflow.com] How do I use Nvidia Multi-process Service (MPS) to run multiple non-MPI CUDA applications?

  • [docs.nvidia.com] MULTI-PROCESS SERVICE(MPSのマニュアル)


    • 特に4節、5節に具体的な使い方が書いてあります



  • [devtalk.nvidia.com] Question about GPU sharing of Multi-process service

  • [manpage] nvidia-cuda-mps-control - NVIDIA CUDA Multi Process Service management program

(注)勘違い等あるかもしれません。その際はご指摘よろしくお願い致します。