TT Lab
はじめる
学ぶ 学習パス コース

HPCとSlurm

sbatchスクリプトの書き方

TT Labで続きを見る

一言でいうと

sbatchスクリプトの#SBATCHの行はコメントのように見えますが、リソースの契約書です。そして、最初の実行コマンドより前にあって初めて読み取られます。

なぜ必要なのか

最もよくある初心者のミスがこれです。

#!/bin/bash
echo "starting"
#SBATCH --gres=gpu:2      # <- 무시된다!
python train.py

#SBATCHは、最初の実行可能なコマンドが現れる前までしかパースされません。それより後ろにあるものは、ただのコメントです。エラーも警告も出ません。ジョブはGPUなしで投入され、ユーザーはなぜCUDAが見えないのかとしばらく迷います。

どう動くのか

基本の骨格

#!/bin/bash
#SBATCH --job-name=resnet-train
#SBATCH --output=/home/me/logs/%x-%j.out
#SBATCH --error=/home/me/logs/%x-%j.err
#SBATCH --partition=gpu
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=8
#SBATCH --mem=64G
#SBATCH --gres=gpu:a100:2
#SBATCH --time=04:00:00

set -euo pipefail
echo "job $SLURM_JOB_ID on $SLURMD_NODENAME"
srun python train.py --epochs 50

出力ファイルのパターンの置換指定子を知っておくと便利です。

パターン 値
%j ジョブID
%x ジョブ名
%A 配列ジョブの親ID
%a 配列のインデックス
%N 最初のノード名

出力パスのディレクトリが事前に存在している必要があります。ないとジョブは開始直後に失敗し、その失敗理由を書き込むファイルも作れないため、原因がわかりにくくなります。

便利な環境変数

ジョブの中でSlurmが入れてくれる値です。

SLURM_JOB_ID          작업 ID
SLURM_JOB_NAME        작업 이름
SLURM_JOB_NODELIST    할당된 노드 목록
SLURMD_NODENAME       현재 실행 중인 노드
SLURM_CPUS_PER_TASK   태스크당 CPU  <- DataLoader num_workers 에 쓰면 좋다
SLURM_ARRAY_TASK_ID   배열 인덱스
SLURM_NTASKS          태스크 총 개수

num_workers=int(os.environ.get("SLURM_CPUS_PER_TASK", 4))のように書くと、リソースの要求とコードが自動で一致します。

srunの役割

sbatchスクリプトの中でsrunを使うと、ジョブステップが作られます。複数のノードやタスクで並列実行するときに必要です。単一のプロセスなら、なくてもかまいませんが、あるとリソースのアカウンティングが正確になります。

srun --ntasks=4 python ddp_train.py     # 4개 프로세스로

配列ジョブ

同じスクリプトを、パラメーターだけ変えて何度も実行するときに使います。

#SBATCH --array=1-100%10

1-100はインデックスの範囲、%10は同時に実行する最大数です。この制限がないと、100個が一度にキューに入って、他のユーザーを妨げます。

python sweep.py --config "configs/exp${SLURM_ARRAY_TASK_ID}.yaml"

依存関係

JOB1=$(sbatch --parsable prep.sh)
sbatch --dependency=afterok:$JOB1 train.sh
条件 意味
afterok:ID そのジョブが成功で終わったあと
afterany:ID 成功か失敗かを問わず、終わったあと
afternotok:ID 失敗で終わったあと(後始末のジョブ用)
singleton 同じ名前の自分のジョブがないとき

--parsableはジョブIDだけを出力するので、変数に入れるのに向いています。

状態の確認

squeue -u $USER
squeue -j 12345 -o '%.10i %.20j %.8T %.10M %.6D %R'
scontrol show job 12345
sacct -j 12345 --format=JobID,JobName,State,Elapsed,MaxRSS,ReqTRES
scancel 12345

squeueの最後の列(%R)が待機理由です。Resources(リソース待ち)、Priority(優先度待ち)、Dependency(依存待ち)、QOSMaxJobsPerUserLimit(制限に達した)などが出ます。ジョブが動かない理由の半分は、この1列に書かれています。

現場での姿

--memを指定せず既定値で動く場合。クラスターの既定値が小さいとOOMで落ち、大きいと他のジョブが入れません。明示する習慣をつけるのがよいです。

--timeを最大値で書く文化。全員がそうすると、バックフィルが無力になって全体のスループットが下がります。実際に近い値を書いて20%ほど余裕を持たせるのが、お互いの利益になります。

次のラボですること

sbatchスクリプトを要件どおりに作成し、配列ジョブと依存関係のチェーンまで構成します。最後に、#SBATCHの位置の誤りを検出する検証スクリプトを自分で作ります。