Skip to content
Snippets Groups Projects
Select Git revision
  • 600625d2544d00b1813f17aeeefea31decbf5f88
  • master default protected
  • original
  • sliced_ellpack_sve
  • sliced_ellpack_rvv
  • sliced_ellpack_avx512
  • m5-instrumentation
7 results

SparseMatrix_functions.hpp

Blame
  • 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)