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

Dro*_*tor 3 bash slurm

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

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"
Run Code Online (Sandbox Code Playgroud)

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

/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
Run Code Online (Sandbox Code Playgroud)

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

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

kkm*_*kkm 5

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

到底是怎么回事?

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

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

$ pwd
/home/bob
$ ls
script.sh funcs.sh
Run Code Online (Sandbox Code Playgroud)

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

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

本地运行

无论当前目录是什么都无关紧要,因此让我们通过指定脚本的相对路径来使我们的测试复杂化,即使它在当前目录中:

$ ../bob/script.sh PARAMETERS-LIST
Run Code Online (Sandbox Code Playgroud)

在这种情况下,脚本按如下方式由 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)
Run Code Online (Sandbox Code Playgroud)

在这里,您funcs.shscript.sh生活工作正常的同一目录采购的预期行为。

Slurm 运行

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

$ sbatch -N1 ./script.sh PARAMETERS-LIST
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

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

那么我该如何解决这个问题呢?

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

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

在实践中,这就是众所周知的语音识别工具包 Kaldi 所采用的方法:您运行的任何脚本、任何命令,都必须从实验的根目录(链接到示例实验)运行

如果这种方法可行,那么您从当前目录(和/或它下面的一个众所周知的路径)中获取的任何内容;例一、./run.sh主实验目录顶层²

. ./cmd.sh
. ./path.sh
Run Code Online (Sandbox Code Playgroud)

示例 2,来自utils/nnet/subset_data_tr_cv.sh目录中的实用程序文件,该目录本身是从主实验目录软链接的:

. utils/parse_options.sh
Run Code Online (Sandbox Code Playgroud)

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

$ 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.
Run Code Online (Sandbox Code Playgroud)

优点:可读的代码。当您有 3,000 个 bash 文件,总共 600,000 行代码时,就像我们目前的情况一样,这很重要。
优点:代码与 HPC 集群无关,几乎所有脚本都可以在您的机器上运行,无论是否有本地多核并行化,或者使用普通 ssh 或使用 Slurm、PBS、Sun GridEngine 将您的计算扩展到迷你集群,你的名字。
缺点:用户必须了解要求。

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

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

你的脚本看起来像

#!/bin/bash
. "${ACME_TOOLKIT_COMMON_SCRIPTS:?}/funcs.sh" || exit
print_header "Some text"
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

launcher脚本是完全微不足道的:

$ cat ~/launcher
#!/bin/bash
prog=${1:?}; shift
exec "$prog" "$@"
Run Code Online (Sandbox Code Playgroud)

运行以下脚本(从 NFS 挂载/xa,自然地)

$ 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'"
Run Code Online (Sandbox Code Playgroud)

当前目录是 /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
Run Code Online (Sandbox Code Playgroud)

产生这个输出文件:

$ 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'
Run Code Online (Sandbox Code Playgroud)

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

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

底线:如果您提交的所有批处理作业都非常相似,因此您可以将绝对大多数 sbatch 选项分解#SBATCH为单个启动程序脚本中的指令,那么这是一个可以考虑的选项。请注意,除非您使用 sbatch 的-J开关来命名它们,否则所有作业都将被命名为“启动器” ,这意味着您要么无法将所有sbatch 开关分解为一个文件,要么一开始就处理这个相当乏味的问题视觉,命名方案³并以其他方式识别您的工作。

所以,最后,选择你认为最美味的毒药,然后一起去。没有完美的解决方案,但应该有一种可接受的方式来实现您想要的。


¹ 其中我碰巧既是活跃用户又是贡献者。
² 表单的测试. ./cmd.sh || exit会更健壮,应该始终使用,但与核心脚本相比,我们的顶级实验脚本通常非常松散。
³ 但正如美国近 10,000,001 人中名为 Smith、Johnson、Williams、Jones、Brown 或 Morris “Moe” Jette 的任何人所证实的那样,这不一定是什么大问题。