SLURM:令人尴尬的并行程序中的令人尴尬的并行程序
SLURM: Embarrassingly parallel program inside an embarrassingly parallel program
我有一个用 Matlab 编写的复杂模型。该模型不是我们编写的,最好将其视为 "black box",即为了从内部解决相关问题,需要重写整个模型,这需要数年时间。
如果我遇到 "embarrassingly parallel" 问题,我可以使用数组提交相同模拟的 X 个变体,选项为 #SBATCH --array=1-X
。但是,集群通常对最大数组大小有一个(令人沮丧的小)限制。
在使用 PBS/TORQUE 集群时,我通过强制 Matlab 在单个线程上 运行 来解决这个问题,请求多个 CPU,然后在 运行 中设置多个 Matlab 实例的背景。示例提交脚本是:
#!/bin/bash
<OTHER PBS COMMANDS>
#PBS -l nodes=1:ppn=5,walltime=30:00:00
#PBS -t 1-600
<GATHER DYNAMIC ARGUMENTS FOR MATLAB FUNCTION CALLS BASED ON ARRAY NUMBER>
# define Matlab options
options="-nodesktop -noFigureWindows -nosplash -singleCompThread"
for sub_job in {1..5}
do
<GATHER DYNAMIC ARGUMENTS FOR MATLAB FUNCTION CALLS BASED ON LOOP NUMBER (i.e. sub_job)>
matlab ${options} -r "run_model(${arg1}, ${arg2}, ..., ${argN}); exit" &
done
wait
<TIDY UP AND FINISH COMMANDS>
谁能帮我在 SLURM 集群上做同样的事情?
par
函数不会 运行 我的模型在 Matlab 的并行循环中。
- PBS/TORQUE 语言非常直观,但 SLURM 的语言让我感到困惑。假设一个与我的 PBS 示例结构相似的提交脚本,这就是我认为某些命令会产生的结果。
- --ncpus-per-task=5 对我来说似乎是最明显的一个。我是将 s运行 放在循环中 matlab 命令的前面,还是将其保留在 PBS 脚本循环中?
- --ntasks=5 我想会请求 5 个 CPU,但会 运行 串行,除非程序特别请求它们(即 MPI 或 Python-Multithreaded 等)。在这种情况下,我需要将 s运行 放在 Matlab 命令前面吗?
我不是数组作业方面的专家,但我可以帮助您处理内部循环。
我总是会使用 GNU parallel 到 运行 几个并行的串行进程,在一个有多个 CPU 可用的作业中。这是一个简单的 perl
脚本,因此 'install' 并不难,而且它的语法非常简单。它基本上做的是 运行 一些(嵌套的)并行循环。此循环的每次迭代都包含一个(长)过程,就像您的 Matlab 命令一样。与您的解决方案相比,它不会一次提交所有这些进程,但它 运行 仅同时 N
个进程(其中 N
是 CPU 的数量你有空)。一个完成后,立即提交下一个,依此类推,直到整个循环完成。并非所有进程都需要相同的时间,这很好,一旦一个 CPU 被释放,另一个进程就会启动。
然后,您想做的是启动 600 个作业(我在下面用 3 个代替,以显示完整的行为),每个作业有 5 CPUs。为此,您可以执行以下操作(因此我没有包括 matlab
的实际 运行,但可以包括在内):
#!/bin/bash
#SBATCH --job-name example
#SBATCH --out job.slurm.out
#SBATCH --nodes 1
#SBATCH --ntasks 1
#SBATCH --cpus-per-task 5
#SBATCH --mem 512
#SBATCH --time 30:00:00
#SBATCH --array 1-3
cmd="echo matlab array=${SLURM_ARRAY_TASK_ID}"
parallel --max-procs=${SLURM_CPUS_PER_TASK} "$cmd,subjob={1}; sleep 30" ::: {1..5}
使用以下方式提交此职位:
$ sbatch job.slurm
向队列提交了 3 个作业。例如:
$ squeue | grep tdegeus
3395882_1 debug example tdegeus R 0:01 1 c07
3395882_2 debug example tdegeus R 0:01 1 c07
3395882_3 debug example tdegeus R 0:01 1 c07
每个作业获得 5 CPUs。这些被 parallel
命令利用,并行地 运行 你的内部循环。再一次,这个内部循环的范围可能(远)大于 5,parallel
负责在这个作业中的 5 个可用 CPU 之间保持平衡。
让我们检查一下输出:
$ cat job.slurm.out
matlab array=2,subjob=1
matlab array=2,subjob=2
matlab array=2,subjob=3
matlab array=2,subjob=4
matlab array=2,subjob=5
matlab array=1,subjob=1
matlab array=3,subjob=1
matlab array=1,subjob=2
matlab array=1,subjob=3
matlab array=1,subjob=4
matlab array=3,subjob=2
matlab array=3,subjob=3
matlab array=1,subjob=5
matlab array=3,subjob=4
matlab array=3,subjob=5
您现在可以清楚地同时看到 3 x 5 个进程 运行(因为它们的输出是混合的)。
在这种情况下不需要使用 srun
。 SLURM 将创造 3 个工作岗位。在每项工作中,一切都发生在单独的计算节点上(即,就好像您在自己的系统上 运行ning 一样)。
安装 GNU Parallel - 选项 1
将 'install' GNU 并行放入您的主文件夹中,例如 ~/opt
。
创建目录 ~/opt
如果它还不存在
mkdir $HOME/opt
'Install' GNU 并行:
tar jxvf parallel-latest.tar.bz2
cd parallel-XXXXXXXX
./configure --prefix=$HOME/opt
make
make install
将 ~/opt
添加到您的路径:
export PATH=$HOME/opt/bin:$PATH
(要使其永久化,将该行添加到您的 ~/.bashrc
。)
安装 GNU Parallel - 选项 2
使用conda
.
(可选)创建新环境
conda create --name myenv
加载现有环境:
conda activate myenv
安装 GNU 并行:
conda install -c conda-forge parallel
请注意,该命令仅在加载环境时可用。
虽然 Tom 关于使用 GNU Parallel 的建议很好,但我会尝试回答所提出的问题。
如果你想 运行 5 个具有相同参数的 matlab
命令实例(例如,如果它们通过 MPI 通信)那么你会想要 --ncpus-per-task=1
, --ntasks=5
并且您应该在 matlab
行前加上 srun
并摆脱循环。
在您的情况下,由于您对 matlab
的 5 次调用中的每一次都是独立的,因此您想请求 --ncpus-per-task=5
、--ntasks=1
。这将确保您根据需要为每个作业分配 5 CPU 个核心。如果您愿意,可以在 matlab
行前加上 srun
,但这不会有什么区别,您只是 运行 完成一项任务。
当然,只有当你的 5 matlab
运行 中的每一个都花费相同的时间时,这才是有效的,因为如果一个比其他 4 CPU 核心花费更长的时间将闲置,等待第五个完成。
您可以使用 python 和子流程来完成,在我下面描述的内容中,您只需设置节点和任务的数量即可,不需要数组,不需要匹配的大小数组到模拟次数等...它只会执行 python 代码直到完成,更多节点执行速度更快。
此外,由于一切都在 python 中准备(比 bash 更容易),因此更容易决定变量。
它假设 Matlab 脚本将输出保存到文件 - 此函数不返回任何内容(它可以更改..)
在 sbatch 脚本中,您需要添加如下内容:
#!/bin/bash
#SBATCH --output=out_cluster.log
#SBATCH --error=err_cluster.log
#SBATCH --time=8:00:00
#SBATCH --nodes=36
#SBATCH --exclusive
#SBATCH --cpus-per-task=2
export IPYTHONDIR="`pwd`/.ipython"
export IPYTHON_PROFILE=ipyparallel.${SLURM_JOBID}
whereis ipcontroller
sleep 3
echo "===== Beginning ipcontroller execution ======"
ipcontroller --init --ip='*' --nodb --profile=${IPYTHON_PROFILE} --ping=30000 & # --sqlitedb
echo "===== Finish ipcontroller execution ======"
sleep 15
srun ipengine --profile=${IPYTHON_PROFILE} --timeout=300 &
sleep 75
echo "===== Beginning python execution ======"
python run_simulations.py
根据您的系统,在此处阅读更多内容:https://ipyparallel.readthedocs.io/en/latest/process.html
和 run_simulations.py 应该包含这样的内容:
import os
from ipyparallel import Client
import sys
from tqdm import tqdm
import subprocess
from subprocess import PIPE
def run_sim(x):
import os
import subprocess
from subprocess import PIPE
# send job!
params = [str(i) for i in x]
p1 = subprocess.Popen(['matlab','-r',f'"run_model({x[0]},{x[1]})"'], env=dict(**os.environ))
p1.wait()
return
##load ipython parallel
rc = Client(profile=os.getenv('IPYTHON_PROFILE'))
print('Using ipyparallel with %d engines', len(rc))
lview = rc.load_balanced_view()
view = rc[:]
print('Using ipyparallel with %d engines', len(rc))
sys.stdout.flush()
map_function = lview.map_sync
to_send = []
#prepare variables <-- here you should prepare the arguments for matlab
####################
for param_1 in [1,2,3,4]:
for param_2 in [10,20,40]:
to_send.append([param_1, param_2])
ind_raw_features = lview.map_async(run_sim,to_send)
all_results = []
print('Sending jobs');sys.stdout.flush()
for i in tqdm(ind_raw_features,file=sys.stdout):
all_results.append(i)
您还会在标准输出中看到一个进度条,这很好...您还可以轻松地添加一个检查以查看输出文件是否存在并忽略 运行。
我有一个用 Matlab 编写的复杂模型。该模型不是我们编写的,最好将其视为 "black box",即为了从内部解决相关问题,需要重写整个模型,这需要数年时间。
如果我遇到 "embarrassingly parallel" 问题,我可以使用数组提交相同模拟的 X 个变体,选项为 #SBATCH --array=1-X
。但是,集群通常对最大数组大小有一个(令人沮丧的小)限制。
在使用 PBS/TORQUE 集群时,我通过强制 Matlab 在单个线程上 运行 来解决这个问题,请求多个 CPU,然后在 运行 中设置多个 Matlab 实例的背景。示例提交脚本是:
#!/bin/bash
<OTHER PBS COMMANDS>
#PBS -l nodes=1:ppn=5,walltime=30:00:00
#PBS -t 1-600
<GATHER DYNAMIC ARGUMENTS FOR MATLAB FUNCTION CALLS BASED ON ARRAY NUMBER>
# define Matlab options
options="-nodesktop -noFigureWindows -nosplash -singleCompThread"
for sub_job in {1..5}
do
<GATHER DYNAMIC ARGUMENTS FOR MATLAB FUNCTION CALLS BASED ON LOOP NUMBER (i.e. sub_job)>
matlab ${options} -r "run_model(${arg1}, ${arg2}, ..., ${argN}); exit" &
done
wait
<TIDY UP AND FINISH COMMANDS>
谁能帮我在 SLURM 集群上做同样的事情?
par
函数不会 运行 我的模型在 Matlab 的并行循环中。- PBS/TORQUE 语言非常直观,但 SLURM 的语言让我感到困惑。假设一个与我的 PBS 示例结构相似的提交脚本,这就是我认为某些命令会产生的结果。
- --ncpus-per-task=5 对我来说似乎是最明显的一个。我是将 s运行 放在循环中 matlab 命令的前面,还是将其保留在 PBS 脚本循环中?
- --ntasks=5 我想会请求 5 个 CPU,但会 运行 串行,除非程序特别请求它们(即 MPI 或 Python-Multithreaded 等)。在这种情况下,我需要将 s运行 放在 Matlab 命令前面吗?
我不是数组作业方面的专家,但我可以帮助您处理内部循环。
我总是会使用 GNU parallel 到 运行 几个并行的串行进程,在一个有多个 CPU 可用的作业中。这是一个简单的 perl
脚本,因此 'install' 并不难,而且它的语法非常简单。它基本上做的是 运行 一些(嵌套的)并行循环。此循环的每次迭代都包含一个(长)过程,就像您的 Matlab 命令一样。与您的解决方案相比,它不会一次提交所有这些进程,但它 运行 仅同时 N
个进程(其中 N
是 CPU 的数量你有空)。一个完成后,立即提交下一个,依此类推,直到整个循环完成。并非所有进程都需要相同的时间,这很好,一旦一个 CPU 被释放,另一个进程就会启动。
然后,您想做的是启动 600 个作业(我在下面用 3 个代替,以显示完整的行为),每个作业有 5 CPUs。为此,您可以执行以下操作(因此我没有包括 matlab
的实际 运行,但可以包括在内):
#!/bin/bash
#SBATCH --job-name example
#SBATCH --out job.slurm.out
#SBATCH --nodes 1
#SBATCH --ntasks 1
#SBATCH --cpus-per-task 5
#SBATCH --mem 512
#SBATCH --time 30:00:00
#SBATCH --array 1-3
cmd="echo matlab array=${SLURM_ARRAY_TASK_ID}"
parallel --max-procs=${SLURM_CPUS_PER_TASK} "$cmd,subjob={1}; sleep 30" ::: {1..5}
使用以下方式提交此职位:
$ sbatch job.slurm
向队列提交了 3 个作业。例如:
$ squeue | grep tdegeus
3395882_1 debug example tdegeus R 0:01 1 c07
3395882_2 debug example tdegeus R 0:01 1 c07
3395882_3 debug example tdegeus R 0:01 1 c07
每个作业获得 5 CPUs。这些被 parallel
命令利用,并行地 运行 你的内部循环。再一次,这个内部循环的范围可能(远)大于 5,parallel
负责在这个作业中的 5 个可用 CPU 之间保持平衡。
让我们检查一下输出:
$ cat job.slurm.out
matlab array=2,subjob=1
matlab array=2,subjob=2
matlab array=2,subjob=3
matlab array=2,subjob=4
matlab array=2,subjob=5
matlab array=1,subjob=1
matlab array=3,subjob=1
matlab array=1,subjob=2
matlab array=1,subjob=3
matlab array=1,subjob=4
matlab array=3,subjob=2
matlab array=3,subjob=3
matlab array=1,subjob=5
matlab array=3,subjob=4
matlab array=3,subjob=5
您现在可以清楚地同时看到 3 x 5 个进程 运行(因为它们的输出是混合的)。
在这种情况下不需要使用 srun
。 SLURM 将创造 3 个工作岗位。在每项工作中,一切都发生在单独的计算节点上(即,就好像您在自己的系统上 运行ning 一样)。
安装 GNU Parallel - 选项 1
将 'install' GNU 并行放入您的主文件夹中,例如 ~/opt
。
创建目录
~/opt
如果它还不存在mkdir $HOME/opt
'Install' GNU 并行:
tar jxvf parallel-latest.tar.bz2 cd parallel-XXXXXXXX ./configure --prefix=$HOME/opt make make install
将
~/opt
添加到您的路径:export PATH=$HOME/opt/bin:$PATH
(要使其永久化,将该行添加到您的
~/.bashrc
。)
安装 GNU Parallel - 选项 2
使用conda
.
(可选)创建新环境
conda create --name myenv
加载现有环境:
conda activate myenv
安装 GNU 并行:
conda install -c conda-forge parallel
请注意,该命令仅在加载环境时可用。
虽然 Tom 关于使用 GNU Parallel 的建议很好,但我会尝试回答所提出的问题。
如果你想 运行 5 个具有相同参数的 matlab
命令实例(例如,如果它们通过 MPI 通信)那么你会想要 --ncpus-per-task=1
, --ntasks=5
并且您应该在 matlab
行前加上 srun
并摆脱循环。
在您的情况下,由于您对 matlab
的 5 次调用中的每一次都是独立的,因此您想请求 --ncpus-per-task=5
、--ntasks=1
。这将确保您根据需要为每个作业分配 5 CPU 个核心。如果您愿意,可以在 matlab
行前加上 srun
,但这不会有什么区别,您只是 运行 完成一项任务。
当然,只有当你的 5 matlab
运行 中的每一个都花费相同的时间时,这才是有效的,因为如果一个比其他 4 CPU 核心花费更长的时间将闲置,等待第五个完成。
您可以使用 python 和子流程来完成,在我下面描述的内容中,您只需设置节点和任务的数量即可,不需要数组,不需要匹配的大小数组到模拟次数等...它只会执行 python 代码直到完成,更多节点执行速度更快。
此外,由于一切都在 python 中准备(比 bash 更容易),因此更容易决定变量。
它假设 Matlab 脚本将输出保存到文件 - 此函数不返回任何内容(它可以更改..)
在 sbatch 脚本中,您需要添加如下内容:
#!/bin/bash
#SBATCH --output=out_cluster.log
#SBATCH --error=err_cluster.log
#SBATCH --time=8:00:00
#SBATCH --nodes=36
#SBATCH --exclusive
#SBATCH --cpus-per-task=2
export IPYTHONDIR="`pwd`/.ipython"
export IPYTHON_PROFILE=ipyparallel.${SLURM_JOBID}
whereis ipcontroller
sleep 3
echo "===== Beginning ipcontroller execution ======"
ipcontroller --init --ip='*' --nodb --profile=${IPYTHON_PROFILE} --ping=30000 & # --sqlitedb
echo "===== Finish ipcontroller execution ======"
sleep 15
srun ipengine --profile=${IPYTHON_PROFILE} --timeout=300 &
sleep 75
echo "===== Beginning python execution ======"
python run_simulations.py
根据您的系统,在此处阅读更多内容:https://ipyparallel.readthedocs.io/en/latest/process.html
和 run_simulations.py 应该包含这样的内容:
import os
from ipyparallel import Client
import sys
from tqdm import tqdm
import subprocess
from subprocess import PIPE
def run_sim(x):
import os
import subprocess
from subprocess import PIPE
# send job!
params = [str(i) for i in x]
p1 = subprocess.Popen(['matlab','-r',f'"run_model({x[0]},{x[1]})"'], env=dict(**os.environ))
p1.wait()
return
##load ipython parallel
rc = Client(profile=os.getenv('IPYTHON_PROFILE'))
print('Using ipyparallel with %d engines', len(rc))
lview = rc.load_balanced_view()
view = rc[:]
print('Using ipyparallel with %d engines', len(rc))
sys.stdout.flush()
map_function = lview.map_sync
to_send = []
#prepare variables <-- here you should prepare the arguments for matlab
####################
for param_1 in [1,2,3,4]:
for param_2 in [10,20,40]:
to_send.append([param_1, param_2])
ind_raw_features = lview.map_async(run_sim,to_send)
all_results = []
print('Sending jobs');sys.stdout.flush()
for i in tqdm(ind_raw_features,file=sys.stdout):
all_results.append(i)
您还会在标准输出中看到一个进度条,这很好...您还可以轻松地添加一个检查以查看输出文件是否存在并忽略 运行。