基本介绍

PBS脚本是一种特殊的shell脚本:

  • #开头的行被bash视作注释
  • #PBS开头的行被PBS系统识别为运行参数
  • 可以直接调用shell命令和系统命令(shell命令的规则比较奇怪,某些情况下甚至对空格是敏感的)
  • 脚本可以使用.sh.pbs后缀
  • 脚本无需使用+x赋予执行权限

通常的PBS脚本结构为:

  1. 通常的 shell 脚本开头 #!/bin/bash(可选)
  2. #PBS选项部分
  3. 切换到工作目录(PBS 作业脚本默认会在用户家目录启动)
  4. 计算前的信息输出(可选)
  5. 计算指令(注意当前位置和可执行文件/脚本的相对位置)
  6. 计算后的信息输出(可选)

PBS选项

在PBS脚本中支持很多选项,常用选项包括:

  • -N name 作业的名称(至多 15 个英文字符,不允许含空格)
  • -q queue 指定作业队列
  • -l 申请作业所需的计算资源
    • -l nodes=N:ppn=M 要求使用 N 个节点,每个节点 M 个核(可以直接占满一个节点的所有核)
    • -l walltime=24000:00:00 要求使用 24000 小时(时间可以设置地足够长)
    • -l mem=100gb 要求使用 100GB 的内存(一般无需设置,自动使用全部内存)

此外还有很多细节选项,一般不用设置:

  • -a time 设置延迟一段时间后才开始执行
  • -r y|n 说明作业是否可重新执行(作业系统可能在异常时尝试重新执行)
  • -p num num 取为绝对值小于 1024 的整数,代表优先级,越大越优先
  • -I PBS 以交互式方式运行
  • -k 是否在计算节点上保留资源,例如oe保留两个输出,默认是n不会在计算节点上专门保存内容
  • -S /bin/bash 让 PBS 脚本识别到 bash 命令
  • 环境变量
    • -V 大写的 V,把当前的所有环境变量导出到计算环境中
    • -v list 小写的 v,把环境变量列表 list 导出到计算环境中

重定向输出

默认情况下,PBS作业系统会将作业执行期间的所有输出重定向到文件中:

  • 标准错误输出:<作业名>.o<作业号>,例如demo.o12134
  • 标准错误输出:<作业名>.e<作业号>,例如demo.e12134

两个文件存放在用户使用qsub命令提交作业的绝对目录下。

我们也可以使用指令对输出重定向规则进行修改:

  • -o <file> 标准输出重定向
  • -e <file> 错误输出重定向
  • -j oe 把错误输出合并到标准输出

环境变量

PBS 系统提供了很多特殊环境变量,大致分为提交时和执行时两类信息。

执行qsub命令提交作业时的信息

  • PBS_O_HOST 执行qsub命令时的节点名称(提交作业的节点)
  • PBS_O_QUEUE 执行qsub命令时的队列名称
  • PBS_O_WORKDIR 执行qsub命令时所在的目录(绝对路径)
  • ...

执行作业时的信息

  • PBS_JOBNAME 作业名称
  • PBS_QUEUE 作业所执行的队列名称
  • PBS_NODEFILE 作业所用计算节点的主机名
  • PBS_JOBID 系统分配的作业号
  • ...

PBS脚本模板

一个PBS脚本模板如下

program1_job.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

#PBS -N <jobname>
#PBS -q cu2
#PBS -l nodes=1:ppn=4
#PBS -l walltime=24000:00:00
#PBS -j oe

cd $PBS_O_WORKDIR

date # 记录开始时间

./a.out # 运行二进制程序/脚本

date # 记录结束时间

这里必须要修改的几部分依次是:

  • 作业名称
  • 队列名称
  • 计算节点/核数
  • 两个date之间的具体计算命令

一个完整的PBS测试项目示例:PBSdemo

计算命令

C/Cpp/Fortran

服务器上可以直接使用的C/C++/Fortran编译器有两套:

  • GNU 编译器:gcc/g++/gfortran,版本只有4.8.5
  • Intel 编译器:icc/icpc/ifort,版本是15.0.1,

这里的icc/icpc基本对标且兼容4.8.5版本的gcc/g++,但是gfortranifort不能保证相互之间的兼容性,不同Fortran编译器之间的差异远大于C/C++编译器之间的差异。

使用上述编译器编译得到可执行文件,有很多方式可以做到:

  • 直接输入编译命令
  • 使用make和Makefile
  • 使用cmake和CMakeLists.txt

在计算命令中直接启动可执行文件即可

1
./a.out

Python

服务器上的Anaconda安装位置为/opt/anaconda3-2024,由root用户安装,默认的base环境中除了自带的库,还有CPU版本的pytorch。

普通用户为了使用conda需要进行一些基础配置,依次执行如下命令

1
2
3
4
5
/opt/anaconda3-2024/bin/conda init

source ~/.bashrc

conda config --set auto_activate_base False

然后就可以正常使用conda相关命令,并且登陆shell不会自动激活环境。

由于普通用户在/opt/目录下没有写入的权限,普通用户在使用时可以从base克隆得到一个新环境,新环境会存储在家目录下,例如

1
/home/<username>/.conda/envs/<env_name>

在PBS脚本的计算命令中,首先需要执行conda相关脚本,然后激活虚拟环境,启动脚本执行即可

1
2
3
4
5
source /opt/anaconda3-2024/etc/profile.d/conda.sh

conda activate <env_name>

python main.py

注:

  • 因为服务器使用的centos系统版本较低,在安装anaconda时并没有考虑相应的版本兼容问题,而是直接使用了最新版进行安装,pytorch官方已经不再支持centos7,某些库可能会因为系统版本过低产生问题。
  • 之所以在安装路径上添加-2024后缀,是因为发现几年前已经有人在系统中安装过Anaconda,安装位置为/opt/anaconda3,但是它的版本很低,因此考虑将其保留,重新安装新版本的Anaconda。

MATLAB

服务器上的MATLAB版本为2015b,安装位置为/opt/software/MATLAB/R2015b/,可以将对应位置添加到PATH中,例如执行下面的命令

1
2
echo 'export PATH=/opt/software/MATLAB/R2015b/bin/:$PATH' >> ~/.bashrc
source ~/.bashrc

然后就可以输入matlab直接打开命令行模式的MATLAB。

可以把MATLAB的调用封装为一个run_matlab脚本

run_matlab
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

if [ "$#" -ne 1 ]; then
echo "Usage: $0 <script_path>"
exit 1
fi

SCRIPT_PATH=$1

matlab -nodisplay -nosplash -nodesktop -r "try, run('$SCRIPT_PATH'); catch ME, disp(ME.message); exit(1); end; exit;"

需要赋予这个脚本执行权限,并且把脚本所在位置添加到PATH中。

在PBS脚本的计算命令中可以只需要这个脚本,传递主脚本的文件名(可以含路径),例如

1
run_matlab src/main.m

MPI

在使用MPI时有很多种不同的搭配方案:

  • 不同的编译器实现:GNU编译器,Intel编译器等
  • 不同的MPI实现:Intel MPI,Open MPI/MPICH等,在windows平台还有微软实现的msmpi

它们的搭配关系和使用命令都是类似的,具体的细节参考文档

常见的编译命令依次为;

  • mpiccgcc + Intel MPI
  • mpicxxg++ + Intel MPI
  • mpifcgfortran + Intel MPI
  • mpiiccicc + Intel MPI
  • mpiicpcicpc + Intel MPI
  • mpiifortifort + Intel MPI

可以使用下面的两个简单程序来测试MPI的编译和使用

main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "mpi.h"
#include <stdio.h>

int main(int argc, char *argv[])
{
int rank, size, len;
char version[MPI_MAX_LIBRARY_VERSION_STRING];

MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Get_library_version(version, &len);

printf("Hello, world! I am %d of %d\n", rank, size);

if(rank==0){
printf("version %s",version);
}

MPI_Finalize();
return 0;
}

main.f90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
program main
use mpi
implicit none

integer :: rank, size, ierr, len
character(len=MPI_MAX_LIBRARY_VERSION_STRING) :: version

call MPI_Init(ierr)
call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
call MPI_Comm_size(MPI_COMM_WORLD, size, ierr)
call MPI_Get_library_version(version, len, ierr)

print *, 'Hello, world! I am ', rank, ' of ', size

if (rank == 0) then
print *, 'version ', trim(version)
end if

call MPI_Finalize(ierr)
end program main

例如对于Fortran程序,需要使用mpiifort指令编译

1
mpiifort main.f90 -o main.out

对于编译得到的MPI程序,直接执行时相当于单个进程,并没有发挥出并行效果,需要使用特殊的命令mpirun启动

1
mpirun -np 4 ./main.out

这里-np 4代表启动4个进程并行,正常执行结果形如

1
2
3
4
5
Hello, world! I am            2  of            4
Hello, world! I am 3 of 4
Hello, world! I am 0 of 4
Hello, world! I am 1 of 4
version Intel(R) MPI Library 5.0 Update 2 for Linux* OS

mpirun启动命令放置PBS脚本的计算命令部分即可,注意申请的计算资源要和这里的并行数目保持匹配。