はじめに
こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の露木です。
前編の記事では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つの設定を加えていきます。
- PBSProの側に仮想的な計算ノードvnodeを設定する
- 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_config
のresources:
の行に 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
- PBS Works Documentation
- Scheduling Jobs onto NVIDIA Tesla GPU Computing Processors using PBS Professional
- PBS Professional with Docker Integration
- GitHub - PBSPro/pbspro: An HPC workload manager and job scheduler for desktops, clusters, and clouds.
- Building and Packaging PBS on Debian/Ubuntu - PBS Pro - PBS Pro Confluence
- Building PBS Pro Using rpmbuild - PBS Pro - PBS Pro Confluence
- pbspro/INSTALL at master · PBSPro/pbspro · GitHub
- pbspro - Chaperone