LabHub
学习 学习路径 课程

HPC 与 Slurm

sbatch 脚本怎么写

在 LabHub 中继续学习

一句话总结

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(触发限制)等状态。作业无法运行的原因,有一半都写在这一列里。

实际工作中会遇到的情况

没有填写 --mem,作业按默认值运行。 集群默认值太小时,作业会因 OOM 终止;默认值太大时,其他作业又无法调度进来。最好养成显式填写的习惯。

习惯把 --time 设置为最大值。 如果所有人都这样做,回填调度就会失效,集群总吞吐量也会下降。填写接近实际的时长,并预留约 20% 的余量,对所有人都有好处。

下一个实验要做什么

按照需求编写 sbatch 脚本,并配置数组作业与依赖链。最后亲手制作一个能够发现 #SBATCH 位置错误的验证脚本