-
Notifications
You must be signed in to change notification settings - Fork 6.9k
[Tune] Add OSS Vizier to Ray Tune #48684
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
Closed
Closed
Changes from all commits
Commits
Show all changes
77 commits
Select commit
Hold shift + click to select a range
e1974f8
Add Vizier to suggestion.rst
xingyousong 0de73fa
Create vizier_search.py
xingyousong 661ded0
Create __init__.py
xingyousong 036c046
Update vizier_search.py
xingyousong c343d00
Update vizier_search.py
xingyousong 9df67be
Update vizier_search.py
xingyousong 3514901
Update vizier_search.py
xingyousong 8769149
Update vizier_search.py
xingyousong 531dd5e
Update vizier_search.py
xingyousong b364fbe
Update vizier_search.py
xingyousong cdf72ee
Update vizier_search.py
xingyousong ba38db7
Update vizier_search.py
xingyousong 03df99b
Update __init__.py
xingyousong 2074b74
Update vizier_search.py
xingyousong 34ff4fe
Update vizier_search.py
xingyousong 33f3f57
Update vizier_search.py
xingyousong 86a96dd
Update usage.py
xingyousong de1767d
Update test_searchers.py
xingyousong c906333
Update test_convergence.py
xingyousong 2b69458
Update test_tune_restore_warm_start.py
xingyousong 450d600
Create vizier_example.py
xingyousong aa684c9
Update vizier_search.py
xingyousong 50c5afb
Update test_tune_restore_warm_start.py
xingyousong eb5bb46
Update vizier_search.py
xingyousong e8fa570
Update vizier_search.py
xingyousong 57774b9
Update vizier_search.py
xingyousong 8a8581c
Update vizier_search.py
xingyousong ae1bbbe
Update vizier_search.py
xingyousong d3d332d
Update vizier_search.py
xingyousong 4a0bb79
Update vizier_search.py
xingyousong 64ab61e
Update test_sample.py
xingyousong 0cb772f
Update test_tune_restore_warm_start.py
xingyousong 9e00290
Merge branch 'ray-project:master' into patch-1
xingyousong 598bc6e
Update suggestion.rst
xingyousong 45af364
Merge branch 'ray-project:master' into patch-1
xingyousong c67d89c
Merge branch 'ray-project:master' into patch-1
xingyousong c6750b3
Merge branch 'ray-project:master' into patch-1
xingyousong 298cd9f
Merge branch 'ray-project:master' into patch-1
xingyousong 251a83a
Merge branch 'ray-project:master' into patch-1
xingyousong e85b126
Update python/ray/tune/search/vizier/vizier_search.py
xingyousong 3900d2b
Fix search space erroring
xingyousong fac06a4
Fix indentation
xingyousong 162430f
Merge branch 'ray-project:master' into patch-1
xingyousong f08fd97
Merge branch 'ray-project:master' into patch-1
xingyousong 659417e
Merge branch 'ray-project:master' into patch-1
xingyousong 00389b2
Update python/ray/tune/search/vizier/vizier_search.py
xingyousong a003fb4
Update tune-requirements.txt
xingyousong f746f8d
Update vizier_search.py
xingyousong e4be104
Update tune-requirements.txt
xingyousong fadc9f4
Merge branch 'master' into patch-1
xingyousong bfe2495
Merge branch 'ray-project:master' into patch-1
xingyousong e4527d7
Update tune-requirements.txt
xingyousong 9152a51
Update vizier_search.py
xingyousong 54e04d5
Merge branch 'ray-project:master' into patch-1
xingyousong 8e054dc
Merge branch 'ray-project:master' into patch-1
xingyousong e1c5cd7
Merge branch 'ray-project:master' into patch-1
xingyousong 5302328
remove attrs
matthewdeng c7875ec
lint
matthewdeng c8cfdc1
lint
matthewdeng ec21366
move to test requirements
matthewdeng e38880d
unpin gprcio-tools
matthewdeng 9ca1581
unpin gprcio-tools
matthewdeng 1421eb5
Update tune-test-requirements.txt
xingyousong 06c4af5
Update tune-test-requirements.txt
xingyousong 0c76cdd
Update tune-test-requirements.txt
xingyousong e7b48cd
Update tune-test-requirements.txt
xingyousong 5a082aa
Update tune-test-requirements.txt
xingyousong 025f008
Update tune-test-requirements.txt
xingyousong 12b17c5
Update tune-test-requirements.txt
xingyousong 727a67a
Merge branch 'master' into patch-1
matthewdeng f6ed011
update requirements_compiled.txt
matthewdeng 1f902f2
Update tune-test-requirements.txt
xingyousong c9bf6d1
update requirements_compiled.txt
matthewdeng 0ffd1ec
disable tests
matthewdeng 7e5643e
unittest
matthewdeng b974a71
fix'
matthewdeng 677172d
Update test_tune_restore_warm_start.py
xingyousong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 |
---|---|---|
|
@@ -31,6 +31,7 @@ | |
"HyperOptSearch", | ||
"NevergradSearch", | ||
"OptunaSearch", | ||
"VizierSearch", | ||
"ZOOptSearch", | ||
} | ||
|
||
|
This file contains hidden or 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,63 @@ | ||
"""This example demonstrates the usage of Vizier with Ray Tune. | ||
|
||
It also checks that it is usable with a separate scheduler. | ||
|
||
Requires the Vizier library to be installed (`pip install google-vizier[jax]`). | ||
""" | ||
import time | ||
|
||
from ray import train, tune | ||
from ray.tune.schedulers import AsyncHyperBandScheduler | ||
from ray.tune.search import ConcurrencyLimiter | ||
from ray.tune.search.vizier import VizierSearch | ||
|
||
|
||
def evaluation_fn(step, width, height): | ||
return (0.1 + width * step / 100) ** (-1) + height * 0.1 | ||
|
||
|
||
def easy_objective(config): | ||
# Hyperparameters | ||
width, height = config["width"], config["height"] | ||
|
||
for step in range(config["steps"]): | ||
# Iterative training function - can be any arbitrary training procedure | ||
intermediate_score = evaluation_fn(step, width, height) | ||
# Feed the score back back to Tune. | ||
train.report({"iterations": step, "mean_loss": intermediate_score}) | ||
time.sleep(0.1) | ||
|
||
|
||
if __name__ == "__main__": | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"--smoke-test", action="store_true", help="Finish quickly for testing" | ||
) | ||
args, _ = parser.parse_known_args() | ||
|
||
algo = VizierSearch() | ||
algo = ConcurrencyLimiter(algo, max_concurrent=4) | ||
scheduler = AsyncHyperBandScheduler() | ||
tuner = tune.Tuner( | ||
easy_objective, | ||
tune_config=tune.TuneConfig( | ||
metric="mean_loss", | ||
mode="min", | ||
search_alg=algo, | ||
scheduler=scheduler, | ||
num_samples=10 if args.smoke_test else 1000, | ||
), | ||
run_config=train.RunConfig( | ||
name="my_exp", | ||
), | ||
param_space={ | ||
"steps": 100, | ||
"width": tune.uniform(0, 20), | ||
"height": tune.uniform(-100, 100), | ||
}, | ||
) | ||
results = tuner.fit() | ||
|
||
print("Best hyperparameters found were: ", results.get_best_result().config) |
This file contains hidden or 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 hidden or 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 @@ | ||
from ray.tune.search.vizier.vizier_search import VizierSearch | ||
|
||
__all__ = ["VizierSearch"] |
This file contains hidden or 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,265 @@ | ||
"""A Vizier Ray Searcher.""" | ||
|
||
import datetime | ||
import json | ||
import logging | ||
import uuid | ||
from typing import Dict, Optional, Union | ||
|
||
from ray.tune.result import DEFAULT_METRIC | ||
from ray.tune.search import UNDEFINED_METRIC_MODE, UNRESOLVED_SEARCH_SPACE, Searcher | ||
from ray.tune.search.variant_generator import parse_spec_vars | ||
|
||
# Make sure that importing this file doesn't crash Ray, even if Vizier wasn't installed. | ||
try: | ||
from vizier import raytune as vzr | ||
from vizier.service import clients | ||
from vizier.service import pyvizier as svz | ||
xingyousong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
IMPORT_SUCCESSFUL = True | ||
except ImportError: | ||
svz = None | ||
IMPORT_SUCCESSFUL = False | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class VizierSearch(Searcher): | ||
"""A wrapper around OSS Vizier to provide trial suggestions. | ||
|
||
OSS Vizier is a Python-based service for black-box optimization based on | ||
Google Vizier, one of the first hyperparameter tuning services | ||
designed to work at scale. | ||
|
||
More info can be found here: https://github.com/google/vizier. | ||
|
||
You will need to install OSS Vizier via the following: | ||
|
||
.. code-block:: bash | ||
|
||
pip install google-vizier[jax] | ||
|
||
Args: | ||
space: A dict mapping parameter names to Tune search spaces | ||
or a Vizier SearchSpace object. | ||
metric: The training result objective value attribute. If None | ||
but a mode was passed, the anonymous metric `_metric` will be used | ||
per default. | ||
mode: One of {"min", "max"}. Determines whether objective is | ||
minimizing or maximizing the metric attribute. | ||
algorithm: Specific algorithm from Vizier's library to use. | ||
"DEFAULT" corresponds to GP-UCB-PE. See https://oss-vizier.readthedocs.io\ | ||
/en/latest/guides/user/supported_algorithms.html for more options. | ||
|
||
Tune automatically converts search spaces to Vizier's format: | ||
|
||
.. code-block:: python | ||
|
||
from ray import tune | ||
from ray.tune.search.bayesopt import VizierSearch | ||
|
||
config = { | ||
"width": tune.uniform(0, 20), | ||
"height": tune.uniform(-100, 100) | ||
} | ||
|
||
vizier = VizierSearch(metric="mean_loss", mode="min") | ||
tuner = tune.Tuner( | ||
my_func, | ||
tune_config=tune.TuneConfig( | ||
search_alg=vizier, | ||
), | ||
param_space=config, | ||
) | ||
tuner.fit() | ||
|
||
Alternatively, you can pass a Vizier `SearchSpace` object manually to the | ||
Searcher: | ||
|
||
.. code-block:: python | ||
|
||
from ray import tune | ||
from ray.tune.search.vizier import VizierSearch | ||
from vizier import pyvizier as vz | ||
|
||
search_space = vz.SearchSpace() | ||
search_space.root.add_float_param('w', 0.0, 5.0) | ||
|
||
vizier = VizierSearch(search_space, metric="mean_loss", mode="min") | ||
tuner = tune.Tuner( | ||
trainable_function, | ||
tune_config=tune.TuneConfig( | ||
search_alg=vizier | ||
) | ||
) | ||
tuner.fit() | ||
|
||
""" | ||
|
||
def __init__( | ||
self, | ||
space: Optional[Union[Dict, "svz.SearchSpace"]] = None, | ||
metric: Optional[str] = None, | ||
mode: Optional[str] = None, | ||
algorithm: Optional[str] = "DEFAULT", | ||
): | ||
assert ( | ||
IMPORT_SUCCESSFUL | ||
), "Vizier must be installed with `pip install google-vizier[jax]`." | ||
super(VizierSearch, self).__init__(metric=metric, mode=mode) | ||
self._algorithm = algorithm | ||
|
||
# For Vizier to identify the unique study. | ||
self._study_id = f"ray_vizier_{uuid.uuid1()}" | ||
|
||
# Mapping from Ray trial id to Vizier Trial client. | ||
self._active_trials: Dict[str, clients.Trial] = {} | ||
|
||
# Vizier service client. | ||
self._study_client: Optional[clients.Study] = None | ||
|
||
if isinstance(space, dict) and space: | ||
resolved_vars, domain_vars, grid_vars = parse_spec_vars(space) | ||
if domain_vars or grid_vars: | ||
logger.warning( | ||
UNRESOLVED_SEARCH_SPACE.format(par="space", cls=type(self)) | ||
) | ||
xingyousong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
space = self.convert_search_space(space) | ||
self._space = space | ||
elif isinstance(space, svz.SearchSpace): | ||
self._space = space | ||
else: | ||
raise TypeError( | ||
"Space must be Tune space or Vizier SearchSpace. Got {}.".format( | ||
type(space) | ||
) | ||
) | ||
|
||
if self._space: | ||
self._setup_vizier() | ||
|
||
def set_search_properties( | ||
self, metric: Optional[str], mode: Optional[str], config: Dict, **spec | ||
) -> bool: | ||
if self._study_client: # The study is already configured. | ||
return False | ||
|
||
self._metric = metric or DEFAULT_METRIC | ||
self._mode = mode | ||
self._space = vzr.SearchSpaceConverter.to_vizier(config) | ||
|
||
self._setup_vizier() | ||
return True | ||
|
||
def _setup_vizier(self) -> None: | ||
if self._mode == "max": | ||
vizier_goal = svz.ObjectiveMetricGoal.MAXIMIZE | ||
elif self._mode == "min": | ||
vizier_goal = svz.ObjectiveMetricGoal.MINIMIZE | ||
else: | ||
raise RuntimeError( | ||
UNDEFINED_METRIC_MODE.format( | ||
cls=self.__class__.__name__, metric=self._metric, mode=self._mode | ||
) | ||
) | ||
|
||
study_config = svz.StudyConfig( | ||
search_space=self._space, | ||
algorithm=self._algorithm, | ||
metric_information=[svz.MetricInformation(self._metric, goal=vizier_goal)], | ||
) | ||
self._study_client = clients.Study.from_study_config( | ||
study_config, owner="raytune", study_id=self._study_id | ||
) | ||
|
||
def on_trial_result(self, trial_id: str, result: Dict) -> None: | ||
if trial_id not in self._active_trials: | ||
raise RuntimeError(f"No active trial for {trial_id}") | ||
if self._study_client is None: | ||
raise RuntimeError( | ||
"VizierSearch not initialized! Set a search space first." | ||
) | ||
trial_client = self._active_trials[trial_id] | ||
xingyousong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
elapsed_secs = ( | ||
datetime.datetime.now().astimezone() | ||
- trial_client.materialize().creation_time | ||
) | ||
trial_client.add_measurement( | ||
svz.Measurement( | ||
{k: v for k, v in result.items() if isinstance(v, float)}, | ||
elapsed_secs=elapsed_secs.total_seconds(), | ||
) | ||
) | ||
|
||
def on_trial_complete( | ||
self, trial_id: str, result: Optional[Dict] = None, error: bool = False | ||
) -> None: | ||
if trial_id not in self._active_trials: | ||
raise RuntimeError(f"No active trial for {trial_id}") | ||
if self.study_client is None: | ||
raise RuntimeError( | ||
"VizierSearch not initialized! Set a search space first." | ||
) | ||
trial_client = self._active_trials[trial_id] | ||
|
||
if error: | ||
# Mark the trial as infeasible. | ||
trial_client.complete( | ||
infeasible_reason=f"Trial {trial_id} failed: {result}" | ||
) | ||
else: | ||
measurement = None | ||
if result: | ||
elapsed_secs = ( | ||
datetime.datetime.now().astimezone() | ||
- trial_client.materialize().creation_time | ||
) | ||
measurement = svz.Measurement( | ||
{k: v for k, v in result.items() if isinstance(v, float)}, | ||
elapsed_secs=elapsed_secs.total_seconds(), | ||
) | ||
trial_client.complete(measurement=measurement) | ||
self._active_trials.pop(trial_id) | ||
|
||
def suggest(self, trial_id: str) -> Optional[Dict]: | ||
suggestions = self._study_client.suggest(count=1, client_id=trial_id) | ||
if not suggestions: | ||
return Searcher.FINISHED | ||
|
||
self._active_trials[trial_id] = suggestions[0] | ||
return self._active_trials[trial_id].parameters | ||
|
||
def save(self, checkpoint_path: str) -> None: | ||
# We assume that the Vizier service continues running, so the only | ||
# information needed to restore this searcher is the mapping from the Ray | ||
# to Vizier trial ids. All other information can become stale and is best | ||
# restored from the Vizier service in restore(). | ||
ray_to_vizier_trial_ids = {} | ||
for trial_id, trial_client in self._active_trials.items(): | ||
ray_to_vizier_trial_ids[trial_id] = trial_client.id | ||
with open(checkpoint_path, "w") as f: | ||
info = { | ||
"study_id": self._study_id, | ||
"ray_to_vizier_trial_ids": ray_to_vizier_trial_ids, | ||
} | ||
json.dump(info, f) | ||
|
||
def restore(self, checkpoint_path: str) -> None: | ||
with open(checkpoint_path, "r") as f: | ||
obj = json.load(f) | ||
|
||
self._study_id = obj["study_id"] | ||
# TODO: This will return a `ResourceNotFoundError` if study doesn't exist. | ||
# Consider adding more explicit error handling later on if this is opaque. | ||
self._study_client = clients.Study.from_owner_and_id("raytune", self._study_id) | ||
self._metric = ( | ||
self._study_client.materialize_study_config().metric_information.item() | ||
) | ||
self._active_trials = {} | ||
for ray_id, vizier_trial_id in obj["ray_to_vizier_trial_ids"].items(): | ||
self._active_trials[ray_id] = self._study_client.get_trial(vizier_trial_id) | ||
|
||
@staticmethod | ||
def convert_search_space(spec: Dict) -> "svz.SearchSpace": | ||
return vzr.SearchSpaceConverter.to_vizier(spec) |
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any more tests you'd recommend running/adding to ensure that all the Vizier functionality is properly supported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question - but first asking, is there any easy way where I can actually test these on my local machine?
I think that's equivalent to: How to install Ray[tune] from a PR, rather than
pip
?ATM I've actually never ran any of these tests myself, just copy pasted + eyeballed from other algorithms' tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can follow the instructions here (main thing is installing with
pip install -e .
from the/ray/python
directory).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a lot of trouble installing manually - my most recent error was:
when I tried
pip install -e .
actually. For now I'm just gonna rely on these Github workflows (e.g. Microcheck) to guide the debugging...