LoginSignup
24
11

More than 3 years have passed since last update.

ジョブスケジューラPBSProでGPU計算クラスタを組みAIを効率的に学習させる方法 (後編)

Last updated at Posted at 2019-12-05

はじめに

こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の露木です。

前編の記事ではPBSProのパッケージをインストール・設定して,CPUを利用するジョブを実行可能にするまでを実施しました。後編となる今回の記事では,GPUを利用したnvidia-dockerによるAI学習用のジョブを実行可能にする手順をご紹介します。

前編で説明した内容は実施済みであることを前提としますので,未実施の場合はまず前編をご覧ください。

また,同じアドベントカレンダーにSlurm HPCクラスタとKubernetesを同居させてみた(前編) - Qiita があり,こちらもHPC向けジョブスケジューラの話題になります。PBSは歴史のある枯れたジョブスケジューラですが,SlurmやKubernetesのような新しい技術に興味のある方はぜひ,そちらの記事もご覧ください。

GPUを計算リソースとして管理する設定

早速ですが,GPUを計算リソースとして管理するために設定を追加していきます。

まず,管理ノードで以下のコマンドを実行します。

sudo /opt/pbs/bin/qmgr -c "create resource ngpus type=long, flag=nh"

管理ノードの設定ファイル/var/spool/pbs/sched_priv/sched_configを編集し,resources: の行に ngpusを追加します。追加後のresources:の行は下記のようになります。

resources: "ncpus, mem, arch, host, vnode, netwins, aoe, ngpus"

PBSProを再起動し,設定を反映させます。

sudo /etc/init.d/pbs restart

計算ノードごとにGPUの本数を登録します。例えば計算ノードsioにGPUが3本,別の計算ノードmisoにGPUが2本ある場合は下記のコマンドを実行します。

sudo /opt/pbs/bin/qmgr -c "set node sio resources_available.ngpus=3"
sudo /opt/pbs/bin/qmgr -c "set node miso resources_available.ngpus=2"

ここで,GPUの登録を動作確認します。 pbsnodes コマンドで計算ノード一覧を見ると,追加した設定に対応してngpusの項目が増えていることがわかります。

$ pbsnodes -a

miso
     Mom = miso
     ntype = PBS
     state = free
     pcpus = 8
     resources_available.arch = linux
     resources_available.host = miso
     resources_available.mem = 32803912kb
     resources_available.ncpus = 8
     resources_available.ngpus = 2
     resources_available.vnode = miso
     resources_assigned.accelerator_memory = 0kb
     resources_assigned.hbmem = 0kb
     resources_assigned.mem = 0kb
     resources_assigned.naccelerators = 0
     resources_assigned.ncpus = 0
     resources_assigned.vmem = 0kb
     resv_enable = True
     sharing = default_shared
     last_state_change_time = Tue Aug  6 20:33:19 2019
     last_used_time = Tue Aug  6 19:16:10 2019

sio
     Mom = sio
     ntype = PBS
     state = free
     pcpus = 48
     resources_available.arch = linux
     resources_available.host = sio
     resources_available.mem = 198036808kb
     resources_available.ncpus = 48
     resources_available.ngpus = 3
     resources_available.vnode = sio
     resources_assigned.accelerator_memory = 0kb
     resources_assigned.hbmem = 0kb
     resources_assigned.mem = 0kb
     resources_assigned.naccelerators = 0
     resources_assigned.ncpus = 0
     resources_assigned.vmem = 0kb
     resv_enable = True
     sharing = default_shared
     last_state_change_time = Tue Aug  6 20:33:19 2019

動作確認のために,GPUの本数を指定してジョブを投入します。
具体的には nvidia-docker を使って nvida-smi コマンドを実行するジョブです。
上手く行けばGPUの情報がジョブ実行結果として保存されるはずです。

echo 'sleep 30; docker run --gpus 3 --rm nvidia/cuda nvidia-smi' | qsub -l select=host=miso:ngpus=1
echo 'sleep 30; docker run --gpus 3 --rm nvidia/cuda nvidia-smi' | qsub -l select=host=sio:ngpus=1

ジョブ投入結果を確認します。misoとsioに1つずつ,GPUを1本専有したジョブが流れている事がわかります。

$ qstat -s

miso:
                                                            Req'd  Req'd   Elap
Job ID          Username Queue    Jobname    SessID NDS TSK Memory Time  S Time
--------------- -------- -------- ---------- ------ --- --- ------ ----- - -----
715.miso        tsuyuki  batch    STDIN       27818   1   1    --  100:0 R 00:00
   Job run at Tue Aug 06 at 15:52 on (miso:ngpus=1:ncpus=1)
716.miso        tsuyuki  batch    STDIN       31988   1   1    --  100:0 R 00:00
   Job run at Tue Aug 06 at 15:52 on (sio:ngpus=1:ncpus=1)

しばらくするとジョブが終了し,qstatコマンドで何も表示されなくなります。

$ qstat

ジョブが正しく終了していれば,カレントディレクトリに実行結果のファイルが保存されているはずです。

$ ls
STDIN.e715
STDIN.e716
STDIN.o715
STDIN.o716

拡張子が .e のファイルには標準エラー出力が保存されています。今回はdockerコマンドをジョブとして実行したため,コンテナイメージを取得したログが保存されています。

$ cat STDIN.e716
Unable to find image 'nvidia/cuda:latest' locally
latest: Pulling from nvidia/cuda
7413c47ba209: Already exists
0fe7e7cbb2e8: Already exists
1d425c982345: Already exists
344da5c95cec: Already exists
43bcc41986db: Pulling fs layer
76661327d908: Pulling fs layer
abdc887b90e5: Pulling fs layer
eb38470de0e2: Pulling fs layer
0adb8d7f107f: Pulling fs layer
eb38470de0e2: Waiting
0adb8d7f107f: Waiting
abdc887b90e5: Verifying Checksum
abdc887b90e5: Download complete
43bcc41986db: Verifying Checksum
43bcc41986db: Download complete
76661327d908: Verifying Checksum
76661327d908: Download complete
43bcc41986db: Pull complete
76661327d908: Pull complete
abdc887b90e5: Pull complete
eb38470de0e2: Verifying Checksum
eb38470de0e2: Download complete
eb38470de0e2: Pull complete
0adb8d7f107f: Verifying Checksum
0adb8d7f107f: Download complete
0adb8d7f107f: Pull complete
Digest: sha256:b89fbc1c8c238f6e838d4394cc4a9fdbb4ea9a3c2f7058cc9700fab3e8c6651b
Status: Downloaded newer image for nvidia/cuda:latest

拡張子が .o のファイルには標準出力が保存されています。今回はdockerコンテナ内部で実行したnvidia-smiコマンドの出力結果が保存されることになります。

下記のnvidia-smiの実行結果をみればわかるように,GPUは3本認識されてしまっています。ジョブ投入時の qsubコマンドでは ngpus=1 としてGPU1本だけ専有するように指定したのですが,ここまでの設定だけではジョブ実行時にリソースが正しく制限されていないことがわかります。PBSProで指定するGPUの本数とジョブが実際に利用するGPUの本数を一致させないと事故の元なので要注意です。

$ cat STDIN.o716
Tue Aug  6 15:51:11 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 430.40       Driver Version: 430.40       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| 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:01:00.0 Off |                  N/A |
| 29%   29C    P8     7W / 250W |     10MiB / 11178MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 108...  Off  | 00000000:02:00.0 Off |                  N/A |
| 29%   29C    P8     8W / 250W |     10MiB / 11178MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  GeForce GTX 108...  Off  | 00000000:03:00.0 Off |                  N/A |
| 29%   27C    P8     7W / 250W |     10MiB / 11178MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

cgroupsによるGPUリソースの分離

ここまでの設定ではジョブの利用可能なリソースを制限する機能がありません。先の例で示したように,qsubコマンドでジョブ投入する際にはGPUを1本しか使わないと言いつつ,実際には3本を利用するような使い方ができてしまうわけです。それではGPUのメモリを使い果たして他人のジョブが異常終了するなど,障害が発生してしまいます。

そこで,Linuxのcgroupsを利用してリソース制限する設定を追加します。本節の設定を追加すれば,例えば ngpus=2 を指定したジョブに対しては 環境変数CUDA_VISIBLE_DEVICES=0,1 が自動的に設定され,GPUを2本までしか使えないようになります。 (細かい話をすると環境変数は本質ではなく,cgroupsによる制限で実際に2本までしかGPUが見えなくなるのですが詳細は割愛します)。

具体的には,以下の2つの設定を加えていきます。

  1. PBSProの側に仮想的な計算ノードvnodeを設定する
  2. Linuxカーネルのcgroupsを自動設定してGPUの分離を実現するhookを有効化

vnodeの設定

まず,下記コマンドを実行して gpu_id` をリソースとして追加定義します。

sudo /opt/pbs/sbin/qmgr -c "create resource gpu_id type=string, flag=h"

/var/spool/pbs/sched_priv/sched_configresources:の行に gpu_id を追加します。

resources: "ncpus, mem, arch, host, vnode, aoe, eoe, ngpus, gpu_id"

PBSProを再起動して設定変更を反映します。

sudo /etc/init.d/pbs restart

次に,GPUごとに仮想的な計算ノードvnodeの設定ファイルを作成します。例えば,計算ノードmisoの設定ファイルとして miso_vnodesというファイルを下記の内容で作成します。ここではmisoがGPUを2本積んでいる計算ノードなので,miso[0]miso[1]という2つのvnodeを設定して管理することになります。また,misoは32GBのメモリと4コアのCPUを持つので,2つのvnodeに均等に配分しています。

$configversion 2
miso: resources_available.ncpus = 0
miso: resources_available.mem = 0
miso[0]: resources_available.ncpus = 4
miso[0]: resources_available.mem = 16gb
miso[0]: resources_available.ngpus = 1
miso[0]: resources_available.gpu_id = gpu0
miso[0]: sharing = default_excl
miso[1]: resources_available.ncpus = 4
miso[1]: resources_available.mem = 16gb
miso[1]: resources_available.ngpus = 1
miso[1]: resources_available.gpu_id = gpu1
miso[1]: sharing = default_excl

以下のコマンドを実行し,作成したvnodeの設定ファイルmiso_vnodesをPBSProの設定ファイル群があるディレクトリへコピーします。

/opt/pbs/sbin/pbs_mom -s insert miso_vnodes miso_vnodes

このようなvnodeの設定ファイルの作成とコピーをすべての計算ノードで実行します。
vnodeの設定が終わったら,設定ファイルのpbsのデーモンを再起動して設定変更を反映します。

sudo /etc/init.d/pbs restart

設定の反映を確認します。これでvnodeの設定は終了です。

$ pbsnodes -v 'miso[0]' 'miso[1]'
miso[0]
     Mom = miso
     ntype = PBS
     state = free
     resources_available.arch = linux
     resources_available.gpu_id = gpu0
     resources_available.host = miso
     resources_available.hpmem = 0b
     resources_available.mem = 33522974720b
     resources_available.ncpus = 4
     resources_available.ngpus = 3
     resources_available.vmem = 35602300928b
     resources_available.vnode = miso[0]
     resources_assigned.accelerator_memory = 0kb
     resources_assigned.hbmem = 0kb
     resources_assigned.mem = 0kb
     resources_assigned.naccelerators = 0
     resources_assigned.ncpus = 0
     resources_assigned.ngpus = 0
     resources_assigned.vmem = 0kb
     resv_enable = True
     sharing = default_excl
     last_state_change_time = Wed Aug  7 23:27:42 2019
     last_used_time = Wed Aug  7 23:27:42 2019

miso[1]
     Mom = miso
     ntype = PBS
     state = free
     pcpus = 4
     resources_available.arch = linux
     resources_available.gpu_id = gpu1
     resources_available.host = miso
     resources_available.mem = 16gb
     resources_available.ncpus = 4
     resources_available.ngpus = 1
     resources_available.vnode = miso[1]
     resources_assigned.accelerator_memory = 0kb
     resources_assigned.hbmem = 0kb
     resources_assigned.mem = 0kb
     resources_assigned.naccelerators = 0
     resources_assigned.ncpus = 0
     resources_assigned.vmem = 0kb
     resv_enable = True
     sharing = default_excl
     last_state_change_time = Wed Aug  7 23:16:34 2019

cgroups用hookの設定

PBSProにおいてcgroupsの設定は,ジョブ実行時のhookでpythonスクリプトを実行して実現しています。そのPythonスクリプトの設定ファイルを編集するために,まずは現在の設定をpbs_cgroups.jsonというファイルにエクスポートします。

sudo /opt/pbs/bin/qmgr -c "export hook pbs_cgroups application/x-config default" > pbs_cgroups.json

このpbs_cgroups.jsonの中身を下記のように変更します。

  • vnode_per_numa_nodeをtrueに変更 (vnodeで利用するCPUが同じ物理CPUに収まるように指定)
  • devicesの enabled をtrueに変更 (cgroupによるデバイスのアクセス制限を有効化)
  • devicesの allow にnvidia-uvm等のデバイスを追加 (nvidia-docker関連のデバイスは常にアクセスを許可する)

一部,環境に依存しますが変更後の pbs_cgroups.json の中身は以下のようになります。

{
    "cgroup_prefix"         : "pbspro",
    "exclude_hosts"         : [] ,
    "exclude_vntypes"       : ["no_cgroups"],
    "run_only_on_hosts"     : [],
    "periodic_resc_update"  : true,
    "vnode_per_numa_node"   : true,
    "online_offlined_nodes" : true,
    "use_hyperthreads"      : false,
    "ncpus_are_cores"       : false,
    "cgroup" : {
        "cpuacct" : {
            "enabled"            : true,
            "exclude_hosts"      : [],
            "exclude_vntypes"    : []
        },
        "cpuset" : {
            "enabled"            : true,
            "exclude_cpus"       : [],
            "exclude_hosts"      : [],
            "exclude_vntypes"    : [],
            "mem_fences"         : true,
            "mem_hardwall"       : false,
            "memory_spread_page" : false
        },
        "devices" : {
            "enabled"            : true,
            "exclude_hosts"      : [],
            "exclude_vntypes"    : [],
            "allow"              : [
                "b *:* m",
                "c *:* m",
                "c 195:* m",
                "c 136:* rwm",
                ["fuse","rwm"],
                ["net/tun","rwm"],
                ["tty","rwm"],
                ["ptmx","rwm"],
                ["console","rwm"],
                ["null","rwm"],
                ["zero","rwm"],
                ["full","rwm"],
                ["random","rwm"],
                ["urandom","rwm"],
                ["cpu/0/cpuid","rwm","*"],
                ["nvidia-modeset", "rwm"],
                ["nvidia-uvm", "rwm"],
                ["nvidia-uvm-tools", "rwm"],
                ["nvidiactl", "rwm"]
            ]
        },
        "hugetlb" : {
            "enabled"            : false,
            "exclude_hosts"      : [],
            "exclude_vntypes"    : [],
            "default"            : "0MB",
            "reserve_percent"    : 0,
            "reserve_amount"     : "0MB"
        },
        "memory" : {
            "enabled"            : true,
            "exclude_hosts"      : [],
            "exclude_vntypes"    : [],
            "soft_limit"         : false,
            "default"            : "256MB",
            "reserve_percent"    : 0,
            "reserve_amount"     : "64MB"
        },
        "memsw" : {
            "enabled"            : true,
            "exclude_hosts"      : [],
            "exclude_vntypes"    : [],
            "default"            : "256MB",
            "reserve_percent"    : 0,
            "reserve_amount"     : "64MB"
        }
    }
}

作成した設定ファイル pbs_cgroups.json をPBSProにインポートします。

sudo /opt/pbs/bin/qmgr -c "import hook pbs_cgroups application/x-config default pbs_cgroups.json"

cgroupのためのhookを有効化します。

sudo /opt/pbs/bin/qmgr -c "set hook pbs_cgroups enabled = true"

PBSを再起動し,設定を反映させます。

sudo /etc/init.d/pbs restart

動作確認

動作確認のために ngpu=1 を指定したジョブを実行します。

$ echo 'hostname; echo CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES; nvidia-smi ' | qsub -l select=host=miso:ngpus=1 -N test;
1576.miso

実行結果を確認します。cgroupsを利用しない場合と比較して,環境変数CUDA_VISIBLE_DEVICES=0が自動的に設定されており,nvidia-smiでも1本しかGPUが見えていないことがわかります。このようにcgroupsによってリソースの利用を制限し,リソース枯渇による障害発生を防止できます。

$ cat test.o1576
miso
CUDA_VISIBLE_DEVICES=0
Wed Aug  7 17:12:29 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 430.40       Driver Version: 430.40       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| 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:01:00.0 Off |                  N/A |
| 29%   29C    P8     7W / 250W |     10MiB / 11178MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

まとめ

ここまで設定すれば,nvidia-dockerを使ってAIを学習させるジョブを安全に実行可能になります。

従来は,例えばハイパーパラメータの異なる1000個の学習ジョブを一度に実行すると計算リソース不足で異常終了しますし,学習ジョブの終了を人手で確認して次のジョブを実行するのでは大変な手間がかかってしまっていました。そこで,本記事ではジョブスケジューラを整備し,qsubコマンドを使ってジョブを登録するようにしました。これにより,計算リソースの空きを待ってから次のジョブが実行されるようになりますので,効率よく大量のAIを学習できます。

[補足] Dockerコンテナでジョブを実行する設定

PBSProには,明示的に docker run コマンドを指定しなくてもコンテナ内部でジョブを実行する設定もありますが,今回は設定しませんでした。その理由はジョブのスクリプト内部でdocker runするほうが環境変数等を柔軟に設定できるからです。また,PBSProのコンテナ統合機能だと,GPU利用時にnvidia-dockerコマンドを内部的に実行するため,最近のnvidia-docker2に対応しているのか不明な問題もありました。

もし,コンテナ向けの設定を追加したい場合は PBS Professional with Docker Integration を参考に実施してみてください。

参考URL

24
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
11