一些例子

这个页面总结了一些使用 SLURM 运行常见任务的例子。不想阅读冗长的说明书的用户 可以直接修改这里面的例子然后运行对应的作业。

目录

我是一个 GPU 用户,如何使用集群的 GPU 卡?

使用 GPU 服务器的过程非常简单,只需要在申请 SLURM 资源的时候指定 GPU 分区并 载入相应的 GPU 库即可。注意我们的 GPU 节点安装多个版本的 CUDA,使用时请选对版本。

run.slurm
#!/bin/bash
#SBATCH -J gpu-job            # 任务名字是 gpu-job
#SBATCH --cpus-per-task=8     # 申请 8 个 cpu 核心,这里防止内存不够必须增加 CPU 核心数
#SBATCH -p gpu                # 任务提交到 gpu 分区,必须填写,只有此分区的机器才有 GPU 卡
#SBATCH --gres=gpu:1          # 申请 1 块 GPU 卡,必须填写,SLURM 默认是不分配 GPU 的
#SBATCH -t 1-00:00:00         # 最长运行时间是 1 天

module add cuda/9.0           # 载入 CUDA 9.0 模块
module add anaconda           # 载入 anaconda 模块

python gpu_task.py            # 运行 python 程序

小提示

虽然说我们总是讲 GPU 编程,但是几乎所有的程序都需要 CPU 的参与,GPU 的作用其实 更多在于加速。换句话说,只有 GPU 是无法运行一个完整的程序的。这也就不难理解 为什么申请 GPU 卡的时候还需要指定 CPU 个数。

我是一个 MATLAB 用户,如何使用 SLURM 运行 MATLAB 任务?

除了使用 matslm.rb 自动提交以外,我们仍然可以手动编写 SLURM 脚本。

run.slurm
#!/bin/bash
#SBATCH -J test               # 任务名字是 test
#SBATCH -N 1                  # 申请 1 个节点
#SBATCH --cpus-per-task=4     # 申请 4 个 cpu 核心

module add matlab             # 添加 MATLAB 模块
# 使用 MATLAB 运行当前文件夹中的 Test.m 文件
matlab -nodesktop -nosplash -nodisplay -r "Test"

上述选项中的 -nodesktop -nosplash -nodisplay 表示禁用 MATLAB 的图形界面和 欢迎界面,而 -r 选项的含义是执行某个 MATLAB 命令。在 MATLAB 中输入 Test 正好就是执行 Test.m 这个脚本,这也就不难理解了为什么 -r 选项跟的参数没有 后缀。

MATLAB 与超线程

如果服务器的 CPU 打开了超线程,则 MATLAB 不会将所有的逻辑核心占满。

>> feature('numcores')
MATLAB detected: 16 physical cores.
MATLAB detected: 32 logical cores.
MATLAB was assigned: 32 logical cores by the OS.
MATLAB is using: 16 logical cores.
MATLAB is not using all logical cores because hyper-threading is enabled.

ans =

    16

如上述结果,系统有 16 个物理核 32 个逻辑核,但是 MATLAB 只使用了 16 个核心。 这是因为超线程技术对 MATLAB 内置多线程函数的加速效果不大。实际测试表明,双核 单线程和双核超线程在处理矩阵乘法的效率相同。因此,在申请 MATLAB 任务时,由于 --cpus-per-task 所指的是逻辑核心,且 SLURM 优先会分配同一物理核心的不同 线程,因此可考虑申请两倍的核心数来达到预期的效果。

如果使用 MATLAB 的 parpool 进行运算,则所有逻辑核心都可以被利用,但是效率未知。 以上总结的规律不适用于使用 parpool 的情况。

我是一个 MPI 用户,如何运行多线程或使用多节点的 MPI 任务?

集群已经安装了两种主流的 MPI 实现:mpich 和 OpenMPI,并且已经和 SLURM 作业调度 系统整合。用户只需要写对 SLURM 脚本即可方便启动 MPI 程序。注意:不推荐用户 自行在自己的 HOME 目录安装 MPI,可能会达不到预期的使用效果。

以下我们以 mpich 为例,给出几种常见的 MPI 使用的例子。

使用纯 MPI 模式运行多节点任务

纯 MPI 模式即程序只使用 MPI 进行并行,每个进程只有一个线程或只开启一个线程。 为此我们只需要告诉 SLURM 我们的任务是纯 MPI 并指定总进程个数即可。

run_mpi.slurm
#!/bin/bash
#SBATCH -J mpi-test              # 任务名为 mpi-test
#SBATCH -p cpu                   # 提交到 cpu 分区
#SBATCH -N 2                     # 申请 2 个节点
#SBATCH --ntasks-per-node=24     # 每个节点开 24 个进程
#SBATCH --cpus-per-task=2        # 每个进程占用 2 个 core

module add mpich/3.2.1-pmi       # 添加 mpich/3.2.1-pmi 模块
export OMP_NUM_THREADS=1         # 设置全局 OpenMP 线程为 1
srun -n 48 ./test                # 运行 ./test 程序

在上面的脚本中,我们注意到运行 MPI 程序需要额外指定线程数和进程数,具体的方式 是配合 --ntasks-per-node--cpus-per-task 参数。即每个节点有多少个 进程,每个进程有多少个线程。在这里需要注意我们将 --cpus-per-task 设置为 2, 但我们运行纯 MPI 程序。这是因为我们的计算节点均开启超线程(Hyper-Threading,简称 HT )技术,一个 CPU core 可以看成是两个逻辑核心。虽然超线程技术可以提供加速,但是把两个 MPI 进程放在 同一个 core 的不同 HT 上的效率会低于将它们放在两个不同 core 上。因此使用 上述脚本设置达到的效果是:

  • 每个节点有 24 个 MPI 进程

  • 每个 MPI 进程占据了每个物理核的两个 HT

  • 每个 MPI 进程只开了一个线程,注意,如果你的程序没有打开 OpenMP 则无需设置 OMP_NUM_THREADS 这个全局变量,否则需要强制将其设置为 1

最后需要注意,我们载入的模块名字是 mpich/3.2.1-pmi,这个模块下,启动 MPI 程序的命令由 MPI 提供的 mpiexec 替换成了 SLURM 的命令 srun,这是 MPI 和 SLURM 整合之后的效果。在 srun 中指定开 MPI 进程数量 是 48 个,根据 SLURM 脚本的设置可以计算出:两个节点,每个节点有 24 个 MPI 进程。 集群也提供使用默认启动器 mpiexec.hydra 的版本,具体用法在稍后给出。用户可 根据个人习惯选择两种启动器中的任何一个。

使用超线程的纯 MPI 程序

有时我们需要充分发挥超线程的性能,这时候我们也可考虑将 MPI 程序绑定到每个逻辑 核上。这使得在相同资源的情况下,我们开的 MPI 进程数量多了一倍。为此只需要 对上述脚本加以改造:

run_mpi.slurm
#!/bin/bash
#SBATCH -J mpi-test              # 任务名为 mpi-test
#SBATCH -p cpu                   # 提交到 cpu 分区
#SBATCH -N 2                     # 申请 2 个节点
#SBATCH --ntasks-per-node=48     # 每个节点开 48 个进程
#SBATCH --cpus-per-task=1        # 每个进程占用 1 个 core

module add mpich/3.2.1-pmi       # 添加 mpich/3.2.1-pmi 模块
export OMP_NUM_THREADS=1         # 设置全局 OpenMP 线程为 1
srun -n 96 ./test                # 运行 ./test 程序

发生改动的只有 --ntasks-per-node--cpus-per-tasksrun -n。用户 可以准确得知所有进程的分配方式。

使用 OpenMP 与 MPI 的混合代码

用户可以通过更改 SLURM 脚本的运行参数来运行混合代码。为此只需要告诉 SLURM 调度 系统我们需要运行多少个 MPI 进程,每个进程开多少线程即可。

run_hybrid.slurm
#!/bin/bash
#SBATCH -J hybrid-test           # 任务名为 hybrid-test
#SBATCH -p cpu                   # 提交到 cpu 分区
#SBATCH -N 2                     # 申请 2 个节点
#SBATCH --ntasks-per-node=12     # 每个节点开 12 个进程
#SBATCH --cpus-per-task=4        # 每个进程占用 4 个 core

module add mpich/3.2.1-pmi       # 添加 mpich/3.2.1-pmi 模块
export OMP_NUM_THREADS=2         # 设置全局 OpenMP 线程为 2
srun -n 24 ./test                # 运行 ./test 程序

需要注意的是 --cpus-per-task 的数量和 OMP_NUM_THREADS 并不对等,原因还是 因为超线程。使用 --cpus-per-task 参数指定的是逻辑 core 数量。实际上每个 进程可能用不了这么多。此时设置 OMP_NUM_THREADS=2,程序的每个进程会优先占据 物理核,当物理核都用满的时候再考虑使用超线程的特性在每个核上多开出一个线程。 因此,上述脚本中也可设置 OMP_NUM_THREADS=4 占据全部 CPU 资源。

使用默认 MPI 启动器

mpich 和 OpenMPI 都支持 srun 运行(其中 mpich 需要载入 pmi 后缀的模块),它 们也支持直接使用原生启动器 mpiexec(此时 mpich 需要载入不带 pmi 后缀的模块)。 两种 MPI 实现的启动器都已经考虑到作业调度系统的整合,当在作业脚本中使用 mpiexec 时,启动器会自动检查作业属性,然后正确启动各个 MPI 进程,无需再显式 指定节点名,CPU 绑定方式等。

run_mpi.slurm
#!/bin/bash
#SBATCH -J mpi-test              # 任务名为 mpi-test
#SBATCH -p cpu                   # 提交到 cpu 分区
#SBATCH -N 2                     # 申请 2 个节点
#SBATCH --ntasks-per-node=4      # 每个节点开 4 个进程
#SBATCH --cpus-per-task=4        # 每个进程占用 4 个 core

module add mpich/3.2.1           # 添加 mpich/3.2.1 模块,注意,不带 -pmi 后缀
#module add openmpi/3.0.0        # OpenMPI 支持两种方式的启动
export OMP_NUM_THREADS=2         # 设置全局 OpenMP 线程为 2
mpiexec ./test                   # 运行程序

注意上述脚本的最后一行,mpiexec 后无需跟任何参数。启动器会根据作业调度系统 的设置自动算出总共开多少进程,要开到哪些节点等,非常方便。

选择网络接口

集群安装的两种 MPI 对网络接口的选择略有不同。mpich 会选择 /etc/hosts 中 第一个和主机名对应的地址的所在网络接口,而 OpenMPI 则默认启用除 lo 以外的全部 网络接口(单节点多线程的任务除外)。由于我们的集群计算节点有两种网络互联,为了 提升速度必须要指定使用哪一个网段。由于 srun 启动不支持网段的选择,所以要选择 网络接口必须使用原生启动器 mpiexec,mpich 用户需要载入不带 pmi 后缀的模块。

我们的网络设备对应关系为:

  • eth0:千兆管理网,数据传输速度约 100MB/s

  • eth2:万兆光纤,数据传输速度约 1000MB/s

使用 mpich 时需要指定 -iface 参数

mpiexec -iface eth2 ./test      # 使用 eth2 这块设备

使用 OpenMPI 时要指定 MCA

mpiexec --mca btl_tcp_if_include eth2 ./test

经测试 OpenMPI 对网络的利用率较高,并且对 Infiniband 支持较好。

交互式 MPI 任务

交互式 MPI 任务可以通过使用 salloc 而后直接在主节点运行 srun 的方式运行。

[liuhy@admin mpi]$ salloc -N 2 --ntasks-per-node=1 -c 4
salloc: Granted job allocation 927
salloc: Waiting for resource configuration
salloc: Nodes comput[1-2] are ready for job
[liuhy@admin mpi]$ srun -n 2 ./test     # 直接在主节点运行 srun
This is process 1 of 2 on host comput2, I have 4 threads.
This is process 0 of 2 on host comput1, I have 4 threads.

注意 srun 可自动识别作业参数并正确启动。缺点是如果是基于 mpich 的程序则无法 选择网络接口。

如果使用 mpiexec 启动,则必须手动指定节点名,每个进程的线程数等。

[liuhy@admin mpi]$ OMP_NUM_THREADS=4 mpiexec -n 2 -host comput1,comput2 ./test
This is process 1 of 2 on host comput2, I have 4 threads.
This is process 0 of 2 on host comput1, I have 4 threads.

此时配置 MPI 启动参数会给用户带来麻烦,但好处是 mpich 程序可以选择网络端口。

我要运行很多串行的相互独立程序,如何在一个任务中并行地运行它们?

作业数组不同,串行的相互独立的程序可在同一个作业脚本中 同时运行。我们需要这个特性是因为集群同时运行作业的数量有上限,在每个程序只占用 一个 core 的条件下,将这些程序放在同一个作业脚本中同时运行似乎是更好的选择。 为此我们需要使用 srun --multi-prog。下面是个小例子:

run.slurm
#!/bin/bash
#SBATCH -J multi
#SBATCH -N 1
#SBATCH -p cpu
#SBATCH --ntasks-per-node=5
#SBATCH -t 1:00:00

# 运行 5 个进程,使用多任务并发模式,使用配置文件 sleep.conf
srun -n 5 --multi-prog sleep.conf

其中 sleep.conf 的内容如下:

sleep.conf
0-4    sleep    10

配置文件的格式为分组书写,一共有 3 列,中间用空格隔开。第一列需要指定子任务 ID, 任务 ID 从 0 开始记,到子任务数减 1,不可以改成别的数字;第二列是需要执行的 程序命令;第三列是程序的参数,若没参数可以不填写,参数可以用 %t 来表示任务 号或者 %o 表示该任务在该组里的位置。每一行可以同时配置多个子任务,最终要保证所有的 子任务都有定义。假设总共要运行 5 个子任务,那么

# 合法
0-4    sleep    10

# 合法,0,2,4 号任务执行 sleep;1,3 号任务执行 hostname
0,2,4  sleep    10
1,3    hostname

# 非法,没定义 0 号任务的内容
1-4    sleep    10


# 非法,总共要运行 5 个子任务但定义了 6 个
0-5    sleep    10

# 非法,0 号任务重复定义
0-4    sleep    10
0      hostname

执行这个任务脚本可以发现总共的 sleep 时间还是 10s,这说明这些程序是并行地执行。

说明

  • 若单机 core 数量不足,可以指定多节点,这样任务可以分配到不同节点,进一步 扩大独立子程序的规模。

  • %t 的含义就是子任务 ID,%o 的含义为该子任务在当前组的位置。例如配置 文件中

0-2    sleep    10
3-4    hostname

则对于 0-2 号子任务,%t%o 的值都是 0,1,2;而对 3 号子任务,%t 的 值是 3,但 %o 的值是 0(由于它是这一组的第一个任务)。