Select Git revision
Create_JupyterKernel_general.ipynb
Forked from
jupyter4jsc / j4j_notebooks
Source project has a limited visibility.
-
Jens Henrik Göbbert authoredJens Henrik Göbbert authored
openmpi.py 10.66 KiB
##
# Copyright 2019-2022 Ghent University
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
##
"""
EasyBuild support for OpenMPI, implemented as an easyblock
@author: Kenneth Hoste (Ghent University)
@author: Robert Mijakovic (LuxProvide)
"""
import os
import re
from distutils.version import LooseVersion
import easybuild.tools.toolchain as toolchain
from easybuild.easyblocks.generic.configuremake import ConfigureMake
from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option
from easybuild.tools.modules import get_software_root
from easybuild.tools.systemtools import check_os_dependency, get_shared_lib_ext
from easybuild.tools.toolchain.mpi import get_mpi_cmd_template
class EB_OpenMPI(ConfigureMake):
"""OpenMPI easyblock."""
def configure_step(self):
"""Custom configuration step for OpenMPI."""
def config_opt_used(key, enable_opt=False):
"""Helper function to check whether a configure option is already specified in 'configopts'."""
if enable_opt:
regex = '--(disable|enable)-%s' % key
else:
regex = '--(with|without)-%s' % key
return bool(re.search(regex, self.cfg['configopts']))
config_opt_names = [
# suppress failure modes in relation to mpirun path
'mpirun-prefix-by-default',
# build shared libraries
'shared',
]
for key in config_opt_names:
if not config_opt_used(key, enable_opt=True):
self.cfg.update('configopts', '--enable-%s' % key)
# List of EasyBuild dependencies for which OMPI has known options
known_dependencies = ('CUDA', 'hwloc', 'libevent', 'libfabric', 'PMIx', 'UCX')
# Value to use for `--with-<dep>=<value>` if the dependency is not specified in the easyconfig
# No entry is interpreted as no option added at all
# This is to make builds reproducible even when the system libraries are changed and avoids failures
# due to e.g. finding only PMIx but not libevent on the system
unused_dep_value = dict()
# Known options since version 3.0 (no earlier ones checked)
if LooseVersion(self.version) >= LooseVersion('3.0'):
# Default to disable the option with "no"
unused_dep_value = {dep: 'no' for dep in known_dependencies}
# For these the default is to use an internal copy and not using any is not supported
for dep in ('hwloc', 'libevent', 'PMIx'):
unused_dep_value[dep] = 'internal'
# handle dependencies
for dep in known_dependencies:
opt_name = dep.lower()
# If the option is already used, don't add it
if config_opt_used(opt_name):
continue
# libfabric option renamed in OpenMPI 3.1.0 to ofi
if dep == 'libfabric' and LooseVersion(self.version) >= LooseVersion('3.1'):
opt_name = 'ofi'
# Check new option name. They are synonyms since 3.1.0 for backward compatibility
if config_opt_used(opt_name):
continue
dep_root = get_software_root(dep)
# If the dependency is loaded, specify its path, else use the "unused" value, if any
if dep_root:
opt_value = dep_root
else:
opt_value = unused_dep_value.get(dep)
if opt_value is not None:
self.cfg.update('configopts', '--with-%s=%s' % (opt_name, opt_value))
if bool(get_software_root('PMIx')) != bool(get_software_root('libevent')):
raise EasyBuildError('You must either use both PMIx and libevent as dependencies or none of them. '
'This is to enforce the same libevent is used for OpenMPI as for PMIx or '
'the behavior may be unpredictable.')
# check whether VERBS support should be enabled
if not config_opt_used('verbs'):
# for OpenMPI v4.x, the openib BTL should be disabled when UCX is used;
# this is required to avoid "error initializing an OpenFabrics device" warnings,
# see also https://www.open-mpi.org/faq/?category=all#ofa-device-error
is_ucx_enabled = ('--with-ucx' in self.cfg['configopts'] and
'--with-ucx=no' not in self.cfg['configopts'])
if LooseVersion(self.version) >= LooseVersion('4.0.0') and is_ucx_enabled:
verbs = False
else:
# auto-detect based on available OS packages
os_packages = EASYCONFIG_CONSTANTS['OS_PKG_IBVERBS_DEV'][0]
verbs = any(check_os_dependency(osdep) for osdep in os_packages)
# for OpenMPI v5.x, the verbs support is removed, only UCX is available
# see https://github.com/open-mpi/ompi/pull/6270
if LooseVersion(self.version) < LooseVersion('5.0.0'):
if verbs:
self.cfg.update('configopts', '--with-verbs')
else:
self.cfg.update('configopts', '--without-verbs')
super(EB_OpenMPI, self).configure_step()
def test_step(self):
"""Test step for OpenMPI"""
# Default to `make check` if nothing is set. Disable with "runtest = False" in the EC
if self.cfg['runtest'] is None:
self.cfg['runtest'] = 'check'
super(EB_OpenMPI, self).test_step()
def load_module(self, *args, **kwargs):
"""
Load (temporary) module file, after resetting to initial environment.
Also put RPATH wrappers back in place if needed, to ensure that sanity check commands work as expected.
"""
super(EB_OpenMPI, self).load_module(*args, **kwargs)
# ensure RPATH wrappers are in place, otherwise compiling minimal test programs will fail
if build_option('rpath'):
if self.toolchain.options.get('rpath', True):
self.toolchain.prepare_rpath_wrappers(rpath_filter_dirs=self.rpath_filter_dirs,
rpath_include_dirs=self.rpath_include_dirs)
def sanity_check_step(self):
"""Custom sanity check for OpenMPI."""
bin_names = ['mpicc', 'mpicxx', 'mpif90', 'mpifort', 'ompi_info', 'opal_wrapper']
if LooseVersion(self.version) >= LooseVersion('5.0.0'):
bin_names.append('prterun')
else:
if '--with-orte=no' not in self.cfg['configopts'] and '--without-orte' not in self.cfg['configopts']:
bin_names.extend(['orterun', 'mpirun'])
bin_files = [os.path.join('bin', x) for x in bin_names]
shlib_ext = get_shared_lib_ext()
lib_names = ['mpi_mpifh', 'mpi', 'open-pal']
if LooseVersion(self.version) >= LooseVersion('5.0.0'):
lib_names.append('prrte')
else:
lib_names.extend(['ompitrace', 'open-rte'])
lib_files = [os.path.join('lib', 'lib%s.%s' % (x, shlib_ext)) for x in lib_names]
inc_names = ['mpi-ext', 'mpif-config', 'mpif', 'mpi', 'mpi_portable_platform']
if LooseVersion(self.version) >= LooseVersion('5.0.0'):
inc_names.append('prte')
inc_files = [os.path.join('include', x + '.h') for x in inc_names]
custom_paths = {
'files': bin_files + inc_files + lib_files,
'dirs': [],
}
# make sure MPI compiler wrappers pick up correct compilers
expected = {
'mpicc': os.getenv('CC', 'gcc'),
'mpicxx': os.getenv('CXX', 'g++'),
'mpifort': os.getenv('FC', 'gfortran'),
'mpif90': os.getenv('F90', 'gfortran'),
}
# actual pattern for gfortran is "GNU Fortran"
for key in ['mpifort', 'mpif90']:
if expected[key] == 'gfortran':
expected[key] = "GNU Fortran"
# for PGI, correct pattern is "pgfortran" with mpif90
if expected['mpif90'] == 'pgf90':
expected['mpif90'] = 'pgfortran'
custom_commands = ["%s --version | grep '%s'" % (key, expected[key]) for key in sorted(expected.keys())]
# Add minimal test program to sanity checks
# Run with correct MPI launcher
mpi_cmd_tmpl, params = get_mpi_cmd_template(toolchain.OPENMPI, dict(), mpi_version=self.version)
# Limit number of ranks to 8 to avoid it failing due to hyperthreading
ranks = min(8, self.cfg['parallel'])
for src, compiler in (('hello_c.c', 'mpicc'), ('hello_mpifh.f', 'mpifort'), ('hello_usempi.f90', 'mpif90')):
src_path = os.path.join(self.cfg['start_dir'], 'examples', src)
if os.path.exists(src_path):
test_exe = os.path.join(self.builddir, 'mpi_test_' + os.path.splitext(src)[0])
self.log.info("Adding minimal MPI test program to sanity checks: %s", test_exe)
# Build test binary
custom_commands.append("%s %s -o %s" % (compiler, src_path, test_exe))
# Run the test if chosen
if build_option('mpi_tests'):
params.update({'nr_ranks': ranks, 'cmd': test_exe})
# Allow oversubscription for this test (in case of hyperthreading)
custom_commands.append("OMPI_MCA_rmaps_base_oversubscribe=1 " + mpi_cmd_tmpl % params)
# Run with 1 process which may trigger other bugs
# See https://github.com/easybuilders/easybuild-easyconfigs/issues/12978
params['nr_ranks'] = 1
custom_commands.append(mpi_cmd_tmpl % params)
super(EB_OpenMPI, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands)