【问题标题】:How to source another bash script if my script is being executing by SLURM?如果我的脚本正在由 SLURM 执行,如何获取另一个 bash 脚本?
【发布时间】:2019-08-28 16:03:03
【问题描述】:

我有在集群上运行我的并行程序的脚本。我用通常的命令运行它:

sbatch -p PARTITION -t TIME -N NODES /full/path/to/my/script.sh PARAMETERS-LIST

script.sh 里面,我需要获取另一个 bash 脚本(位于 script.sh 所在的同一目录中)来加载一些例程/变量。对于在本地计算机上执行的常用脚本,我使用以下内容:

SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd )"
source "$SCRIPTDIR/funcs.sh"
print_header "Some text"

它工作得很好。但是,在集群上这不起作用,我收到以下错误(仅举例):

/var/tmp/slurmd/job1043319/slurm_script: line 9: /var/tmp/slurmd/jobID/funcs.sh: No such file or directory
/var/tmp/slurmd/job1043319/slurm_script: line 13: print_header: command not found

似乎 SLURM 创建了自己的要提交的脚本副本,因此我无法获取任何本地脚本/文件。

在这种情况下可以做什么?如果我可以避免在脚本中硬编码绝对路径,那就太好了...

【问题讨论】:

    标签: bash slurm


    【解决方案1】:

    问题是 sbatch shell 脚本的位置,只有这个脚本,在你只是从桌面的命令提示符运行它的情况下与 slurmstepd 运行它的情况不同在一个节点上。发生这种情况是因为 sbatch 将您的脚本物理复制到分配的每个头节点,并使用 Slurm 的快速分层网络拓扑机制从那里运行它。这样做的最终效果是,当 当前目录 传播到脚本执行环境时,脚本的路径 不同(并且在不同节点上可能不同)。让我用你的例子来解释一下。

    发生了什么事?

    当然,您所包含的脚本必须被视为文件系统树中同一位置的同一文件(通常在 NFS 挂载上)。在这个例子中,我假设你的用户名是bob(因为它肯定不是),并且你的主目录/home/bob是从每个节点上的NFS导出挂载的,以及你自己的机器。

    阅读您的代码,我了解到主脚本script.sh 和源文件funcs.sh 位于同一目录中。为简单起见,让我们将它们直接放入您的主目录:

    $ pwd
    /home/bob
    $ ls
    script.sh funcs.sh
    

    让我也修改script.sh 如下:我将添加pwd 行以查看我们的位置,并删除失败的. 内置函数之外的其余部分,因为无论如何这无关紧要。

    #!/bin/bash
    pwd
    SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd )"
    

    本地运行

    哪个目录是当前目录无关紧要,所以让我们通过指定脚本的相对路径来使我们的测试稍微复杂一点,即使它在当前目录中:

    $ ../bob/script.sh PARAMETERS-LIST
    

    在这种情况下,脚本由 bash 评估如下(逐步,使用命令 stdout,变量扩展结果或变量赋值显示在以 => 为前缀的每一行。

    pwd
     => '/home/bob'
    
    # Evaluate: SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd )"
    ${BASH_SOURCE[0]}
     => '../bob/script.sh'
    dirname '../bob/script.sh'
     => '../bob'
    cd '../bob'
     => Success, $? is 0
    pwd
     => '/home/bob'
    SCRIPTDIR='/home/bob'
    
    # Evaluate: source "$SCRIPTDIR/funcs.sh"
    $SCRIPTDIR
     => '/home/bob'
    source '/home/bob/funcs.sh'
     => (Successfully sourced)
    

    在这里,您从 script.sh 所在的同一目录中获取 funcs.sh 的预期行为工作正常。

    Slurm 奔跑

    Slurm 将您的 script.sh 复制到节点上的 spool 目录,然后从那里执行它。如果您将-D 开关指定为sbatch,则当前目录将设置为该值(如果失败,则设置为$TMPDIR 的值;或者设置为/tmp 则依次失败)。如果不指定-D,则使用当前目录。现在,假设/home/bob 安装在节点上,并且您只需提交没有-D 的脚本:

    $ sbatch -N1 ./script.sh PARAMETERS-LIST
    

    Slurm 为你分配一个节点机器,复制你脚本的内容 ./script.sh 到一个本地文件(在你的例子中它恰好被命名为 /var/tmp/slurmd/job1043319/slurm_script),将当前目录设置为/home/bob 并执行脚本文件/var/tmp/slurmd/job1043319/slurm_script。我想你已经明白会发生什么了。

    pwd
     => '/home/bob'
    
    # Evaluate: SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd )"
    ${BASH_SOURCE[0]}
     => '/var/tmp/slurmd/job1043319/slurm_script'
    dirname '/var/tmp/slurmd/job1043319/slurm_script'
     => '/var/tmp/slurmd/job1043319'
    cd '../bob'
     => Success, $? is 0
    pwd
     => '/home/bob'
    SCRIPTDIR='/var/tmp/slurmd/job1043319'
    

    我认为我们应该到此为止。您已经看到,您假设的主脚本不变量及其位于同一目录中的源文件被违反了。您的脚本依赖于这个不变量,因此会中断。

    那么我该如何解决呢?

    这取决于您的要求。您没有说明任何内容,但我可以给出一些建议,这些建议可能在不同程度上与您的目标保持一致。这可能对我的回答有积极的一面,对更广泛的 SO 受众有用。

    选项 1。 与您自己(以及您的脚本的其他用户,如果有的话)签订具有约束力的协议,以始终在特定目录中启动您的脚本。

    在实践中,这是采用的方法 e。 G。通过著名的语音识别工具包 Kaldi¹:任何脚本、任何你运行的命令,都必须从 experiment's root directory (link to example experiment) 运行。

    如果这种方法可行,那么您获取的任何内容都来自当前目录(和/或它下的知名路径); example 1, top-level ./run.sh in the main experiment directory²

    . ./cmd.sh
    . ./path.sh
    

    example 2, from a utility file utils/nnet/subset_data_tr_cv.sh 在一个本身与主实验目录软链接的目录中:

    . utils/parse_options.sh
    

    这些. 语句都不适用于从非常规目录调用的任何脚本:

    $ pwd
    /home/bob/kaldi/egs/fisher_english/s5
    $ utils/nnet/some_utility_script.sh  # This works.
    $ cd utils/nnet
    $ ./some_utility_script.sh           # This fails, by design.
    

    优点:可读的代码。当您有 3,000 个 bash 文件,总计 600,000 行代码时,正如我们在这一点上所做的那样,这很重要。
    优点:该代码与 HPC 集群无关,几乎所有脚本都可以在您的机器上运行,有或没有本地多核并行化,或者使用普通 ssh 将您的计算分布在一个小型集群上,或者使用 Slurm、PBS、Sun GridEngine,等等。
    缺点:用户必须了解该要求。

    要评估这种方法的底线,如果您有大量相互依赖的脚本文件,并且您的工具包很复杂,并且自然具有中等或较高的学习曲线和/或许多其他约定,则利大于弊 -这在 Kaldi 的情况下是正确的,w.r.t 数据准备和布局。将cd 强加到一个目录并从中执行所有操作的要求可能只是您的情况之一,相对不繁琐。

    选项 2. 导出一个变量,命名您的脚本来源的所有文件的根位置。

    您的脚本将如下所示

    #!/bin/bash
    . "${ACME_TOOLKIT_COMMON_SCRIPTS:?}/funcs.sh" || exit
    print_header "Some text"
    

    您必须确保在环境中通过钩子或骗子定义此变量。如果变量未定义或为空,变量扩展中的:? 后缀使脚本以致命错误消息结束,并且对于 (a) 更好的错误消息和 (b) 采购意外代码的相当小的安全风险是首选。

    优点: 代码仍然非常可读。
    缺点: 应该有一个外部机制来设置每次安装的变量,无论是每个用户还是机器范围.
    缺点/Meh:必须允许 Slurm 将您的环境传播到作业步骤。这通常是这样,并且默认情况下处于启用状态,但可能存在将用户的环境传播限制为管理员批准的变量列表的集群设置。

    回到 Kaldi 的例子,如果你的工作量很低,并且你希望能够并行化到 e。 G。 5-10 台机器使用 ssh 而不是 Slurm,您必须在 sshd 和 ssh 客户端配置中将此特定环境变量列入白名单,或者确保在每台机器上将其设置为相同的正确值。

    总的来说,这里的底线(即,没有考虑其他因素)与选项 1 的底线大致相同:还有一件事要解决;可能的基础架构配置问题,但仍然非常适合包含十多个或两个相互依赖的 bash 脚本的大型程序。

    但是,如果您知道无需将代码移植到 Slurm 以外的任何其他工作负载管理器,则此选项会变得更有利可图,如果您的 WLM 是一个或几个特定的,则更有利可图集群,因此您可以依赖它们不变的配置。

    选项 3。 编写一个“启动器”脚本,让 sbatch 启动任何命令。

    启动器会将脚本(或任何程序)的名称作为其第一个参数运行,并将其余参数传递给调用的脚本/命令。该脚本可以是一个相同的脚本来包装您的任何脚本,并且单独存在以使您的源脚本发现逻辑工作。

    launcher 脚本非常简单:

    $ cat ~/launcher
    #!/bin/bash
    prog=${1:?}; shift
    exec "$prog" "$@"
    

    运行以下脚本(自然是从位于 /xa 的 NFS 挂载)

    $ cat '/xa/var/tmp/foo bar/myscript.sh'
    #!/bin/bash
    printf 'Current dir: '; pwd
    printf 'My command line:'; printf ' %q' "$0" "$@"; printf '\n'
    echo "BASH_SOURCE[0]='${BASH_SOURCE[0]}'"
    # The following line is the one that gave fits in your case.
    my_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
    echo "my_dir='$my_dir'"
    

    当前目录为 /tmp 并使用下面的 sbatch 命令(并且测试正确的引用永远不会受到伤害)

    $ pwd
    /tmp
    $ sbatch -o /xa/var/tmp/%x-%A.out -N1 ~/launcher \
        '/xa/var/tmp/foo bar/myscript.sh' "The skies are painted with unnumber'd sparks" 1 2 '' "3 4"
    Submitted batch job 19740
    

    产生这个输出文件:

    $ cat /xa/var/tmp/launcher-19740.out
    Current dir: /tmp
    My command line: /xa/var/tmp/foo\ bar/myscript.sh The\ skies\ are\ painted\ with\ unnumber\'d\ sparks 1 2 '' 3\ 4
    BASH_SOURCE[0]='/xa/var/tmp/foo bar/myscript.sh'
    my_dir='/xa/var/tmp/foo bar'
    

    优点:您可以按原样运行现有脚本。
    优点:您向launcher 发出的命令不必是shell 脚本。
    缺点:这是一个很大的问题。您不能在脚本中使用 #SBATCH 指令。

    最后,您可能会编写一个单独的顶级脚本来简单地调用 sbatch 通过这个带有大量 sbatch 开关的通用启动器调用您的脚本,或者为您的每个计算编写一个自定义启动器脚本脚本,列出所有必需的 #SBATCH 指令。这里赢不了多少。

    底线:如果您提交的所有批处理作业都非常相似,以便您可以将绝大多数 sbatch 选项纳入单个启动器脚本中的 #SBATCH 指令中,那么这是一个可以考虑的选项。请注意,除非您使用 sbatch 的 -J 开关命名所有作业,否则所有作业都将被命名为“启动器”,这意味着您将无法将 所有 sbatch 开关分解到单个文件中, 或者应付这个乍看之下相当枯燥的命名方案³并以其他方式标识你的工作。

    所以,最后,挑选你觉得最好吃的毒药,然后继续下去。没有完美的解决方案,但应该有一种可接受的方式来实现您想要的。


    ¹其中我恰好是活跃用户和贡献者。
    ² . ./cmd.sh || exit 形式的测试会更加健壮,应该始终使用,但与核心脚本相比,我们的顶级实验脚本通常相当松散。
    ³ 但正如美国近 10,000,001 名史密斯、约翰逊、威廉姆斯、琼斯、布朗或莫里斯 "Moe" Jette 中的任何一个人都可以证实的那样,这不一定是什么大问题。

    【讨论】:

    • 非常有帮助的答案,谢谢!仅供参考,我发现了类似的问题(在这里)[stackoverflow.com/questions/56962129/….但是,我不能使用这个灵魂,因为我们集群的管理员已经禁止使用scontrol 实用程序。关于你的建议。我最终得到了 Option 3 的一些变体 - 我有打算手动运行的主脚本。在该脚本中,我使用我的发行版根(所有补充文件都驻留)预定义了变量,并且我通过内部的sbatch 命令直接将此路径传递给脚本
    • 我想过scontrol输出解析,但决定不提。问题是它无法解析。想想我们的例子。 scontrol show job $N 以已知方式包装其输出,因此 Command=... 自己位于一行上。但在我们的测试用例中,scontrol 打印以下行:Command=/xa/var/tmp/foo bar/myscript.sh The skies are painted with unnumber'd sparks 1 2 3 4。你能从 this 猜出你的启动脚本文件名吗?我不敢。而-o oneliner 格式更糟糕。如果一个命令包含 `KeyWord=5`,这会破坏当场解析的尝试。
    【解决方案2】:

    您可以通过更改 script.sh 的工作目录来做到这一点:

    sbatch -p PARTITION -t TIME -N NODES -D /full/path/to/my/ /full/path/to/my/script.sh PARAMETERS-LIST
    

    然后在你的脚本中你可以简单地做source "funcs.sh"

    【讨论】:

    • 那无济于事;他们已经知道他们在哪个目录,但它不包含他们想要复制到环境中的文件。
    • 你试过了吗? SLURM 不会将任何文件复制到环境中,除了提交脚本,因此您必须使用 -D 或指定完整路径。
    • 这不能按预期工作。我需要我的脚本所在的目录,而不是任何其他目录。我不需要 chdir,因为 slurm 输出会写在这里,这是不需要的行为
    猜你喜欢
    • 2018-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-26
    • 1970-01-01
    • 2021-08-28
    相关资源
    最近更新 更多