使用SLURM sbatch作业数组并行运行具有不同输入参数的同一脚本

15

我有一个问题,需要使用不同的输入参数来启动相同的脚本。

假设我有一个脚本 myscript.py -p <par_Val> -i <num_trial>,我需要考虑N个不同的par_values(在x0x1之间),每个par_value需要进行M次试验。

M次试验中的每一次都几乎达到了我所在集群的时间限制(我没有更改此限制的特权)。因此,实际上我需要运行NxM个独立的作业。

由于每个批处理作业具有相同的节点/ CPU配置,并调用相同的Python脚本,除了更改输入参数外,在原则上,我应该有一个sbatch脚本,应该执行类似以下的操作:

#!/bin/bash
#SBATCH --job-name=cv_01
#SBATCH --output=cv_analysis_eis-%j.out
#SBATCH --error=cv_analysis_eis-%j.err
#SBATCH --partition=gpu2
#SBATCH --nodes=1
#SBATCH --cpus-per-task=4

for p1 in 0.05 0.075 0.1 0.25 0.5
do
    for i in {0..150..5}
    do
        python myscript.py -p p1 -v i
    done
done
每次调用脚本本身都是一个批处理作业。查看sbatch文件说明-a --array选项似乎很有前途。但在我的情况下,我需要为我拥有的每个NxM脚本更改输入参数。我该怎么做呢?我不希望像这篇文章建议的那样编写NxM批处理脚本,然后将它们列在一个txt文件中。此外,这里提出的解决方案也不理想,因为这是作业数组的情况。此外,我想确保所有的NxM脚本同时启动,并且上述脚本在终止之后立即终止,以便它不会与时间限制相冲突,导致我的整个作业被系统终止并保持不完整(而如果它们在独立的并行状态下运行,则每个NxM作业都在此限制内,这种情况将不会发生)。
3个回答

13

最佳做法是使用作业数组。

一种选择是在提交作业脚本时传递参数p1,这样您只需要一个脚本,但需要多次提交,每次提交一个不同的p1值。

代码将如下所示(未经测试):

#!/bin/bash
#SBATCH --job-name=cv_01
#SBATCH --output=cv_analysis_eis-%j-%a.out
#SBATCH --error=cv_analysis_eis-%j-%a.err
#SBATCH --partition=gpu2
#SBATCH --nodes=1
#SBATCH --cpus-per-task=4
#SBATCH -a 0-150:5

python myscript.py -p $1 -v $SLURM_ARRAY_TASK_ID

而且您需要提交它,并附上:

sbatch my_jobscript.sh 0.05
sbatch my_jobscript.sh 0.075
...

另一种方法是将所有的p1参数定义在bash数组中,并提交NxM个作业(未经测试)

#!/bin/bash
#SBATCH --job-name=cv_01
#SBATCH --output=cv_analysis_eis-%j-%a.out
#SBATCH --error=cv_analysis_eis-%j-%a.err
#SBATCH --partition=gpu2
#SBATCH --nodes=1
#SBATCH --cpus-per-task=4
#Make the array NxM
#SBATCH -a 0-150

PARRAY=(0.05 0.075 0.1 0.25 0.5)    

#p1 is the element of the array found with ARRAY_ID mod P_ARRAY_LENGTH
p1=${PARRAY[`expr $SLURM_ARRAY_TASK_ID % ${#PARRAY[@]}`]}
#v is the integer division of the ARRAY_ID by the lenght of 
v=`expr $SLURM_ARRAY_TASK_ID / ${#PARRAY[@]}`
python myscript.py -p $p1 -v $v

谢谢,这正是我在寻找的。然而,在我批准之前,请在您提供的第二个示例中澄清expr中的$1参数,当您分配p1时,我对此并不满意。因为据我所知,$<num>是指输入参数,这对我来说没有意义... - maurizio
好的,你的答案是正确的,只有一个小错误在p1的赋值上,应该改为p1=${PARRAY['expr $SLURM_ARRAY_TASK_ID % ${#PARRAY[@]}']} - maurizio
为什么不使用srun调用你的Python脚本? - PlasmaBinturong

1
如果您使用SLURM作业数组,可以将两个for循环的索引线性化,然后比较循环索引和数组任务ID:
#!/bin/bash
#SBATCH --job-name=cv_01
#SBATCH --output=cv_analysis_eis-%j.out
#SBATCH --error=cv_analysis_eis-%j.err
#SBATCH --partition=gpu2
#SBATCH --nodes=1
#SBATCH --cpus-per-task=4
#SBATCH -a 0-154

# NxM = 5 * 31 = 154

p1_arr=(0.05 0.075 0.1 0.25 0.5)

# SLURM_ARRAY_TASK_ID=154 # comment in for testing

for ip1 in {0..4} # 5 steps
do
    for i in {0..150..5} # 31 steps
    do
        let task_id=$i/5+31*$ip1

        # printf $task_id"\n" # comment in for testing

        if [ "$task_id" -eq "$SLURM_ARRAY_TASK_ID" ]
        then
          p1=${p1_arr[ip1]}
          # printf "python myscript.py -p $p1 -v $i\n" # comment in for testing
          python myscript.py -p $p1 -v $i\n
        fi
    done
done

这个答案与Carles的答案非常相似。因此,我更愿意将它写成评论,但是我的声望不够。

0
根据此页面,作业数组会产生显著的开销:

如果您的程序运行时间很短,比如十分钟或更短,创建作业数组将会产生大量开销,您应该考虑打包您的作业。

该页面提供了一些示例来运行您的作业类型,使用数组和“打包作业”。
如果您不想/不需要为您的作业指定资源,这里有另一种方法:我不确定这是否是Slurm预期的用例,但它似乎可以工作,并且提交脚本看起来更好一些,因为我们不必将索引线性化以适应作业数组范例。而且它可以很好地处理任意深度的嵌套循环。
直接将其作为shell脚本运行:
#!/bin/bash
FLAGS="--ntasks=1 --cpus-per-task=1"
for i in 1 2 3 4 5; do
        for j in 1 2 3 4 5; do
            for k in 1 2 3 4 5; do
                sbatch $FLAGS testscript.py $i $j $k
        done
    done
done

在编程中,你需要确保testscript.py文件的第一行使用#!指定正确的解释器,例如:

#!/usr/bin/env python 
import time
import sys
time.sleep(5)
print "This is my script"
print sys.argv[1], sys.argv[2], sys.argv[3] 

或者(未经测试),您可以像这样使用--wrap标志

sbatch $FLAGS --wrap="python testscript.py $i $j $k"

而且你在 testscript.py 中不需要 #!/usr/bin/env python 这一行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接