引言:什么是Slurm与sbatch?

在高性能计算(HPC)环境中,Slurm(Simple Linux Utility for Resource Management)是最常用的作业调度系统。它负责管理集群资源,确保多个用户可以高效、公平地共享计算节点。sbatch 是Slurm系统中用于提交批处理作业的核心命令。通过编写一个脚本并使用sbatch提交,用户可以将任务排队,等待系统分配资源后自动执行。

对于初学者来说,面对复杂的集群环境和报错信息往往感到无从下手。本攻略将从最基础的概念讲起,逐步深入到高级技巧和排错指南,帮助你彻底掌握sbatch的使用。


第一部分:新手入门——编写并提交你的第一个作业

1.1 理解作业脚本的结构

sbatch 作业本质上是一个Shell脚本(通常是Bash),其中包含两部分内容:

  1. Slurm指令(Directives):以 #SBATCH 开头的注释,用于告诉调度器你需要多少资源(如CPU、内存、时间等)。
  2. 执行命令(Body):实际要运行的程序或脚本命令。

1.2 一个最基础的作业脚本示例

假设我们要运行一个简单的Python脚本 hello.py,内容如下:

# hello.py
import time
print("Hello, Slurm!")
time.sleep(10)
print("Job Finished.")

对应的作业提交脚本 job_submit.sh 应该这样写:

#!/bin/bash
#SBATCH --job-name=hello_test      # 作业名称,方便在队列中识别
#SBATCH --output=hello_%j.out      # 标准输出文件,%j会被替换为作业ID
#SBATCH --error=hello_%j.err       # 标准错误文件
#SBATCH --ntasks=1                 # 运行1个任务(进程)
#SBATCH --cpus-per-task=1          # 每个任务使用1个CPU核心
#SBATCH --mem=1G                   # 申请1GB内存
#SBATCH --time=00:05:00            # 最大运行时间:5分钟

# 加载必要的环境模块(视集群配置而定)
module load python/3.8

# 运行你的程序
echo "Starting job..."
python hello.py
echo "Job done."

1.3 提交与查看状态

在终端中,使用以下命令提交:

sbatch job_submit.sh

提交成功后,系统会返回一个作业ID,例如 Submitted batch job 123456。

常用查看命令:

  • squeue -u <你的用户名>:查看你的作业在队列中的状态(PD表示等待,R表示运行)。
  • sinfo:查看各个节点的状态(idle表示空闲,alloc表示占用)。
  • scontrol show job <作业ID>:查看作业的详细信息。

第二部分:核心参数详解——精准控制资源申请

要精通sbatch,必须理解每一个核心参数的含义。错误的资源申请不仅会导致作业排队时间过长,还可能被系统拒绝或杀掉。

2.1 任务与核心分配 (--ntasks vs --cpus-per-task)

这是最容易混淆的概念。

  • --ntasks=<number>:指定要运行的任务总数。通常用于MPI程序。
  • --cpus-per-task=<number>:指定每个任务需要的CPU核心数。通常用于多线程程序(如OpenMP、PyTorch多进程)。

场景举例: 如果你要运行一个使用4个节点,每个节点跑1个MPI进程,且每个进程使用16线程的程序:

#SBATCH --nodes=4
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=16

总CPU数 = 4节点 * 1任务/节点 * 16核心/任务 = 64核心。

2.2 内存管理 (--mem vs --mem-per-cpu)

内存申请至关重要。

  • --mem=<size>:申请每个节点的总内存(单位:M, G, T)。
  • --mem-per-cpu=<size>:申请每个CPU核心的内存。

最佳实践: 除非你非常清楚每个核心的负载,否则通常建议使用 --mem 直接指定节点总内存,这样更直观。

#SBATCH --mem=50G  # 申请节点上50GB内存

注意:如果你申请的内存超过了节点物理内存,作业将无法启动(Pending)。

2.3 时间限制 (--time)

格式为 DD-HH:MM:SS。

  • --time=0-01:30:00:1小时30分钟。
  • --time=1-00:00:00:24小时。

技巧: 初次运行时,建议设置比预估时间稍长一点(例如预估1小时,设置1.5小时),以防数据量突变导致超时被杀。但也不要设置过长,否则会降低排队优先级。

2.4 分区选择 (--partition)

集群通常将节点划分为不同的分区(Partition),如 debug(调试,限时短)、normal(普通)、bigmem(大内存)、gpu(显卡节点)。

#SBATCH --partition=bigmem

第三部分:进阶技巧——数组作业与依赖提交

3.1 作业数组 (--array)

当你需要对大量数据运行相同的脚本时(例如处理100个不同的输入文件),手动提交100个作业太繁琐。作业数组允许你一次提交多个相似作业。

示例: 处理 data_1.txt 到 data_100.txt。

#!/bin/bash
#SBATCH --array=1-100
#SBATCH --ntasks=1
#SBATCH --mem=2G

# SLURM_ARRAY_TASK_ID 是系统自动分配的任务ID,范围在1到100之间
INPUT_FILE="data_${SLURM_ARRAY_TASK_ID}.txt"
OUTPUT_FILE="result_${SLURM_ARRAY_TASK_ID}.out"

echo "Processing $INPUT_FILE"
./my_program < $INPUT_FILE > $OUTPUT_FILE

限制数组数量: 如果你只想运行第1, 5, 9到20号任务:

#SBATCH --array=1,5,9-20

并发限制: 为了避免数组作业占满整个集群,可以使用 %<数字> 限制同时运行的数量:

#SBATCH --array=1-100%10  # 最多同时运行10个,其他的排队

3.2 作业依赖 (--dependency)

有时你需要等一个作业成功完成后,才运行下一个作业(例如:先跑数据清洗,清洗成功后再跑模型训练)。

语法:

sbatch --dependency=afterok:<作业ID1>:<作业ID2> next_job.sh

实际应用场景(脚本内嵌套):

# 1. 提交第一步任务,获取ID
JOB1_ID=$(sbatch --parsable step1.sh)

# 2. 提交第二步任务,依赖第一步成功
JOB2_ID=$(sbatch --parsable --dependency=afterok:$JOB1_ID step2.sh)

# 3. 提交第三步任务,依赖第二步成功
sbatch --parsable --dependency=afterok:$JOB2_ID step3.sh

注:--parsable 参数让 sbatch 仅输出作业ID,方便在脚本中捕获。


第四部分:高级应用——GPU作业与环境配置

4.1 申请GPU资源

在深度学习等场景下,你需要显卡资源。

基本配置:

#SBATCH --partition=gpu
#SBATCH --gres=gpu:1        # 申请1块GPU卡
#SBATCH --mem=10G
#SBATCH --cpus-per-task=4

指定GPU型号(如果集群有多种卡):

#SBATCH --gres=gpu:v100:1   # 申请1块V100显卡

多GPU并行:

#SBATCH --gres=gpu:4        # 申请4块卡(通常意味着在同一个节点上)

4.2 环境加载与虚拟环境

集群通常使用 module 来管理软件环境。在脚本中显式加载模块可以保证代码的可复现性。

# 加载编译器和CUDA
module load gcc/9.3.0
module load cuda/11.1

# 激活Python虚拟环境
source /path/to/your/venv/bin/activate

# 或者使用Conda
# conda activate my_env

python train.py

第五部分:常见问题解决方案 (Troubleshooting)

新手最常遇到的报错及解决方法:

5.1 问题:作业一直处于 PENDING 状态

原因分析:

  1. 资源不足:你申请的资源(如大内存、特定分区、GPU)目前没有空闲。
  2. 优先级:你的账户优先级较低,正在排队。
  3. 时间限制:你申请的时间超过了分区允许的最大时间。

解决方案:

  • 使用 squeue -u <用户名> 查看。
  • 使用 sinfo -N -l 查看节点详细状态。
  • 使用 scontrol show job <作业ID> 查看具体原因(Reason列),常见原因有 Resources(资源不足)或 Priority(优先级)。

5.2 问题:作业运行几秒后变成 CG (Completing) 或直接消失

原因分析: 通常是脚本写错了,导致立即退出,或者申请的资源与实际需求不符。

  • Exit code 1:脚本语法错误或命令不存在。
  • Exit code 137:内存不足(OOM),被系统杀掉。
  • Exit code 143:超时(Time Limit)。

解决方案: 查看输出文件(hello_123456.out)和错误文件(hello_123456.err)。

  • 如果是内存问题,增加 --mem。
  • 如果是超时,增加 --time。

5.3 问题:sbatch: command not found

原因分析: 你的终端环境没有加载Slurm客户端,或者你不在登录节点上。

解决方案:

  • 确保你在登录节点(Login Node)上,而不是计算节点(Compute Node)。
  • 检查是否安装了Slurm:which sbatch。

5.4 问题:作业卡在 Dependency 状态

原因分析: 使用了依赖提交,但前置作业失败了(非 0 退出码)。

解决方案:

  • 检查前置作业的日志,修复错误。
  • 如果前置作业是被手动取消的(scancel),依赖作业也会永远卡住,需要手动取消并重新提交。

第六部分:最佳实践与性能优化

6.1 避免在登录节点运行繁重任务

严禁在登录节点直接运行耗时长、占CPU高的脚本。这会导致整个集群变慢,影响其他用户。必须使用 sbatch 提交。

6.2 监控作业资源使用

提交作业后,你可能想知道实际用了多少资源,以便优化下次申请。

  • seff <作业ID>:查看作业的效率报告(CPU利用率、内存使用率)。
  • sacct -j <作业ID> --format=JobID,MaxRSS,Elapsed:查看内存峰值和运行时间。

示例输出分析: 如果 seff 显示内存使用率只有 20%,说明你申请了过多内存,下次可以适当减少以提高排队速度。

6.3 使用 sbatch 的交互式调试

如果你不确定需要多少资源,可以先申请一个交互式作业进行测试:

salloc --ntasks=1 --cpus-per-task=2 --mem=4G --time=01:00:00

这会给你分配一个Shell,你在里面运行命令,测试完毕后输入 exit 释放资源。

6.4 善用临时目录 ($TMPDIR)

在计算节点上,通常有一个高速的本地临时目录(/tmp 或 $TMPDIR)。处理大量小文件读写时,先将数据拷贝到 $TMPDIR,处理完再移回共享存储,能极大提升IO性能。

示例代码:

#!/bin/bash
#SBATCH --ntasks=1
#SBATCH --mem=10G

# 创建临时目录变量
TMP_DIR=$TMPDIR

# 将数据拷贝到本地
cp -r /shared/data/input $TMP_DIR/
cd $TMP_DIR/input

# 运行程序
./my_program

# 将结果拷贝回共享存储
cp -r results /shared/data/output/

# 清理(可选,退出时会自动清理)
rm -rf $TMP_DIR/input

结语

掌握 sbatch 不仅仅是学会一条命令,更是理解高性能计算的资源管理哲学。通过合理地评估资源、利用数组作业处理批量任务、以及建立完善的错误处理机制,你可以将集群的威力发挥到极致。

记住:“先测试,再大规模提交” 是避免浪费计算资源和排队时间的黄金法则。希望这篇攻略能成为你探索科学计算之路的得力助手!