-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Tasks] Add run_experiment_group() task type (#27)
- Loading branch information
Showing
10 changed files
with
198 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import pathlib | ||
|
||
_MODULE_PATH = pathlib.Path(__file__).resolve().parent | ||
|
||
STDLIB_FILES = [_MODULE_PATH / "run_experiment_group.py"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from typing import Dict, Iterable, NamedTuple, Optional, Sequence | ||
from conductor.utils.experiment_options import OptionValue | ||
from conductor.errors import ( | ||
ExperimentGroupDuplicateName, | ||
ExperimentGroupInvalidExperimentInstance, | ||
) | ||
|
||
|
||
class ExperimentInstance(NamedTuple): | ||
name: str | ||
options: Dict[str, OptionValue] | ||
|
||
|
||
def run_experiment_group( | ||
name: str, | ||
run: str, | ||
experiments: Iterable[ExperimentInstance], | ||
deps: Optional[Sequence[str]] = None, | ||
) -> None: | ||
task_deps = deps if deps is not None else [] | ||
relative_experiment_identifiers = [] | ||
|
||
try: | ||
seen_experiment_names = set() | ||
for experiment in experiments: | ||
if not isinstance(experiment, ExperimentInstance): | ||
raise ExperimentGroupInvalidExperimentInstance(task_name=name) | ||
if experiment.name in seen_experiment_names: | ||
raise ExperimentGroupDuplicateName( | ||
task_name=name, instance_name=experiment.name | ||
) | ||
|
||
seen_experiment_names.add(experiment.name) | ||
# run_experiment(): Defined by Conductor at runtime | ||
# pylint: disable=undefined-variable | ||
run_experiment( # type: ignore | ||
name=experiment.name, | ||
run=run, | ||
options=experiment.options, | ||
deps=task_deps, | ||
) | ||
relative_experiment_identifiers.append(":" + experiment.name) | ||
|
||
except TypeError as ex: | ||
raise ExperimentGroupInvalidExperimentInstance(task_name=name) from ex | ||
|
||
# combine(): Defined by Conductor at runtime | ||
# pylint: disable=undefined-variable | ||
combine( # type: ignore | ||
name=name, | ||
deps=relative_experiment_identifiers, | ||
) |
8 changes: 8 additions & 0 deletions
8
tests/fixture-projects/experiments/invalid-group-duplicate/COND
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
run_experiment_group( | ||
name="test", | ||
run="exit 0", | ||
experiments=[ | ||
ExperimentInstance(name="test-inst", options={}), | ||
ExperimentInstance(name="test-inst", options={}), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
run_experiment_group( | ||
name="test", | ||
run="exit 0", | ||
experiments=[ | ||
{"name": "test-inst", "options": {}}, | ||
{"name": "test-inst-2", "options": {}}, | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
run_experiment_group( | ||
name="threads", | ||
run="./run.sh", | ||
experiments=[ | ||
ExperimentInstance( | ||
name="threads-{}".format(threads), | ||
options={"threads": threads}, | ||
) | ||
for threads in range(1, 5) | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#! /bin/bash | ||
|
||
echo $1 > ${COND_OUT}/output.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import pathlib | ||
from conductor.config import TASK_OUTPUT_DIR_SUFFIX | ||
from .conductor_runner import ( | ||
ConductorRunner, | ||
FIXTURE_TEMPLATES, | ||
) | ||
|
||
|
||
def test_run_experiment_group(tmp_path: pathlib.Path): | ||
cond = ConductorRunner.from_template(tmp_path, FIXTURE_TEMPLATES["experiments"]) | ||
result = cond.run("//sweep:threads") | ||
assert result.returncode == 0 | ||
assert cond.output_path.is_dir() | ||
|
||
combined_dir = pathlib.Path( | ||
cond.output_path, "sweep", ("threads" + TASK_OUTPUT_DIR_SUFFIX) | ||
) | ||
assert combined_dir.is_dir() | ||
|
||
expected_tasks = ["threads-{}".format(threads) for threads in range(1, 5)] | ||
|
||
# Ensure combined task dirs all exist and are non-empty. | ||
combined_dir_names = [path.name for path in combined_dir.iterdir()] | ||
for task_name in expected_tasks: | ||
assert task_name in combined_dir_names | ||
assert any(True for _ in (combined_dir / task_name).iterdir()) | ||
|
||
# Ensure individual experiment dirs also exist. | ||
sweep_output = combined_dir.parent | ||
assert sweep_output.is_dir() | ||
sweep_output_count = len(list(sweep_output.iterdir())) | ||
|
||
# 4 experiment instances plus the combined output dir. | ||
assert sweep_output_count == 5 | ||
|
||
|
||
def test_run_experiment_group_invalid_duplicate(tmp_path: pathlib.Path): | ||
cond = ConductorRunner.from_template(tmp_path, FIXTURE_TEMPLATES["experiments"]) | ||
result = cond.run("//invalid-group-duplicate:test") | ||
assert result.returncode != 0 | ||
|
||
|
||
def test_run_experiment_group_invalid_type(tmp_path: pathlib.Path): | ||
cond = ConductorRunner.from_template(tmp_path, FIXTURE_TEMPLATES["experiments"]) | ||
result = cond.run("//invalid-group-type:test") | ||
assert result.returncode != 0 |