TT Lab
开始
学习 学习路径 课程

HPC 与 Slurm

编写 sbatch 脚本

在 TT Lab 中继续学习

目标

按要求编写 sbatch 脚本,配置数组作业和依赖链,并制作能发现 #SBATCH 位置错误的检查器。

为什么重要

#SBATCH 只会在出现第一条可执行命令之前被解析。 写在其后的指令会变成普通注释,既不报错也不警告。 于是就会出现这样的局面:你以为已经申请了 GPU,CUDA 却看不到,用户只好怀疑自己的代码,白白花时间。这种错误靠人眼很难发现,用脚本却很容易发现。

另外,数组作业如果漏掉 %N 限制,100 个作业会一下子占满队列,挡住其他用户。在共享集群中,这同时也是一个社会性问题。

步骤

  1. 创建 /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 目录。
  2. 在同一个文件中添加资源请求:--nodes=1、--ntasks=1、--cpus-per-task=8、--mem=64G
  3. 添加时间和分区:--time=04:00:00、--partition=gpu
  4. 添加 GPU 请求:--gres=gpu:a100:2
  5. 创建 /root/jobs/sweep.sbatch。它是数组作业,必须包含 --array=1-20%4 和 --output=/root/jobs/logs/%A_%a.out,并且在正文中使用 SLURM_ARRAY_TASK_ID。
  6. 在 train.sbatch 的正文中加入下列内容:以 set -euo pipefail 开头,使用 SLURM_CPUS_PER_TASK 环境变量,并且有一行用 srun 执行。
  7. 创建 /root/jobs/pipeline.sh。必须有两行:用 --parsable 取得第一个作业的 ID 并存入变量,再用 --dependency=afterok: 提交第二个作业。
  8. 编写 /root/jobs/lint.sh。把 sbatch 脚本的路径作为第一个参数,检查下面的规则,没有违规时以退出码 0 结束,有违规时以非 0 的值结束。
    • 规则 1:第一行必须以 #! 开头
    • 规则 2:所有 #SBATCH 行都必须在第一条可执行命令(既不是注释也不是空行的行)之前
    • 规则 3:必须有 --job-name 和 --time 指令 评分器会分别用你的 train.sbatch(必须通过)和故意把指令放在后面的错误脚本(必须失败)来运行。

参考

基本骨架

创建 /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 的值结束。

第一条可执行命令之后的指令会被忽略。发现这种情况,正是这个检查器的目的。