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

## はじめに

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

多人数で効率的に機械学習・AIの学習処理や，HPCライクな数値実験をしたければジョブスケジューラは必須といえます。表題の通り，本記事ではジョブスケジューラの一種である[PBSPro](https://www.pbspro.org/)をインストールして，GPUを利用した計算ジョブをnvidia-dockerで実行可能にする手順を前後編に分けて示します。

前編である今回は，CPUを利用したジョブを実行可能にするまでの手順を解説します。[後編](https://qiita.com/MasafumiTsuyuki/items/a3674c2654fc50477d0b)では，GPU利用に関する部分を解説します。

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

## 本記事で目指す構成

本記事では図のようにPBSProの管理ノードと計算ノードのシンプルな2台構成を目指します。

```
    +------------+     +------------+
    | 管理ノード | --- | 計算ノード |
    +------------+     +------------+
```

それぞれのノードの詳細は下記の通りです。

- 管理ノードの構成
    - ホスト名: miso
    - IPアドレス: 192.168.1.2
    - GPU: 2本
    - 計算ノードとしての役割も兼用させる

- 計算ノードの構成
    - ホスト名: sio
    - IPアドレス: 192.168.1.3
    - GPU: 3本

## 前提環境

管理ノードと計算ノードには下記の環境が整っていることを前提とします。その上で，PBSProをインストールする手順を次節以後に説明していきます。

- OS (Ubuntu 18.04) はインストール済み
- Docker と nvidia-docker 2.0をインストール済み
- 管理ノードと計算ノードの `/home` ディレクトリはNFSで共有済み

## PBSProパッケージのコンパイル

まず，PBSProのインストール用パッケージ (rpm, debファイル) をコンパイルします。コンパイル環境として，以下の内容でDockerfileを作成します。

```
FROM ubuntu:bionic
MAINTAINER tsuyuki

ARG DEBIAN_FRONTEND="noninteractive"
RUN apt update && apt upgrade -y \
&&  apt install -y git make python tar wget

WORKDIR /root
RUN wget https://github.com/PBSPro/pbspro/releases/download/v19.1.2/pbspro-19.1.2.tar.gz
RUN tar xzf pbspro-19.1.2.tar.gz
WORKDIR  /root/pbspro-19.1.2/
RUN apt install debhelper build-essential autotools-dev gcc automake autoconf comerr-dev libhwloc-dev libx11-dev x11proto-core-dev libxt-dev libedit-dev libical-dev libncurses-dev perl libpq-dev libpython2.7-minimal:amd64 libpython2.7-dev tcl-dev tk-dev swig dpkg-dev libexpat-dev libssl-dev zlib1g-dev:amd64 libxt-dev:amd64 libxext-dev libxft-dev dh-make debhelper devscripts fakeroot xutils lintian -y
RUN bash ./autogen.sh
RUN bash ./configure
RUN make dist

# build rpm
RUN apt install -y rpm alien
RUN mkdir -p /root/rpmbuild
WORKDIR /root/rpmbuild
RUN mkdir  BUILD  BUILDROOT  RPMS  SOURCES  SPECS  SRPMS
RUN cp /root/pbspro-19.1.2/pbspro*tar.gz /root/rpmbuild/SOURCES
RUN cp /root/pbspro-19.1.2/pbspro.spec /root/rpmbuild/SPECS
WORKDIR /root/rpmbuild/SPECS
RUN rpmbuild -ba --nodeps pbspro.spec
WORKDIR /root/rpmbuild/RPMS/x86_64

# convert rpm to deb
RUN alien --to-deb --scripts *.rpm
```

下記コマンドを実行し，Dockerコンテナの中でPBSProをコンパイルします。

```bash
docker build -t pbsbuild:19.1.2 .
```

### UbuntuやDebianにインストールする場合

debファイルをカレントディレクトリにコピーするため，下記コマンドを実行します。

```bash
docker run --rm -it -v `pwd`:/host pbsbuild:19.1.2 bash -c "cp pbs*.deb /host"
```

正しくコピーできていれば，debファイルがコンテナホスト側に保存されています。

```bash
$ ls *.deb
pbspro-client_19.1.2-0_amd64.deb
pbspro-devel_19.1.2-0_amd64.deb
pbspro-execution_19.1.2-0_amd64.deb
pbspro-server_19.1.2-0_amd64.deb
pbspro_19.1.2-0_amd64.deb
```

### [オプション] CentOSやRHLにインストールする場合

Ubuntuにインストールする場合は不要ですが，下記コマンドでrpmファイルを取得することもできます。もし，CentOSやRedHat Enterprise Linux などに PBSPro をインストールしたい場合は，このrpmファイルを利用したうえで以後のコマンドを適宜読み替えながら進めてください。

```bash
docker run --rm -it -v `pwd`:/host pbsbuild:19.1.2 bash -c "cp pbs*.rpm /host"
```

## PBSProのインストールと設定

コンパイルしたパッケージを利用してPBSProをインストールし，設定していきます。なお， 開発元の [PBS Works Documentation](https://www.pbsworks.com/SupportGT.aspx?d=PBS-Professional,-Documentation) にはすべての情報がまとまっています。本稿でわからないことがあったら，開発元のドキュメントを参照するとスムーズに問題解決できるはずです。

### 管理ノードのインストールと設定

まず，管理ノードに必要な依存パッケージ (PostgreSQL) をインストールします。

```bash
sudo apt install postgresql libpq-dev
```

次に，コンパイルしておいたPBSProを下記コマンドでインストールします。なお，`<pbsproのバージョン>` は適宜読み替えてください。もし，ここで依存関係のエラーがでた場合は `sudo apt install -f` コマンドの実行で解決できます。

```bash
sudo dpkg -i pbspro-server_<pbsproのバージョン>_amd64.deb
```

ジョブ実行時の環境変数にタイムゾーンを追加します。

```bash
sudo bash -c 'echo TZ=\"Asia/Tokyo\" >> /var/spool/pbs/pbs_environment'
```

`/etc/pbs.conf` を編集し，下記の2点を変更します。


1. `PBS_SERVER=miso` に変更 (管理ノードのホスト名を記入する)
1. `PBS_START_MOM=1` に変更 (管理ノードへのジョブ投入を許可する設定)

変更後の `/etc/pbs.conf` は以下のようになります。

```bash
PBS_EXEC=/opt/pbs
PBS_SERVER=miso
PBS_START_SERVER=1
PBS_START_SCHED=1
PBS_START_COMM=1
PBS_START_MOM=1
PBS_HOME=/var/spool/pbs
PBS_CORE_LIMIT=unlimited
PBS_SCP=/usr/bin/scp
```

`/var/spool/pbs/mom_priv/config` に `$usecp *:/home/ /home/` の記載を追加します。今回は`/home`ディレクトリ以下をNFSで共有するため，scpではなくcpコマンドでジョブ実行結果をコピーするように`$usecp`オプションを指定しています。設定変更後の`/var/spool/pbs/mom_priv/config` の中身は以下のようになります。

```
$clienthost miso
$restrict_user_maxsysid 999
$usecp *:/home/ /home/
```

なお，計算ノードと管理ノードの`/home`ディレクトリをNFSで共有していない場合は，`$usecp` の設定は不要ですい。この場合，scpコマンドでジョブ実行結果をコピーすることになるので，計算ノードと管理ノードの間はパスワードなしでsshログインできるように設定しておく必要があります (具体的にはパスフレーズなしの証明書を設定するなど)。

次に，`/etc/hosts`を変更し，管理ノード `miso` と計算ノード `sio` の名前解決を可能にします。ここでは，自ホストもNICに割り当てられたIPアドレス `192.168.1.2` に解決できるように指定する必要がります。デフォルトの `127.0.0.1` ではpbs_momは起動しません。

```
192.168.1.2  miso
192.168.1.3  sio
127.0.0.1    localhost
# 127.0.1.1  miso
```

ここまで設定ができたら，PBSProの各種daemonを起動します。

```bash
sudo /etc/init.d/pbs start
```

daemonの起動は，以下のコマンドで確認できます。

```bash
$ sudo /etc/init.d/pbs status
pbs_server is pid 18909
pbs_mom is pid 18721
pbs_sched is pid 18733
pbs_comm is 18689
```

もし，ここで4つのdaemonが起動していなければ障害対応が必要です。下記のディレクトリにある，PBSの動作ログを見ると，起動しない原因がわかると思います。

- /var/spool/pbs/server_logs/
- /var/spool/pbs/mom_logs/
- /var/spool/pbs/sched_logs/
- /var/spool/pbs/comm_logs/

正しく4つのdaemonが起動していたら，次は管理ノード自体を計算ノードとして登録します。

```
sudo /opt/pbs/bin/qmgr -c "create node miso"
```

ジョブを流すキューを作成するため，下記のコマンドを実行します。細かい設定は [PBS Works Documentation](https://www.pbsworks.com/SupportGT.aspx?d=PBS-Professional,-Documentation) を見ながらお好みに合わせて設定してください。

```
sudo /opt/pbs/bin/qmgr -c "create queue batch queue_type=execution"
sudo /opt/pbs/bin/qmgr -c "set queue batch enabled=True"
sudo /opt/pbs/bin/qmgr -c "set queue batch resources_default.nodes=1"
sudo /opt/pbs/bin/qmgr -c "set queue batch resources_default.walltime=360000"
sudo /opt/pbs/bin/qmgr -c "set queue batch started=True"
sudo /opt/pbs/bin/qmgr -c "set server acl_hosts = "`hostname`
sudo /opt/pbs/bin/qmgr -c "set server default_queue = batch"
sudo /opt/pbs/bin/qmgr -c "set server scheduling = True"
sudo /opt/pbs/bin/qmgr -c 'set server node_pack = True'

# 他ユーザのジョブもqstatで見えるようにする
sudo /opt/pbs/bin/qmgr -c "set server query_other_jobs = True"

# 管理ノード以外からもジョブ投入を許可する
sudo /opt/pbs/bin/qmgr -c "set server flatuid = True"
```

下記コマンドで登録済みの計算ノード一覧を確認します。ここまでの設定では管理ノード `miso` が計算ノードとしても認識されていることがわかるはずです。

```
$ 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.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 18:57:38 2019
```

今後，管理ノードからジョブ投入や計算ノードの死活管理をします。PBSProのコマンド群をパスに追加しておくと便利です。

```
export PAHT=$PATH:/opt/pbs/bin/
```

動作確認のためにジョブを投入してみます。

```
echo "sleep 60; echo done" | qsub
```

qstatコマンドでジョブの状態を確認すると，正しくジョブが登録されていることがわかります。

```
$ qstat
Job id            Name             User              Time Use S Queue
----------------  ---------------- ----------------  -------- - -----
1.miso            STDIN            tsuyuki           00:00:00 R batch
```

今回は `sleep 60` を実行するジョブなので1分立つとジョブが終わります。
ジョブの実行結果としてカレントディレクトリにstdoutとstderrが保存されます。

```
$ qstat

$ ls
STDIN.e1
STDIN.o1

$ cat STDIN.o1
done
```

### 計算ノードのインストールと設定

次に，計算ノード`sio`を追加します。計算ノードにSSHでログインしてから下記コマンドを実行し，計算ノード用のパッケージをインストールします。ただし，`<pbsproのバージョン>` は適宜読み替えてください。

```
sudo dpkg -i pbspro-execution_<pbsproのバージョン>_amd64.deb
```

このとき，依存関係のエラーが出たら場合は下記コマンドの実行で解決できます。

```
sudo apt install -f
```

計算ノードの `/etc/pbs.conf` を変更します。具体的な変更点は下記の1点のみです。

1. `PBS_SERVER=miso` に変更 (管理ノードのホスト名を記入)

変更後の `/etc/pbs.conf` は以下のようになります。

```bash
PBS_EXEC=/opt/pbs
PBS_SERVER=miso
PBS_START_SERVER=0
PBS_START_SCHED=0
PBS_START_COMM=0
PBS_START_MOM=1
PBS_HOME=/var/spool/pbs
PBS_CORE_LIMIT=unlimited
PBS_SCP=/usr/bin/scp
```

計算ノードの `/var/spool/pbs/mom_priv/config` に下記の2点の変更を加えます。

1. `$clienthost miso` の記載に変更 (管理ノードのホスト名を記入)
1. `$usecp *:/home/ /home/` の記載を追加
    - 今回は`/home`ディレクトリ以下をNFSで共有するため，scpではなくcpコマンドで実行結果をコピーする設定

設定変更後の`/var/spool/pbs/mom_priv/config` の中身は以下のようになります。

```
$clienthost miso
$restrict_user_maxsysid 999
$usecp *:/home/ /home/
```

なお，計算ノードと管理ノードの`/home`ディレクトリをNFSで共有しない場合は，`$usecp` の設定は不要です。この場合，scpでジョブ実行結果をコピーすることになるので，計算ノードと管理ノードの間はパスワードなしでsshログインできるように設定しておく必要があります (具体的にはパスフレーズなしの証明書を設定するなど)。

次に，計算ノードでも管理ノードのホスト名 `miso` と計算ノードのホスト名 `sio` を名前解決できるように `/etc/hosts` に書き込みます。

```
192.168.1.2  miso
192.168.1.3  sio
```

タイムゾーンをジョブ実行時の環境変数に追加します。

```
sudo bash -c 'echo TZ=\"Asia/Tokyo\" >> /var/spool/pbs/pbs_environment'
```

PBSProのdaemonを起動します。

```
sudo /etc/init.d/pbs start
```

PBSProのdaemon起動を確認します。計算ノードではpbs_momのみが動作していればOKです。

```
$ sudo /etc/init.d/pbs status
pbs_mom is pid 29095
```

計算ノード`sio`を追加登録させるため，管理ノードにSSHでログインして下記コマンドを実行します。

```
sudo /opt/pbs/bin/qmgr -c "create node sio"
```

ここまで設定すると，下記のように計算ノードが増えていることがわかります。

```
$ 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.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.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
```

新しい計算ノードにジョブが流れるか，動作確認のためにノード名を指定してジョブを投入します。

```
echo "sleep 60; hostname; echo done" | qsub -l select=host=miso
echo "sleep 60; hostname; echo done" | qsub -l select=host=sio
```

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

```
$ qstat -s

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

ここまでの設定で，CPUを利用した計算ジョブは問題なく実行できるようになりました。[次回](https://qiita.com/MasafumiTsuyuki/items/a3674c2654fc50477d0b)は，GPUを計算リソースとしてジョブスケジューリングするための設定をし，動作確認を行います。