集群调度 SLURM
SLURM 架构
SLURM 组件:
slurmctld(控制守护进程):
运行在管理节点
负责:作业调度、资源管理、状态维护
slurmd(节点守护进程):
运行在每个计算节点
负责:执行作业、汇报资源状态
slurmdbd(数据库守护进程):
运行在数据库节点
负责:记录作业历史、计费数据
slurmrestd(REST API 守护进程):
提供 HTTP REST API
用于与外部系统集成SLURM 配置
slurm.conf 关键配置
ini
# /etc/slurm/slurm.conf
# 集群名称
ClusterName=h3c-hpc
# 控制节点
SlurmctldHost=mgmt01
SlurmctldHost=mgmt02 # 备用控制节点(HA)
# 数据库
AccountingStorageType=accounting_storage/slurmdbd
AccountingStorageHost=db01
# 调度策略
SchedulerType=sched/backfill
SelectType=select/cons_tres # 精细资源分配
SelectTypeParameters=CR_Core_Memory
# 优先级
PriorityType=priority/multifactor
PriorityWeightAge=1000
PriorityWeightFairshare=10000
PriorityWeightJobSize=1000
PriorityWeightPartition=1000
PriorityWeightQOS=1000
# 资源限制
DefMemPerCPU=2048 # 默认每核 2GB 内存
MaxMemPerCPU=8192 # 最大每核 8GB 内存
# 节点定义
NodeName=compute[01-20] \
CPUs=64 \
RealMemory=512000 \
Sockets=2 \
CoresPerSocket=32 \
ThreadsPerCore=1 \
State=UNKNOWN
NodeName=gpu[01-08] \
CPUs=64 \
RealMemory=512000 \
Gres=gpu:a100:8 \
State=UNKNOWN
# 分区定义
PartitionName=cpu \
Nodes=compute[01-20] \
Default=YES \
MaxTime=7-00:00:00 \
State=UP
PartitionName=gpu \
Nodes=gpu[01-08] \
MaxTime=3-00:00:00 \
State=UP
PartitionName=debug \
Nodes=compute[01-02] \
MaxTime=01:00:00 \
MaxNodes=2 \
State=UPgres.conf(GPU 资源配置)
ini
# /etc/slurm/gres.conf(在 GPU 节点上)
Name=gpu Type=a100 File=/dev/nvidia[0-7]作业提交进阶
数组作业(Job Array)
bash
#!/bin/bash
# 数组作业:并行处理 100 个数据文件
#SBATCH --job-name=data-process
#SBATCH --array=1-100%20 # 100 个任务,最多同时运行 20 个
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=16G
#SBATCH --time=02:00:00
#SBATCH --output=logs/process-%A_%a.out # %A=作业ID, %a=数组索引
# 每个任务处理对应编号的文件
INPUT_FILE="data/input_${SLURM_ARRAY_TASK_ID}.csv"
OUTPUT_FILE="results/output_${SLURM_ARRAY_TASK_ID}.csv"
python process_data.py --input $INPUT_FILE --output $OUTPUT_FILE
echo "任务 ${SLURM_ARRAY_TASK_ID} 完成"依赖作业(Job Dependencies)
bash
# 提交预处理作业
PREPROCESS_JOB=$(sbatch --parsable preprocess.sh)
echo "预处理作业 ID: $PREPROCESS_JOB"
# 提交训练作业(依赖预处理完成)
TRAIN_JOB=$(sbatch --parsable \
--dependency=afterok:$PREPROCESS_JOB \
train.sh)
echo "训练作业 ID: $TRAIN_JOB"
# 提交评估作业(依赖训练完成)
sbatch --dependency=afterok:$TRAIN_JOB evaluate.sh
# 依赖类型:
# afterok:前置作业成功完成后运行
# afterany:前置作业完成后运行(无论成功失败)
# afternotok:前置作业失败后运行
# after:前置作业开始后运行交互式作业
bash
# 申请交互式 GPU 节点(调试用)
srun --partition=gpu \
--nodes=1 \
--ntasks=1 \
--cpus-per-task=8 \
--gres=gpu:a100:1 \
--mem=64G \
--time=02:00:00 \
--pty bash
# 在交互式节点上运行
nvidia-smi
python -c "import torch; print(torch.cuda.is_available())"资源管理
账户与配额
bash
# 创建账户(部门)
sacctmgr add account research_dept \
Description="研究部门" \
Organization="H3C"
# 创建用户并关联账户
sacctmgr add user zhangsan \
Account=research_dept \
DefaultAccount=research_dept
# 设置资源限制(QOS)
sacctmgr add qos normal_qos \
MaxTRESPerUser=cpu=256,mem=1024G,gres/gpu=8 \
MaxJobsPerUser=50 \
MaxWallDurationPerJob=7-00:00:00
# 将 QOS 关联到账户
sacctmgr modify account research_dept \
set QOS=normal_qos
# 查看账户使用情况
sreport cluster AccountUtilizationByUser \
Start=2024-01-01 End=2024-01-31节点维护
bash
# 将节点设为维护模式(排空作业)
scontrol update NodeName=compute01 State=DRAIN Reason="硬件维护"
# 查看节点状态
sinfo -N -l | grep compute01
# 等待节点上的作业完成
squeue -w compute01
# 维护完成后恢复节点
scontrol update NodeName=compute01 State=RESUME
# 紧急下线节点(强制终止作业)
scontrol update NodeName=compute01 State=DOWN Reason="紧急故障"SLURM 监控
python
# 通过 SLURM REST API 监控集群
import requests
SLURM_API = "http://slurmrestd:6820"
TOKEN = "your-jwt-token"
headers = {
"X-SLURM-USER-NAME": "admin",
"X-SLURM-USER-TOKEN": TOKEN
}
# 获取集群信息
resp = requests.get(f"{SLURM_API}/slurm/v0.0.39/diag", headers=headers)
diag = resp.json()
print(f"运行作业数: {diag['jobs_running']}")
print(f"等待作业数: {diag['jobs_pending']}")
# 获取节点状态
resp = requests.get(f"{SLURM_API}/slurm/v0.0.39/nodes", headers=headers)
nodes = resp.json()['nodes']
idle_nodes = [n for n in nodes if n['state'] == ['idle']]
alloc_nodes = [n for n in nodes if 'allocated' in n['state']]
down_nodes = [n for n in nodes if 'down' in n['state']]
print(f"\n节点状态:")
print(f" 空闲: {len(idle_nodes)}")
print(f" 使用中: {len(alloc_nodes)}")
print(f" 故障: {len(down_nodes)}")
# GPU 使用率统计
gpu_total = sum(n.get('tres', {}).get('total', {}).get('gres/gpu', 0) for n in nodes)
gpu_alloc = sum(n.get('tres', {}).get('allocated', {}).get('gres/gpu', 0) for n in nodes)
print(f"\nGPU 使用率: {gpu_alloc}/{gpu_total} ({gpu_alloc/gpu_total*100:.1f}%)")