GPUを口約束で分け合うと起きること
一言でいうと
バッチスケジューラーは、誰がいつ何を使うかを、人の合意ではなくポリシーで決めるシステムです。
なぜ必要なのか
GPUサーバーが1台のうちは、こうします。「今使っていますか。」「いいえ、どうぞ。」これでうまく回ります。
2台になってユーザーが5人になると、Slackに予約用のスレッドができます。3台になってユーザーが10人になると、こんなことが起きます。
- 誰かが
nvidia-smiで確認してから起動したのに、その間に別の人が先に起動して、OOMで両方とも落ちます - 夜通し走らせるはずのジョブが午前3時に終わったのに、朝9時までGPUが遊んでいました
- 急ぎの実験があるのに、前の人のジョブがいつ終わるのか誰も知りません
- 誰かがうっかり8枚すべてを確保したまま忘れてしまいました
これらの問題に共通するのは、リソースの割り当てが状態として管理されていないことです。スケジューラーはそれをキューとポリシーに置き換えます。
どう動くのか
Slurmの構成要素
| 構成要素 | 役割 | どこで動くか |
|---|---|---|
slurmctld |
中央のコントローラー。キューの管理とスケジューリングの判断を行う | 管理ノード1台(HA構成では2台) |
slurmd |
各計算ノードのエージェント。ジョブを実行する | すべての計算ノード |
slurmdbd |
アカウントと使用量のデータベース(任意) | 管理ノード |
munge |
ノード間の認証 | すべてのノード |
mungeはこの構成で最も頻繁に問題を起こします。クラスターのすべてのノードが同じmungeキーを持っていて、時計も合っている必要があります(既定の許容誤差は数分です)。キーが違ったり時計がずれたりするとMunge decode failedが出て、ノード同士が通信できません。
ls -l /etc/munge/munge.key # 0400, munge:munge 여야 한다
権限が少しでも緩いと、mungedはそもそも起動しません。
スケジューリングの基本概念
- ノード(Node): 計算リソースの単位です。CPU、メモリ、GRES(GPUなど)を持ちます。
- パーティション(Partition): ノードの集まりであり、ポリシーの単位です。最大実行時間、優先度、アクセス権限を決めます。他のスケジューラーの「キュー」に当たります。
- ジョブ(Job): ユーザーが投入した、リソースの要求と実行内容です。
- ジョブステップ(Step): ジョブの中で
srunによって実行される単位です。
バックフィル(backfill)
基本のスケジューラーは優先度の順にジョブを配置します。ところが、大きなジョブがリソースを待っている間にノードが遊んでしまうことがあります。バックフィルは、前のジョブの開始時刻を遅らせない範囲で、後ろの小さなジョブを先に埋め込みます。
バックフィルがうまく動くには、ユーザーが--timeを正直に書く必要があります。全員が最大値を書くと、スケジューラーは隙間を計算できません。そのため成熟したクラスターは、「短く書けば早く動く」というインセンティブをポリシーとして作ります。
リソース要求の意味
sbatch --nodes=1 --ntasks=1 --cpus-per-task=8 --mem=64G --gres=gpu:a100:2 --time=04:00:00 train.sh
--nodes: ノードの数です。--ntasks: 実行するタスク(プロセス)の数です。MPIのランク数に当たります。--cpus-per-task: タスクあたりのCPUコア数です。PyTorch DataLoaderのworker数に直結します。--mem/--mem-per-cpu: メモリです。どちらか一方だけを使います。--gres=gpu:<타입>:<개수>: GPUです(プレースホルダーはGPUのタイプと個数です)。
要求していないリソースは使えません。cgroupで強制されるため、--gres=gpu:1で受け取ったのにコードから2枚使おうとすると、2枚目はそもそも見えません。
キューが進まない理由の読み方
バッチスケジューラーを入れたあとに最もよく聞くのは、「自分のジョブが動かない」という言葉です。 Slurmはその理由を状態文字列で教えてくれるので、表を知っていればたいていは自分で解決できます。
squeue -u $USER -o "%.10i %.9P %.20j %.8T %.10M %R"
scontrol show job <작업번호> | grep -E 'JobState|Reason|NodeList'
| 理由 | 意味 | たいていの解決方法 |
|---|---|---|
Resources |
要求したリソースが今は空いていません | 待ちます。要求を減らすと早くなります |
Priority |
前に優先度の高いジョブがあります | フェアシェアポリシーの問題です |
QOSMaxJobsPerUserLimit |
そのQoSの同時実行数の上限です | 配列ジョブにまとめます |
AssocGrpCPUMinutesLimit |
アカウントの割り当て量を使い切りました | 管理者に連絡します |
ReqNodeNotAvail |
そのノードが予約中か停止中です | ノードの指定を外します |
PartitionTimeLimit |
要求した時間がパーティションの上限より長いです | --timeを短くします |
要求を大きくするほど、長く待ちます。ノード4台を8時間要求すると、それだけの空き枠ができるまで待つことになります。バックフィル(backfill)スケジューラーは短くて小さなジョブを隙間に差し込むので、--timeを正直に(余裕はあっても過剰にならないように)書くと、はるかに早く始まります。上限いっぱいに書いておく習慣は、自分自身を後回しにしてしまいます。
ノードがdrainなら、理由が書かれています。
sinfo -R # drain 사유 목록
scontrol show node <노드> | grep -E 'State|Reason'
ディスク不足、GPUエラー、ヘルスチェックの失敗がほとんどです。理由を消さずにノードだけを復帰させても、次の検査でまた外されます。
ジョブが落ちたのに理由がわからないときは、アカウンティングの記録を見ます。標準出力に何も残らずに消えてしまう場合は、sacctが終了コードとともにOUT_OF_MEMORYやTIMEOUTを教えてくれます。
sacct -j <작업번호> --format=JobID,State,ExitCode,MaxRSS,Elapsed,ReqMem
MaxRSSがReqMemに張り付いていれば、メモリが原因です。
現場での姿
ホームラボでも意味があります。GPUが2枚のサーバーでも、複数の実験を順番に回す必要があるならスケジューラーを使うほうが楽です。夜のうちにキューへ入れておけば、自動で続けて動きます。そしてその経験は、そのまま大規模なクラスターにつながります。
ノードがDRAINで外れること。ハードウェアエラーやGRESの不一致が検出されると、Slurmはそのノードを自動で外します。原因を直したあと、scontrol update NodeName=... State=RESUMEで復帰させる必要があります。自動では戻りません。これを知らないと、ノード1台が何日も遊んだままになります。
次の確認で見ること
続くクイズではまず、パーティション・バックフィル・リソース要求がそれぞれどのようなスケジューリングの判断を生むのかを確認します。その基準を通過したら、次のモジュールからslurm.confの作成、GPU GRESの定義、sbatchスクリプト、壊れた設定の診断を順に実習します。