Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
if: ${{ needs.release-please.outputs.release_created }}
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
Expand Down
21 changes: 18 additions & 3 deletions abses/core/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from hydra.core.hydra_config import HydraConf, HydraConfig
from joblib import Parallel, delayed
from omegaconf import DictConfig, OmegaConf
from omegaconf.errors import OmegaConfBaseException
from tqdm.auto import tqdm

from abses.core.job_manager import ExperimentManager
Expand Down Expand Up @@ -102,8 +103,9 @@ def relative_path_from_to(from_path: Path, to_path: Path) -> Path:

# Hydra ships exactly one built-in launcher, `BasicLauncher`, and it is serial.
# Anything outside this prefix is a third-party launcher plugin, which we treat
# as parallel: the launchers that exist in practice (joblib, submitit, ray) all
# are, and assuming parallel only costs us a layer of nesting we skip.
# as parallel unless its own config says otherwise: the launchers that exist in
# practice (joblib, submitit, ray) all are, and assuming parallel only costs us
# a layer of nesting we skip.
_SERIAL_LAUNCHER_PREFIX = "hydra._internal.core_plugins."


Expand All @@ -125,7 +127,20 @@ def launcher_is_parallel(launcher: Optional[DictConfig]) -> bool:
if not launcher:
return False
target = str(launcher.get("_target_", ""))
return bool(target) and not target.startswith(_SERIAL_LAUNCHER_PREFIX)
if not target or target.startswith(_SERIAL_LAUNCHER_PREFIX):
return False
# A plugin can still be told to run serially. `n_jobs` is joblib's knob and
# joblib is the only launcher that spells it that way; `n_jobs: 1` selects
# joblib's sequential backend, which runs every job in the calling process
# exactly like BasicLauncher does. Other values -- including the -1 default
# and an absent key -- leave the launcher concurrent.
try:
n_jobs = OmegaConf.select(launcher, "n_jobs", default=None)
except OmegaConfBaseException:
# An unreadable value tells us nothing; keep the optimistic default
# rather than aborting the run from inside a predicate.
return True
return n_jobs != 1


def run_single(
Expand Down
30 changes: 30 additions & 0 deletions tests/core/test_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,36 @@ def test_plugin_launchers_are_parallel(self, target):
launcher = OmegaConf.create({"_target_": target})
assert launcher_is_parallel(launcher) is True

def test_joblib_with_one_job_is_not_parallel(self):
"""`n_jobs: 1` makes joblib run every job in the calling process.

joblib's `Parallel(n_jobs=1)` uses the sequential backend, so this
launcher is as serial as BasicLauncher despite being a plugin.
"""
launcher = OmegaConf.create(
{
"_target_": "hydra_plugins.hydra_joblib_launcher"
".joblib_launcher.JoblibLauncher",
"n_jobs": 1,
}
)
assert launcher_is_parallel(launcher) is False

def test_unreadable_n_jobs_falls_back_to_parallel(self):
"""An `n_jobs` we cannot read must not crash the run.

This is a predicate on the way to `batch_run`; raising here would abort
the whole experiment. Assuming parallel only costs a layer of nesting.
"""
launcher = OmegaConf.create(
{
"_target_": "hydra_plugins.hydra_joblib_launcher"
".joblib_launcher.JoblibLauncher",
"n_jobs": "${undefined_key}",
}
)
assert launcher_is_parallel(launcher) is True


@contextmanager
def _inside_hydra_job(launcher_target: str, output_dir: Path):
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading