Skip to content

集群调度 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=UP

gres.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}%)")

褚成志的云与计算笔记