Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add easyblock for RAxML #2180

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
194 changes: 194 additions & 0 deletions easybuild/easyblocks/r/raxml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
##
# Copyright 2020-2023 Vrije Universiteit Brussel
#
# 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 building and installing RAxML, implemented as an easyblock

@author: Alex Domingo (Vrije Universiteit Brussel)
"""
import os

from easybuild.easyblocks.generic.makecp import MakeCp
from easybuild.framework.easyconfig import CUSTOM
from easybuild.tools.build_log import EasyBuildError, print_warning
from easybuild.tools.config import build_option
from easybuild.tools.systemtools import get_cpu_features, HAVE_ARCHSPEC
from easybuild.tools.toolchain.compiler import OPTARCH_GENERIC

RAXML_BINARY_NAME = "raxmlHPC"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure RAXML_BINARY_NAME is useful, since it's only used in one place currently?

# Supported instruction sets ordered by priority (high to low) and related CPU features
RAXML_INSTRUCT_SETS = ["AVX2", "AVX", "SSE3"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about AVX-512? If there's a reason no to include that, add a comment on it?
Where can the list of supported instruction sets be found (docs link? README?)

Also, out of curiosity, does it also support NEON (for aarch64)?
If someone cares enough the easyblock can be updated later to also cover that, doesn't need to be done here.

RAXML_CPU_FEATURES = {
"SSE3": ["sse3", "see4_1", "sse4_2"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:

Suggested change
"SSE3": ["sse3", "see4_1", "sse4_2"],
"SSE3": ["sse3", "sse4_1", "sse4_2"],

"AVX": ["avx", "avx1.0"],
"AVX2": ["avx2"],
}
# Supported parallelization features grouped in non-MPI and MPI
RAXML_PARALLEL_FEATURES = {
"nompi": [None, "PTHREADS"],
"mpi": ["MPI", "HYBRID"],
}


class EB_RAxML(MakeCp):
"""Support for building and installing RAxML."""

@staticmethod
def extra_options(extra_vars=None):
"""Change default values of options"""
extra = MakeCp.extra_options()
# files_to_copy is not mandatory
extra["files_to_copy"][2] = CUSTOM
return extra

def __init__(self, *args, **kwargs):
"""RAxML easyblock constructor, define class variables."""
super(EB_RAxML, self).__init__(*args, **kwargs)

def filter_optarch_features(support_features):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a function outside of the easyblock? Same for others below.

Advantage is that tests can be added easily to CI for these, and code becomes quite a bit easier to parse for humans

"""
Filter list of supported CPU instruction sets based on optarch string
"""
if not isinstance(support_features, list):
support_features = list(support_features)
unfilter_features = [feat.upper() for feat in support_features]

optarch = build_option("optarch") or self.toolchain.options.get("optarch", "")
# optarch is a dictionary with settings per compiler family
if isinstance(optarch, dict):
comp_fam = self.toolchain.comp_family()
optarch = optarch.get(comp_fam, "")
# convert boolean optarch settings to string equivalents
if optarch is False:
optarch = OPTARCH_GENERIC
elif optarch is True:
optarch = ""
optarch = optarch.upper()

# check for generic build
if optarch == OPTARCH_GENERIC:
self.log.debug("Building generic RAxML per optarch setting: %s", optarch)
return [None]
# check for supported labels in optarch (by given priority order)
for instruct_set in unfilter_features:
if instruct_set in optarch:
dbg_msg = "Restricting supported features in RAxML to '%s' per optarch: %s"
self.log.debug(dbg_msg, instruct_set, optarch)
return [instruct_set]
# anything else is unsupported
if optarch:
print_warning("Unsupported 'optarch' configuration setting for RAxML, ignoring: %s" % optarch)

return support_features

def has_cpu_feature(feature):
"""
Check if host CPU architecture has given feature
"""
if HAVE_ARCHSPEC:
import archspec.cpu

host = archspec.cpu.host()
return feature.lower() in host

try:
return any(f in get_cpu_features() for f in RAXML_CPU_FEATURES[feature])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nicer with a single return as the end, I'm not a big fan of inline returns, see also https://realpython.com/python-return-statement/#remembering-the-return-value (but that's partially personal, I guess)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this function should be moved into framework, it's pretty generic... Same for filter_optarch_features

except KeyError:
raise EasyBuildError("Unknown CPU feature level for RAxML: %s", feature)

def list_filename_variants(main_features, extra_features, prefix, suffix, divider):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also go outside of easyblock, maybe rename to list_raxml_filename_variants to indicate it's specific to RAxML

"""
Returns list of RAxML filenames for the combination of all given features
"""
if not isinstance(main_features, list):
main_features = list(main_features)
if not isinstance(extra_features, list):
extra_features = list(extra_features)

# Permutations of features
all_features = [(mf, xf) for mf in main_features for xf in extra_features]

# Prepend/Append prefix/suffix
file_variants = [(prefix,) + variant + (suffix,) for variant in all_features]
file_variants = [divider.join([segment for segment in variant if segment]) for variant in file_variants]

return file_variants

# Filter target CPU instructions sets according to optarch
cpu_features = filter_optarch_features(RAXML_INSTRUCT_SETS)

if len(cpu_features) > 1:
# unrestricted optimization settings, set optimization level for host micro-architecture
cpu_features = [feat for feat in cpu_features if has_cpu_feature(feat)]
dbg_msg = "Enabling the following CPU optimizations for RAxML by autodetection: %s"
self.log.debug(dbg_msg, ", ".join(cpu_features))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this info log message?

# add generic build
cpu_features.append(None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify (in the comment) why this is done?


# Set parallelization level of RAxML for current toolchain
parallel_features = RAXML_PARALLEL_FEATURES["nompi"]
if self.toolchain.options.get("usempi", None):
parallel_features.extend(RAXML_PARALLEL_FEATURES["mpi"])

# List of builds to carry out
self.target_makefiles = list_filename_variants(cpu_features, parallel_features, "Makefile", "gcc", ".")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is always using gcc OK? What if we're installing RAxML with Intel compilers?

self.target_bins = list_filename_variants(parallel_features, cpu_features, RAXML_BINARY_NAME, None, "-")

def build_step(self):
"""Build all binaries of RAxML compatible with host CPU architecture"""

# Compiler is manually set through 'buildopts'
compiler = os.getenv("CC")
compiler_nompi = compiler
if self.toolchain.options.get("usempi", None):
compiler_nompi = os.getenv("CC_SEQ")

# Build selected RAxML makefiles
user_buildopts = self.cfg["buildopts"]

for mf in self.target_makefiles:
cc_opt = compiler
if not any(feature in mf for feature in RAXML_PARALLEL_FEATURES["mpi"]):
cc_opt = compiler_nompi
self.cfg["buildopts"] = '-f %s CC="%s" %s' % (mf, cc_opt, user_buildopts)
self.log.debug("Building RAxML makefile with %s: %s", cc_opt, mf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.info?

super(EB_RAxML, self).build_step()

def install_step(self):
"""Copy files into installation directory"""

self.cfg["files_to_copy"] = [
(self.target_bins, "bin"),
(["README", "manual", "usefulScripts"], "share"),
]
super(EB_RAxML, self).install_step()

def sanity_check_step(self):
"""Custom sanity check for RAxML."""

custom_paths = {
"files": [os.path.join("bin", x) for x in self.target_bins],
"dirs": ["share/manual", "share/usefulScripts"],
}
super(EB_RAxML, self).sanity_check_step(custom_paths=custom_paths)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also include a custom sanity check command like raxmlHPC -h?