如果 SLURM 正在执行我的脚本,如何获取另一个 bash 脚本?

How to source another bash script if my script is being executing by SLURM?

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

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

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

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 创建了自己的要提交的脚本副本,因此我无法获取任何本地 scripts/files。

遇到这种情况怎么办?如果我可以避免在我的脚本中硬编码绝对路径,那就太好了...

您可以通过以下方式更改 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"

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

这是怎么回事?

当然,您所包含的脚本必须被视为位于文件系统树中相同位置的相同文件(通常在 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,变量扩展结果或变量赋值显示在每一行以 [=32 为前缀=].

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 的预期行为很好。

The Slurm 运行

Slurm 将您的 script.sh 复制到节点上的假脱机目录,然后从那里执行它。如果将 -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).

如果这种方法可行,那么您获取的任何内容,都来自当前目录(and/or 其下的众所周知的路径); 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

None 这些 . 语句可以在从非常规目录调用的任何脚本中运行:

$ 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,随你便。
缺点:用户必须了解该要求。

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

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

你的脚本看起来像

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

你必须确保这个变量是在环境中定义的,不管是骗子还是骗子。如果变量未定义或为空,变量扩展中的 :? 后缀会使脚本以致命错误消息结束,并且首选用于 (a) 更好的错误消息和 (b) 非常小的采购意外的安全风险代码。

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

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

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

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

选项 3. 写一个 "launcher" 脚本给 sbatch 启动任何命令。

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

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' "[=21=]" "$@"; 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 指令中,这是一个可以考虑的选项。请注意,所有作业都将被命名为 "launcher" 除非您使用 sbatch 的 -J 开关命名它们,这意味着您要么无法分解出 all sbatch 切换到单个文件,或者处理这个相当沉闷的,乍一看,命名方案³并以其他方式标识您的工作。

所以,最后,挑你觉得最好吃的毒药,服下。没有完美的解决方案,但应该有一个可以接受的方法来实现你想要的。


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