Skip to content

Commit a2512ce

Browse files
committed
extending tests
1 parent f3fb533 commit a2512ce

8 files changed

Lines changed: 109 additions & 25 deletions

File tree

hydra_plugins/hyper_smac/hyper_smac.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from copy import deepcopy
56
from pathlib import Path
67

78
import pandas as pd
@@ -10,6 +11,7 @@
1011
from omegaconf import OmegaConf
1112
from smac import Scenario
1213
from smac.facade import HyperparameterOptimizationFacade
14+
from smac.intensifier.hyperband import Hyperband
1315
from smac.runhistory.dataclasses import TrialInfo, TrialValue
1416

1517

@@ -43,12 +45,27 @@ class HyperSMACAdapter:
4345
def __init__(self, smac):
4446
"""Initialize the adapter."""
4547
self.smac = smac
48+
if isinstance(self.smac._intensifier, Hyperband):
49+
self.hyperband = True
50+
self.total_configs = 0
51+
self.smac._intensifier.__post_init__()
52+
self.n_configs_per_stage = deepcopy(self.smac._intensifier._n_configs_in_stage)
53+
self.current_bracket = 0
54+
self.total_stages = len(self.n_configs_per_stage.keys())
4655

4756
def ask(self):
4857
"""Ask for the next configuration."""
4958
smac_info = self.smac.ask()
5059
info = Info(config=smac_info.config, budget=smac_info.budget, load_path=None, seed=smac_info.seed)
51-
return info, False, False
60+
terminate = False
61+
if self.hyperband:
62+
self.total_configs += 1
63+
if self.total_configs >= sum(self.n_configs_per_stage[self.current_bracket]):
64+
self.current_bracket += 1
65+
self.total_configs = 0
66+
terminate = True
67+
print(info, terminate)
68+
return info, terminate, False
5269

5370
def tell(self, info, value):
5471
"""Tell the result of the configuration."""

hydra_plugins/hypersweeper/hypersweeper_backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def sweep(self, arguments: list[str]) -> list | None:
167167
if isinstance(v, np.integer | np.floating):
168168
v = v.item() # noqa: PLW2901
169169
if isinstance(v, np.str_):
170-
v = str(v)
170+
v = str(v) # noqa: PLW2901
171171
OmegaConf.update(final_config, k, v, force_add=True)
172172

173173
with open(Path(optimizer.output_dir) / "final_config.yaml", "w+") as fp:

hydra_plugins/hypersweeper/hypersweeper_sweeper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ def run(self, verbose=False):
462462
infos = []
463463
t = 0
464464
terminate = False
465+
print(t < self.max_parallel, terminate, trial_termination, optimizer_termination)
465466
while t < self.max_parallel and not terminate and not trial_termination and not optimizer_termination:
466467
if not any(b is None for b in self.history["budget"]) and self.budget is not None:
467468
budget_termination = sum(self.history["budget"]) >= self.budget

tests/test_initialization.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,16 @@ def test_init_without_n_trials(config):
192192
config["n_trials"] = None
193193
sweeper = HypersweeperSweeper(**config)
194194
assert sweeper.n_trials is None, "Number of trials should be set to None"
195-
assert sweeper.max_parallel == config.get("job_array_size_limit", 100), (
196-
"Max parallelization should match job array size limit without n_trials"
195+
job_array_limit = config.get("job_array_size_limit", 100)
196+
if "seeds" in config and config["seeds"] is not None:
197+
job_array_limit = max(job_array_limit // len(config["seeds"]), 1)
198+
assert sweeper.max_parallel == job_array_limit, (
199+
f"Max parallelization should match job array size limit without n_trials (is {sweeper.max_parallel})"
197200
)
198201
shutil.rmtree("some_dir", ignore_errors=True) # Clean up the base directory if it was created
199202
shutil.rmtree("checkpoints", ignore_errors=True)
200203

204+
201205
@mark.skipif(IN_GITHUB_ACTIONS, reason="Test doesn't work in Github Actions due to wandb logging.")
202206
@mark.parametrize("config", [TEST_CONFIG1, TEST_CONFIG2])
203207
def test_init_with_wandb(config):

tests/test_job_spawning.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import re
77
import shutil
88
import subprocess
9-
import pytest
109
from pathlib import Path
1110

11+
import pytest
12+
1213

13-
@pytest.mark.parametrize("max_parallel, job_limit", [(0.05, 10), (0.5, 9)])
14+
@pytest.mark.parametrize(("max_parallel", "job_limit"), [(0.05, 10), (0.5, 9)])
1415
def test_max_parallel(max_parallel, job_limit):
1516
if Path("branin_non_max").exists():
1617
shutil.rmtree(Path("branin_max_parallel"))
@@ -30,16 +31,21 @@ def test_max_parallel(max_parallel, job_limit):
3031
keyword = "Launching "
3132
all_keyword_indices = [m.start() for m in re.finditer(keyword, process_logs)]
3233
n_jobs_spawned = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
33-
n_times_spawned = process_logs.count(keyword)
34+
n_times_spawned = process_logs.count(keyword)
3435
total_jobs_spawned = sum(n_jobs_spawned)
3536

36-
assert total_jobs_spawned == job_limit, f"Total number of spawned jobs doesn't match the overall budget. Used: {total_jobs_spawned}, expected: {job_limit}."
37+
assert total_jobs_spawned == job_limit, (
38+
f"Total number of spawned jobs doesn't match the budget. Used: {total_jobs_spawned}, expected: {job_limit}."
39+
)
3740
for i, n_jobs in enumerate(n_jobs_spawned):
38-
assert n_jobs > 0, f"No jobs spawned in execution {i+1}/{n_times_spawned}."
39-
assert n_jobs <= max_parallel*job_limit or n_jobs == 1, f"Number of spawned jobs exceeds the maximum parallelization limit in execution {i+1}/{n_times_spawned}: {n_jobs}."
41+
assert n_jobs > 0, f"No jobs spawned in execution {i + 1}/{n_times_spawned}."
42+
assert n_jobs <= max_parallel * job_limit or n_jobs == 1, (
43+
f"Number of spawned jobs exceeds the maximum parallelization limit {i + 1}/{n_times_spawned}: {n_jobs}."
44+
)
4045
shutil.rmtree(Path("branin_max_parallel"))
4146

42-
@pytest.mark.parametrize("max_parallel, job_limit, seeds", [(0.05, 10, []), (0.5, 9, [1,2])])
47+
48+
@pytest.mark.parametrize(("max_parallel", "job_limit", "seeds"), [(0.05, 10, []), (0.5, 9, [1, 2])])
4349
def test_max_parallel_with_seeds(max_parallel, job_limit, seeds):
4450
if Path("branin_non_max").exists():
4551
shutil.rmtree(Path("mlp_max_parallel_with_seeds"))
@@ -60,11 +66,15 @@ def test_max_parallel_with_seeds(max_parallel, job_limit, seeds):
6066
keyword = "Launching "
6167
all_keyword_indices = [m.start() for m in re.finditer(keyword, process_logs)]
6268
n_jobs_spawned = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
63-
n_times_spawned = process_logs.count(keyword)
69+
n_times_spawned = process_logs.count(keyword)
6470
total_jobs_spawned = sum(n_jobs_spawned)
6571

66-
assert total_jobs_spawned == job_limit*max(1, len(seeds)), f"Total number of spawned jobs doesn't match the overall budget. Used: {total_jobs_spawned}, expected: {job_limit*max(1, len(seeds))}."
72+
assert total_jobs_spawned == job_limit * max(1, len(seeds)), (
73+
f"Number of jobs doesn't match budget. Used: {total_jobs_spawned}, expected: {job_limit * max(1, len(seeds))}."
74+
)
6775
for i, n_jobs in enumerate(n_jobs_spawned):
68-
assert n_jobs > 0, f"No jobs spawned in execution {i+1}/{n_times_spawned}."
69-
assert n_jobs <= max_parallel*job_limit or n_jobs == 1, f"Number of spawned jobs exceeds the maximum parallelization limit in execution {i+1}/{n_times_spawned}: {n_jobs}."
70-
shutil.rmtree(Path("mlp_max_parallel_with_seeds"))
76+
assert n_jobs > 0, f"No jobs spawned in execution {i + 1}/{n_times_spawned}."
77+
assert n_jobs <= max_parallel * job_limit or n_jobs == 1, (
78+
f"Number of spawned jobs exceeds the maximum parallelization limit {i + 1}/{n_times_spawned}: {n_jobs}."
79+
)
80+
shutil.rmtree(Path("mlp_max_parallel_with_seeds"))

tests/test_logging.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import shutil
66
import subprocess
77
from pathlib import Path
8+
89
import pandas as pd
910

1011

@@ -45,7 +46,8 @@ def test_logfiles():
4546
assert "budget" in incumbent.columns, "Incumbent file missing expected column 'budget'."
4647
assert "config_id" in incumbent.columns, "Incumbent file missing expected column 'config_id'."
4748
assert "total_wallclock_time" in incumbent.columns, "Incumbent file missing expected column 'wall_clock_time'."
48-
assert "total_optimization_time" in incumbent.columns, "Incumbent file missing expected column 'total_optimization_time'."
49-
49+
assert "total_optimization_time" in incumbent.columns, (
50+
"Incumbent file missing expected column 'total_optimization_time'."
51+
)
5052

51-
shutil.rmtree(Path("branin_logging"))
53+
shutil.rmtree(Path("branin_logging"))

tests/test_runs/test_smac.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import re
34
import shutil
45
import subprocess
56
from pathlib import Path
@@ -27,3 +28,46 @@ def test_smac_branin_example():
2728
assert not runhistory.empty, "Run history is empty"
2829
assert len(runhistory) == 5, "Run history should contain 5 entries"
2930
shutil.rmtree(Path("branin_smac"))
31+
32+
33+
def test_smac_hyperband_termination():
34+
if Path("mlp_smac_hyperband").exists():
35+
shutil.rmtree(Path("mlp_smac_hyperband"))
36+
process_logs = subprocess.check_output(
37+
[
38+
"python",
39+
"examples/mlp.py",
40+
"--config-name=mlp_smac",
41+
"-m",
42+
"hydra.sweeper.n_trials=5",
43+
"hydra.run.dir=mlp_smac_hyperband",
44+
"hydra.sweep.dir=mlp_smac_hyperband",
45+
"hydra.sweeper.n_trials=24",
46+
"+hydra.sweeper.sweeper_kwargs.max_parallelization=1",
47+
]
48+
).decode("utf-8")
49+
assert Path("mlp_smac_hyperband").exists(), "Run directory not created"
50+
51+
# Goal: first launch 13 jobs in bracket 1, then 6 in bracket 2, then 3 in bracket 3, then 2 and terminate
52+
keyword = "Launching "
53+
all_keyword_indices = [m.start() for m in re.finditer(keyword, process_logs)]
54+
n_jobs_spawned = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
55+
total_jobs_spawned = sum(n_jobs_spawned)
56+
57+
assert total_jobs_spawned == 24, (
58+
f"Total number of spawned jobs doesn't match the overall budget. Used: {total_jobs_spawned}, expected: 24."
59+
)
60+
assert n_jobs_spawned[0] == 13, (
61+
f"Number of spawned jobs in bracket 1 is incorrect. Used: {n_jobs_spawned[0]}, expected: 13."
62+
)
63+
assert n_jobs_spawned[1] == 6, (
64+
f"Number of spawned jobs in bracket 2 is incorrect. Used: {n_jobs_spawned[1]}, expected: 6."
65+
)
66+
assert n_jobs_spawned[2] == 3, (
67+
f"Number of spawned jobs in bracket 3 is incorrect. Used: {n_jobs_spawned[2]}, expected: 3."
68+
)
69+
assert n_jobs_spawned[3] == 2, (
70+
f"Number of spawned jobs in bracket 4 before termination is incorrect. Used: {n_jobs_spawned[3]}, expected: 2."
71+
)
72+
73+
shutil.rmtree(Path("mlp_smac_hyperband"))

tests/test_termination_conditions.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import re
77
import shutil
88
import subprocess
9-
import pytest
109
from pathlib import Path
1110

11+
import pytest
12+
1213

1314
@pytest.mark.parametrize("n_trials", [1, 7, 10])
1415
def test_terminate_n_trials(n_trials):
@@ -28,12 +29,15 @@ def test_terminate_n_trials(n_trials):
2829
assert Path("branin_trial_termination").exists(), "Run directory not created"
2930
keyword = "Launching "
3031
all_keyword_indices = [m.start() for m in re.finditer(keyword, process_logs)]
31-
n_jobs_spawned = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
32+
n_jobs_spawned = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
3233
total_jobs_spawned = sum(n_jobs_spawned)
3334

34-
assert total_jobs_spawned == n_trials, f"Total number of spawned jobs doesn't match the n_trials. Used: {total_jobs_spawned}, expected: {n_trials}."
35+
assert total_jobs_spawned == n_trials, (
36+
f"Total number of spawned jobs doesn't match the n_trials. Used: {total_jobs_spawned}, expected: {n_trials}."
37+
)
3538
shutil.rmtree(Path("branin_trial_termination"))
3639

40+
3741
@pytest.mark.parametrize("budget", [5, 10, 25])
3842
def test_terminate_budget(budget):
3943
if Path("mlp_budget_termination").exists():
@@ -52,6 +56,8 @@ def test_terminate_budget(budget):
5256
assert Path("mlp_budget_termination").exists(), "Run directory not created"
5357
keyword = "epochs="
5458
all_keyword_indices = [m.start() for m in re.finditer(keyword, process_logs)]
55-
all_budgets = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
56-
assert sum(all_budgets) <= budget, f"Total budget used exceeds the maximum budget. Used: {sum(all_budgets)}, budget: {budget}."
57-
shutil.rmtree(Path("mlp_budget_termination"))
59+
all_budgets = [int(process_logs[occ + len(keyword)]) for occ in all_keyword_indices]
60+
assert sum(all_budgets) <= budget, (
61+
f"Total budget used exceeds the maximum budget. Used: {sum(all_budgets)}, budget: {budget}."
62+
)
63+
shutil.rmtree(Path("mlp_budget_termination"))

0 commit comments

Comments
 (0)