Rocky 8.10 上 intel 2016 编译 vasp

一、起因

原来系统是 CentOS 7.9,现在升级到了 Rocky 8.10。之前在 CentOS 7 下用 intel 2016 编译器 (ifort, icpc) 编译出的 vasp 在运行的时候会出现如下错误
*** buffer overflow detected ***: /public/software/apps/vasp/5.4.4-i16/bin/vasp_std terminated
我猜应该是运行时 GLIBC 版本不一致导致的。CentOS7.9 的 GLIBC 是 2.17 版(可通过 ldd --version 查看版本),而 Rocky 8.10 的则是 2.28 版,相差较多。前者默认 gcc 为 4.8.5 版,而后者则为 8.5.0 版。

尝试在 Rocky8 下重新编译,结果编译出错如下

In file included from /usr/include/math.h(420), 
from /public/software/compiler/intel-compiler/2016u3/include/math.h(19), 
from derrf_.c(5): /usr/include/bits/mathcalls-helper-functions.h(21): 
error: identifier "_Float128" is undefined __MATHDECL_1 (int, __fpclassify,, (_Mdouble_ __value)) 
                   ^

虽然 vasp 基本上是用 fortran 写的,但是有个别文件是 C++ 写的,比如 src/parser 下的几个文件。于是需要 icpc,而 icpc 虽然是 intel 的编译器,但仍依赖 gcc/g++ 的头文件及 libc (即 GLIBC)等系统库。所以 intel 编译器要正常运行也要和特定版本的 gcc 搭配才行,并不完全独立于 gcc。intel 2016 编译器最高支持的 gcc 版本为 6.x。 再高版本就会因为彼此不匹配而编译报错。上面的错误就是因为 glibc 2.28 引入了 _Float128 类型, 但 intel 2016 太老了,不支持 glibc 2.28 的新特性。事实上,即使是在 CentOS7 上(glibc 2.17)通过 devtoolset-8 安装 gcc8 环境,它使用的 glibc 依然是系统的 2.17,这样虽然不存在 glibc 及其头文件引起的 _Float128 问题但仍然会报错,类似如下。这说明即使不存在 glibc 问题时 intel 2016 与 gcc8 同样不兼容;而且与 gcc7 也不兼容。

/opt/rh/devtoolset-8/root/usr/include/c++/8/type_traits(1049): 
error: identifier "__is_assignable" is undefined : public __bool_constant<__is_assignable(_Tp, _Up)> ^

二、关于 gcc 和 glibc 的关系(该部分来自 GPT)

🌟 GCC 和 glibc 不是绑定的,但 glibc 决定了运行时 ABI (应用程序二进制接口,是程序与操作系统间的接口),而 GCC 只是编译器。

devtoolset-8 明明是 GCC 8,却能在 CentOS 7(glibc 2.17)正常工作
那为何 Rocky8 + GCC 6.5 就不行?

这一块容易误解,我来完整讲清楚 GCC 与 glibc 的关系


1. 虽然 GCC 与 glibc 不绑定,但是强耦合(通过 ABI)

项目 作用
GCC 负责编译,生成机器码、调用 glibc 函数
glibc 提供 C 库函数、动态链接、符号、runtime ABI

它们不是“一起升级的组件”,但 GCC 编译出来的程序必须使用与之 ABI 兼容的 glibc


2. 为何 devtoolset-8 能在 CentOS 7 运行?(关键点!)

因为 devtoolset(SCL)做了一个特殊处理:

✔ devtoolset- 仍然链接系统 glibc 2.17,而不是自带 glibc

虽然它是 GCC 8,有新版本的 libstdc++.so.6 和 libgcc_s.so.1,但是它 明确保持对 glibc 2.17 的 ABI 兼容性。也就是说: devtoolset-8 + glibc 2.17 是官方维护的兼容组合。 这是 Red Hat SCL 官方长期支持的方式,因此不会出现运行时 ABI 冲突。


3. 那为什么 Rocky 8 + 自己安装的 GCC 6.5 就不行?

关键区别在这里: 你手动编译的 GCC 6.5 是为了 glibc 2.17 设计的,它的 libstdc++.so.6 内部调用依赖的是 glibc 2.17 的符号与行为。比如:__memcpy_chk@GLIBC_2.17__abort_msg@GLIBC_2.17。但 Rocky 8 的 glibc 版本是 glibc 2.28,其 结构体变化、堆检查逻辑变化、malloc 实现变化, 于是 VASP 在 Rocky 8 上运行时可能会出现: *** buffer overflow detected ***。因为: 编译时 glibc ABI = 2.17 而运行时 glibc ABI = 2.28, ABI 不一致 → 有风险甚至直接崩溃


4. 再看看 devtoolset-8(CentOS 7)为什么没问题?

因为 devtoolset 是 Red Hat 官方维护的,要求:
✔ 保证 GCC 8 + glibc 2.17 完全 ABI 兼容
✔ 不引入新 glibc
✔ 不破坏现有系统(关键点)
所以 devtoolset-8 在 CentOS 7 上不触发 ABI 问题。


5. 总结:GCC 和 glibc 到底是什么关系?

环境 glibc GCC 是否兼容?
CentOS7 2.17 devtoolset-8 (GCC 8) ✔ 官方维护,兼容
CentOS7 2.17 GCC 6.5 ✔ 兼容
Rocky8 2.28 自己编的 GCC 6.5 🚫 ABI 不匹配,风险极高
Rocky8 2.28 devtoolset-11 (GCC 11) ✔ 官方支持(但与 ifort 2016 不配套)

三、搭建容器编译环境

由上面内容可知,要 vasp 正常运行,按说应该是 intel 2016 结合与 glibc 2.28 相匹配的 gcc 使用。然而,intel 2016 最高支持 gcc6,而 gcc6 又不支持 Rocky8 的 glibc 2.28。那么退而求其次,尝试一下 intel 2016 结合 gblic 2.17 (即CentOS7里的)下的 gcc6。虽然这样未必能解决问题,但毕竟 gcc 版本升级了,或许问题有所改善。于是尝试在 CentOS7 上通过 devtoolset-8 方式先安装 gcc8(与 Rocky8 中大版本一致),然后用 gcc8 来编译安装 gcc6.5。使用更高版本的编译器编译 gcc6.5 感觉应该比使用系统自带的 gcc4.8.5 编译比它高版本的 gcc6.5 更靠谱些。 (最后发现然并卵!,换 gcc6.5 不解决任何问题!)

如何搭建 CentOS7 环境呢?最方便的是使用容器。Rocky8 中默认已经装有 podman 容器。与 docker 不同,podman 可以直接使用,不需要运行守护进程。于是可通过如下命令创建一个 CentOS7 的容器,并命名为 centos7-vasp

podman run -it \
    --name centos7-vasp \
    -v /public/software:/public/software \
    -v /public/software/modules:/usr/share/Modules/modulefiles \
    -v /public/software/modules:/etc/modulefiles \
    quay.io/centos/centos:7 \
    /bin/bash

上述命令只需创建容器时运行一次即可,之后不能再运行了,否则可能会覆盖已创建好的容器。之后再次运行并进入容器可使用如下命令:

# (1)方案1
podman start centos7-vasp                #启动容器
podman exec -it centos7-vasp /bin/bash   #进入容器
#(2)方案2
podman start -ia centos7-vasp            #一步到位,先启动再进入,-i = attach stdin, -a = attach stdout/stderr
# 查看容器列表
podman ps -a

安装好容器后首先进入容器,然后更改 yum 源,由于 CentOS7 比较老了,常规镜像里都没有了,需要使用 Vault 镜像,比如阿里的。将原来的 repo 文件放入备份目录,创建一个 CentOS-Vault.repo 到 /etc/yum.repos.d/ 中。

[base]
name=CentOS-7 - Base
#baseurl=http://vault.centos.org/7.9.2009/os/x86_64/
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/os/x86_64/
gpgcheck=1
enabled=1
gpgkey=http://vault.centos.org/7.9.2009/os/x86_64/RPM-GPG-KEY-CentOS-7

[updates]
name=CentOS-7 - Updates
#baseurl=http://vault.centos.org/7.9.2009/updates/x86_64/
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/updates/x86_64/
gpgcheck=1
enabled=1
gpgkey=http://vault.centos.org/7.9.2009/os/x86_64/RPM-GPG-KEY-CentOS-7

[extras]
name=CentOS-7 - Extras
#baseurl=http://vault.centos.org/7.9.2009/extras/x86_64/
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/extras/x86_64/
gpgcheck=1
enabled=1
gpgkey=http://vault.centos.org/7.9.2009/os/x86_64/RPM-GPG-KEY-CentOS-7

然后安装 epel 源,安装 module,安装 devtoolset 的源,安装 gcc8:

yum install -y epel-release                   # epel 源
yum install -y environment-modules            # 在 epel 源里
yum install -y centos-release-scl-rh          # devtoolset 的源
yum install -y devtoolset-8                   # 安装 gcc8,装到 /opt/rh 里
yum install -y scl-utils
source /opt/rh/devtoolset-8/enable            # 启用 gcc8 环境

epel 的默认源好像就能用,可不用改。但是 scl-rh 的源不能用,得改 CentOS-SCLo-scl-rh.repo 文件,只需改第一段 [centos-sclo-rh] 中的源即可

[centos-sclo-rh]
name=CentOS-7 - SCLo rh
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/sclo/$basearch/rh/
...

装好了 gcc8 就可以编译 gcc 6.5 了

cd /public/software/compiler/gcc/
wget https://ftp.gnu.org/gnu/gcc/gcc-6.5.0/gcc-6.5.0.tar.gz
tar zxf gcc-6.5.0.tar.gz
mv gcc-6.5.0  6.5.0  && cd 6.5.0
mkdir build && cd build
../configure   --prefix=/public/software/compiler/gcc/6.5.0   \
               --disable-multilib   --enable-languages=c,c++
make -j 64    # 根据实际核数调整数字,编译时间比较久
make install

编译好以后创建 /public/software/modules/compiler/gcc/6.5.0-rh7 配置文件,内容如下:

#%Module1.0
proc ModulesHelp { } {
    puts stderr "GNU C/C++/Fortran Compiler"
    puts stderr "See http://gcc.gnu.org/"
}

conflict compiler/gcc
set             GCC_HOME                /public/software/compiler/gcc/6.5.0

prepend-path  PATH       ${GCC_HOME}/bin
prepend-path  LD_LIBRARY_PATH    ${GCC_HOME}/lib64
prepend-path  LIBRARY_PATH      ${GCC_HOME}/lib64
prepend-path  CPATH         ${GCC_HOME}/include

然后 module add compiler/gcc/6.5.0-rh7 之后就可以 module load compiler/gcc/6.5.0-rh7 加载 gcc6.5 了。然后再用 moudle 加载上 intel 2016,就可以到 vasp 目录下去编译了。注意先把之前的 bin 可执行文件备份一下。开始编译后一切顺利,只是比较慢。最终成功编译。这样编译出的 vasp 在容器里运行肯定没问题,就是不知道拿到 Rocky8 里是否依然正常,能否解决之前的 buffer overflow 问题。如果还不行的话,就只能打包 CentOS7 的运行时环境了。

四、打包 CentOS7 下 vasp 的运行时环境

先通过 yum install pax-utils 安装 pax-utils 包,其中包含 lddtree 工具,可以方便的递归查找程序所依赖的动态库。vasp 的依赖如下:

# lddtree vasp_std 
vasp_std => bin/vasp_std (interpreter => /lib64/ld-linux-x86-64.so.2)
    libmkl_intel_lp64.so => /public/software/compiler/intel-compiler/2016u3/mkl/lib/intel64/libmkl_intel_lp64.so
    libmkl_sequential.so => /public/software/compiler/intel-compiler/2016u3/mkl/lib/intel64/libmkl_sequential.so
    libmkl_core.so => /public/software/compiler/intel-compiler/2016u3/mkl/lib/intel64/libmkl_core.so
    libstdc++.so.6 => /public/software/compiler/gcc/6.5.0/lib64/libstdc++.so.6
        ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2
    libmkl_blacs_intelmpi_lp64.so => /public/software/compiler/intel-compiler/2016u3/mkl/lib/intel64/libmkl_blacs_intelmpi_lp64.so
    libmpifort.so.12 => /public/software/compiler/intel-compiler/2016u3/impi/5.1.3.210/intel64/lib/libmpifort.so.12
    libmpi.so.12 => /public/software/compiler/intel-compiler/2016u3/impi/5.1.3.210/intel64/lib/release_mt/libmpi.so.12
    libdl.so.2 => /lib64/libdl.so.2
    librt.so.1 => /lib64/librt.so.1
    libpthread.so.0 => /lib64/libpthread.so.0
    libm.so.6 => /lib64/libm.so.6
    libc.so.6 => /lib64/libc.so.6
    libgcc_s.so.1 => /public/software/compiler/gcc/6.5.0/lib64/libgcc_s.so.1

除 intel 2016 的库外,主要依赖以下文件

/lib64/libc.so.6
/lib64/libm.so.6
/lib64/libdl.so.2
/lib64/librt.so.1
/lib64/libpthread.so.0
/lib64/ld-linux-x86-64.so.2
/public/software/compiler/gcc/6.5.0/lib64/libgcc_s.so.1
/public/software/compiler/gcc/6.5.0/lib64/libstdc++.so.6

其中前面 6 个是 CentOS7 系统的文件,最后两个是新装的 gcc6.5 里的。当然,如果不是基于 gcc6.5 的话,那么最后两个也要用 CentOS7 系统的。把这些文件都拷到 Rocky8 的某个地方,比如放到路径 /public/software/apps/vasp/lib-centos7-gcc6.5 下,然后将 vasp 程序用脚本封装,指定运行环境,这样 vasp 就相当于运行在 CentOS7 的环境中了。不过需注意,只是简单指定 LD_LIBRARY_PATH 是不行的, 比如

RTPATH=/public/software/apps/vasp/lib-centos7-gcc6.5
LD_LIBRARY_PATH=$RTPATH:$LD_LIBRARY_PATH     /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori

因为 LD_LIBRARY_PATH 设置无法影响 /lib64/ld-linux-x86-64.so.2 这个最重要的库,它其实是个可执行文件,它是 linux 程序的入口,可通过 readelf -l 查看程序的入口文件

$ readelf -l vasp_std.ori  |grep Request
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

所以,若要程序使用 CentOS7 里的 ld-linux-x86-64.so.2 文件必须如下指定才行

RTPATH=/public/software/apps/vasp/lib-centos7
$RTPATH/ld-linux-x86-64.so.2    --library-path $RTPATH:$LD_LIBRARY_PATH \
    /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori

这样运行改脚本(命名为 vasp_std,封装了原可执行程序 vasp_std.ori)时就可以在原 CentOS7 的库下正常运行了。

还有另外一种方法,就是通过 patchelf 命令直接修改可执行文件,可将 vasp_std.ori 复制一份为 vasp_std-test,然后对它执行如下命令

# 改 interpreter 和库路径(这样就不用设 LD_LIBRARY_PATH了)
patchelf --set-interpreter /public/software/apps/vasp/lib-centos7/ld-linux-x86-64.so.2 \
         --set-rpath /public/software/apps/vasp/lib-centos7 \
         vasp_std-test
# 然后再查看 vast_std-test 的 interpreter 就变了
readelf -l vasp_std-test  |grep Request
      [Requesting program interpreter: /public/software/apps/vasp/lib-centos7/ld-linux-x86-64.so.2]

之后就可以直接运行 vasp_std-test 了。

到这里,问题看似解决了。然而……
如上封装或 patchelf 后的 vasp 作为单个可执行程序运行没有任何问题,但无法 MPI 并行,一并行就出错!
开始的错误是

SyntaxError: invalid syntax
File "/public/software/compiler/intel-compiler/2016u3/impi/5.1.3.210/bin64/mpiexec", line 187except EOFError, e:

这个还好说,它是 mpiexec 脚本(其实是个 python 脚本)开头的 #!/usr/bin/env python 找不到 python 命令导致的,Rocky8 上面没有默认 python,只有带版本号的 python2 和 python3,于是把 /public/software/compiler/intel-compiler/2016u3/impi/5.1.3.210/bin64 下所有相关脚本中的 python 命令都改成了 python2。这样,上述错误就没有了。但是,却又回到了最初的问题,又出现了 buffer overflow 错误,而且是已运行就出错,不论是使用 vasp_std (封装脚本)还是 patchelf 后的 vasp_std-test 都一样。

$ mpirun -n 16 vasp_std
*** buffer overflow detected ***: /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori terminated
======= Backtrace: =========
/public/software/apps/vasp/lib-centos7/libc.so.6(__fortify_fail+0x37)[0x1461bd66a7a7]
/public/software/apps/vasp/lib-centos7/libc.so.6(+0x116922)[0x1461bd668922]
/public/software/apps/vasp/lib-centos7/libc.so.6(+0x115e2b)[0x1461bd667e2b]
/public/software/apps/vasp/lib-centos7/libc.so.6(_IO_default_xsputn+0xe1)[0x1461bd5cf031]
/public/software/apps/vasp/lib-centos7/libc.so.6(_IO_vfprintf+0x28c5)[0x1461bd59cec5]
/public/software/apps/vasp/lib-centos7/libc.so.6(__vsprintf_chk+0x88)[0x1461bd667eb8]
/public/software/apps/vasp/lib-centos7/libc.so.6(__sprintf_chk+0x7d)[0x1461bd667e0d]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x18911b3]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x1892f79]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x18476ed]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x1879539]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x43a016]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x157d7b2]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x40a25e]
/public/software/apps/vasp/lib-centos7/libc.so.6(__libc_start_main+0xf5)[0x1461bd574555]
/public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori[0x40a169]
======= Memory map: ========
00400000-01b47000 r-xp 00000000 00:2e 5292733967                         /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori
01d47000-01d4a000 r--p 01747000 00:2e 5292733967                         /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori
01d4a000-01f12000 rw-p 0174a000 00:2e 5292733967                         /public/software/apps/vasp/5.4.4-i16/bin/vasp_std.ori
01f12000-0a1ab000 rw-p 00000000 00:00 0 
1461b4436000-1461bd33c000 rw-s 00000000 00:16 14868826                   /dev/shm/Intel_MPI_SEl0MW (deleted)
1461bd33c000-1461bd351000 r-xp 00000000 00:2e 6744324108                 /public/software/apps/vasp/lib-centos7/libgcc_s.so.1
1461bd351000-1461bd550000 ---p 00015000 00:2e 6744324108                 /public/software/apps/vasp/lib-centos7/libgcc_s.so.1
1461bd550000-1461bd551000 r--p 00014000 00:2e 6744324108                 /public/software/apps/vasp/lib-centos7/libgcc_s.so.1
1461bd551000-1461bd552000 rw-p 00015000 00:2e 6744324108                 /public/software/apps/vasp/lib-centos7/libgcc_s.so.1
1461bd552000-1461bd716000 r-xp 00000000 00:2e 6744323340                 /public/software/apps/vasp/lib-centos7/libc.so.6
1461bd716000-1461bd915000 ---p 001c4000 00:2e 6744323340                 /public/software/apps/vasp/lib-centos7/libc.so.6
...... 后面还有很多,就省略了

五、结论

Intel 2016 下的 mpi 已无法在 Rocky8 上正常工作! (串行运行正常)
原因在于 Intel MPI 2016 (IMPI 5.1.3) 与 Rocky8 的 glibc 2.28 不兼容!

这是一个简单的测试程序 tt.f90

program test_mpi
    use mpi
    implicit none
    integer :: ierr, rank, size

    call MPI_Init(ierr)
    call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
    call MPI_Comm_size(MPI_COMM_WORLD, size, ierr)

    print *, "Hello from rank ", rank, " of ", size

    call MPI_Finalize(ierr)
end program test_mpi

在 intel 2016 环境下用 mpiifort -o tt16 tt.f90 编译,然后运行出错如下

$ mpirun -n 4 ./tt16
*** buffer overflow detected ***: ./tt16 terminated
*** buffer overflow detected ***: ./tt16 terminated
*** buffer overflow detected ***: ./tt16 terminated
*** buffer overflow detected ***: ./tt16 terminated
forrtl: error (76): Abort trap signal
Image              PC                Routine            Line        Source             
tt16               00000000004772E5  Unknown               Unknown  Unknown
tt16               0000000000474F07  Unknown               Unknown  Unknown
tt16               0000000000444764  Unknown               Unknown  Unknown

换成 intel 2021 环境,编译出 tt21,运行正常。再切换回 2016 环境,用 2016 的 mpirun 运行 tt21,结果也正常!

$ mpirun -n 4 ./tt21
 Hello from rank            1  of            4
 Hello from rank            0  of            4
 Hello from rank            2  of            4
 Hello from rank            3  of            4
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容