빠른 시작 / TL;DR
준비물
- ssh로 master 서버에 접속이 돼있어야 한다
- 자신의 account, partition, qos 이름들을 알아야 한다. 관리자한테 물어보는 게 제일 낫다. 아래 커맨드로 알아낼 수도 있다. Accounting을 사용하지 않는 클러스터라면 아래 정보가 안 뜨거나 비어있을 수도 있다.
sacctmgr show assoc tree format=cluster,acct,user,qos,part user=$USER
커맨드
일단 `srun`으로 Interactive Job부터 잡아보기
빠른 커맨드
srun -p [내 파티션] --pty bash
정석 커맨드
srun -p [사용할 파티션] -q [사용할 QoS] -A [사용할 account] --pty [사용할 쉘]
적절한 리소스도 할당 받기
위 커맨드를 실행하면 cpu 1개, gpu 0개, 기본 설정 메모리를 사용할 수 있는 job을 준다. 원하는 만큼의 리소스를 할당받으려면 저기에 리소스 정보도 넣어줘야 한다.
srun --gres=gpu:[GPU 개수] --cpus-per-gpu=[적당히] --mem-per-gpu=[적당히] \
-p [내 파티션] --pty bash
개요
이 글에서는 slurm으로부터 자원을 할당받아보고 할당 받은 자원 안에서 interactive 하게 작업하거나 batch script를 만들어 제출하는 방법을 알아볼 것이다.
Job의 개념을 알고 가면 좋으나, 마음의 여유가 없다면 이 문단은 생략해도 된다. 하나의 job은 여러 개의 step으로 이루어져있고, 또 각 step은 여러 task로 이루어진다. 실험 1개가 보통 job이다. 실험을 위해 먼저 필요한 자원을 모두 할당 받아 job이라는 것을 생성하고 그 안에서 job step 단위로 자원 중 일부를 재할당 받아 쓴다. Task는 한 개 또는 여러 개의 CPU(GPU 아님)에 의해 실행되는 command의 단위이다. 프로세스도 아니고 CPU도 아니고 왜 task라는 또 다른 개념을 만들었는가 하면 `--cpus-per-task` 라는 옵션을 통해 한 개의 task에서 실행되는 CPU 개수를 조절할 수 있다. Task를 CPU를 살 수 있는 화폐 같은 개념으로 보면 편하다. 나중에 `--ntasks-per-node`, `--ntasks`, `--ntasks-per-gpu` 등의 옵션에서 자원 관리를 task 단위로 하게 된다. 그리고 자원을 할당 받는 한 줄을 job step으로 정의한다. 그냥 srun 한 줄이 job step 한 개이다. Job step을 묶은 것이 job이다.
딥러닝 할 땐 웬만해선 task 어쩌구 건드릴 일이 없다. 석사 졸업할 때까지 모든 job의 step은 1개, 모든 step의 task는 1개라고 생각하고 살아도 된다. 박사는 잘 모르겠다.
자 그럼 이제 job 하나를 "slurm한테 자원을 할당 받는 단위" 또는 "자원 신청서" 정도로 생각하면 되겠다. 내가 자원을 요청하려면 job을 정의해서 slurm에게 줘야 한다. 예를 들어, "GPU 8개, CPU 64개, 메모리 384GiB를 4일 동안 쓰고 싶어"라고 요청하려면 먼저 job을 작성해야 한다. 그러면 slurm은 interactive session을 주든, 코드를 실행시키든 한다.
Job 제출
사용할 CPU 개수, MEM 크기, GPU 개수를 명시하여 리소스 매니저에게 제출하면 자리가 났을 때 할당시켜준다. 각 리소스의 크기를 명시하는 방법은 다양하다. 아래 세 명령어(`srun`, `sbatch`, `salloc`)들은 대부분의 parameter를 공유한다. 명령어 한 줄 외워놓으면 셋 다 자유자재로 쓸 수 있다. 그리고 범용적인 개념이기에 슬럼 뿐만 아니라 다른 리소스 매니저의 명령어들도 비슷하다.
`srun`: 리소스 잡고 명령어 하나만 실행
- `srun` 한 줄은 job step 한 개이다. Job step 단위로 관리하면 slurm이 핸들링 하기 편하게 만들어줄 수 있지만 이로 얻는 이득은 학생 레벨에선 그렇게 크지 않다. 보통은 간단한 디버깅을 위해 interactive job을 잡는 용도로 사용한다.
srun --gres=gpu:4 --cpus-per-gpu=12 --mem=100G -p debug [명령어]
- `nvidia-smi`, `python hello.py` 등 명령어 하나를 실행시킬 수 있다
- 명령어 자리에 `--pty bash`를 주게 되면 리소스가 할당된 쉘 세션을 열 수 있다. 디버깅 용도로 쓰게 된다.
- 위 코드는 `debug`라는 파티션(아직 설명 안 한 개념)에서 GPU 4개, CPU 48개(=12x4), RAM 100GiB를 잡고 명령어를 실행시키라는 뜻이다.
- 노드를 명시하지 않았으므로 debug 파티션 소속의 비어있는 아무 노드에서 실행되게 된다.
- `sbatch` 스크립트 내에서 job step을 나누는 용도로 사용할 수 있다. (`sbatch` 섹션 참조)
아래는 여러 가지 사용 예시이다.
# 인터렉티브 쉘 실행: bash 대신 다른 쉘을 사용해도 된다.
srun --gres=gpu:4 --cpus-per-gpu=12 --mem=100G -p debug --pty bash
# nvidia-smi만 실행해서 GPU 스펙이나 드라이버 버전 확인
srun --gres=gpu:4 --cpus-per-gpu=12 --mem=100G -p debug nvidia-smi
# 파이썬 스크립트 실행
# 이 때, 사용하려는 노드에서도 스크립트 파일에 접근할 수 있어야 한다.
# 그래서 보통 스크립트 파일은 모든 노드에서 접근 가능한 NAS 안에 넣어놓는다.
# 상대경로로도 사용가능하다. 이때 pwd는 해당 job을 제출한 경로이다.
srun --gres=gpu:4 --cpus-per-gpu=12 --mem=100G -p debug python /mnt/hyogun/repos/mmaction2/train.py
# 노드 지정: tesla1이라는 노드만 사용할 것이라고 명시한다.
# tesla1이라는 이름은 slurm.conf와 /etc/hosts 등에 지정되어 있어야 한다.
srun --gres=gpu:4 --cpus-per-gpu=12 --mem=100G -p debug -w tesla1 --pty bash
`salloc`: 리소스를 잡기만 하기
- 얘도 본래 디버깅 용도이지만 `srun`에 비해 활용도가 낮다.
- `srun`과의 차이점이라 하면 `-N [노드개수]` 옵션을 통해 노드를 여러 개 할당받은 상태에서 인터렉티브하게 작업할 수 있다.
- [심화] 좀 더 정확한 차이점은 `srun`은 리소스를 할당 받고 job step을 실행시키기까지 하지만 `salloc`은 할당만 받는다. 따라서 `slurm.conf`의 `PrologFlags`가 set이 안 돼있다면 `salloc`만 시킨 상태에서는 prolog가 아직 동작하지 않는다. 여기서 뭔가를 하면 동작한다. 만약 ` PrologFlags=Alloc`이라면 `salloc`만으로도 prolog가 실행된다.
- Multi-node CPU job을 돌릴 떄 자주 쓰는 것 같던데 나는 아직 salloc과 친숙하지 않다. 웬만해선 srun으로 해결되는 것 같다.
`sbatch`: batch job을 스크립트로 제출
인터렉티브가 아니고 background에서 돌아가는 job을 batch job이라 한다. 보통 한번에 대량으로 제출하기에 batch라는 이름이 붙은 것 같다(뇌피셜). Job에 필요한 옵션들과 실행 명령어를 일반적인 쉘 스크립트로 작성하면 된다. 확장자는 통상적으로 `.sh`나 `.sbatch`를 사용한다. 개인적으로 `.sbatch`가 더 나은 것 같다. 왜냐면
- 제출용 스크립트(`.sbatch`)와 일반 스크립트(`.sh`)를 구분할 수 있다.
- Slurm용 스크립트라는 정보를 준다.
의 이유가 있다. 보통 일반 스크립트를 `sbatch` 파일에 `source` 등의 방식으로 import 하는 식으로 많이 쓴다. 일반 스크립트는 보통 데이터셋 세팅, 환경변수 셋팅, 학습 완료 후 visualization 작업 등의 용도로 사용한다.
sbatch job 제출용 스크립트를 알아보자.
#!/bin/bash
#SBATCH -J hello_slurm # Job 이름
#SBATCH -p batch # Job에 사용하는 파티션 (slurm.conf 게시글에서 설명)
#SBATCH --gres=gpu:2 # Job에 사용할 리소스 (GPU)
#SBATCH --cpus-per-gpu=4 # Job에 사용할 리소스 (CPU)
#SBATCH --mem-per-gpu=10G # Job에 사용할 리소스 (RAM)
#SBATCH -t 1:00:00 # 1 hour # 이 job이 리소스를 물고 있을 최대 시간
#SBATCH -o slurm/logs/slurm-%A_%a-%x.out # 해당 스크립트가 실행됐을 때 stdout이 기록될 파일
# 여기까지 마스터에서 실행
####################################################
# 여기부터 할당된 노드에서 실행됨
# sbatch 하는 시점에 해당 env가 활성화돼있으면
# 자동으로 env var들을 inherit 하기 때문에 안 해줘도 됨
# conda activate my_env
# module load cuda-11.2 # SPACK 세팅이 된 경우 CUDA runtime을 불러올 수 있음
hostname # 무슨 노드에 배치됐는지 표시
python train.py # 스크립트 실행
exit 0 # explicit 하게 job을 끝내 잠재적인 completing 문제가 생기는 것을 방지
한 줄 씩 알아보자
- 먼저 shebang(`#!`)으로 시작을 해서 해당 스크립트를 실행시킬 executable을 설정해준다.
- 이후 sbatch directive(`#SBATCH`)가 붙은 줄은 주석이 아니고 slurm에게 옵션을 전달하는 줄이다. 마스터가 `#SBATCH` 줄들까지만 실행하고 job을 생성한 다음 이후 할당된 노드에서 나머지가 실행된다. Job 옵션들은 case를 무시한다. Sbatch directive들은 반드시 스크립트 제일 위에 모여있어야 한다.
- `-J`, `--job-name`: Job 이름을 설정한다.
- `-p`, `--partition`: 사용할 파티션을 설정한다. 파티션 개념은 이후에 자세히 설명할 예정이다.
- `--gres`: GRES(General RESource)라고 슬럼에서 정의한 개념이다. GPU를 명시한다.
- `--cpus-per-gpu`: 사용할 CPU 개수를 GPU를 기반으로 명시한다. `--cpus` 등 다른 방법들도 많지만 딥러닝 할 때는 이 옵션이 가장 편한 것 같다.
- `--mem-per-gpu`: RAM을 명시한다. `10G`는 10GiB를 뜻한다. 단위가 없으면 MiB로 인식한다.
- `-t`, `--time`: Job을 사용할 시간을 명시한다. `1:00:00`은 1시간, `1-0`은 1일, `30`은 30분을 의미한다. 정확한 포맷은 공식 독스에 나와있다. 시간은 어차피 최대로 땡겨서 명시하면 되는 것 아닌가??? 라고 생각할 수도 있을 것이다. 하지만 아니다. 슬럼은 backfill이라는 스케줄링 알고리즘을 사용하는데, 대기 중인 작업 중 간단해보이는 작업이 있으면 새치기 시켜준다. 시간이 적으면 새치기 할 수 있을 확률이 높아진다. 예상시간 x 120% 정도로 설정하는 것을 추천한다.
- `-o`: 작업 중 발생하는 stdout 로그들을 저장할 수 있다.
- 단일 파일이 아니고 위와 같이 디렉토리도 같이 명시했다면 해당 디렉토리가 무조건 존재해야한다. 위의 경우에는 `slurm/logs`가 존재해야 한다.
- Job을 제출한 경로에서 상대경로로만 찍힌다. 절대경로로 찍어도 job을 제출한 곳 pwd 뒤에 append 시킨다.
- `-e`를 따로 명시하지 않으면 에러 로그도 같이 찍힌다.
- `%A`, `%a`, `%x`등은 슬럼에서 제공하는 해당 job의 정보를 담고있는 변수들이다. 공식 독스 참조
- `-e`: 에러 로그들이 여기에 찍힌다. 따로 이 옵션을 명시하지 않으면 `-o`에 명시된 파일에 같이 찍힌다.
자세한 옵션은 공식 독스에 자세히 나와있다.
아래는 내가 실제로 사용하는 스크립트 예시이다. `SLURM_ARRAY_JOB_ID` 등은 슬럼에서 job이 할당되고 나서 부여해주는 환경변수이다. 이 마저도 공식 독스에 나와있다.
아래는 실사용 예시이다. Backbone 모델을 pretrain 시킨 다음 (할당 받은 GPU 중) GPU 1개만 써서 feature를 뽑는 동시에 바로 다음 downstream task를 진행하고, 테스트 한다.
#!/bin/bash
#SBATCH -J train-a-big-model
#SBATCH -p batch
#SBATCH --gres=gpu:8
#SBATCH --cpus-per-gpu=12
#SBATCH --mem-per-gpu=37G
#SBATCH -t 4-0
#SBATCH -o slurm/logs/slurm-%A-%x.out
current_time=$(date +'%Y%m%d-%H%M%S')
ckpt_dir="/mnt/hyogun/repos/mmaction/ckpts/${current_time}"
# job step 1
srun python pretrain.py \
--checkpoint-dir ${ckpt_dir}/backbone
echo 'pretrain done!'
# job step 2: Extract features with only 1 GPU in background (&)
echo 'Extracting backbone features!'
srun --gres=gpu:1 \
-t 1-0 \
python extract_features.py \
--checkpoint-dir ${ckpt_dir}/backbone \
--feature-dir ./features/ &
# job step 3
srun python downstream.py \
--backbone-checkpoint-dir ${ckpt_dir}/backbone \
--checkpoint-dir ${ckpt_dir}/downstream
echo 'downstream done!'
# job step 4
srun python eval.py \
--checkpoint-dir ${ckpt_dir}/downstream
echo 'eval done!'
`srun`을 이용하긴 했지만, 안 해도 상관 없다.
Job은 제출된 시점에 slurmctld(Slurm Controller Daemon)이 복사해서 가지고 있는다. slurmctld가 실행되고 있는 마스터 노드의 별도의 저장 공간에 올라간다. 따라서 Job을 제출하고 나서 스크립트를 수정해도 제출된 job에는 영향이 없다. 하지만 파이썬 스크립트는 아직 읽지 않은 시점이기에 파이썬 코드를 수정하면 영향이 있다.
자주 궁금해하는 것 (FAQ)
`sbatch`나 `salloc`으로 할당 받을 노드 후보 지정하기
문제상황은 이렇다. 노드 1개만 필요한 job이긴 한데 내가 필요한 GPU 혹은 CPU가 `ada[1-5]`에만 있어서 이중에 한 노드를 할당받았으면 좋겠는 상황이다. 그래서 아래와 같이 작성한다.
salloc -w ada[1-5] gres=gpu:4 --cpus-per-gpu=8 --mem-per-gpu=29G -p debug
하지만 이렇게 하면 `ada[1-5]` 각각에 GPU 4개씩, 총 GPU 20개를 할당받는다!!!
이 경우 우리가 원하는대로 동작시키려면 불필요한 노드를 제외해주는 식으로 요청해야 한다. `-x`, `--exclude` 옵션으로 원하지 않는 노드를 제외시킬 수 있다.
salloc -x turing[1-10],ampere[1-5] gres=gpu:4 --cpus-per-gpu=8 --mem-per-gpu=29G -p debug
뭔가 workaround 같지만 무려 Slurm 저자가 제시한 표준 방법이다.
'MLOPs > Slurm-user' 카테고리의 다른 글
Slurm-user | Slurm이란? (12) | 2022.08.20 |
---|