超算也能用来建站吗?


概述

传统超算由多个计算节点组成,每个节点有自己的操作系统,可独立或与其他节点协同工作。设计难点在于传输带宽和延迟,共享内存,并行存储,散热,软件优化等方面。

文中使用的超算,其计算节点看起来是个 Linux 服务器,可以 SSH 登录,节点间使用 InfiniBand 协议通信,共享 home 目录。超算常用于并行计算,实际使用中一般不会亲自 SSH 上去部署服务,而是通过任务调度工具,比如 Slurm (Simple Linux Utility for Resource Management),开源的超算资源管理软件,在 TOP500 超算中占有率超过 50%。

超算上跑的程序一般都会针对任务调度系统定制优化,比如将大规模优化问题通过数学方法分解成数个子问题,或者 FFT 之类的适合分解的算法,配合调度工具并行计算出结果再迭代解出原问题。

食材

Slurm

Slurm 调度工具常见于 Linux 超算,简单说下部署任务时常用的方法。

  • salloc: 先抢占资源,后部署任务。执行 salloc 命令获取节点资源(如果是超算中心需要排队等待,排到后就开始计费),此时可以手动 ssh 登录计算节点。当用户退出节点时任务自动终止,适合软件测试。
  • srun: 交互式提交任务。命令提交后会在终端实时返回程序输出内容,程序退出自动释放资源,适合交互式单节点任务。
  • sbatch: 批量提交任务。将节点类型,任务数量,资源需求等参数和执行程序的命令写在 Bash 脚本中提交,在所有任务执行完成后释放资源(执行失败也会释放),适合并行或多步任务。

Modules

使用 pacman / apt 安装软件时会也会自动安装依赖,而超算出于安全和稳定的考虑,禁止用户获取 root 权限,用户不能使用 apt 之类的包管理工具,取而代之的是 environment modules。

Modules 是一个 Shell 初始化工具,可在 Shell 中动态加载依赖的软件/库(由管理员预装)。

查看可用的 gcc 软件版本。

module avail >> module.log 2>&1cat module.log | grep gccgcc/4.4.7-kdgcc/4.9.2-fengglgcc/7.3.0-wzmgcc/8.1.0-wjlgcc/8.3.0-wzmgcc/9.1.0-fenggl...

复制软件全称,载入到当前环境。

module load gcc/8.1.0-wjl

之后就能用 GCC 8.1 编译程序了。但如果是 Python Web 程序,依赖太多,找管理员一个个添加也不太合适。不如换个思路,不知道大家有没有想到容器呢?

Singularity

Singularity 是专为超算打造的容器,支持 InfiniBand 和 GPU 等 PCIe 设备,对性能的损耗可以忽略不计。我们可以在本地打包 Singularity 镜像,再到超算上用 environment modules 加载 Singularity 来运行镜像。

回到正题,那么先尝试部署个 screenfetch 获取节点的硬件信息。

Singularity 兼容 Docker 镜像,也有自己的镜像格式。创建 singularity 定义文件screenfetch.def,写入如下内容。

Bootstrap: dockerFrom: ubuntu:18.04%help    App: screenfetch    Usage: singulaity exec <image name> <command>%files    /etc/apt/sources.list    /etc/ssl/certs /etc/ssl/certs%post    apt-get update && apt-get install -y screenfetch    rm -rf /var/lib/apt/lists/*%environment    export LC_ALL=C%runscript    exec screenfetch

Bootstrap 是基础镜像源,From 是基础镜像,%help 是帮助信息,%files 是需要打包的文件。

这个栗子中,我们从 Docker 源获取 Ubuntu 18.04 基础镜像,将本机 apt 软件源配置和 ssl 证书拷贝到容器内的相同位置,然后安装 screenfetch 并清理缓存节省空间,最后设置环境变量和启动容器时执行的命令。

打包镜像到当前目录。

singularity build run.sif screenfetch.def

run.sif 就是镜像本体了。容器有多种启动方式,比如,使用 run 指令会运行 runscript 中的语句,而 exec 指令可以自定义容器运行的程序,比如。

singularity run run.sifsingularity exec run.sif screenfetch

两条命令返回相同的结果。如果你对 Dockerfile 比较熟悉,也可以打包 Docker 镜像,上传到 Docker Hub,然后使用 Singularity 运行 Docker 镜像,这样的镜像体积小很多,比如。

singularity exec docker://ubuntu:latest lsb_release -a

料理

有了容器,就算把博客挂到超算上也没问题呦!

多节点部署(相同参数)

下面尝试部署分布式程序,包含一个主程序和多个子程序,通信协议是 RESTful API。子程序分别部署在多个节点上,相互没有直接通信,由主程序统一调度。

新建 sbatch 脚本job-worker.sh

#!/bin/sh#SBATCH --job-name worker#SBATCH --error=job-%j.err#SBATCH --partition=amd#SBATCH --nodes=3#SBATCH --ntasks=3#SBATCH --cpus-per-task=64#SBATCH --no-kill=onmodule load singularity/3.6singularity exec bin.sif <command>

sbatch 配置中的 nodes 和 ntasks 保持一致,cpus-per-task 是单个节点的核心数,以保证每个节点运行一个程序(只设定 ntasks 也可以),no-kill 参数可以让单一节点的故障不影响其他节点。

执行sbatch job-worker.sh即可部署集群,但似乎没办法修改任务的运行参数。这里 filename pattern 的说明,通配符 %A 和 %a 代指作业编号和索引编号,有兴趣可以尝试下能否用在命令参数中。

多节点部署(不同参数)

为解决上述问题,将 nodes 和 ntasks 改为 1,这样 sbatch 脚本只会部署单个节点,然后循环执行 sbatch,在每次循环中修改 sbatch 脚本参数,以此实现动态传参的效果。

  • sbatch 脚本
#!/bin/sh#SBATCH --job-name worker#SBATCH --error=job-%j.err#SBATCH --partition=amd#SBATCH --ntasks=1module load singularity/3.6ip=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d '/')singularity exec bin.sif <command> -i $ip -p $1
  • 外部脚本
#!/bin/bashset -o pipefailfor i in {23300..23333};do    sbatch job-worker.sh $i    echo "Task $i works."done

在这个栗子中我们部署了 33 个程序,监听的端口各不相同(23300 ~ 23333)。

单节点部署

如果想要多个程序放在一个节点运行,也就是单节点一次提交多个不同的程序。但 Slurm 好像没有这种方法。不过 sbatch 支持 bash 脚本,那直接用 bash 也是可以的吧。

#!/bin/sh#SBATCH --job-name worker#SBATCH --error=job-%j.err#SBATCH --partition=amd#SBATCH --nodes=1#SBATCH --ntasks=1#SBATCH --cpus-per-task=64module load singularity/3.6nohup singularity exec bin.sif <bin1> <command1> >> module1.log 2>&1 &sleep 30snohup singularity exec bin.sif <bin2> <command2> >> module2.log 2>&1 &wait

这样就在单个节点运行了两个任务。当 sbatch 脚本执行完成后,Slurm 会认为任务结束并释放资源,使用 wait 可以让 Slurm 等待所有后台任务执行完成。

结论

基于上述方法(主要是容器),就是超算也能为所欲为。至于💰代价嘛...