From 81096351971fd6dc0ed514b45d7bcb649170d98e Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 27 Oct 2022 11:00:47 +0200 Subject: [PATCH] Add a plethora of config options to a-b-c. (#1958) Closes #1861 Closes #1783 - arbor-build-catalogue (a-b-c) does no longer need to be in a fixed location - nor do we statically fix the configuration for a-b-c - instead, we rely on `arbor.config` to read the relevant default values - each value can be overriden, if desired - added many more values to the configuration - gpu type and arch - paths and prefix - CXX - report default settings for better diagnosis - implement a fallback for prefix if that does not exist; in particular for the amazing skbuild. In essence you can now use a-b-c as a standalone tool, as long as you have a properly configured py-arbor. Example output after removing `_skbuild` ``` Warning: prefix '/Users/hater/src/arbor/_skbuild/macosx-11.0-x86_64-3.10/cmake-install' does not exist, falling back to '/Users/hater/src/arbor/.direnv/python-3.10.6'. usage: arbor-build-catalogue catalogue_name mod_source_dir Generate dynamic catalogue and build it into a shared object. positional arguments: name Catalogue name. modpfx Directory name where *.mod files live. options: --raw raw [raw ...] Advanced: Raw mechanisms as C++ files. Per <name> the files <name>.hpp, <name>_cpu.cpp (if CPU is enabled) must be present in the target directory and with GPU support also <name>_gpu.cpp and <name>_gpu.cu. -v, --verbose Verbose. -q, --quiet Less output. --cpu CPU Enable CPU support. --debug [path] Don't clean up the generated temp cpp code. Can be a target path for the generated code. --gpu gpu Enable GPU support --gpu-arch gpu_arch Enable GPU support; default=- --cxx cxx Use this C++ compiler; default=/usr/local/opt/llvm/bin/clang++. --prefix prefix Arbor's install prefix; default=/Users/hater/src/arbor/.direnv/python-3.10.6. --bin bin Look here for Arbor utils like modcc; relative to prefix, default=bin. --lib lib Look here for Arbor's CMake config; relative to prefix, default=lib. --data data Look here for Arbor supplementals like generate_catalogue; relative to prefix, default=lib -h, --help Display this help and exit. ``` --- .github/workflows/lint.yml | 6 +- CMakeLists.txt | 9 +- arbor/include/CMakeLists.txt | 9 + python/CMakeLists.txt | 8 + python/config.cpp | 27 ++- python/context.cpp | 4 +- python/test/fixtures.py | 3 +- .../test/unit/test_domain_decompositions.py | 4 +- .../test_domain_decompositions.py | 2 +- ...ild-catalogue.in => arbor-build-catalogue} | 161 +++++++++++------- 10 files changed, 151 insertions(+), 82 deletions(-) rename scripts/{build-catalogue.in => arbor-build-catalogue} (63%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4d109e4b..743f145d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,8 +26,8 @@ jobs: - name: Python Formatting uses: psf/black@stable with: - options: --check - src: scripts/build-catalogue.in . + options: --check --extend-exclude '/(ext|doc/scripts/.*_theme|doc/scripts/inputs.py)' + src: . - name: Python analysis run: | - flake8 scripts/build-catalogue.in . + flake8 . diff --git a/CMakeLists.txt b/CMakeLists.txt index 0773d6a1..41675d28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,12 +177,6 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Data and internal scripts go here set(ARB_INSTALL_DATADIR ${CMAKE_INSTALL_DATAROOTDIR}/arbor) -# Derived paths for arbor-build-catalogue -get_filename_component(absolute_full_bindir ${CMAKE_INSTALL_BINDIR} REALPATH) -get_filename_component(absolute_full_datarootdir ${CMAKE_INSTALL_DATAROOTDIR} REALPATH) -get_filename_component(absolute_full_libdir ${CMAKE_INSTALL_LIBDIR} REALPATH) -file(RELATIVE_PATH ARB_REL_DATADIR ${absolute_full_bindir} ${absolute_full_datarootdir}/arbor) -file(RELATIVE_PATH ARB_REL_PACKAGEDIR ${absolute_full_bindir} ${absolute_full_libdir}/cmake/arbor) # Interface library `arbor-config-defs` collects configure-time defines # for arbor, arborenv, arborio, of the form ARB_HAVE_XXX. These @@ -227,8 +221,7 @@ install(TARGETS arborio-public-deps EXPORT arborio-targets) # Add scripts and supporting CMake for setting up external catalogues -configure_file(scripts/build-catalogue.in ${CMAKE_CURRENT_BINARY_DIR}/arbor-build-catalogue @ONLY) -install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/arbor-build-catalogue DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(PROGRAMS scripts/arbor-build-catalogue DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES mechanisms/BuildModules.cmake DESTINATION ${ARB_INSTALL_DATADIR}) # External libraries in `ext` sub-directory: json, tinyopt and randon123. # Creates interface libraries `ext-json`, `ext-tinyopt` and `ext-random123` diff --git a/arbor/include/CMakeLists.txt b/arbor/include/CMakeLists.txt index 1cfc13b0..461b7d54 100644 --- a/arbor/include/CMakeLists.txt +++ b/arbor/include/CMakeLists.txt @@ -53,6 +53,15 @@ endif() if(ARB_VECTORIZE) list(APPEND arb_features VECTORIZE) endif() +if(ARB_WITH_NVCC) + list(APPEND arb_features NVCC) +endif() +if(ARB_WITH_CUDA_CLANG) + list(APPEND arb_features CUDA_CLANG) +endif() +if(ARB_WITH_HIP_CLANG) + list(APPEND arb_features HIP) +endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" arb_config_str) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 61f789ee..678bfe06 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,3 +1,5 @@ +include(GNUInstallDirs) + set(PYBIND11_CPP_STANDARD -std=c++17) if(ARB_USE_BUNDLED_PYBIND11) @@ -41,6 +43,12 @@ set(pyarb_source env.cpp ) +set_property(SOURCE config.cpp PROPERTY COMPILE_DEFINITIONS ARB_BINARY="${CMAKE_INSTALL_BINDIR}" APPEND) +set_property(SOURCE config.cpp PROPERTY COMPILE_DEFINITIONS ARB_LIB="${CMAKE_INSTALL_LIBDIR}" APPEND) +set_property(SOURCE config.cpp PROPERTY COMPILE_DEFINITIONS ARB_DATA="${CMAKE_INSTALL_DATAROOTDIR}" APPEND) +set_property(SOURCE config.cpp PROPERTY COMPILE_DEFINITIONS ARB_CXX_COMPILER="${CMAKE_CXX_COMPILER}" APPEND) +set_property(SOURCE config.cpp PROPERTY COMPILE_DEFINITIONS ARB_PREFIX="${CMAKE_INSTALL_PREFIX}" APPEND) + # compile the pyarb sources into an object library that will be # use by both the Python wrapper target (pyarb) and for the # unit tests of the C++ components in the Python wrapper. diff --git a/python/config.cpp b/python/config.cpp index 6f8ae58e..62d604ee 100644 --- a/python/config.cpp +++ b/python/config.cpp @@ -24,10 +24,17 @@ pybind11::dict config() { #else dict[pybind11::str("mpi4py")] = pybind11::bool_(false); #endif -#ifdef ARB_GPU_ENABLED - dict[pybind11::str("gpu")] = pybind11::bool_(true); -#else - dict[pybind11::str("gpu")] = pybind11::bool_(false); +#ifdef ARB_NVCC_ENABLED + dict[pybind11::str("gpu")] = pybind11::str("cuda"); +#endif +#ifdef ARB_CUDA_CLANG_ENABLED + dict[pybind11::str("gpu")] = pybind11::str("cuda-clang"); +#endif +#ifdef ARB_HIP_ENABLED + dict[pybind11::str("gpu")] = pybind11::str("hip"); +#endif +#ifndef ARB_GPU_ENABLED + dict[pybind11::str("gpu")] = pybind11::none(); #endif #ifdef ARB_VECTORIZE_ENABLED dict[pybind11::str("vectorize")] = pybind11::bool_(true); @@ -49,9 +56,15 @@ pybind11::dict config() { #else dict[pybind11::str("bundled")] = pybind11::bool_(false); #endif - dict[pybind11::str("version")] = pybind11::str(ARB_VERSION); - dict[pybind11::str("source")] = pybind11::str(ARB_SOURCE_ID); - dict[pybind11::str("arch")] = pybind11::str(ARB_ARCH); + + dict[pybind11::str("version")] = pybind11::str(ARB_VERSION); + dict[pybind11::str("source")] = pybind11::str(ARB_SOURCE_ID); + dict[pybind11::str("arch")] = pybind11::str(ARB_ARCH); + dict[pybind11::str("prefix")] = pybind11::str(ARB_PREFIX); + dict[pybind11::str("binary_path")] = pybind11::str(ARB_BINARY); + dict[pybind11::str("lib_path")] = pybind11::str(ARB_LIB); + dict[pybind11::str("data_path")] = pybind11::str(ARB_DATA); + dict[pybind11::str("CXX")] = pybind11::str(ARB_CXX_COMPILER); { #define mk_tok(x) #x #define mk_ver(M, m, p) mk_tok(M) "." mk_tok(m) "." mk_tok(p) diff --git a/python/context.cpp b/python/context.cpp index 2f0e6ffd..a2f3da57 100644 --- a/python/context.cpp +++ b/python/context.cpp @@ -132,7 +132,7 @@ void register_contexts(pybind11::module& m) { "threads"_a=1, "gpu_id"_a=pybind11::none(), "mpi"_a=pybind11::none(), "Construct a distributed context with arguments:\n" " threads: The number of threads available locally for execution. Must be set to 1 at minimum. 1 by default.\n" - " gpu_id: The identifier of the GPU to use, None by default. Only available if arbor.__config__['gpu']==True.\n" + " gpu_id: The identifier of the GPU to use, None by default. Only available if arbor.__config__['gpu']!=\"none\".\n" " mpi: The MPI communicator, None by default. Only available if arbor.__config__['mpi']==True.\n") .def(pybind11::init( [](std::string threads, pybind11::object gpu, pybind11::object mpi){ @@ -146,7 +146,7 @@ void register_contexts(pybind11::module& m) { "threads"_a, "gpu_id"_a=pybind11::none(), "mpi"_a=pybind11::none(), "Construct a distributed context with arguments:\n" " threads: A string option describing the number of threads. Currently, only \"avail_threads\" is supported.\n" - " gpu_id: The identifier of the GPU to use, None by default. Only available if arbor.__config__['gpu']==True.\n" + " gpu_id: The identifier of the GPU to use, None by default. Only available if arbor.__config__['gpu']!=\"none\".\n" " mpi: The MPI communicator, None by default. Only available if arbor.__config__['mpi']==True.\n") .def(pybind11::init( [](proc_allocation_shim alloc, pybind11::object mpi){ diff --git a/python/test/fixtures.py b/python/test/fixtures.py index 19f88ae9..4fbb3514 100644 --- a/python/test/fixtures.py +++ b/python/test/fixtures.py @@ -86,10 +86,11 @@ def _build_cat_local(name, path): ["arbor-build-catalogue", name, str(path)], check=True, stderr=subprocess.PIPE, + stdout=subprocess.PIPE, ) except subprocess.CalledProcessError as e: raise _BuildCatError( - "Tests can't build catalogues:\n" + e.stderr.decode() + f"Tests can't build catalogue '{name}' from '{path}':\n{e.stderr.decode()}\n\n{e.stdout.decode()}" ) from None diff --git a/python/test/unit/test_domain_decompositions.py b/python/test/unit/test_domain_decompositions.py index b08ef2b1..28f3cda9 100644 --- a/python/test/unit/test_domain_decompositions.py +++ b/python/test/unit/test_domain_decompositions.py @@ -76,7 +76,7 @@ class TestDomain_Decompositions(unittest.TestCase): self.assertEqual(grp.kind, arb.cell_kind.cable) # 1 cpu core, 1 gpu; assumes all cells will be placed on gpu in a single cell group - @unittest.skipIf(gpu_enabled is False, "GPU not enabled") + @unittest.skipIf(not gpu_enabled, "GPU not enabled") def test_domain_decomposition_homogenous_GPU(self): n_cells = 10 recipe = homo_recipe(n_cells) @@ -139,7 +139,7 @@ class TestDomain_Decompositions(unittest.TestCase): self.assertEqual(k, recipe.cell_kind(gid)) # 1 cpu core, 1 gpu; assumes cable cells will be placed on gpu in a single cell group; spike cells are on cpu in cell groups of size 1 - @unittest.skipIf(gpu_enabled is False, "GPU not enabled") + @unittest.skipIf(not gpu_enabled, "GPU not enabled") def test_domain_decomposition_heterogenous_GPU(self): n_cells = 10 recipe = hetero_recipe(n_cells) diff --git a/python/test/unit_distributed/test_domain_decompositions.py b/python/test/unit_distributed/test_domain_decompositions.py index b8fbbfb8..5535f02f 100644 --- a/python/test/unit_distributed/test_domain_decompositions.py +++ b/python/test/unit_distributed/test_domain_decompositions.py @@ -207,7 +207,7 @@ class TestDomain_Decompositions_Distributed(unittest.TestCase): self.assertEqual(grp.kind, arb.cell_kind.cable) # 1 node with 1 cpu core, 1 gpu; assumes all cells will be placed on gpu in a single cell group - @unittest.skipIf(gpu_enabled is False, "GPU not enabled") + @unittest.skipIf(not gpu_enabled, "GPU not enabled") def test_domain_decomposition_homogenous_GPU(self): if mpi_enabled: diff --git a/scripts/build-catalogue.in b/scripts/arbor-build-catalogue similarity index 63% rename from scripts/build-catalogue.in rename to scripts/arbor-build-catalogue index 6deea74e..1bf7b0e3 100755 --- a/scripts/build-catalogue.in +++ b/scripts/arbor-build-catalogue @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import arbor as A import subprocess as sp import sys from tempfile import mkdtemp @@ -10,32 +11,24 @@ import argparse import re -def parse_arguments(): - def append_slash(s): - return s + "/" if s and not s.endswith("/") else s - - class ConciseHelpFormatter(argparse.HelpFormatter): - def __init__(self, **kwargs): - super(ConciseHelpFormatter, self).__init__(max_help_position=20, **kwargs) - - def _format_action_invocation(self, action): - if not action.option_strings: - return super(ConciseHelpFormatter, self)._format_action_invocation( - action - ) - else: - optstr = ", ".join(action.option_strings) - if action.nargs == 0: - return optstr - else: - return optstr + " " + self._format_args(action, action.dest.upper()) +config = A.config() +prefix = Path(config['prefix']) +if not prefix.exists(): + try: + # Example <>/lib/python3.10/site-packages/arbor + altern = Path(A.__path__[0]).parent.parent.parent.parent + print(f"Warning: prefix '{prefix}' does not exist, falling back to '{altern}'.", file=sys.stderr) + prefix = altern + except: + print(f"Error: Neither prefix '{prefix}' nor fallback '{altern}' exist; giving up.", file=sys.stderr) + exit(-1) +def parse_arguments(): parser = argparse.ArgumentParser( description="Generate dynamic catalogue and build it into a shared object.", usage="%(prog)s catalogue_name mod_source_dir", add_help=False, - formatter_class=ConciseHelpFormatter, - ) + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("name", metavar="name", type=str, help="Catalogue name.") @@ -45,10 +38,9 @@ def parse_arguments(): nargs="+", default=[], type=str, - help="""Advanced: Raw mechanisms as C++ files. Per <name> the -files <name>.hpp, <name>_cpu.cpp must be present -in the target directory and with GPU support -also <name>_gpu.cpp and <name>_gpu.cu (if not given -C).""", + help="""Raw C++ mechanisms; per <name> the files <name>.hpp, +<name>_cpu.cpp (if CPU enabled), <name>_gpu.cpp and <name>_gpu.cu (if GPU +enabled) must be present in the target directory.""", ) parser.add_argument( @@ -62,26 +54,61 @@ also <name>_gpu.cpp and <name>_gpu.cu (if not given -C).""", parser.add_argument("-q", "--quiet", action="store_true", help="Less output.") + parser.add_argument("--cpu", + default=True, + help="Enable CPU support.") + + parser.add_argument( + "--debug", + nargs="?", + metavar="path", + const=True, + default=None, + help="Don't clean up the generated temp cpp code." + +" Can be a target path for the generated code.", + ) + parser.add_argument( - "-g", "--gpu", metavar="gpu", - help="Enable GPU support, valid options: cuda|hip|cuda-clang.", + default=config["gpu"] if config["gpu"] else "none", + choices=["none", "cuda", "hip", "cuda-clang"], + help=f"Enable GPU support", ) parser.add_argument( - "-C", "--no-cpu", action="store_true", help="Disable CPU support." + "--cxx", + metavar="cxx", + default=config["CXX"], + help='Use this C++ compiler.', ) parser.add_argument( - "-d", - "--debug", - nargs="?", - metavar="path", - const=True, - default=False, - help="Don't clean up the generated temp cpp code." - + " Can be a target path for the generated code.", + "--prefix", + metavar="prefix", + default=prefix, + help="Arbor's install prefix.", + ) + + parser.add_argument( + "--bin", + metavar="bin", + default=config["binary_path"], + help="Look here for Arbor utils like modcc; relative to prefix.", + ) + + parser.add_argument( + "--lib", + metavar="lib", + default=config["lib_path"], + help="Look here for Arbor's CMake config; relative to prefix.", + ) + + parser.add_argument( + "--data", + metavar="data", + default=config["data_path"], + help="Look here for Arbor supplementals like generate_catalogue; relative to prefix.", ) parser.add_argument( @@ -102,37 +129,52 @@ verbose = args["verbose"] and not quiet debug = args["debug"] raw = args["raw"] gpu = args["gpu"] -cpu = not args["no_cpu"] +cpu = args["cpu"] -if gpu: - if gpu == "cuda": - gpu_support = """ +if gpu == "cuda": + gpu_support = f""" +include(FindCUDAToolkit) add_compile_definitions(ARB_CUDA) add_compile_definitions(ARB_HAVE_GPU) +find_package(CUDAToolkit) +enable_language(CUDA) +set(CMAKE_CUDA_STANDARD 14) +set(CMAKE_CUDA_HOST_COMPILER {args["cxx"]}) +""" +elif gpu == "cuda-clang": + print("CUDA-Clang support is currently considered experimental only.") + gpu_support = f""" +add_compile_definitions(ARB_CUDA) +add_compile_definitions(ARB_HAVE_GPU) +find_package(CUDAToolkit) enable_language(CUDA) -set(CMAKE_CUDA_HOST_COMPILER @CMAKE_CXX_COMPILER@) -set(CMAKE_CUDA_ARCHITECTURES @CMAKE_CUDA_ARCHITECTURES@) +set(CMAKE_CUDA_STANDARD 14) +add_compile_options(-xcuda --cuda-gpu-arch=sm_60 --cuda-gpu-arch=sm_70 --cuda-gpu-arch=sm_80 --cuda-path=${CUDA_TOOLKIT_ROOT_DIR})) """ - else: - print( - f"Unsupported GPU target: {gpu}. If you need support for HIP or Clang-CUDA, please check here: https://github.com/arbor-sim/arbor/issues/1783" - ) - exit(-1) -else: +elif gpu == "hip": + print("HIP support is currently considered experimental only.") + gpu_support = f""" +add_compile_definitions(ARB_HIP) +add_compile_definitions(ARB_HAVE_GPU) +add_compile_options(-xhip --amdgpu-target=gfx906 --amdgpu-target=gfx900) +""" +elif gpu == "none": gpu_support = """ # GPU: Disabled """ +else: + print(f"Internal Error: Unknown GPU type: {gpu}", file=sys.stderr) + exit(-1) -this_path = Path(__file__).parent -data_path = (this_path / "@ARB_REL_DATADIR@").resolve() -pack_path = (this_path / "@ARB_REL_PACKAGEDIR@").resolve() -exec_path = this_path.resolve() +bindir = Path(args["prefix"]) / args["bin"] +datdir = Path(args["prefix"]) / args["data"] / "arbor" +pakdir = Path(args["prefix"]) / args["lib"] / "cmake" / "arbor" for path in [ - exec_path / "modcc", - data_path / "BuildModules.cmake", - pack_path / "arbor-config.cmake", + bindir / "modcc", + datdir / "BuildModules.cmake", + pakdir / "arbor-config.cmake", ]: if not path.exists(): print(f"Could not find required tool: {path}. Please check your installation.") @@ -141,8 +183,11 @@ for path in [ cmake = f""" cmake_minimum_required(VERSION 3.9) project({name}-cat LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -set(arbor_DIR {pack_path}) +set(arbor_DIR {pakdir}) find_package(arbor REQUIRED) {gpu_support} set(CMAKE_BUILD_TYPE release) @@ -152,7 +197,7 @@ set(CMAKE_CXX_FLAGS ${{ARB_CXX_FLAGS}}) include(BuildModules.cmake) set(ARB_WITH_EXTERNAL_MODCC true) -find_program(modcc NAMES modcc PATHS {exec_path}) +find_program(modcc NAMES modcc PATHS {bindir}) make_catalogue_standalone( NAME {name} @@ -210,7 +255,7 @@ with TemporaryDirectory() as tmp: os.chdir(tmp / "build") with open(tmp / "CMakeLists.txt", "w") as fd: fd.write(cmake) - shutil.copy2(f"{data_path}/BuildModules.cmake", tmp) + shutil.copy2(f"{datdir}/BuildModules.cmake", tmp) out = tmp / "build" / "generated" / name os.makedirs(out, exist_ok=True) -- GitLab