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

Feature/experiment scaling #375

Draft
wants to merge 30 commits into
base: develop
Choose a base branch
from
Draft

Conversation

rfhaque
Copy link
Collaborator

@rfhaque rfhaque commented Sep 25, 2024

Draft implementation of the scaling functionality for benchmarks. Currently only strong scaling is implemented. Works for amg2023/openmp
bin/benchpark experiment init --dest ./experiments kripke programming_model=openmp scaling=strong strong-scaling-factor=2 strong-scaling-num-exprs=3

and kripke/openmp
bin/benchpark experiment init --dest ./experiments amg2023 programming_model=openmp experiment=strong strong-scaling-factor=2 strong-scaling-num-exprs=4

@github-actions github-actions bot added the experiment New or modified experiment label Sep 25, 2024
Comment on lines 5 to 27
variant(
"strong-scaling-factor",
default="2",
description="Strong-scaling factor (factor by which to increase resources)",
)

variant(
"strong-scaling-num-exprs",
default="4",
description="Number of strong-scaling experiments",
)

variant(
"weak-scaling-factor",
default="2",
description="Weak-scaling factor (factor by which to increase resources and problem sizes)",
)

variant(
"weak-scaling-num-exprs",
default="4",
description="Number of weak-scaling experiments",
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to implement conditional variants so we can make this clearer. This could be

variant("scaling", default="none", values=("none", "strong", "weak"), description="...")

variant("scaling-factor", default=2, values=lambda x: x >= 2, when="scaling=weak", description="...")
variant("scaling-factor", default=2, values=lambda x: x >= 2, when="scaling=strong", description="...")

variant("scaling-iterations", default=4, values=lambda x: x > 1, when="scaling=weak", description="...")
variant("scaling-iterations", default=4, values=lambda x: x > 1, when="scaling=strong", description="...")

And with a little additional syntax beyond that we could get rid of the duplicate lines.

from benchpark.directives import variant


class Scaling(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would call this ScalingExperiment instead of just Scaling, I think it's a bit clearer that way

Comment on lines 35 to 48
def generate_strong_scaling_parameters(self, initial_resource_list: list):
scaling_factor = int(self.spec.variants["strong-scaling-factor"][0])
num_exprs = int(self.spec.variants["strong-scaling-num-exprs"][0]) - 1
round_robin_order = self.compute_round_robin_order(initial_resource_list)
resource_list = [[x] for x in initial_resource_list]

while num_exprs > 0:
for idx in round_robin_order:
for i, r in enumerate(resource_list):
r.append(r[-1]*scaling_factor if i == idx else r[-1])
num_exprs=num_exprs-1
if not num_exprs:
break
return resource_list
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 method can be simplified slightly

Suggested change
def generate_strong_scaling_parameters(self, initial_resource_list: list):
scaling_factor = int(self.spec.variants["strong-scaling-factor"][0])
num_exprs = int(self.spec.variants["strong-scaling-num-exprs"][0]) - 1
round_robin_order = self.compute_round_robin_order(initial_resource_list)
resource_list = [[x] for x in initial_resource_list]
while num_exprs > 0:
for idx in round_robin_order:
for i, r in enumerate(resource_list):
r.append(r[-1]*scaling_factor if i == idx else r[-1])
num_exprs=num_exprs-1
if not num_exprs:
break
return resource_list
def generate_strong_scaling_parameters(self, initial_resource_list: list):
scaling_factor = int(self.spec.variants["strong-scaling-factor"][0])
num_exprs = int(self.spec.variants["strong-scaling-num-exprs"][0])
round_robin_order = self.compute_round_robin_order(initial_resource_list)
resource_list = [[x] for x in initial_resource_list]
for i in range(num_exprs - 1):
idx = (i + round_robin_order[0]) * len(initial_resource_list)
for r_idx, resource in enumerate(resource_list):
next_value = resource[-1] * scaling_value if r_idx == idx else resource[-1]
resource.append(next_value)
return resource_list

break
return resource_list

def generate_weak_scaling_parameters(self, initial_resource_list: list, initial_problem_size_list: list):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it's necessarily safe for us to assume that the resource requirements and the problem size are expressed in such neatly compatible ways. I think we should probably assume these could be different dimensions (e.g. resource list is a scalar, problem size is 3D) and round-robin them each separately.

@becker33
Copy link
Collaborator

The variant logic can be simplified once we merge #379

@pearce8 pearce8 marked this pull request as draft September 27, 2024 19:47
@pearce8 pearce8 added the WIP A work-in-progress not yet ready to commit label Sep 27, 2024
Copy link
Collaborator

@scheibelp scheibelp left a comment

Choose a reason for hiding this comment

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

I don't see any major issues, but have a few suggestions

from benchpark.directives import variant


class ScalingExperiment(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

(no change needed) I originally thought it would be problematic that this defines variant()s but does not inherit Experiment, but I think that as long as any package that inherits this also inherits Experiment, there will be no issue with this.

That being said, IIRC one of your arguments about not inheriting Experiment was a concern with overriding methods in Experiment, which is technically still possible regardless of whether ScalingExperiment inherits Experiment.

)

# input parameters:
# 1. input_variables: dict[str, int | tuple(str), list[int]]. Input variables
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is another way to express:

input_variables key value pairs are either of type str: int or tuple(str): list(int)

I can say this mypy-like syntax of expression is confusing (e.g. if list(int) values always have tuple(str) keys and never str keys, this isn't how it can be expressed in mypy). I recommend replacing what you have on this line with the quoted explanation that you used elsewhere.

If that's not the intended meaning, can you explain how this input differs?

# the number of dimensions in an (ascending) round-robin order
#
# output:
# scaling_order: list[int]. list of num_exprs values, one for each dimension,
Copy link
Collaborator

Choose a reason for hiding this comment

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

num_exprs is not an input to this particular function, I think this is a list of indices.

# output:
# output_variables: dict[str, int | list[int]]. num_exprs values for each
# dimension of the input variable scaled by the scaling_factor according to the
# scaling policy
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 example inputs and outputs would be useful in the comments/doc for this function, which you could adapt from https://github.com/LLNL/benchpark/blob/develop/experiments/amg2023/openmp/ramble.yaml, for example:

input:
  [[10], [10], [10]]
  [[2], [2], [2]]
output:
  nx: ['10', '20', '20']
  ny: ['10', '10', '20']
  nz: ['10', '10', '10']
  px: ['2', '4', '4']
  py: ['2', '2', '4']
  pz: ['2', '2', '2']

if p_idx
== scaling_order_index[exp_num % len(scaling_order_index)]
else p_val[-1]
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO a comment like:

Take initial parameterized vector for experiment, for each experiment after the first, scale one dimension of that vector by the scaling factor; cycle through the dimensions in round-robin fashion.

would be useful here

nx = "nx"
ny = "ny"
nz = "nz"
num_procs = f"{{{px}}} * {{{py}}} * {{{pz}}}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

(minor) I'm assuming you are using px as an abbreviation for "px", and not because it's value might change: in that case"{px} * {py} * {pz}" is simpler

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@scheibelp This will be removed once I add variants for all px, py and pz

val = input_variables[next(iter(input_variables))]
min_dim = val.index(min(val)) if isinstance(val, list) else 0

return [(min_dim + i) % n_dims for i in range(n_dims)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

(suggestion) Since this doesn't sort (e.g. doesn't make sure that the second-lowest dimension is handled second), IMO this would be simpler if it just returned min_dim.

default="4",
values=int,
description="Number of experiments to be generated",
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

#379 was merged, but these are not conditioned on when= (as mentioned in #375 (comment)). I think you can effectively turn scaling off by setting scaling-iterations=1, although it still might be clearer to use when.

Comment on lines +52 to +65
variant(
"scaling-factor",
default="2",
values=int,
description="Factor by which to scale values of problem variables",
)

variant(
"scaling-iterations",
default="4",
values=int,
description="Number of experiments to be generated",
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@scheibelp I have incorporated your earlier suggestions into the scaling methods. I also moved the entire scaling implementation directly into the base Experiment class. Please review and comment this is a good approach. Especially the fact that the base Experiment class will have two variants scaling-factor and scaling-iterations defined in it

# dimension of the input variable scaled by the scaling_factor according to the
# scaling policy
def scale_experiment_variables(
self, input_variables, scaling_factor, num_exprs, scaling_variable=None
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hi @dyokelson, I've added the option to specify a scaling_variable to the experiment (all variables in input_variables will be scaled according to the ordering defined on scaling_variable

input_params,
int(self.spec.variants["scaling-factor"][0]),
int(self.spec.variants["scaling-iterations"][0]),
scaling_variable,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dyokelson Usage of the scaling_variable

@rfhaque
Copy link
Collaborator Author

rfhaque commented Oct 12, 2024

@pearce8 @scheibelp If the changes in the PR are acceptable, we can take this to main. I can make further changes in a separate PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experiment New or modified experiment WIP A work-in-progress not yet ready to commit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants