ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从单机到集群:Slurm作业调度实战指南与核心命令解析

从单机到集群:Slurm作业调度实战指南与核心命令解析 1. 从“单机脚本”到“集群任务”为什么我们需要Slurm如果你还在用nohup python train.py 或者screen来跑一个需要几天的深度学习训练任务然后把电脑锁屏祈祷它不要中途崩溃那么是时候认识一下 Slurm 了。这不仅仅是一个“命令集”而是一套完整的分布式工作负载管理器。简单来说它把你手头那几台、几十台甚至上千台服务器从一堆独立的“计算器”变成了一个可以统一调度、排队、管理的“超级大脑”。我第一次接触 Slurm 是在一个高性能计算HPC集群上当时我需要跑一个需要 128 个 CPU 核心、512GB 内存的流体模拟。我本能地想写个脚本用 SSH 把任务分发到各个节点但立刻被管理员制止了。他告诉我“用 Slurm 提交告诉它你要什么资源它会帮你排队、分配、运行并在结束后通知你。” 那一刻我意识到管理集群资源和个人电脑的思维模式完全不同。个人电脑是“独占”的而集群资源是“共享”的。Slurm 就是那个确保共享公平、高效、有序的“交通警察”和“资源分配器”。它的核心价值在于资源抽象与任务调度。你不需要关心你的任务具体跑在哪台机器的哪个核心上你只需要用一套简单的命令声明你的任务需要多少 CPU、多少 GPU、多少内存、跑多久。Slurm 会根据整个集群的负载情况、队列策略、优先级在合适的时机把你的任务调度到合适的节点上去执行。这对于从单机开发转向集群计算的工程师和研究员来说是必须跨越的一道坎。本文的目的就是帮你把这道坎变成一条清晰、可执行的操作路径让你能像在本地运行脚本一样从容地在集群上提交和管理大规模计算任务。2. 作业提交你的第一个Slurm脚本长什么样提交作业是使用 Slurm 的起点。最核心的命令是sbatch但它需要一个“剧本”也就是 Slurm 作业脚本。这个脚本本质上是一个 Bash Shell 脚本只是在开头加上了一些以#SBATCH开头的特殊注释用于向 Slurm 申请资源。2.1 解剖一个标准的作业脚本让我们从一个最经典的例子开始提交一个 Python 计算任务。假设我们有一个计算圆周率的脚本pi.py。#!/bin/bash #SBATCH --job-namepi_calculation # 作业名在队列中显示 #SBATCH --outputslurm-%j.out # 标准输出重定向到文件%j会被替换为作业ID #SBATCH --errorslurm-%j.err # 标准错误重定向到文件 #SBATCH --partitiongpu # 指定分区队列例如 gpu, cpu, debug #SBATCH --nodes1 # 申请1个计算节点 #SBATCH --ntasks-per-node1 # 每个节点上运行1个任务进程 #SBATCH --cpus-per-task4 # 每个任务分配4个CPU核心 #SBATCH --mem8G # 每个节点申请8GB内存 #SBATCH --time01:00:00 # 作业最大运行时间时:分:秒超时会被强制终止 #SBATCH --gresgpu:1 # 申请1块GPU卡如果分区支持 # 加载必要的环境模块取决于集群配置 module load cuda/11.3 module load python/3.9 # 这里是你的实际任务命令 echo Starting calculation on host: $(hostname) echo Using GPU: $CUDA_VISIBLE_DEVICES python pi.py逐行解读与避坑指南#!/bin/bash 必须的 Shebang指明解释器。#SBATCH指令 这是关键。所有给 Slurm 调度器的指令都写在这里。必须放在所有可执行命令之前否则会被当作普通注释忽略。--job-name 给你的作业起个名字。在查询时一个好名字能帮你快速定位。避免使用默认的“bash”。--output/--error极其重要。指定输出和错误日志文件。%j是作业 ID 的占位符能保证每次作业的日志不会互相覆盖。如果不指定输出默认会放在一个名为slurm-jobid.out的文件中但明确指定是好习惯。坑点 确保你指定的路径有写入权限否则作业会因无法写日志而失败。--partition 指定作业提交到哪个分区Partition。分区是集群管理员根据硬件如 GPU 节点、大内存节点或用途如调试、长时作业划分的逻辑队列。你必须知道你的集群有哪些分区可以用sinfo命令查看。提交到错误的分区会导致作业一直排队Pending或直接失败。--nodes, --ntasks-per-node, --cpus-per-task 这是资源申请的核心组合容易混淆。--nodesN 我需要 N 个完整的计算节点。--ntasks-per-nodeM 在每个节点上我要运行 M 个独立的进程MPI 任务通常这样用。--cpus-per-taskC 为每个任务进程分配 C 个 CPU 核心。如果你跑的是 OpenMP 或单纯的多线程程序这个参数决定了线程数。常见场景单节点单进程多线程任务--nodes1 --ntasks-per-node1 --cpus-per-task8。你的程序会在一个节点上启动一个进程该进程可以使用 8 个 CPU 核心。单节点多进程任务如 Python multiprocessing--nodes1 --ntasks-per-node4 --cpus-per-task2。这会启动 4 个进程每个进程分配 2 个核心总共占用 8 个核心。注意 Slurm 会为每个任务分配独立的核心确保不冲突。--mem 申请的内存总量。注意这是每个节点申请的内存不是每个任务。如果你申请了--nodes2 --mem8G那么总共会申请 16GB 内存2节点 * 8G。大坑 很多作业失败是因为内存申请不足被系统 OOMOut Of Memory杀死。建议根据程序实际需求稍多申请一些例如预估需要6G就申请8G。可以用sacct命令查看已结束作业的实际内存使用量来校准。--time作业的生命线。必须准确预估。Slurm 用它来做调度决策短作业可能优先。如果作业超时会被无情地SIGKILL。对于不确定的任务可以申请一个较长时间但注意有些分区对作业时长有限制。调试时可以用debug分区通常限时15-30分钟。--gres 申请通用资源最常见的就是gpu。--gresgpu:2表示申请 2 块 GPU。有些集群还细分类型如--gresgpu:v100:1。module load 在 HPC 集群中软件环境通常通过 Environment Modules 管理。你需要加载任务所需的编译器、库、软件包。这也是常见失败点脚本在本地能跑在集群上失败往往是缺少对应的模块。用module avail查看可用模块。任务命令 最后写你真正要执行的命令。环境变量如$CUDA_VISIBLE_DEVICES会被 Slurm 自动设置你的程序直接读取即可获得可用的 GPU 编号。2.2 提交作业与立即执行的技巧写好脚本比如叫submit.slurm后使用sbatch命令提交sbatch submit.slurm提交成功后会返回一个作业 IDSubmitted batch job 123456。几个实用技巧和高级用法依赖作业 作业 B 必须等作业 A 完成后再开始。# 先提交作业A jobid_a$(sbatch --parsable job_a.slurm) # 作业B依赖作业A的成功完成 sbatch --dependencyafterok:$jobid_a job_b.slurmafterok表示 A 成功状态为 COMPLETED后才运行 B。还有afternotok失败后运行、afterany结束后无论成功失败都运行。数组作业 如果你要跑 100 个参数类似的独立任务例如不同的随机种子用数组作业效率最高而不是提交 100 个独立作业。#SBATCH --array1-100%10 # 提交ID从1到100的数组作业%10表示最多同时运行10个在脚本中可以用环境变量$SLURM_ARRAY_TASK_ID来获取当前任务的索引并据此调整参数。python train.py --seed $SLURM_ARRAY_TASK_ID交互式作业 用于调试、开发或需要实时交互的场景。这相当于申请一个临时节点供你登录使用。# 申请一个节点1个任务4个CPU8G内存1块GPU用时1小时 srun --partitiongpu --nodes1 --ntasks-per-node1 --cpus-per-task4 --mem8G --gresgpu:1 --time01:00:00 --pty /bin/bash命令执行后如果你的作业开始运行你会直接登录到分配的计算节点上就像 SSH 过去一样。退出 bash shell作业即结束。3. 作业查询与监控你的任务跑到哪一步了作业提交后就进入了调度队列。掌握查询和监控命令是高效利用集群、排查问题的基础。3.1 核心查询命令squeuesqueue是查看作业状态的瑞士军刀。最常用的命令是squeue -u $USER # 查看自己所有作业的状态输出类似JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON) 123456 gpu pi_calcula alice R 0:05 1 gpu-node-03 123455 cpu data_prep alice PD 0:00 1 (Resources)关键列解析ST状态 这是最重要的信息。R (RUNNING) 正在运行。PD (PENDING) 排队中。括号里会给出原因(REASON)。常见 PENDING 原因(Resources) 等待所需资源如GPU空闲。(Priority) 有更高优先级的作业在前面。(Dependency) 在等待依赖的作业完成。(PartitionTimeLimit) 作业申请的时间超过了分区的最大限制。TIME 作业已运行时间。NODELIST(REASON) 对于运行中的作业显示运行的节点对于排队中的作业显示排队原因。高级过滤与格式化# 查看所有正在运行R的作业 squeue -t RUNNING # 查看特定作业ID的详细信息 squeue -j 123456 # 自定义输出格式例如只显示作业ID、名称、状态、节点和原因 squeue -u $USER -o %.10i %.20j %.10T %.20R %.20E3.2 历史作业查询sacctsqueue只看当前和未完成的作业。对于已经结束无论成功失败的作业需要用sacct来查看。这是事后分析作业表现如实际内存消耗、CPU效率的利器。# 查看今天的所有作业 sacct --formatJobID,JobName,Partition,State,ExitCode,Elapsed,MaxRSS,AllocCPUS,NodeList -S today # 查看特定作业的详细信息 sacct -j 123456 --formatJobID,JobName,State,ExitCode,Elapsed,MaxRSS,ReqMem,AllocTRES -l关键字段解析State 最终状态COMPLETED成功FAILED失败CANCELLED被取消TIMEOUT超时OUT_OF_MEMORY内存不足等。ExitCode 作业退出码。0:0通常表示成功。非零值表示失败例如1:0是你的程序返回了1。MaxRSS实际最大常驻内存。这是你调整--mem参数最重要的参考。如果MaxRSS接近你申请的ReqMem说明申请得刚刚好如果远小于说明申请多了浪费了资源如果作业因OUT_OF_MEMORY失败而MaxRSS接近申请值说明你需要申请更多内存。AllocTRES 分配的资源详情可以看到具体分配了哪些 GPU。一个实战排查案例你的一个训练作业FAILED了。首先用sacct -j jobid查看ExitCode和State。如果State是OUT_OF_MEMORY那么基本确定是内存爆了。接着去看错误日志文件slurm-jobid.err通常会有Killed信息。然后用sacct查看该作业的MaxRSS对比你申请的--mem就能确认问题。下次提交时将--mem申请量提高到MaxRSS的 1.2-1.5 倍。3.3 节点与分区状态查询sinfo在提交作业前或者作业一直排队时可以用sinfo看看集群的整体状况。# 查看所有分区状态 sinfo # 更清晰的格式化输出 sinfo -o %20P %5D %14F %8z %10m %10d %10c %10O %E # 分别显示分区、节点数、各状态节点数、空闲内存等输出会显示每个分区有哪些节点节点是idle空闲、alloc已分配、mix部分占用还是down宕机。如果你的作业需要的资源比如特定型号的 GPU在所有节点上都处于alloc状态那自然就会一直PENDING。4. 作业控制修改、取消与挂起计划赶不上变化作业提交后可能需要调整。4.1 修改排队中的作业参数scontrol作业一旦开始运行RUNNING很多参数就不能改了。但对于还在排队PENDING的作业你可以用scontrol命令进行修改。这个命令功能强大但需要管理员权限的操作我们这里不讨论。# 查看作业123456的详细配置 scontrol show job 123456 # 修改排队作业的时间限制比如发现预估太短 scontrol update jobid123456 TimeLimit2-00:00:00 # 改为2天 # 修改排队作业的优先级需要有相应权限通常用户不能修改 # scontrol update jobid123456 Priority1000 # 修改作业的依赖关系 scontrol update jobid123456 Dependencyafterany:123455重要限制 无法修改正在运行作业的核心资源需求如--mem--cpus-per-task--gres。这些必须在提交前确定好。能修改的主要是TimeLimit、JobName、Dependency等非核心调度参数。4.2 取消作业scancel这是最常用的控制命令之一。# 取消单个作业 scancel 123456 # 取消自己所有作业危险请确认 scancel -u $USER # 取消自己所有处于排队状态的作业 scancel -t PENDING -u $USER # 取消一个数组作业的所有任务 scancel 123456_* # 假设123456是数组作业ID # 取消数组作业的特定任务 scancel 123456_[1,3,5] # 取消第135个任务4.3 挂起与恢复作业scontrol hold/release有时你可能想让一个排队中的作业暂时不要被调度但又不想取消它比如等待一个关键数据文件。这时可以用挂起。# 挂起作业123456状态会变为 PDReason显示为 JobHeldUser scontrol hold 123456 # 释放恢复被挂起的作业 scontrol release 1234565. 深入原理Slurm如何调度你的作业理解了基本命令我们稍微深入一点看看 Slurm 背后是怎么工作的。这能帮你更好地编写脚本和排查问题。5.1 资源请求与分配的逻辑当你提交一个作业脚本时Slurm 的slurmctld中央管理守护进程会解析你的#SBATCH指令生成一个资源需求“模板”。这个模板被放入对应分区的队列中。调度器例如默认的backfill调度插件会持续扫描所有节点slurmd节点守护进程报告的状态空闲 CPU、内存、GPU 等并尝试将队列中的作业“匹配”到节点上。匹配的原则包括资源满足 节点必须有足够的 CPU、内存、GPU 等。时间满足 作业的TimeLimit必须在节点/分区允许的范围内。优先级策略 集群通常会配置一套复杂的优先级公式考虑作业大小、用户公平份额、等待时间等来决定谁先谁后。回填调度 这是提高资源利用率的关键。假设一个大作业需要 10 个节点跑 24 小时目前只有 8 个节点空闲它就需要等待。但此时如果来了一个只需要 2 个节点跑 1 小时的小作业调度器不会傻等而是会“回填”这个小作业先运行充分利用资源。5.2 环境变量与任务执行当作业被分配到节点上开始运行时Slurm 会为你的任务进程设置一系列环境变量你的程序可以通过它们来感知运行环境SLURM_JOB_ID 作业 ID。SLURM_JOB_NODELIST/SLURM_JOB_CPUS_PER_NODE 分配的节点列表和每个节点的 CPU 数。对于多节点作业程序需要自己解析这个变量来进行进程间通信如 MPI。SLURM_PROCID 当前任务的进程 ID在 MPI 作业中很重要。SLURM_LOCALID 节点内的本地任务 ID。CUDA_VISIBLE_DEVICES对于 GPU 作业至关重要。Slurm 会将它设置为分配给该作业的 GPU 在物理节点上的本地索引。你的 CUDA 程序应该读取这个变量而不是试图使用所有 GPU。5.3 常见失败场景与根因分析结合sacct和日志文件我们可以快速定位问题作业状态FAILED退出码非零首先看错误日志.err 文件 里面通常有 Python 的 Traceback、C 的 core dump 信息、找不到文件等直接错误。检查sacct中的ExitCode 例如137通常代表SIGKILL被系统杀死可能是内存超限OOM或超时。作业状态OUT_OF_MEMORY根因 作业实际内存消耗MaxRSS超过了申请值ReqMem。排查 用sacct对比两者。优化程序内存使用或增加--mem申请量。注意如果你用了--mem-per-cpu计算的是mem-per-cpu * cpus-per-task。作业状态TIMEOUT根因 运行时间超过--time限制。排查 程序本身运行慢还是因为排队导致实际计算时间不足增加--time或者优化程序性能。对于长时间作业可以考虑使用检查点Checkpoint机制让程序能从中断处恢复。作业一直PENDINGReason 为(Resources)根因 集群当前没有满足你资源需求的节点。排查 用sinfo查看目标分区的资源状态。你是否申请了过于特殊的资源如特定型号的 GPU、超大的内存是否可以调整资源需求如减少节点数、缩短时间以更快获得调度作业运行节点上的程序报错“找不到模块”或“命令不存在”根因 计算节点的环境与登录节点不同。排查 确保作业脚本中通过module load加载了所有必要的软件依赖。永远不要假设计算节点上有你需要的软件或环境变量。对于 Python强烈建议使用 Conda 虚拟环境并在作业脚本中source activate你的环境或者使用--exportALL参数传递环境变量但需谨慎。掌握这些命令和背后的原理你就能从“集群小白”成长为可以自主提交、监控、调试和优化大规模计算任务的“集群用户”。Slurm 的命令行接口虽然丰富但日常使用的核心就是sbatchsqueuescancelsacct这几个。花点时间理解它们你的科研或工程计算效率将会得到质的提升。
返回列表