클러스터에서는 계산을 로그인 노드에서 직접 돌리지 않고, 작업 스크립트를 스케줄러에 넘긴다. 스케줄러가 자원이 빌 때 계산 노드에서 스크립트를 실행한다. 클러스터 접속과 기본 개념은 Linux 스타터팩의 HPC 장에 있다. 이 장은 스크립트 자체를 다룬다.
PBS Pro 작업 스크립트
작업 스크립트는 평범한 bash 스크립트에 #PBS로 시작하는 줄을 앞에 붙인 것이다. bash에게는 주석이고,
qsub에게는 자원 요청이다. 이 줄들은 첫 명령보다 앞에 있어야 한다.
#!/bin/bash
#PBS -N water-opt
#PBS -l select=1:ncpus=8:mem=16gb
#PBS -l walltime=12:00:00
#PBS -j oe
#PBS -o water-opt.log
cd "$PBS_O_WORKDIR" || exit 1
echo "running on $(hostname) with $NCPUS cores"
| 줄 | 뜻 |
|---|---|
-N water-opt | 작업 이름. qstat에 보이는 이름이다 |
-l select=1:ncpus=8:mem=16gb | 노드 1개(chunk 1개)에 코어 8개, 메모리 16 GB |
-l walltime=12:00:00 | 최대 실행 시간. 넘기면 스케줄러가 작업을 죽인다 |
-j oe | 표준 출력과 표준 오류를 한 파일로 합친다 |
-o 파일 | 그 로그 파일 이름. 없으면 작업이름.o작업번호 |
-q 큐 | 큐 지정. 기본 큐가 있는 클러스터에서는 쓰지 않는다 |
작업은 홈 디렉터리에서 시작하므로 첫 명령은 cd "$PBS_O_WORKDIR"다. 이 변수에는 qsub을 실행한 디렉터리가 들어 있다.
PBS Pro는 요청한 코어 수를 $NCPUS로 알려 준다. 큐 이름, 노드당 최대 코어와 메모리, walltime 상한은 클러스터마다 다르니
사용 중인 클러스터의 문서나 qstat -Q로 확인한다.
같은 작업을 Slurm으로
Slurm은 #SBATCH 줄을 쓴다. 옵션 이름은 다르지만 요청하는 내용은 같다.
#!/bin/bash
#SBATCH --job-name=water-opt
#SBATCH --nodes=1
#SBATCH --ntasks=8
#SBATCH --cpus-per-task=1
#SBATCH --mem=16G
#SBATCH --time=12:00:00
#SBATCH --output=water-opt.%j.log
cd "$SLURM_SUBMIT_DIR" || exit 1
echo "running on $(hostname) with $SLURM_NTASKS tasks"
| PBS Pro | Slurm |
|---|---|
-N 이름 | --job-name=이름 |
select=1:ncpus=8 | --nodes=1와 --ntasks / --cpus-per-task |
mem=16gb | --mem=16G (노드당) |
-l walltime=12:00:00 | --time=12:00:00 |
-o 파일, -j oe | --output=파일 (기본으로 합쳐짐). %j는 작업 번호 |
$PBS_O_WORKDIR | $SLURM_SUBMIT_DIR (Slurm은 원래 제출 디렉터리에서 시작한다) |
$NCPUS | $SLURM_NTASKS, $SLURM_CPUS_PER_TASK |
--ntasks는 MPI 프로세스 수, --cpus-per-task는 프로세스 하나가 쓰는 스레드 수다.
ORCA처럼 MPI로 병렬화된 프로그램은 --ntasks=8 --cpus-per-task=1, xtb처럼 OpenMP만 쓰는 프로그램은
--ntasks=1 --cpus-per-task=8로 요청한다.
module과 conda
작업 스크립트는 비대화형 셸에서 돈다. 비대화형 bash는 ~/.bashrc를 읽지 않으므로, 터미널에서 되던
conda activate나 PATH 설정이 작업 안에서는 없다. 필요한 환경은 스크립트 안에서 직접 만든다.
# 클러스터가 제공하는 프로그램
module purge
module load openmpi/4.1.6
# conda 환경: activate 전에 conda.sh를 읽는다
source "$HOME/miniforge3/etc/profile.d/conda.sh"
conda activate xtb
# 무엇이 잡혔는지 로그에 남긴다
which xtb mpirun
module list
conda.sh 없이 conda activate를 부르면 셸이 초기화되지 않았다는 오류가 난다. 모듈 이름과 버전은
module avail로 확인한다. 작업 안에서 module: command not found가 나면 모듈 초기화 파일을 먼저 읽어야 하는
클러스터다. 경로는 클러스터 문서를 본다.
ORCA 병렬 실행
ORCA를 여러 코어로 돌릴 때 지켜야 할 것이 셋 있다.
orca를 전체 경로로 실행한다. ORCA는 병렬 단계에서 자기 경로를 기준으로 하위 프로그램을 부르기 때문에, 이름만으로 실행하면 병렬 계산이 실패한다.- 입력의
%pal nprocs를 요청한 코어 수와 맞춘다. 더 크면 코어를 나눠 쓰느라 느려지고, 더 작으면 받은 코어가 논다. - ORCA 빌드가 요구하는 OpenMPI 버전을 모듈로 불러 둔다. 버전은 ORCA 다운로드 페이지와 릴리스 노트에 적혀 있다.
! B3LYP def2-SVP Opt
%pal nprocs 8 end
%maxcore 1500
* xyz 0 1
O 0.000000 0.000000 0.119262
H 0.000000 0.763239 -0.477047
H 0.000000 -0.763239 -0.477047
*
%maxcore는 코어당 메모리(MB)다. ORCA는 이 값을 넘겨 쓸 때가 있으므로 nprocs × maxcore를 요청 메모리의 75% 정도로 잡는다.
위 입력은 8 × 1500 MB = 12 GB로, 16 GB 요청에 맞춘 값이다.
#!/bin/bash
#PBS -N water-opt
#PBS -l select=1:ncpus=8:mpiprocs=8:mem=16gb
#PBS -l walltime=12:00:00
#PBS -j oe
cd "$PBS_O_WORKDIR" || exit 1
module load openmpi/4.1.6
export PATH="$HOME/opt/orca_6_1_0:$PATH"
export LD_LIBRARY_PATH="$HOME/opt/orca_6_1_0:${LD_LIBRARY_PATH:-}"
ORCA=$(command -v orca)
"$ORCA" water.inp > water.out 2>&1
mpiprocs=8은 PBS Pro에 MPI 프로세스 8개를 띄울 자리를 알려 준다. ORCA 설치 경로와 OpenMPI 버전은 예시이니
자기 환경에 맞게 바꾼다.
노드 로컬 scratch
ORCA는 계산 중에 큰 임시 파일을 많이 쓴다. 공유 파일 시스템(홈, 프로젝트 디렉터리)에서 돌리면 느리고 다른 사용자에게도 부담이 된다.
계산 노드의 로컬 디스크로 입력을 복사해 거기서 돌리고, 끝나면 결과만 가져온다. PBS Pro는 작업마다 임시 디렉터리를 만들어
$TMPDIR에 넣어 주는 경우가 많다. Slurm 클러스터는 경로가 제각각이니 클러스터 문서에서 scratch 위치를 확인한다.
JOB=water
SCR=$(mktemp -d "${TMPDIR:-/tmp}/orca.XXXXXX")
cp "$JOB.inp" "$SCR"/
sed -i "s/^%pal.*/%pal nprocs $NCPUS end/" "$SCR/$JOB.inp"
copy_back() {
cp "$SCR"/*.xyz "$SCR"/*.gbw "$SCR"/*.hess "$PBS_O_WORKDIR"/ 2>/dev/null
rm -rf "$SCR"
}
trap copy_back EXIT
cd "$SCR" || exit 1
"$ORCA" "$JOB.inp" > "$PBS_O_WORKDIR/$JOB.out" 2>&1
trap copy_back EXIT는 스크립트가 어떻게 끝나든(정상 종료, ORCA 오류, 스케줄러가 보낸 종료 신호) 마지막에
copy_back을 실행한다. 출력 파일은 처음부터 제출 디렉터리에 쓰게 해서, 계산 중에도 tail -f로 볼 수 있고
작업이 죽어도 남는다. sed 줄은 입력의 %pal을 실제로 받은 코어 수로 고친다.
trap과 종료 코드는 04장에서 자세히 다룬다.
walltime을 넘기면 스케줄러는 종료 신호를 보내고 잠시 뒤 강제로 죽인다. 그 사이에 복사가 끝나야 하므로
.gbw처럼 큰 파일은 제때 오지 못할 수 있다. walltime은 예상 시간보다 넉넉하게 잡는다.
OpenMP 스레드 수
OpenMP 프로그램(xtb, CREST, numpy가 부르는 BLAS)은 OMP_NUM_THREADS만큼 스레드를 만든다.
값이 없으면 노드의 모든 코어를 잡으려 해서 다른 작업과 부딪친다. 받은 코어 수로 맞춘다.
export OMP_NUM_THREADS="${NCPUS:-${SLURM_CPUS_PER_TASK:-1}}"
제출, 감시, 취소
| 하는 일 | PBS Pro | Slurm |
|---|---|---|
| 제출 | qsub job.pbs | sbatch job.sh |
| 내 작업 목록 | qstat -u $USER | squeue -u $USER |
| 작업 상세 | qstat -f 작업번호 | scontrol show job 작업번호 |
| 끝난 작업 | qstat -x -u $USER | sacct -j 작업번호 |
| 취소 | qdel 작업번호 | scancel 작업번호 |
상태는 PBS에서 Q(대기), R(실행), F(끝남, -x로 볼 때),
Slurm에서 PD(대기), R(실행)이다. Slurm의 sacct는 끝난 작업이 실제로 쓴 시간과 메모리를 보여 준다.
sacct -j 123456 --format=JobID,JobName,State,Elapsed,MaxRSS,ExitCode
작업 의존성
최적화가 성공한 뒤에 진동수 계산을 시작하게 하려면 두 번째 작업에 의존성을 건다. afterok은 앞 작업이
종료 코드 0으로 끝났을 때만 시작한다는 뜻이다. 앞 작업이 실패하면 뒤 작업은 대기 상태로 남으니 직접 지운다.
# PBS Pro: qsub이 출력하는 작업 번호를 받아 둔다
OPT=$(qsub opt.pbs)
qsub -W depend=afterok:"$OPT" freq.pbs
# Slurm: --parsable은 작업 번호만 출력한다
OPT=$(sbatch --parsable opt.sh)
sbatch --dependency=afterok:"$OPT" freq.sh
자주 나는 실패
| 증상 | 원인과 확인 |
|---|---|
출력이 중간에 끊기고 TERMINATED NORMALLY가 없다 | walltime 초과. 작업 로그 끝의 스케줄러 메시지, Slurm은 sacct의 TIMEOUT |
갑자기 죽고 로그에 Killed나 메모리 관련 메시지 | 메모리 초과. %maxcore를 줄이거나 mem을 늘린다. Slurm은 OUT_OF_MEMORY |
command not found, 라이브러리를 못 찾음 | 작업 안에서 module이나 conda를 불러오지 않았다. 스크립트에 which를 넣어 확인한다 |
| ORCA 병렬 단계에서 MPI 오류 | orca를 전체 경로로 부르지 않았거나 OpenMPI 버전이 ORCA 빌드와 다르다 |
$'\r': command not found 같은 이상한 오류 | Windows에서 편집한 파일의 CRLF 줄 끝. 아래 참고 |
Windows 편집기로 쓴 스크립트는 줄 끝에 \r이 붙어 있어 bash가 그 문자까지 명령의 일부로 읽는다.
$ bash job.sh
job.sh: line 2: cd: $'run1\r': No such file or directory
done
$ file job.sh
job.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
$ sed -i 's/\r$//' job.sh
마지막 줄처럼 \r을 지우거나 dos2unix job.sh를 쓴다. 편집기 설정에서 줄 끝을 LF로 바꿔 두면 다시 생기지 않는다.