Multi-valued variants (#2386)

Modifications:
- added support for multi-valued variants
- refactored code related to variants into variant.py
- added new generic features to AutotoolsPackage that leverage multi-valued variants
- modified openmpi to use new features
- added unit tests for the new semantics
This commit is contained in:
Massimiliano Culpo
2017-05-01 22:08:47 +02:00
committed by Todd Gamblin
parent 5d0d670b72
commit 9e4b0eb34a
22 changed files with 1959 additions and 436 deletions

View File

@@ -22,11 +22,20 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
from spack import *
import sys
from spack import *
from spack.error import SpackError
class Mvapich2(Package):
def _process_manager_validator(values):
if len(values) > 1 and 'slurm' in values:
raise SpackError(
'slurm cannot be activated along with other process managers'
)
class Mvapich2(AutotoolsPackage):
"""MVAPICH2 is an MPI implementation for Infiniband networks."""
homepage = "http://mvapich.cse.ohio-state.edu/"
url = "http://mvapich.cse.ohio-state.edu/download/mvapich/mv2/mvapich2-2.2.tar.gz"
@@ -55,62 +64,39 @@ class Mvapich2(Package):
# serialized - User serializes calls to MPI (MPI_THREAD_SERIALIZED)
# multiple - Fully multi-threaded (MPI_THREAD_MULTIPLE)
# runtime - Alias to "multiple"
variant('threads', default='multiple',
description='Control the level of thread support')
variant(
'threads',
default='multiple',
values=('single', 'funneled', 'serialized', 'multiple'),
multi=False,
description='Control the level of thread support'
)
# 32 is needed when job size exceeds 32768 cores
variant('ch3_rank_bits', default=32,
description='Number of bits allocated to the rank field (16 or 32)'
)
variant(
'ch3_rank_bits',
default='32',
values=('16', '32'),
multi=False,
description='Number of bits allocated to the rank field (16 or 32)'
)
##########
# TODO : Process managers should be grouped into the same variant,
# as soon as variant capabilities will be extended See
# https://groups.google.com/forum/#!topic/spack/F8-f8B4_0so
SLURM = 'slurm'
HYDRA = 'hydra'
GFORKER = 'gforker'
REMSHELL = 'remshell'
SLURM_INCOMPATIBLE_PMS = (HYDRA, GFORKER, REMSHELL)
variant(SLURM, default=False,
description='Set slurm as the only process manager')
variant(HYDRA, default=False,
description='Set hydra as one of the process managers')
variant(GFORKER, default=False,
description='Set gforker as one of the process managers')
variant(REMSHELL, default=False,
description='Set remshell as one of the process managers')
##########
variant(
'process_managers',
description='List of the process managers to activate',
values=('slurm', 'hydra', 'gforker', 'remshell'),
multi=True,
validator=_process_manager_validator
)
##########
# TODO : Network types should be grouped into the same variant, as
# soon as variant capabilities will be extended
PSM = 'psm'
SOCK = 'sock'
NEMESISIBTCP = 'nemesisibtcp'
NEMESISIB = 'nemesisib'
NEMESIS = 'nemesis'
MRAIL = 'mrail'
SUPPORTED_NETWORKS = (PSM, SOCK, NEMESIS, NEMESISIB, NEMESISIBTCP)
variant(
PSM, default=False,
description='Configure for QLogic PSM-CH3')
variant(
SOCK, default=False,
description='Configure for TCP/IP-CH3')
variant(
NEMESISIBTCP, default=False,
description='Configure for both OFA-IB-Nemesis and TCP/IP-Nemesis')
variant(
NEMESISIB, default=False,
description='Configure for OFA-IB-Nemesis')
variant(
NEMESIS, default=False,
description='Configure for TCP/IP-Nemesis')
variant(
MRAIL, default=False,
description='Configure for OFA-IB-CH3')
##########
'fabrics',
description='The fabric enabled for this build',
default='psm',
values=(
'psm', 'sock', 'nemesisib', 'nemesis', 'mrail', 'nemesisibtcp'
)
)
# FIXME : CUDA support is missing
depends_on('bison')
@@ -123,110 +109,52 @@ def url_for_version(self, version):
else:
return "%s/mvapich/mv2/mvapich2-%s.tar.gz" % (base_url, version)
@staticmethod
def enabled(x):
"""Given a variant name returns the string that means the variant is
enabled
@property
def process_manager_options(self):
spec = self.spec
:param x: variant name
:return:
"""
return '+' + x
other_pms = []
for x in ('hydra', 'gforker', 'remshell'):
if 'process_managers={0}'.format(x) in spec:
other_pms.append(x)
opts = ['--with-pm=%s' % ':'.join(other_pms)]
def set_build_type(self, spec, configure_args):
"""Appends to configure_args the flags that depends only on the build
type (i.e. release or debug)
:param spec: spec
:param configure_args: list of current configure arguments
"""
if '+debug' in spec:
build_type_options = [
"--disable-fast",
"--enable-error-checking=runtime",
"--enable-error-messages=all",
# Permits debugging with TotalView
"--enable-g=dbg", "--enable-debuginfo"
]
else:
build_type_options = ["--enable-fast=all"]
configure_args.extend(build_type_options)
def set_process_manager(self, spec, configure_args):
"""Appends to configure_args the flags that will enable the
appropriate process managers
:param spec: spec
:param configure_args: list of current configure arguments
"""
# Check that slurm variant is not activated together with
# other pm variants
has_slurm_incompatible_variants = \
any(self.enabled(x) in spec
for x in Mvapich2.SLURM_INCOMPATIBLE_PMS)
if self.enabled(Mvapich2.SLURM) in spec and \
has_slurm_incompatible_variants:
raise RuntimeError(" %s : 'slurm' cannot be activated \
together with other process managers" % self.name)
process_manager_options = []
# See: http://slurm.schedmd.com/mpi_guide.html#mvapich2
if self.enabled(Mvapich2.SLURM) in spec:
if 'process_managers=slurm' in spec:
if self.version > Version('2.0'):
process_manager_options = [
"--with-pmi=pmi2",
"--with-pm=slurm"
opts = [
'--with-pmi=pmi2',
'--with-pm=slurm'
]
else:
process_manager_options = [
"--with-pmi=slurm",
"--with-pm=no"
opts = [
'--with-pmi=slurm',
'--with-pm=no'
]
elif has_slurm_incompatible_variants:
pms = []
# The variant name is equal to the process manager name in
# the configuration options
for x in Mvapich2.SLURM_INCOMPATIBLE_PMS:
if self.enabled(x) in spec:
pms.append(x)
process_manager_options = [
"--with-pm=%s" % ':'.join(pms)
]
configure_args.extend(process_manager_options)
return opts
def set_network_type(self, spec, configure_args):
# Check that at most one variant has been activated
count = 0
for x in Mvapich2.SUPPORTED_NETWORKS:
if self.enabled(x) in spec:
count += 1
if count > 1:
raise RuntimeError('network variants are mutually exclusive \
(only one can be selected at a time)')
network_options = []
@property
def network_options(self):
opts = []
# From here on I can suppose that only one variant has been selected
if self.enabled(Mvapich2.PSM) in spec:
network_options = ["--with-device=ch3:psm"]
elif self.enabled(Mvapich2.SOCK) in spec:
network_options = ["--with-device=ch3:sock"]
elif self.enabled(Mvapich2.NEMESISIBTCP) in spec:
network_options = ["--with-device=ch3:nemesis:ib,tcp"]
elif self.enabled(Mvapich2.NEMESISIB) in spec:
network_options = ["--with-device=ch3:nemesis:ib"]
elif self.enabled(Mvapich2.NEMESIS) in spec:
network_options = ["--with-device=ch3:nemesis"]
elif self.enabled(Mvapich2.MRAIL) in spec:
network_options = ["--with-device=ch3:mrail", "--with-rdma=gen2"]
configure_args.extend(network_options)
if 'fabrics=psm' in self.spec:
opts = ["--with-device=ch3:psm"]
elif 'fabrics=sock' in self.spec:
opts = ["--with-device=ch3:sock"]
elif 'fabrics=nemesisibtcp' in self.spec:
opts = ["--with-device=ch3:nemesis:ib,tcp"]
elif 'fabrics=nemesisib' in self.spec:
opts = ["--with-device=ch3:nemesis:ib"]
elif 'fabrics=nemesis' in self.spec:
opts = ["--with-device=ch3:nemesis"]
elif 'fabrics=mrail' in self.spec:
opts = ["--with-device=ch3:mrail", "--with-rdma=gen2"]
return opts
def setup_environment(self, spack_env, run_env):
if self.enabled(Mvapich2.SLURM) in self.spec and \
self.version > Version('2.0'):
spec = self.spec
if 'process_managers=slurm' in spec and spec.satisfies('@2.0:'):
run_env.set('SLURM_MPI_TYPE', 'pmi2')
def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
@@ -251,48 +179,44 @@ def setup_dependent_package(self, module, dependent_spec):
join_path(self.prefix.lib, 'libmpi.{0}'.format(dso_suffix))
]
def install(self, spec, prefix):
@run_before('configure')
def die_without_fortran(self):
# Until we can pass variants such as +fortran through virtual
# dependencies depends_on('mpi'), require Fortran compiler to
# avoid delayed build errors in dependents.
if (self.compiler.f77 is None) or (self.compiler.fc is None):
raise InstallError('Mvapich2 requires both C and Fortran ',
'compilers!')
raise InstallError(
'Mvapich2 requires both C and Fortran compilers!'
)
# we'll set different configure flags depending on our
# environment
configure_args = [
"--prefix=%s" % prefix,
"--enable-shared",
"--enable-romio",
"--disable-silent-rules",
def configure_args(self):
args = [
'--enable-shared',
'--enable-romio',
'-disable-silent-rules',
'--enable-fortran=all',
"--enable-threads={0}".format(spec.variants['threads'].value),
"--with-ch3-rank-bits={0}".format(
spec.variants['ch3_rank_bits'].value),
]
if self.compiler.f77 and self.compiler.fc:
configure_args.append("--enable-fortran=all")
elif self.compiler.f77:
configure_args.append("--enable-fortran=f77")
elif self.compiler.fc:
configure_args.append("--enable-fortran=fc")
if '+debug' in self.spec:
args.extend([
'--disable-fast',
'--enable-error-checking=runtime',
'--enable-error-messages=all',
# Permits debugging with TotalView
'--enable-g=dbg',
'--enable-debuginfo'
])
else:
configure_args.append("--enable-fortran=none")
args.append('--enable-fast=all')
# Set the type of the build (debug, release)
self.set_build_type(spec, configure_args)
# Set the process manager
self.set_process_manager(spec, configure_args)
# Determine network type by variant
self.set_network_type(spec, configure_args)
configure(*configure_args)
make()
make("install")
self.filter_compilers()
args.extend(self.process_manager_options)
args.extend(self.network_options)
return args
@run_after('install')
def filter_compilers(self):
"""Run after install to make the MPI compilers use the
compilers that Spack built the package with.
@@ -302,7 +226,7 @@ def filter_compilers(self):
be bound to whatever compiler they were built with.
"""
bin = self.prefix.bin
mpicc = join_path(bin, 'mpicc')
mpicc = join_path(bin, 'mpicc')
mpicxx = join_path(bin, 'mpicxx')
mpif77 = join_path(bin, 'mpif77')
mpif90 = join_path(bin, 'mpif90')