From 4cdc7206c4c8b7969f6f02f426004dbd036b5225 Mon Sep 17 00:00:00 2001 From: "Olga T. Pearce" Date: Mon, 14 Oct 2024 17:57:18 -0500 Subject: [PATCH 1/3] Adding a CudaExperiment, and variables to Base and CudaExperiment --- lib/benchpark/cuda.py | 131 ++++++++++++++++++++++++++++++++++++ lib/benchpark/experiment.py | 31 +++++++++ 2 files changed, 162 insertions(+) create mode 100644 lib/benchpark/cuda.py diff --git a/lib/benchpark/cuda.py b/lib/benchpark/cuda.py new file mode 100644 index 00000000..192e7a1f --- /dev/null +++ b/lib/benchpark/cuda.py @@ -0,0 +1,131 @@ +# Copyright 2023 Lawrence Livermore National Security, LLC and other +# Benchpark Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: Apache-2.0 + +from typing import Dict +import yaml # TODO: some way to ensure yaml available + +from benchpark.directives import ExperimentSystemBase +import benchpark.spec +import benchpark.paths +import benchpark.repo +import benchpark.runtime +import benchpark.variant + +bootstrapper = benchpark.runtime.RuntimeResources(benchpark.paths.benchpark_home) +bootstrapper.bootstrap() + +import ramble.language.language_base # noqa +import ramble.language.language_helpers # noqa + + +class CudaExperiment(ExperimentSystemBase): + """This is the superclass for all benchpark experiments. + + ***The Experiment class*** + + Experiments are written in pure Python. + + There are two main parts of a Benchpark experiment: + + 1. **The experiment class**. Classes contain ``directives``, which are + special functions, that add metadata (variants) to packages (see + ``directives.py``). + + 2. **Experiment instances**. Once instantiated, an experiment is + essentially a collection of files defining an experiment in a + Ramble workspace. + """ + + variant("cuda", default=False, description="Build and run with CUDA") + + + + # + # These are default values for instance variables. + # + + # This allows analysis tools to correctly interpret the class attributes. + variants: Dict[ + "benchpark.spec.Spec", + Dict[str, benchpark.variant.Variant], + ] + + def __init__(self, spec): + self.spec: "benchpark.spec.ConcreteExperimentSpec" = spec + super().__init__() + + def compute_include_section(self): + # include the config directory + # TODO: does this need to change to interop with System class + return ["./configs"] + + def compute_config_section(self): + # default configs for all experiments + return { + "deprecated": True, + "spack_flags": {"install": "--add --keep-stage", "concretize": "-U -f"}, + } + + def compute_modifiers_section(self): + # by default we use the allocation modifier and no others + return [{"name": "allocation"}] + + def compute_applications_section(self): + # TODO: is there some reasonable default? + variables = {} + variables["n_gpus"] = num_procs # TODO: num_procs will be defined in the child... + + raise NotImplementedError( + "Each experiment must implement compute_applications_section" + ) + + def compute_spack_section(self): + # TODO: is there some reasonable default based on known variable names? + system_specs = {} + system_specs["cuda_version"] = "{default_cuda_version}" + system_specs["cuda_arch"] = "{cuda_arch}" + system_specs["blas"] = "cublas-cuda" #TODO: can we define this in Lapack/Blas CudaExperiment? + + package_specs = {} + package_specs["cuda"] = { + "pkg_spec": "cuda@{}+allow-unsupported-compilers".format( + system_specs["cuda_version"] + ), + "compiler": system_specs["compiler"], + } + + # TODO: can we define this in Lapack/Blas CudaExperiment? + package_specs[system_specs["blas"]] = ( + {} + ) # empty package_specs value implies external package + + # TODO: can we define this in Hypre CudaExperiment? + package_specs["hypre"]["pkg_spec"] += "+cuda cuda_arch={}".format( + system_specs["cuda_arch"] + ) + package_specs[app_name]["pkg_spec"] += "+cuda cuda_arch={}".format( + system_specs["cuda_arch"] + ) + + raise NotImplementedError( + "Each experiment must implement compute_spack_section" + ) + + def compute_ramble_dict(self): + # This can be overridden by any subclass that needs more flexibility + return { + "ramble": { + "include": self.compute_include_section(), + "config": self.compute_config_section(), + "modifiers": self.compute_modifiers_section(), + "applications": self.compute_applications_section(), + "software": self.compute_spack_section(), + } + } + + def write_ramble_dict(self, filepath): + ramble_dict = self.compute_ramble_dict() + with open(filepath, "w") as f: + yaml.dump(ramble_dict, f) diff --git a/lib/benchpark/experiment.py b/lib/benchpark/experiment.py index a461a9ee..6698d181 100644 --- a/lib/benchpark/experiment.py +++ b/lib/benchpark/experiment.py @@ -70,12 +70,43 @@ def compute_modifiers_section(self): def compute_applications_section(self): # TODO: is there some reasonable default? + variables = {} + variables["n_ranks"] = num_procs # TODO: num_procs will be defined in the child... + n_resources = num_procs + raise NotImplementedError( "Each experiment must implement compute_applications_section" ) def compute_spack_section(self): # TODO: is there some reasonable default based on known variable names? + system_specs = {} + system_specs["compiler"] = "default-compiler" + system_specs["mpi"] = "default-mpi" + system_specs["lapack"] = "lapack" #TODO: can we define this in lapack experiment? + + package_specs = {} + package_specs[system_specs["mpi"]] = ( + {} + ) # empty package_specs value implies external package + + # TODO: Can we define this in lapack/blas experiment? + package_specs[system_specs["lapack"]] = ( + {} + ) # empty package_specs value implies external package + + # TODO: is there a way to define hypre experiment, where this would live? + package_specs["hypre"] = { + "pkg_spec": f"hypre@{hypre_version} +mpi+mixedint~fortran", + "compiler": system_specs["compiler"], + } + + # TODO: is there a way to generically do this? + package_specs[app_name] = { + "pkg_spec": f"{app_name}@{app_version} +mpi", + "compiler": system_specs["compiler"], + } + raise NotImplementedError( "Each experiment must implement compute_spack_section" ) From eab92115fb73d0c7d9c9d53d6b8391169f5bd9aa Mon Sep 17 00:00:00 2001 From: pearce8 Date: Thu, 17 Oct 2024 08:38:03 -0500 Subject: [PATCH 2/3] WIP, addressing some of the comments --- lib/benchpark/experiment.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/benchpark/experiment.py b/lib/benchpark/experiment.py index 6698d181..614a624c 100644 --- a/lib/benchpark/experiment.py +++ b/lib/benchpark/experiment.py @@ -69,38 +69,29 @@ def compute_modifiers_section(self): return [{"name": "allocation"}] def compute_applications_section(self): - # TODO: is there some reasonable default? + # Require that the experiment defines num_procs variables = {} - variables["n_ranks"] = num_procs # TODO: num_procs will be defined in the child... - n_resources = num_procs - + variables["n_ranks"] = self.num_procs + raise NotImplementedError( "Each experiment must implement compute_applications_section" ) + def needs_external(pkgs_dict, system_specs, pkg_name): + # TODO: how to compose these here? + pkgs_dict[system_specs[pkg_name]] = {} + def compute_spack_section(self): # TODO: is there some reasonable default based on known variable names? system_specs = {} system_specs["compiler"] = "default-compiler" system_specs["mpi"] = "default-mpi" - system_specs["lapack"] = "lapack" #TODO: can we define this in lapack experiment? package_specs = {} package_specs[system_specs["mpi"]] = ( {} ) # empty package_specs value implies external package - # TODO: Can we define this in lapack/blas experiment? - package_specs[system_specs["lapack"]] = ( - {} - ) # empty package_specs value implies external package - - # TODO: is there a way to define hypre experiment, where this would live? - package_specs["hypre"] = { - "pkg_spec": f"hypre@{hypre_version} +mpi+mixedint~fortran", - "compiler": system_specs["compiler"], - } - # TODO: is there a way to generically do this? package_specs[app_name] = { "pkg_spec": f"{app_name}@{app_version} +mpi", From ce0d81df2bff0a1226063746b434953514a71aac Mon Sep 17 00:00:00 2001 From: pearce8 Date: Thu, 17 Oct 2024 08:38:12 -0500 Subject: [PATCH 3/3] WIP, addressing some of the comments --- lib/benchpark/cuda.py | 63 +++++++++++-------------------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/lib/benchpark/cuda.py b/lib/benchpark/cuda.py index 192e7a1f..7390a083 100644 --- a/lib/benchpark/cuda.py +++ b/lib/benchpark/cuda.py @@ -6,7 +6,7 @@ from typing import Dict import yaml # TODO: some way to ensure yaml available -from benchpark.directives import ExperimentSystemBase +from benchpark.directives import Experiment import benchpark.spec import benchpark.paths import benchpark.repo @@ -20,28 +20,12 @@ import ramble.language.language_helpers # noqa -class CudaExperiment(ExperimentSystemBase): - """This is the superclass for all benchpark experiments. - - ***The Experiment class*** - - Experiments are written in pure Python. - - There are two main parts of a Benchpark experiment: - - 1. **The experiment class**. Classes contain ``directives``, which are - special functions, that add metadata (variants) to packages (see - ``directives.py``). - - 2. **Experiment instances**. Once instantiated, an experiment is - essentially a collection of files defining an experiment in a - Ramble workspace. +class CudaExperiment(Experiment): + """Auxiliary class which contains CUDA variant, dependencies and conflicts + and is meant to unify and facilitate its usage. """ - variant("cuda", default=False, description="Build and run with CUDA") - - # # These are default values for instance variables. # @@ -68,27 +52,20 @@ def compute_config_section(self): "spack_flags": {"install": "--add --keep-stage", "concretize": "-U -f"}, } - def compute_modifiers_section(self): - # by default we use the allocation modifier and no others - return [{"name": "allocation"}] - - def compute_applications_section(self): - # TODO: is there some reasonable default? - variables = {} - variables["n_gpus"] = num_procs # TODO: num_procs will be defined in the child... - - raise NotImplementedError( - "Each experiment must implement compute_applications_section" - ) + # def compute_applications_section(self): + # # TODO: The default in GPU experiments is 1 MPI rank per GPU. + # # What goes here? def compute_spack_section(self): - # TODO: is there some reasonable default based on known variable names? + # CUDA specific versions system_specs = {} system_specs["cuda_version"] = "{default_cuda_version}" system_specs["cuda_arch"] = "{cuda_arch}" - system_specs["blas"] = "cublas-cuda" #TODO: can we define this in Lapack/Blas CudaExperiment? package_specs = {} + # TODO: help!! + # package_specs["cuda"] = needs_external( + package_specs["cuda"] = { "pkg_spec": "cuda@{}+allow-unsupported-compilers".format( system_specs["cuda_version"] @@ -96,22 +73,14 @@ def compute_spack_section(self): "compiler": system_specs["compiler"], } - # TODO: can we define this in Lapack/Blas CudaExperiment? - package_specs[system_specs["blas"]] = ( - {} - ) # empty package_specs value implies external package - - # TODO: can we define this in Hypre CudaExperiment? - package_specs["hypre"]["pkg_spec"] += "+cuda cuda_arch={}".format( - system_specs["cuda_arch"] - ) package_specs[app_name]["pkg_spec"] += "+cuda cuda_arch={}".format( system_specs["cuda_arch"] ) - - raise NotImplementedError( - "Each experiment must implement compute_spack_section" - ) + + return { + "packages": {k: v for k, v in package_specs.items() if v}, + "environments": {app_name: {"packages": list(package_specs.keys())}}, + } def compute_ramble_dict(self): # This can be overridden by any subclass that needs more flexibility