引言
最近又在继续 many-body localization 的计算,并且升级了以前写的新型实空间重整化群的程序。于是打算借此机会,基于广州天河超算和该数值项目,把在超级计算机上进行大规模科学计算相关的工具链和我认为较佳实践的工作流进行一下梳理备忘。刚入坑的读者也可结合本文的关键词,google 多了解一下,以便快速上手。
工具链
Linux
作为超算的操作系统,linux 这坑深不可测。从最简单的 linux 文件操作相关的命令,到进阶的对 linux 系统一切皆是文本的理解和 grep
, sed
, awk
等命令与管道结合的灵活运用,再扩展各种 shell 及相应脚本的编写,最后到整个 GNU 软件和工具的生态,对 linux 了解的越多,无疑使用起来就越高效。当然,如果只是想简单的完成计算,掌握了基本的 cd
, ls
就可以开始了。哦,在这之前,你得稍微了解一下 ssh 的工作原理和配置,因为首先你得能远程登录到超算上去。(至少不要用着 mac,然后问出 mac 上有什么类似 PuTTY 的软件这种问题,真的会很尴尬的)。
linux 最靠谱的教程,就是那些 manpage 了,当然有些 manpage 写的也挺烂的,这时你可能得配合一下 google,问题也不大。想要熟悉 linux 是一个系统工程,因为稍微深入一点,就要求你了解计算机体系结构,软硬件接口,软件工程,操作系统,编译原理,网络协议,数据库,文件格式,字符编码,正则表达式等大量知识;同时,了解这些内容都对更好的进行高性能科学计算有着帮助。所以重点还是保有一颗好奇心,不排斥学习新知识,热爱折腾本身。反之,在超算上算了几年东西,还是只会 cd
的人也大有人在。所以,我推荐读者在超算上安装 z,从此告别 cd
,这么好的工具,谁用谁知道。
简单总结一下,作为最基本的要求,需要掌握 linux 文件操作相关的基本指令,ssh 的原理和配置,scp 及 sftp 的使用,环境变量的理解和配置等。更进一步的,你有极大概率还需要学会不同语言程序和库的手动编译和安装,简单 bash 脚本编写和文件批处理,screen 或 tmux 等 session 管理工具的使用等。注意到,反正在超算上你也拿不到 sudo 权限,好消息是,所有和管理员相关的 linux 知识,都不需要着急学。坏消息是,有些 sudo 一下很简单的操作,想在普通用户身份下完成,参考资料又少,操作起来又复杂。另外大量和网络相关的 linux 配置与使用,在科学计算这件事上也没什么用武之地。(广州天河默认是没有外网连接的,不过还是提供了 http 代理方案,细节可参考1)。
vim
我强烈推荐掌握 vim 的基本使用(广州天河上好像没有 emacs,噗),这样就不需要随便一个小文件阅读和修改都得用 sftp 拖回本地来处理,从而反复切换窗口,很是影响效率。一切操作都在 terminal 里就解决是最行云流水的。如果对 linux,bash script, vim 等足够熟悉,远程 ssh 过去, terminal 里操作起来的速度与快感(当然超算本身也有可能直接卡的你怀疑人生),是慢吞吞的利用图形界面的 sftp 软件,或是使用一些远程可视化方案所无法比拟的。
slurm
slurm 是大部分超算默认的任务分配和管理系统,用来分配节点和提交并行计算任务。因此关于 slurm 的使用成为了并行计算中必不可少的一环。这方面的具体指令,可以参考2和3。需要注意的是,天河二号把 slurm 的几乎全部指令都做了 alias,也即将指令开始的 s 用 yh (用户意) 代替。如 yhq
= squeue
,yhrun
= srun
等,当然原来的 s 开头的指令依旧能用。由于我们不可能一直连接着 session 等待交互式计算的结果,因此毫无例外的任务提交都是以 sbatch
加 .sh
脚本的形式进行任务后台提交的。几种不同并行模型的程序和串行程序分配的脚本可以参考4。
MPI and OpenMP
MPI 和 OpenMP 都是一整套支持并行编程的 API 接口,都可以支持 Fortran,C,C++ 等多种语言。其区别是前者是分布式多进程并行模型,计算不共享内存,而依靠数据通讯来联系,后者是共享内存的多线程编程模型,只能在单个主机上运算,无法进行分布式计算。两者各有其应用范围,当然也可以程序中同时使用两者,这被称为 hybrid parallel programming。前者的难点在于如何优化通讯的次数和减少同步的等待,后者的难点在于如何防止资源竞争,更好的实现加锁和原子操作等。考虑到线性代数的计算库 BLAS 的大部分实现已经利用了多线程(参考该答案,需要注意的是有些 BLAS 的实现甚至会和调用 BLAS 程序的多线程冲突),以及最大程度利用超算的优势,大部分的大规模计算还是和 MPI 打交道。关于 MPI 的 API 的使用,有个非常好的快速上手教程系列5。其实 MPI 的 API 调用并不复杂,难的是并行编程实践本身,如何将计算任务并行化,又如何减少通讯数量,提高并行效率,减少同步的等待等等,这些对于具体实践都可能非常复杂。
工作流
同为大规模科学计算工具的编程语言相关的内容,将在本部分叙述,因为其和工作流的联系更加紧密。
Fortran or C
首先科学计算的核心是快,能满足这一点的只有 Fortran,C 和 C++ 了。因此无论如何程序的主体都得是这几种语言的天下。此外熟悉 LAPACK 和 BLAS 等的接口,各种实现的马甲(比如 intel 的 mkl)和调用也是科学计算所必需的。当然也可以选择 LAPACK 外边再套一层的 wrapper,使得开发友好一点,比如 armadillo。此外,针对不同超算的具体设置和库的路径以及环境变量,快速掌握如何正确配置链接动态库,如何正确的编译出高效的 Fortran 或 C 程序,写出 Makefile 实现自动化编译,也是必备技能。
程序的设计原则上,建议采取读取输入文件,写入输出文件的交互方式。因为科学计算需要指定的参数可能很多,输出数据就更多,都以文件 IO 的方式交互,再结合 sbatch 的脚本运行,效率和可维护性都会高些。需要注意如果同一个 binary 程序同时进行多个不同输入的并行计算任务的话,较佳实践是把 binary 和 input 文件都复制多份在不同的文件夹,之后分别提交任务,防止互相之间出现干扰和奇怪的问题。这一创建新任务文件夹和复制程序以及脚本的过程,可以通过一个简单的 bash 脚本自动化。
这一部分主体程序通常比较耗时,其实践中的要点是把能输出的,可能有用的东西都输出出来,存在文件里。以蒙特卡洛计算为例,每次构型更新相当耗时,因此要将能够想到的观察量都测量一遍并且全部输出出来。这一步千万不要嫌输出的东西太多麻烦,只要输出文件的标题包含参数等可分类信息,对于海量输出数据的处理就是工作流下一步的事情了。但反之要是没有充分的数据输出,只输出了当时需要的少量数据,如果之后研究的想法有了变化和补充,其他数据就只能重新计算,浪费大量的时间。因此工作流第一步的设计原则,一定是输出一切你能想到可能有用的内容到文件里。
大量数据输出保存的一些建议包括,注意文件的命名规范,方便后续对大量数据文件的自动化处理,如果每次都随便生成个 uuid 当文件名,那后续就没办法整理了。其次是要注意输出数据的频度和未输出数据占用内存大小的平衡。如果输出数据过于频繁,硬盘 IO 阻塞也会浪费掉可观的计算时间;反之如果输出数据的频率过低,可能待保存的数据消耗掉大量计算必需的内存资源。另外,还需要注意命名的冲突,同一次计算的不同进程之间,不同次计算之间,生成的输出文件命名是否会冲突,如果冲突是否使用 append 的写入方式,这些逻辑和自己想要达到的效果是否相符,这些问题都要仔细考虑。最后,针对天河二号的架构,需要指出这种文件输出非常方便,只需要每个进程单独输出即可,最后输出文件都会自动呈现在自己主节点的硬盘内,而不需要使用计算节点取回或者计算时将数据用 mpi 传回主节点等方案。
Python or Bash
工作流的第一步通过主体程序计算产生了大量的数据文件。但对于一个具体的想法的验证或者文章里具体的一张图的制作,我们只需要不多的一些处理过的数据,这一部分就在第二步进行。由于大量数据拖回本地并不现实,所以第二步的数据处理和简单分析提取也是在超算上通过脚本的形式完成。考虑到这一部分的耗时不会非常瓶颈,我们可以选择开发起来更灵活友好,对于文件 IO 支持也更全面的脚本语言,我个人比较推荐 python。需要注意天河是支持 python3 的,并且有很多版本,不过需要通过 module load
的方式加载。这一步骤主要有两个任务,第一个是对不同进程不同任务计算产生的大量数据文件,根据所处子文件夹,自身标题甚至内部数据内容的不同,进行分类,合并,汇总和删除等文件操作。一个典型的例子是,并行计算时为了防止不同进程读写同一文件可能出现的错误,可以直接令所有进程生成独立的数据文件,最后再利用脚本,将对应同一参数的数据文件合并。第二个任务是,从预处理过的大量数据文件中,提取出本次想用的相关数据,并进行简单的处理(比如求平均值,标准差等),最后把直接所需的极少数数据写入新的文件。而这一文件的大小,无疑可以下载回本地进行最后的分析和可视化。
这一工作流中需要注意的是,虽然处理数据的时间比第一步计算要短很多,但也可能耗费数分钟甚至更长(取决于数据的量级和脚本处理部分的合理程度)。这一过程,可以直接把脚本运行挂在 screen 或 tmux 里,不需要一直保持 ssh 连接,否则由于网络原因连接一断,运行的脚本就挂了。
Mathematica
最后一步,就是把脚本生成的少量有用数据拖回本地,利用界面更加友好,交互性更强的工具,比如 mathematica,matlab 或是 jupyter notebook 等,做最后的分析验证和可视化。这方面的工作我还是更喜欢 mathematica。无论是制图,拟合,验证,统计分析,都非常好用。特别值得一提的就是 manipulate
这种有交互的可视化方案,可以快速得到很多结果。当有其他想法需要验证和补充时,再回到工作流第二步,修改分析脚本,提取出所需的少量数据下载回本地即可。
Summary
以下是大规模科学计算的较佳实践的整个工作流的一个总结,从开始就遵循这样的工作流和良好习惯,可以少走些弯路。
- 上传
code
代码文件夹到超算,正确配置环境,链接不同的库,写好 makefile,编译 Fortran 或 C 代码,生成支持 mpi 的二进制程序exec
。 - 建立不同的文件夹,例如
task[no.]
,每个文件夹包括程序的输入文件input.txt
,程序本身exec
和 sbatch 任务提交脚本task_run.sh
,脚本内的主体内容类似srun -N [no. of cores] -n [no. of procs] ./exec -i input.txt
。这一创立任务文件夹的过程可通过脚本自动化实现 (task_create.sh
)。 - 通过
sbatch -N [no. of cores] run.sh
提交不同文件夹下的计算任务到计算节点计算,并通过 slurm 系统进行监视和管理。 - 建议将输出的大量数据存放在同一文件夹内 (
output
),当然该文件夹可进一步分成子文件夹存放,实现数据与程序分开。 - 使用编写好的 bash 或 python 脚本 (
merge.py
),对数据文件夹内的数据文件进行合并,删除,整理等简单的预处理。并用相关脚本从整理过的数据文件中,进行分析计算,提取出规模较小的,计算必需的数据,生成新的整合后的数据文件(summary/report.txt
)。 - 将预处理过较小的数据文件传回本地,用 mathematica 等可视化和交互强大的工具,进行最后的定量分析和制图等工作。
一个对应以上工作流的超算上的工程文件夹结构示例如下:
.
├── code
│ ├── bin
│ │ └── exec
│ ├── build
│ │ └── plugin.o
│ ├── include
│ │ └── plugin.h
│ ├── makefile
│ └── src
│ ├── main.c
│ └── plugin.c
├── output
│ ├── classA
│ │ └── 2.7-0.9-33.txt
│ ├── classB
│ │ ├── r1-2.5-3-153.txt
│ │ └── r2-2.5-3-153.txt
│ └── summary
│ └── report2018-01-01.txt
├── task
│ ├── task1
│ │ ├── exec
│ │ ├── input.txt
│ │ └── task1_run.sh
│ └── task2
│ ├── exec
│ ├── input.txt
│ └── task2_run.sh
└── utils
├── analysis.py
├── merge.py
└── task_create.sh