简介面向国科大超算中心用户编写的Slurm作业调度系统使用指南重点介绍超算平台上作业提交、队列查看与资源管理的基本流程适合高性能计算入门者与科研人员快速上手。内容从基本概念讲起系统梳理了sinfo显示队列与节点、squeue查看作业、scontrol查询详细分区/节点/作业信息等常用命令并对三种作业提交方式——交互式srun、批处理sbatch、分配式salloc——给出参数说明、环境变量解析与典型实例还覆盖GPU作业提交、sbcast文件同步、sattach作业步吸附等进阶操作基本涵盖日常超算使用的完整链路。压缩包内共1个文件为PDF格式电子手册整包约595KB便于离线查阅。已有3344人学习下载适合作为超算入门和日常查询的参考手册。1. 高校超算中心的 Slurm 作业调度系统先弄懂它管的是什么高校超算中心里最容易被新手当成黑匣子的就是 Slurm 作业调度系统。你登录上去敲命令提交一个脚本调度器就把任务排到成百上千个运算核心上看起来像魔法一样。实际上绝大多数人浪费的机时、踩过的坑都和没搞懂“调度器到底怎么想”有关。这篇文章把 Slurm 从提交到排队、再到跑完排查这一整条链路讲清楚新手能照着脚本一步步复现熟手也能在参数边界和踩坑记录里找到对应自己问题的解法。Slurm 管的事只有三件收作业、排作业、跑作业。你在登录节点写号脚本交给它它来决定你的任务什么时候上哪台节点、占用多少资源、跑多久、日志写到哪里。适合什么人读刚把程序迁到超算上的同学、总在排队和超时之间反复挣扎的工程师以及在 GPU 节点上报 CUDA 错误却查不出原因的人。2. 提交链路从登录节点到 SBATCH 脚本的最小闭环2.1 登录节点与计算节点之间隔着层调度器每个超算中心的物理架构基本一样一堆计算节点组成一个或几个分区一个控制节点负责调度决策登录节点让你做轻量交互。登录节点是公共入口不是用来跑计算的。A 同学直接在登录节点上跑过大矩阵分解还没到半分钟终端就被系统警告说已经影响了登录节点的响应再继续就会强杀进程。正确做法是把计算任务写进作业脚本通过 sbatch 提交。Slurm 收到后会把脚本安排到某个计算节点上去执行。登录节点只需要处理一件事把脚本和输入数据准备好然后等待结果写回工作目录。判断自己在哪台节点上可以用 hostname绝大多数超算中心登录节点的主机名都会带有 login 字样。2.2 最小可运行的 SBATCH 脚本第一次提交的完整示例第一次提交作业不要急着上复杂参数。先写一个最小脚本确认整条链路是通的。下面这个例子在任何配置了 Slurm 的集群上都能用#!/bin/bash #SBATCH --job-namehello_slurm #SBATCH --partitioncpu #SBATCH --nodes1 #SBATCH --cpus-per-task4 #SBATCH --mem8G #SBATCH --time00:30:00 #SBATCH --outputlogs/hello_%j.log mkdir -p logs module load python/3.9 python -c import time; print(hello, slurm); time.sleep(10)提交命令就一行sbatch run.sh。脚本里以#SBATCH开头的行是调度器指令bash 会把它们当注释忽略掉但 Slurm 会读这些行来决定作业属性。--partitioncpu指定提交到 cpu 分区--nodes1表示只申请一个计算节点--cpus-per-task4表示申请 4 个 CPU 核心--mem8G表示这个作业最多使用 8G 内存--time00:30:00是墙钟时间上限超时会被系统直接杀掉。%j是作业号的占位符最终日志文件名会变成hello_123456.log这种形式。注意mkdir -p logs写在脚本里而不是靠调度器帮你建这是一个常见的坑--output指定的目录不存在时有些集群的 slurmd 会静默失败或者报错导致你等了半天看不到日志文件。2.3 用 squeue、sacct、scontrol 掌握作业的每个阶段提交之后最常用的三个命令分别负责当前状态、历史记录和完整属性我会把它们组合起来用squeue -u $USER # 当前在排队和运行中的作业列表STATE 列是 PENDING 或 RUNNING sacct -j 123456 --formatJobID,JobName,State,ExitCode,Elapsed,End -X # 查看作业的完整生命周期State 显示 COMPLETED、FAILED 等 scontrol show job 123456 # 查看单个作业的完整调度属性包括排队原因、分区、节点列表squeue -u $USER只看自己提交的任务。PENDING表示在排队RUNNING表示正在跑。理解作业卡在哪需要用scontrol show job 123456 | grep Reason这一行的值会直接告诉你排队原因常见的是Resources资源不够、Priority优先级被压住、Dependency等待依赖任务。sacct -j适合跑完复盘作业被杀了、异常退出了看ExitCode比看日志更直接。3. 排队、分区与并行理解调度器后你的任务才算真正属于集群3.1 分区与 QOS为什么有的作业能插队有的作业只能等每个超算中心都会划出几个分区。cpu 分区是最基础的通用计算池gpu 分区里有带显卡的节点large 或 highmem 分区提供大内存节点。分区不是摆设它对作业能申请的资源上限、最大运行时长都有硬限制。你提交作业时如果不写--partition系统会读默认分区如果默认分区和你的需求不匹配就会出现排队很久或者提交直接被拒的情况。QOSQuality of Service比分区细一层管的是限额和优先级。有的 QOS 允许你跑 48 小时有的只允许 4 小时有的允许一次提交 100 个作业有的只允许 2 个。查看这些限制用下面两条命令sinfo -s # 只看分区列表、节点数和分区状态确认分区名拼写 scontrol show partitionlarge # 看某个分区的 MaxTime、QOS、节点列表、默认内存如果提交时提示Invalid partition先用sinfo -s确认分区名不要凭记忆写。如果提示QOSMaxCpuPerUserLimit说明你的用户级配额满了不是集群没资源而是你的 QOS 限制到了上限。这个时候不是去催管理员而是看自己有没有提交大量重复作业占着配额。优先级这个东西决定了同等条件下谁先跑。它由多个维度计算包括 QOS 级别、作业等待时间、用户过往公平份额FairShare使用情况。你会发现刚提交的作业 Priority 值可能是负数这很常见尤其是最近跑了很多任务之后。等一段时间作业的 Priority 会随时间增长慢慢被调度。3.2 从单机到多节点srun 与 mpirun 的正确用法多数人第一次跑并行程序时会困惑一件事脚本里到底用 srun 还是 mpirun。Slurm 生态下最推荐的是 srun。它由调度器直接管理任务启动能正确感知当前作业申请的节点列表和任务数不会出现“作业申请了 2 个节点但 mpirun 只在本机拉起 8 个进程”这种诡异问题。一个典型的多节点 MPI 作业脚本长这样#!/bin/bash #SBATCH --job-namempi_job #SBATCH --partitionlarge #SBATCH --nodes2 #SBATCH --ntasks-per-node8 #SBATCH --cpus-per-task1 #SBATCH --time01:00:00 #SBATCH --outputlogs/mpi_%j.log module load openmpi/4.1 srun ./a.out这里--nodes2申请两台节点--ntasks-per-node8指定每个节点跑 8 个进程总共 16 个 MPI 进程。srun 会根据这些参数自动把进程分布到两台节点上不需要你自己写 hostfile。如果你的代码是 OpenMP 多线程而不是 MPI那么应该调--cpus-per-task而不是--ntasks-per-node。两种并行模式混在一起是另一套玩法但超算中心的规范通常是每个节点的进程数 × 每个进程的线程数要小于等于该节点允许的核心数。3.3 申请资源与实际占用为什么不能默认整节点独占看过很多新手的脚本只写--nodes1不写--mem和--cpus-per-task。这时候调度器按分区默认值处理有些集群的默认内存就是整节点的物理内存。你只跑一个单线程程序却把整台 64 核 128G 的节点占住了后果就是排队时间暴涨系统资源大量闲置。查看节点实际资源分配情况可以用scontrol show nodescontrol show node | grep -E NodeName|CPUs|RealMemory|AllocMem|Gres # 看节点总资源与已分配资源确认节点当前负载申请资源的黄金法则是只申请你实际会用的量再加上一点点冗余。这既是对其他人负责也是对你自己的排队时间负责。申请 8 核 32G 能跑的任务不要申请 16 核 64G除非你确定编译器优化和中间数据会让你吃下这么多。4. 把脚本写到能上线的程度CPU、内存、GPU 与环境的参数组合4.1 CPU 与内存cpus-per-task、mem、mem-per-cpu 怎么选资源申请参数里最容易写错的就是--mem和--cpus-per-task的搭配。下面这张表是我每次都拿来对照的参数作用常见写法常见误用--cpus-per-task每个任务分配的 CPU 核心数--cpus-per-task8与--ntasks混用--mem整个作业的内存上限--mem32G忘记写导致默认整节点内存--mem-per-cpu每个 CPU 核心的内存--mem-per-cpu4G和--mem同时写冲突--ntasks进程数--ntasks1用--ntasks16跑单线程程序我的习惯是纯串行或多线程程序用--cpus-per-task和--memMPI 程序用--nodes加--ntasks-per-node内存用--mem-per-cpu来估算。如果一个任务运行时间比较长比如超过 10 小时--time一定要留出余量。写--time08:00:00却跑了 8 小时 5 分钟的作业最后 5 分钟会被强制 kill之前的计算全部白费。计算内存需求有一个经验公式先看输入数据的大小再估峰值内存最后加上 20% 的余量。不要用自己的笔记本电脑上的内存表现直接推断超算节点上的表现数据加载方式和并行库的缓冲策略会带来明显差异。4.2 GPU 作业申请一张卡时还要带上 CPU 和内存GPU 节点的调度是 Slurm 里最容易翻车的地方。很多第一次用 GPU 分区的人直接沿用 CPU 作业的脚本跑起来之后报 CUDA error原因是作业压根没有申请 GPU。在 GPU 分区显存不是自动分配的必须用--gresgpu:1显式声明#!/bin/bash #SBATCH --partitiongpu #SBATCH --job-nametorch_train #SBATCH --gresgpu:1 #SBATCH --nodes1 #SBATCH --ntasks1 #SBATCH --cpus-per-task8 #SBATCH --mem32G #SBATCH --time12:00:00 #SBATCH --outputlogs/train_%j.log module load cuda/11.8 conda activate pytorch python train.py --epochs 100--gresgpu:1表示申请一张 GPU 卡。注意这行不能写成--gresgpu:1 --gpus1两套语法只需要用一个。申请 2 张卡就写--gresgpu:2但在一个节点有 4 张卡的情况下--gresgpu:2有可能被调度到两个不同节点所以最好加上--nodes1确保 2 张卡在同一台节点上。GPU 作业的 CPU 和内存配额很容易被忽略。数据预处理在 CPU 上做数据加载用到内存如果只申请了 4 核 8G预处理阶段会成为瓶颈。常见配置是每张 GPU 配 6 到 8 个 CPU 核心和 16 到 32G 内存具体取决于你的模型大小和 batch size。4.3 环境初始化module 与 conda 必须在脚本里自己加载很多同学在登录节点上手动module load python/3.9、conda activate pytorch然后直接提交脚本脚本里却不带这些命令。结果作业运行时报Command not found花半天也找不到原因。原因很简单计算节点上的环境是干净的你登录时加载的模块不会自动传递过去。正确做法是把环境初始化写进脚本#!/bin/bash #SBATCH --job-nameenv_test #SBATCH --outputlogs/env_%j.log source ~/.bashrc module load cuda/11.8 conda activate myenv which python python train.py如果你用 conda建议在脚本里写source ~/.bashrc然后再conda activate不要只写conda activate。有些集群的 bash 环境默认不加载 conda init 的内容只写 activate 会报错。module load和conda activate的先后顺序也有讲究先加载 CUDA 驱动相关的 module再激活 conda 环境避免两个 Python 的库路径互相干扰。另外不要在你的代码里硬编码绝对路径比如/home/yourname/project/...这种写死的东西换成相对路径或者使用环境变量$SLURM_SUBMIT_DIR。这个变量指向你提交作业时所在的目录很多集群在计算节点上运行时的工作目录会指向临时目录导致脚本找不到输入文件。我一般会在脚本开头加上cd $SLURM_SUBMIT_DIR保证执行目录正确。5. 避坑看起来能跑、实际跑不动的五个高频场景5.1 现象提交瞬间失败提示 Invalid partition提交命令刚跑完系统直接弹出一个错误内容是sbatch: error: Batch job submission failed: Invalid partition。原因基本是两个分区名拼写错误或者你的账号没有该分区的访问权限。解决方法先用sinfo -s看集群上有哪些分区再用scontrol show partition分区名查看是否包含你的账号。不要让“刚才明明还能提”这种经验干扰判断。有些集群在维护期间会临时隐藏某个分区sinfo -s里的 STATE 会是down或drain。我一个朋友把当年的模拟项目X的作业提到 large 分区排了很久才注意节点状态变成了 drain作业一直 PENDING最后取消了在重提的时候选了 cpu 分区。第一次提交之前先看sinfo -s花不了十秒钟能省下大量等错节点的时间。5.2 现象作业一直 PENDINGReason 写着 Resourcessqueue -u $USER显示作业一直 PENDING用scontrol show job 123456 | grep Reason看到Resources。这个原因不是特权问题是集群里暂时没有足够匹配你请求的空闲资源。你申请了 4 张 GPU 卡但整个分区只剩下 2 张那就一直等着。这种属于资源分配不当引发的问题是自己跟别的作业互相卡位。解决思路是减小资源申请量。把--gresgpu:4改成--gresgpu:2或者把--nodes2改成--nodes1大概率立刻就能跑。如果任务确实必须要那么多资源那就没有捷径只能等。另外可以换个 QOS 再提一次不同 QOS 的优先级不同有时低优先级分区反而更容易排队排上。5.3 现象GPU 程序报错 CUDA error原因竟是根本没申请 GPU这是 GPU 分区最尴尬的翻车现场。脚本里没有写--gresgpu:1作业被调度到普通的 CPU 节点上程序运行到一半CUDA runtime 找不到设备报出CUDA error: no kernel image is available for execution on the GPU或者直接段错误。很多人以为是代码问题在代码里加了无数条检查结果白折腾。排查方法很简单在脚本里加上nvidia-smi让作业自己把 GPU 的信息打印出来。如果输出里没有显卡信息说明资源申请本来就没带 GPU。另一种可能性是申请到了 GPU 节点但显存不足导致的 OOM这种情况看报错信息里有没有CUDA out of memory有的话就是 batch size 太大或者--gresgpu:1不够用。5.4 现象作业正常结束但日志文件一个字节都没有sbatch提交后很快变成COMPLETED但logs/目录下就是看不到输出文件。可能原因有三个第一日志目录logs/在提交时并不存在--outputlogs/xxx_%j.log路径创建失败第二脚本启动后cd到了别的目录--output里写的相对路径相对于提交目录第三程序输出被某个缓冲区缓存了作业结束时缓冲区内容没有刷到文件里。我的习惯是提交前在命令行里先手动执行一次mkdir -p logs不要依赖脚本去建目录。同时用相对路径时在脚本开头先cd $SLURM_SUBMIT_DIR这样日志路径就一定落在你预期的地方。如果验证作业输出确实没内容但作业又是 COMPLETED先看一眼sacct -j 作业号 --formatJobID,State,ExitCode,End看 ExitCode 是不是 0不是 0 说明程序非正常退出只是没打印出错误到日志里。5.5 现象作业跑一半被杀日志末行写着 DUE TO TIME LIMIT日志文件末尾会出现DUE TO TIME LIMIT或者Killed by signal 0。原因是--time申请的时间小于程序实际运行时间到了墙钟时间上限调度器强制终止作业。这个问题在长训练任务里最常见尤其是需要 20 小时但只申请了 12 小时的任务半夜作业被杀第二天起来才发现。如果作业还在运行中可以用scontrol update JobId123456 TimeLimit02:00:00延长运行时间前提是集群 QOS 允许。如果作业已经被杀了没有后悔药只能调整--time后重新提交。对训练任务我会先跑一个小规模测试用进度条估算总时间再乘 1.5 倍写进--time。记住一句话时间申请太多只是排队久一点申请太少则是白跑一次。6. 进阶玩法数组作业、作业依赖与排队卡住的后悔药真正把 Slurm 用到得心应手靠的是三类技巧数组作业、作业依赖和运行中的调度干预。数组作业适合成百上千个相互独立的小任务比如参数扫描、超参数搜索一次提交就搞定#!/bin/bash #SBATCH --job-nameparam_scan #SBATCH --array1-100%10 #SBATCH --outputlogs/scan_%A_%a.log #SBATCH --partitioncpu #SBATCH --cpus-per-task2 python run_exp.py --param-idx ${SLURM_ARRAY_TASK_ID}--array1-100%10含义是共 100 个子任务同时最多跑 10 个%10这个并发限制很重要既避免一次抢占全部资源又不会让排队时间无限拉长。%A是主作业号%a是子任务号日志文件按子任务单独命名很好排查哪个任务出了问题。作业依赖适合有明确上下游关系的流程。比如要等前面那些参数扫描跑完后再做聚合分析sbatch --dependencyafterok:123456 aggregate.shafterok表示只有 123456 号作业成功结束才启动后续任务。同理还有afterany无论成功失败都跑和afternotok前序失败才跑。依赖写错了也不会报错只会让后面的作业一直 PENDINGReason 里写着Dependency。排队卡住的时候还有后悔药可用。发现--time写少了作业还在 RUNNING用scontrol update JobId123456 TimeLimit02:00:00直接改。改了脚本之后想重新开始用scontrol requeue 123456把作业放回排队队列下次调度会按新脚本重新执行。这两个命令在我日常工作中比重新提交整个作业省事得多因为它们保留了作业号和历史记账记录排查问题不至于乱成一团。我现在的习惯是每次提交前先sinfo -s确认分区再scontrol show partition看一眼限制最后检查脚本里有没有cd $SLURM_SUBMIT_DIR。这些动作加起来不到一分钟但已经帮我避开了绝大部分低级错误。Slurm 的报错信息不一定直接告诉你问题在哪但你愿意多花十秒去查状态、看 Reason它其实把所有答案都摆在队列里了。希望帮到你。本文还有配套的精品资源点击获取