4、作业提交:sbatch命令详解、srun交互式作业、作业脚本编写规范

好,咱们进入正题。作业提交这块,是大家每天都要打交道的。说白了,你写好的并行程序,怎么交给集群去跑?就靠这俩命令:sbatchsrun。我见过不少新手,上来就 srun ./a.out,结果把登录节点搞崩了……嗯,咱们今天就把这事彻底捋清楚。

4.1 sbatch:批量作业提交的“正规军”

sbatch 是我个人最常用的命令。它会把你的作业脚本提交到调度队列里,然后后台排队、分配资源、执行。整个过程你不需要盯着,适合跑那种跑几小时甚至几天的任务。

基本用法很简单:

sbatch my_job.sh

但这里有个坑——脚本里必须写清楚你要多少资源。我曾经遇到一个用户,脚本里啥资源参数都没写,结果默认只给了一个CPU核心,他的MPI程序跑了三天还没跑完……

所以,我建议你在脚本头部加上这些参数:

#!/bin/bash
#SBATCH -J my_test_job        # 作业名称
#SBATCH -N 2                  # 申请2个节点
#SBATCH -n 32                 # 总共32个任务(进程)
#SBATCH --cpus-per-task=1     # 每个任务1个CPU核心
#SBATCH --mem=64G             # 每个节点内存64GB
#SBATCH -t 02:00:00           # 运行时间上限2小时
#SBATCH -p normal             # 分区(队列)名称
#SBATCH -o %j.out             # 标准输出文件
#SBATCH -e %j.err             # 标准错误文件

# 加载环境
module load intel/2021.4.0
module load openmpi/4.1.2

# 运行程序
mpirun ./my_mpi_app

这里 %j 会被自动替换成作业ID。我个人习惯把输出和错误分开,方便排查问题。

小技巧: 如果你不确定要多少资源,可以先申请少一点,跑个小规模测试。等程序调通了,再按实际需求放大。别一上来就申请256个节点,万一代码有bug,整个队列都被你堵住了。

4.2 srun:交互式作业的“快刀手”

srun 是另一种提交方式。它直接在前台运行,你盯着终端看输出。适合调试代码、跑短任务、或者需要实时交互的场景。

比如你想快速测试一下MPI程序能不能跑通:

srun -N 2 -n 16 --pty bash

这条命令会给你分配两个节点、16个核心,然后弹出一个交互式shell。你可以在里面编译、调试、跑程序。跑完了 exit 退出,资源自动释放。

为什么会用 --pty?说白了,就是分配一个伪终端,让你能像在本地一样操作。我刚开始用的时候没加这个参数,结果连 ls 命令都执行不了……

另一个常用场景是直接跑程序:

srun -N 4 -n 64 ./my_mpi_app

注意,srun 默认会从当前终端继承环境变量。所以你在 sbatch 脚本里需要 module load,但 srun 交互式环境下,你提前加载好就行。

警告: 千万不要在登录节点上直接跑大型计算任务!登录节点是给大家共享的,你跑个全核占用的程序,别人连个 ls 都卡。我曾经见过有人直接在登录节点跑深度学习训练,结果整个集群的登录服务都挂了……

4.3 作业脚本编写规范:别让调度器“看不懂”

作业脚本,说白了就是一个带特殊注释的Shell脚本。调度器通过 #SBATCH 开头的注释来解析你的需求。这里有几个规范,是我多年运维经验总结的:

4.3.1 脚本头部必须清晰

我建议每个脚本都包含以下信息:

  • 作业名称:别用默认的 job,改成有意义的,比如 weather_model_v2
  • 资源需求:节点数、核心数、内存、时间,一个都不能少。
  • 分区选择:不同分区可能有不同限制,比如GPU分区、大内存分区。
  • 输出重定向:把标准输出和错误分开,方便定位问题。

4.3.2 环境加载要放在前面

我见过不少脚本,把 module load 写在程序运行命令后面……那肯定加载不上啊。正确的顺序是:先加载环境,再运行程序。

# 正确做法
module purge
module load intel/2021.4.0
module load openmpi/4.1.2

# 再运行
mpirun ./app

4.3.3 善用变量和参数

别把路径写死。我习惯用变量:

#!/bin/bash
#SBATCH -J my_job
#SBATCH -N 2
#SBATCH -n 32

INPUT_DIR="/data/input"
OUTPUT_DIR="/data/output/${SLURM_JOB_ID}"
APP_DIR="/home/user/app"

mkdir -p ${OUTPUT_DIR}
cd ${APP_DIR}
mpirun ./simulation -i ${INPUT_DIR}/case1.dat -o ${OUTPUT_DIR}/result.dat

这样每次提交不同任务时,只需要改变量值,不用改整个脚本。

4.3.4 错误处理不能少

我曾经遇到一个脚本,程序跑崩了,但脚本继续往下执行,把错误结果当成正确结果传给了下一个任务……所以,我建议加上简单的错误检查:

mpirun ./app
if [ $? -ne 0 ]; then
    echo "Error: application failed"
    exit 1
fi

4.4 实战对比:sbatch vs srun

特性 sbatch srun
运行方式 后台排队,非交互 前台运行,交互式
适用场景 长时间、批量任务 调试、短任务、交互
资源释放 脚本执行完自动释放 退出终端或程序结束释放
环境继承 脚本内需手动加载 继承当前终端环境
典型用法 sbatch job.sh srun -N 2 ./app
核心原则: 生产环境用 sbatch,调试环境用 srun。别搞反了,否则要么浪费资源,要么耽误时间。

嗯,作业提交这块,说白了就是让调度器听懂你的需求。你给它说清楚要多少资源、跑什么程序、输出放哪里,它就能帮你安排得明明白白。我见过太多因为脚本不规范导致的“惨案”——资源申请太多被管理员警告,申请太少程序跑崩,输出没重定向找半天日志……

所以,养成好习惯,从写好一个 sbatch 脚本开始。