编写 sbatch 脚本
目标
按要求编写 sbatch 脚本,配置数组作业和依赖链,并制作能发现 #SBATCH 位置错误的检查器。
为什么重要
#SBATCH 只会在出现第一条可执行命令之前被解析。 写在其后的指令会变成普通注释,既不报错也不警告。 于是就会出现这样的局面:你以为已经申请了 GPU,CUDA 却看不到,用户只好怀疑自己的代码,白白花时间。这种错误靠人眼很难发现,用脚本却很容易发现。
另外,数组作业如果漏掉 %N 限制,100 个作业会一下子占满队列,挡住其他用户。在共享集群中,这同时也是一个社会性问题。
步骤
- 创建
/root/jobs目录,并创建/root/jobs/train.sbatch。第一行是#!/bin/bash,接着写入--job-name=resnet-train、--output=/root/jobs/logs/%x-%j.out、--error=/root/jobs/logs/%x-%j.err三条指令。同时创建/root/jobs/logs目录。 - 在同一个文件中添加资源请求:
--nodes=1、--ntasks=1、--cpus-per-task=8、--mem=64G - 添加时间和分区:
--time=04:00:00、--partition=gpu - 添加 GPU 请求:
--gres=gpu:a100:2 - 创建
/root/jobs/sweep.sbatch。它是数组作业,必须包含--array=1-20%4和--output=/root/jobs/logs/%A_%a.out,并且在正文中使用SLURM_ARRAY_TASK_ID。 - 在
train.sbatch的正文中加入下列内容:以set -euo pipefail开头,使用SLURM_CPUS_PER_TASK环境变量,并且有一行用srun执行。 - 创建
/root/jobs/pipeline.sh。必须有两行:用--parsable取得第一个作业的 ID 并存入变量,再用--dependency=afterok:提交第二个作业。 - 编写
/root/jobs/lint.sh。把 sbatch 脚本的路径作为第一个参数,检查下面的规则,没有违规时以退出码 0 结束,有违规时以非 0 的值结束。- 规则 1:第一行必须以
#!开头 - 规则 2:所有
#SBATCH行都必须在第一条可执行命令(既不是注释也不是空行的行)之前 - 规则 3:必须有
--job-name和--time指令 评分器会分别用你的train.sbatch(必须通过)和故意把指令放在后面的错误脚本(必须失败)来运行。
- 规则 1:第一行必须以
参考
- 输出模式替换符:
%j作业 ID,%x作业名称,%A数组父 ID,%a数组索引。 - 依赖示例:
J1=$(sbatch --parsable prep.sh)的下一行是sbatch --dependency=afterok:$J1 train.sh - 本实验不会真正运行 sbatch。评分对象是脚本的内容。
- 常见错误 1:像
#SBATCH--time这样不留空格地写指令。#SBATCH后面需要有空格。 - 常见错误 2:第 8 步的检查器把空行或注释当成“可执行命令”来计数,导致把正常脚本判为失败。
基本骨架
创建 /root/jobs 目录,并创建 /root/jobs/train.sbatch。第一行是 #!/bin/bash,接着写入 --job-name=resnet-train、--output=/root/jobs/logs/%x-%j.out、--error=/root/jobs/logs/%x-%j.err 三条指令。同时创建 /root/jobs/logs 目录。
从 shebang 的下一行开始写指令。名称以及输出和错误路径是基本项。
资源请求
在同一个文件中添加资源请求:--nodes=1、--ntasks=1、--cpus-per-task=8、--mem=64G
需要指定节点、任务、每个任务的 CPU 和内存这四项。
时间与分区
添加时间和分区:--time=04:00:00、--partition=gpu
时间格式是 时:分:秒。分区请使用上一个实验中创建的那个。
GPU 请求
添加 GPU 请求:--gres=gpu:a100:2
GRES 的格式是 名称:类型:数量。请使用上一个实验中定义的类型。
数组作业
创建 /root/jobs/sweep.sbatch。它是数组作业,必须包含 --array=1-20%4 和 --output=/root/jobs/logs/%A_%a.out,并且在正文中使用 SLURM_ARRAY_TASK_ID。
可以在范围后面用百分号限制同时运行的数量。请在输出模式中加入数组替换符。
环境变量与 srun
在 train.sbatch 的正文中加入下列内容:以 set -euo pipefail 开头,使用 SLURM_CPUS_PER_TASK 环境变量,并且有一行用 srun 执行。
通过环境变量读取每个任务的 CPU 数量,资源请求和代码就会自动保持一致。
依赖链
创建 /root/jobs/pipeline.sh。必须有两行:用 --parsable 取得第一个作业的 ID 并存入变量,再用 --dependency=afterok: 提交第二个作业。
有一个只输出作业 ID 的选项。请让第二个作业只在成功时才继续。
脚本检查器
编写 /root/jobs/lint.sh。把 sbatch 脚本的路径作为第一个参数,检查下面的规则,没有违规时以退出码 0 结束,有违规时以非 0 的值结束。
- 规则 1:第一行必须以
#!开头 - 规则 2:所有
#SBATCH行都必须在第一条可执行命令(既不是注释也不是空行的行)之前 - 规则 3:必须有
--job-name和--time指令 评分器会分别用你的train.sbatch(必须通过)和故意把指令放在后面的错误脚本(必须失败)来运行。
第一条可执行命令之后的指令会被忽略。发现这种情况,正是这个检查器的目的。