sbatch 脚本怎么写
一句话总结
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 位置错误的验证脚本。