Skip to content

Commit 60aaed3

Browse files
mpolson64facebook-github-bot
authored andcommitted
Genericize interactive loop away from AxClient (#1143)
Summary: Pull Request resolved: #1143 Move the interactive loop away from requiring AxClient. Use new generic infra to replicate behavior of previous diff in a new method named `interactive_optimize_with_client`. Reviewed By: lena-kashtelyan Differential Revision: D39545664 fbshipit-source-id: af1b923b17770b159fc374359a3e94a42f7f02ba
1 parent ac00137 commit 60aaed3

File tree

2 files changed

+68
-34
lines changed

2 files changed

+68
-34
lines changed

ax/service/interactive_loop.py

+65-31
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from logging import Logger
88
from queue import Queue
99
from threading import Event, Lock, Thread
10-
from typing import Callable, Tuple
10+
from typing import Any, Callable, Dict, Tuple
1111

1212
from ax.core.types import TEvaluationOutcome, TParameterization
1313

@@ -21,11 +21,15 @@
2121
IDLE_SLEEP_SEC = 0.1
2222

2323

24-
# TODO[mpolson64] Create `optimize` method that constructs its own ax_client
25-
def optimize_with_client(
26-
ax_client: AxClient,
24+
def interactive_optimize(
2725
num_trials: int,
2826
candidate_queue_maxsize: int,
27+
# Callable[[Queue[Tuple[TParameterization, int]], Event, ...], None]
28+
candidate_generator_function: Callable[..., None],
29+
candidate_generator_kwargs: Dict[str, Any],
30+
# Callable[[Queue[Tuple[int, TEvaluationOutcome]], Event, ...], None]
31+
data_attacher_function: Callable[..., None],
32+
data_attacher_kwargs: Dict[str, Any],
2933
elicitation_function: Callable[[TParameterization], TEvaluationOutcome],
3034
) -> None:
3135
"""
@@ -40,27 +44,23 @@ def optimize_with_client(
4044
only be used in contexts where it is necessary for the user to not experience any
4145
"lag" while candidates are being generated.
4246
43-
Extract results of the experiment from the AxClient passed in.
44-
45-
The basic structure is as follows: One thread tries for a lock on the AxClient,
46-
generates a candidate, and enqueues it to a candidate queue. Another thread tries
47-
for the same lock, takes all the trial outcomes in the outcome queue, and attaches
48-
them to the AxClient. The main thread pops a candidate off the candidate queue,
49-
elicits response from the user, and puts the response onto the outcome queue.
50-
5147
Args:
52-
ax_client: An AxClient properly configured for the experiment intended to be
53-
run. Construct and configure this before passing in.
5448
num_trials: The total number of trials to be run.
5549
candidate_queue_maxsize: The maximum number of candidates to pregenerate.
50+
candidate_generator_function: A function taking in a queue and event that
51+
enqueues candidates (generated by any means). See
52+
`ax_client_candidate_generator` for an example.
53+
candidate_generator_kwargs: kwargs to be passed into
54+
`candidate_generator_function` when it is spawned as a thread.
55+
data_attacher_function: A function taking in a queue and event that attaches
56+
observations to Ax. See `ax_client_data_attacher` for an example.
57+
data_attacher_kwargs: kwargs to be passed into `data_attacher_function` when
58+
it is spawned as a thread.
5659
elicitation_function: Function from parameterization (as returned by
57-
`AxClient.get_next_trial`) to outcome (as expected by
58-
`AxClient.complete_trial`).
60+
`AxClient.get_next_trial`) to outcome (as expected by
61+
`AxClient.complete_trial`).
5962
"""
6063

61-
# Construct a lock to ensure only one thread my access the AxClient at any moment
62-
ax_client_lock = Lock()
63-
6464
# Construct queues to buffer inputs and outputs of the AxClient
6565
candidate_queue: "Queue[Tuple[TParameterization, int]]" = Queue(
6666
maxsize=candidate_queue_maxsize
@@ -74,18 +74,21 @@ def optimize_with_client(
7474
# Construct threads to run candidate thread-safe pregeneration and thread-safe
7575
# data attaching respectively
7676
candidate_generator_thread = Thread(
77-
target=_candidate_generator,
77+
target=candidate_generator_function,
7878
args=(
79-
ax_client,
80-
ax_client_lock,
81-
candidate_generator_stop_event,
8279
candidate_queue,
80+
candidate_generator_stop_event,
8381
num_trials,
8482
),
83+
kwargs=candidate_generator_kwargs,
8584
)
8685
data_attacher_thread = Thread(
87-
target=_data_attacher,
88-
args=(ax_client, ax_client_lock, data_attacher_stop_event, data_queue),
86+
target=data_attacher_function,
87+
args=(
88+
data_queue,
89+
data_attacher_stop_event,
90+
),
91+
kwargs=data_attacher_kwargs,
8992
)
9093

9194
candidate_generator_thread.start()
@@ -105,12 +108,43 @@ def optimize_with_client(
105108
data_attacher_stop_event.set()
106109

107110

108-
def _candidate_generator(
111+
def interactive_optimize_with_client(
109112
ax_client: AxClient,
110-
lock: Lock,
111-
stop_event: Event,
113+
num_trials: int,
114+
candidate_queue_maxsize: int,
115+
elicitation_function: Callable[[TParameterization], TEvaluationOutcome],
116+
) -> None:
117+
"""
118+
Implementation of `interactive_loop` using the AxClient. Extract results of the
119+
experiment from the AxClient passed in.
120+
121+
The basic structure is as follows: One thread tries for a lock on the AxClient,
122+
generates a candidate, and enqueues it to a candidate queue. Another thread tries
123+
for the same lock, takes all the trial outcomes in the outcome queue, and attaches
124+
them to the AxClient. The main thread pops a candidate off the candidate queue,
125+
elicits response from the user, and puts the response onto the outcome queue.
126+
"""
127+
128+
# Construct a lock to ensure only one thread my access the AxClient at any moment
129+
ax_client_lock = Lock()
130+
131+
interactive_optimize(
132+
num_trials=num_trials,
133+
candidate_queue_maxsize=candidate_queue_maxsize,
134+
candidate_generator_function=ax_client_candidate_generator,
135+
candidate_generator_kwargs={"ax_client": ax_client, "lock": ax_client_lock},
136+
data_attacher_function=ax_client_data_attacher,
137+
data_attacher_kwargs={"ax_client": ax_client, "lock": ax_client_lock},
138+
elicitation_function=elicitation_function,
139+
)
140+
141+
142+
def ax_client_candidate_generator(
112143
queue: "Queue[Tuple[TParameterization, int]]",
144+
stop_event: Event,
113145
num_trials: int,
146+
ax_client: AxClient,
147+
lock: Lock,
114148
) -> None:
115149
"""Thread-safe method for generating the next trial from the AxClient and
116150
enqueueing it to the candidate queue. The number of candidates pre-generated is
@@ -138,11 +172,11 @@ def _candidate_generator(
138172
time.sleep(IDLE_SLEEP_SEC)
139173

140174

141-
def _data_attacher(
175+
def ax_client_data_attacher(
176+
queue: "Queue[Tuple[int, TEvaluationOutcome]]",
177+
stop_event: Event,
142178
ax_client: AxClient,
143179
lock: Lock,
144-
stop_event: Event,
145-
queue: "Queue[Tuple[int, TEvaluationOutcome]]",
146180
) -> None:
147181
"""Thread-safe method for attaching evaluation outcomes to the AxClient from the
148182
outcome queue. If the AxClient's lock is acquired all data in the outcome queue

ax/service/tests/test_interactive_loop.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import numpy as np
1313
from ax.core.types import TEvaluationOutcome
1414
from ax.service.ax_client import AxClient, TParameterization
15-
from ax.service.interactive_loop import optimize_with_client
15+
from ax.service.interactive_loop import interactive_optimize_with_client
1616
from ax.utils.common.testutils import TestCase
1717
from ax.utils.measurement.synthetic_functions import hartmann6
1818
from ax.utils.testing.mock import fast_botorch_optimize
@@ -49,7 +49,7 @@ def _elicit(parameterization: TParameterization) -> TEvaluationOutcome:
4949
minimize=True,
5050
)
5151

52-
optimize_with_client(
52+
interactive_optimize_with_client(
5353
ax_client=ax_client,
5454
num_trials=15,
5555
candidate_queue_maxsize=3,
@@ -90,7 +90,7 @@ def _elicit(parameterization: TParameterization) -> TEvaluationOutcome:
9090
ax_client.generation_strategy._steps[0].max_parallelism = 1
9191

9292
with self.assertLogs(logger="ax", level=WARN) as logger:
93-
optimize_with_client(
93+
interactive_optimize_with_client(
9494
ax_client=ax_client,
9595
num_trials=3,
9696
candidate_queue_maxsize=3,

0 commit comments

Comments
 (0)