Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .github/workflows/run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,6 @@ on:
BENCHPARK_CODECOV_TOKEN:
required: true
jobs:
check_errors:
runs-on: ubuntu-24.04
steps:
- name: Checkout Benchpark
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Add needed Python libs
run: |
pip install -r ./requirements.txt
- name: Run negative tests via helper
shell: bash
run: |
fail() { echo "TEST FAILED: $*" >&2; exit 1; }

# one-time setup
./bin/benchpark system init --dest dane llnl-cluster

# 1) Non-MPIOnlyExperiment Test
set +e
# babelstream is not an MPIOnlyExperiment
stderr_output="$({ ./bin/benchpark experiment init --dest babelstream --system dane babelstream; } 2>&1)"
status=$?
set -e
[[ $status -ne 0 ]] || fail "Expected non-zero exit status, got $status"
# Check for the specific BenchparkError message
expected="cannot run with MPI only"
grep -Fq "$expected" <<< "$stderr_output" && echo "failed as expected: $expected" || fail "Expected error message not found in stderr. Got: $stderr_output"

# 2) Multiple scaling options
set +e
# Cannot use both +strong and +weak
stderr_output="$({ ./bin/benchpark experiment init --dest kripke --system dane kripke+strong+weak; } 2>&1)"
status=$?
set -e
[[ $status -ne 0 ]] || fail "Expected non-zero exit status, got $status"
# Check for the specific BenchparkError message
expected="spec cannot specify multiple scaling options"
grep -Fq "$expected" <<<"$stderr_output" && echo "failed as expected: $expected" || fail "Expected error message not found in stderr. Got: $stderr_output"
Comment on lines -24 to -44
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These tests can now be moved to lib/benchpark/test/experiment_errors.py, which shows how much more concise the pytest format is

saxpy:
runs-on: ubuntu-24.04
steps:
Expand Down
8 changes: 2 additions & 6 deletions experiments/babelstream/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@
from benchpark.directives import variant, maintainers
from benchpark.experiment import Experiment
from benchpark.caliper import Caliper
from benchpark.cuda import CudaExperiment
from benchpark.rocm import ROCmExperiment
from benchpark.openmp import OpenMPExperiment
from benchpark.models import ModelsType, Models


class Babelstream(
Experiment,
Caliper,
CudaExperiment,
ROCmExperiment,
OpenMPExperiment,
Models(ModelsType.Openmp, ModelsType.Cuda, ModelsType.Rocm),
):
variant(
"workload",
Expand Down
10 changes: 2 additions & 8 deletions experiments/kripke/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@

from benchpark.directives import variant, maintainers
from benchpark.experiment import Experiment
from benchpark.mpi import MpiOnlyExperiment
from benchpark.openmp import OpenMPExperiment
from benchpark.cuda import CudaExperiment
from benchpark.rocm import ROCmExperiment
from benchpark.models import ModelsType, Models
from benchpark.scaling import ScalingMode, Scaling
from benchpark.caliper import Caliper


class Kripke(
Experiment,
MpiOnlyExperiment,
OpenMPExperiment,
CudaExperiment,
ROCmExperiment,
Models(ModelsType.Mpionly, ModelsType.Openmp, ModelsType.Cuda, ModelsType.Rocm),
Scaling(ScalingMode.Strong, ScalingMode.Weak, ScalingMode.Throughput),
Caliper,
):
Expand Down
30 changes: 0 additions & 30 deletions lib/benchpark/cuda.py

This file was deleted.

32 changes: 22 additions & 10 deletions lib/benchpark/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,30 +256,39 @@ def __init__(self, spec):

self.package_specs = {}

# Set available programming models for checks
models = set()
for cls in self.__class__.mro():
models.update(getattr(cls, "_available_programming_models", ()))
self.programming_models = list(models)

# Explicitly ordered list. "mpi" first
models = ["mpi"] + ["openmp", "cuda", "rocm"]
valid_models = []
invalid_models = []
for model in models:
# Experiment specifying model in add_package_spec that it doesn't implement
if (
self.spec.satisfies("+" + model)
and model not in self.programming_models
):
invalid_models.append(model)
if self.spec.satisfies("+" + model):
valid_models.append(model)
# Experiment specifying model in add_package_spec that it doesn't implement
if model not in self.programming_models:
invalid_models.append(model)
# MPI is always valid if with another programming model, even if no mpionly
if "mpi" in invalid_models and len(valid_models) > 1:
invalid_models.remove("mpi")
# Case where there are no experiments specified in experiment.py
if len(self.programming_models) == 0:
raise NotImplementedError(
raise BenchparkError(
f"Please specify a programming model in your {self.name}/experiment.py (e.g. MpiOnlyExperiment, OpenMPExperiment, CudaExperiment, ROCmExperiment). See other experiments for examples."
)
elif len(invalid_models) > 0:
raise NotImplementedError(
raise BenchparkError(
f'{invalid_models} are not valid programming models for "{self.name}". Choose from {self.programming_models}.'
)
# Check if experiment is trying to run in MpiOnly mode without being an MpiOnlyExperiment
elif "mpi" not in str(self.spec) and not any(
self.spec.satisfies("+" + model) for model in models[1:]
):
raise NotImplementedError(
raise BenchparkError(
f'"{self.name}" cannot run with MPI only without inheriting from MpiOnlyExperiment. Choose from {self.programming_models}'
)

Expand Down Expand Up @@ -456,7 +465,10 @@ def compute_applications_section_wrapper(self):
for cls in self.helpers:
helper_prefix = cls.get_helper_name_prefix()
if helper_prefix:
expr_helper_list.append(helper_prefix)
if isinstance(helper_prefix, list):
expr_helper_list.extend(helper_prefix)
else:
expr_helper_list.append(helper_prefix)
expr_name_suffix = "_".join(expr_helper_list + self.expr_var_names)

self.check_required_variables()
Expand Down
85 changes: 85 additions & 0 deletions lib/benchpark/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2023 Lawrence Livermore National Security, LLC and other
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this module should be called experiment_models, or live in an an experiments subdir

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok I will rename this to experiment_models

# Benchpark Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: Apache-2.0


from benchpark.error import BenchparkError
from benchpark.directives import variant, requires
from benchpark.experiment import ExperimentHelper
from enum import Enum


class ModelsType(Enum):
Mpionly = "mpi"
Openmp = "openmp"
Cuda = "cuda"
Rocm = "rocm"


def Models(*types):
for ty in types:
if not isinstance(ty, ModelsType):
raise ValueError(f"Invalid programming model: {ty}")

# Normalize once so we can reuse
_available = tuple(t.value for t in types)

class BaseModel:
requires("mpi", when="+mpi")
requires("rocm", when="+rocm")
requires("cuda", when="+cuda")
requires("openmp", when="+openmp")

variant("mpi", default=True, description="Run with MPI")
variant("rocm", default=False, description="Build and run with ROCm")
variant("cuda", default=False, description="Build and run with CUDA")
variant("openmp", default=False, description="Build and run with OpenMP")
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like these variants are always defined on every experiment, regardless of whether it inherits the associated model types.

It should be allowed to split these variants into separate modules and imported classes (as they were originally); at the same time, customizing it all from one file is ok too, I wonder if it's possible to address the above concern though (it might not be possible to conditionally define the variants).

Copy link
Collaborator Author

@michaelmckinsey1 michaelmckinsey1 Oct 2, 2025

Choose a reason for hiding this comment

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

This is true, regardless of which models are defined, if the experiment inherits from Models(...) all programming models will show in the spec +mpi~rocm~cuda~openmp. I would like to note that this is the same behavior for the Scaling, e.g. even if the experiment does not define Throughput scaling it will show ~throughput in the spec. Not that this is correct either, but if we can find a solution for this it should be applied to both the programming models and scaling.

I don't know of a solution to the underlying bug that would enable separate classes. Perhaps something like

# mpi.py
def Mpi():
  class BaseMPI:
       variant("mpi")
# rocm.py
def Rocm():
  class BaseRocm:
       variant("rocm")

would provide the correct behavior, but the design seems maybe a bit convoluted.

It seems like the relevant piece for the behavior we desire is making sure the variant is not applied when the class is imported.


# Class-level list of supported models for any class that includes this mixin
_available_programming_models = _available

# Handy instance-level property (works on Experiment instances)
@property
def available_programming_models(self):
# If multiple mixins contribute, merge them from MRO at runtime
models = set()
for cls in type(self).mro():
models.update(getattr(cls, "_available_programming_models", ()))
return tuple(sorted(models))

# Quick check helper
@staticmethod
def supports_model(name: str) -> bool:
return name in _available

# Helper class (unchanged except for optional new method)
class Helper(ExperimentHelper):
def get_helper_name_prefix(self):
models = []
for s in [
ModelsType.Mpionly.value,
ModelsType.Openmp.value,
ModelsType.Cuda.value,
ModelsType.Rocm.value,
]:
if self.spec.satisfies("+" + s):
models.append(s)
if len(models) > 0:
return models
return "no_model"

# Optional: expose *available* (not selected) models via helper, too
def get_available_models(self):
models = set()
for cls in type(self).__mro__:
models.update(getattr(cls, "_available_programming_models", ()))
return tuple(sorted(models))

return type(
"ProgrammingModel",
(BaseModel,),
{
"Helper": Helper,
},
)
23 changes: 0 additions & 23 deletions lib/benchpark/mpi.py

This file was deleted.

26 changes: 0 additions & 26 deletions lib/benchpark/openmp.py

This file was deleted.

30 changes: 0 additions & 30 deletions lib/benchpark/rocm.py

This file was deleted.

28 changes: 28 additions & 0 deletions lib/benchpark/test/experiment_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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

import pytest

import benchpark.spec
from benchpark.error import BenchparkError


def test_programming_model_checks():
# babelstream mpi-only not valid
with pytest.raises(BenchparkError, match=r"mpi.*are not valid programming models"):
spec = benchpark.spec.ExperimentSpec("babelstream").concretize()
experiment = spec.experiment # noqa: F841

# stream+openmp not valid
with pytest.raises(Exception, match="not a valid variant"):
spec = benchpark.spec.ExperimentSpec(
"stream+openmp workload=stream"
).concretize()
experiment = spec.experiment

# Multiple scaling options not valid
with pytest.raises(BenchparkError, match="cannot specify multiple scaling options"):
spec = benchpark.spec.ExperimentSpec("kripke+strong+weak").concretize()
experiment = spec.experiment
Loading