Pin a strategy and seed#
suggest is a pure function of the experiment state, the config, and the ask’s explicit intents (none on this page), so a pinned config plus a pinned seed replays the same sequence of suggestions on the same machine and dependency set. The field catalogue is in Strategies and config; why determinism holds is in Provenance and determinism. Across CPU classes or torch builds the GP rungs can drift, so the numbers below come from one machine.
Pin the strategy, params, and seed#
Build the config once:
{"strategy": <name>, "params": {...}, "seed": <int>, "timebox_mode": <mode>}.strategyis one ofrandom,sobol,gp_ei,ucb_cl,gp_nehvi, or"auto"(the default when omitted: the policy’s own routing with your seed carried along; it takes noparams).Pass the same dict to every
suggest. An explicit seed is used verbatim; without one the engine derives a seed from the state, which is still deterministic.Read
suggestion.provenance:strategyis the rung that answered,fallback_fromthe rung you asked for when the ladder had to step down, andconfig_hashthe config that produced the answer. Pinning a GP rung before any evidence exists ladders tosobolon the first ask, and the receipt says so.
import json
from tauopt import Client
CONFIG = {
"strategy": "gp_ei",
"params": {"num_restarts": 4, "raw_samples": 32},
"seed": 7,
"timebox_mode": "caps",
}
def run(seed):
client = Client(driver="local")
exp = client.create_experiment(
name=f"pinned-seed-{seed}",
parameters=[
{"name": "x1", "type": "double", "bounds": (0.0, 1.0)},
{"name": "x2", "type": "double", "bounds": (0.0, 1.0)},
],
metric={"name": "y", "objective": "minimize"},
)
rows = []
for _ in range(6):
sug = client.suggest(exp.id, config={**CONFIG, "seed": seed})
a = sug.assignments
client.observe(exp.id, suggestion_id=sug.id, value=(a["x1"] - 0.3) ** 2 + (a["x2"] - 0.6) ** 2)
p = sug.provenance
rows.append([p.strategy, p.fallback_from, p.seed, p.config_hash, a])
return rows
first, second, other = run(7), run(7), run(8)
for row in first:
print(json.dumps(row))
print("same seed, identical sequence:", first == second)
print("different seed, identical sequence:", first == other)
["sobol", "gp_ei", 7, "blake2b_0c33c580abb4ef52", {"x1": 0.5792599907144904, "x2": 0.7402846803888679}]
["gp_ei", null, 7, "blake2b_5a8dcaac58aafacc", {"x1": 0.0, "x2": 0.0}]
["gp_ei", null, 7, "blake2b_5a8dcaac58aafacc", {"x1": 0.7783309004894556, "x2": 0.9680527482321146}]
["gp_ei", null, 7, "blake2b_5a8dcaac58aafacc", {"x1": 0.49411697609275884, "x2": 0.6428542374832091}]
["gp_ei", null, 7, "blake2b_5a8dcaac58aafacc", {"x1": 0.28465737967191573, "x2": 0.747232579756825}]
["gp_ei", null, 7, "blake2b_5a8dcaac58aafacc", {"x1": 0.14225492258083972, "x2": 0.6457468785976386}]
same seed, identical sequence: True
different seed, identical sequence: False
The first row’s config_hash differs from the rest because a fallback rung runs with its own default params, and the receipt hashes the config that ran.
Choose a timebox mode for replay#
"budget"(the default): each GP rung gets a wall-clock timebox (params.timebox_s, 10 seconds by default). Pure as long as the timebox does not bind; on a loaded machine a binding timebox ladders down and records it."off": no wall budget; fits and acquisition run to convergence."caps": clock-free like"off", with deterministic work ceilings (fit_maxiter,acq_maxiter), so the answer never depends on load.
Pin "caps" or "off" whenever you need load-independent replay.
Bound the wait#
max_wait_s (a number in (0, 300]) deadlines the whole ask. What happens when it expires depends on the mode and on on_timeout:
Default
on_timeout="ladder": the GP rungs trip and the instant rungs answer, withfallback_fromrecorded.on_timeout="fail": the ask raises503 suggest_timeoutinstead of degrading; hosted responses carryRetry-After, exposed ase.retry_after.timebox_mode="caps"under a deadline: the engine refuses withAskDeadlineExceeded(importable fromtauoptand fromtauopt.engine; a 503ask_deadline_exceededApiErroron both drivers, with noRetry-Afterbecause a caps re-ask repeats deterministic work) at a rung boundary or at delivery, never substituting another rung’s answer, and no suggestion opens; the refusal receipt ridese.point,e.max_wait_s,e.elapsed_s,e.rung, ande.details. Catch the class next toOptimizationCompletein caps-mode loops, or let anexcept ApiErrorhandler see it (both drivers raise the same class).On the local driver, in every timebox mode, whenever the process supervisor is engaged (the default
"auto"or"process"; notsupervisor="off", and not"auto"on Windows, where the ask runs inline with no backstop): a deadline-bearing ask is also backstopped above the engine. If the worker process running it has not answered withinmax(1.25 * max_wait_s, max_wait_s + 3 s)(a kernel that never returns, not the engine’s own clocks), the ask is refused withBackstopExceeded(the same 503ask_deadline_exceededApiError, withpointbackstopand astage), the worker killed and replaced behind the refusal, and no suggestion opens;Client(driver="local", supervisor="off")runs the ask inline as before. The hosted plane answers the same on its queue lane (provenance.laneworker).
A misspelled strategy name never raises: the ladder answers and fallback_from carries the typo. The run forces each path with an unreachable budget.
from tauopt import ApiError, Client
from tauopt.engine import AskDeadlineExceeded
client = Client(driver="local")
exp = client.create_experiment(
name="wait-budget",
parameters=[{"name": "x", "type": "double", "bounds": (0.0, 1.0)}],
metric={"name": "y", "objective": "minimize"},
)
for _ in range(4): # past the space-filling bootstrap, so the GP rung is routed
sug = client.suggest(exp.id, config={"strategy": "auto", "seed": 1})
client.observe(exp.id, suggestion_id=sug.id, value=(sug.assignments["x"] - 0.4) ** 2)
# default on_timeout="ladder": an unreachable budget trips the GP rungs and the
# instant rungs answer, with the fallback recorded
sug = client.suggest(exp.id, max_wait_s=1e-6)
print("ladder:", sug.provenance.strategy, "fallback_from", sug.provenance.fallback_from)
client.close_suggestion(exp.id, sug.id)
# on_timeout="fail": a timeout error instead of a degraded answer
try:
client.suggest(exp.id, max_wait_s=1e-6, on_timeout="fail")
except ApiError as e:
print("fail:", e.status_code, e.error_type, "-", e.message)
# caps mode under a deadline: the engine's own refusal escapes the local driver as is
try:
client.suggest(exp.id, config={"strategy": "auto", "seed": 1, "timebox_mode": "caps"}, max_wait_s=1e-6)
except AskDeadlineExceeded as e:
print("caps:", type(e).__name__, "point", e.point, "rung", e.rung)
# a misspelled strategy never raises: the ladder answers and provenance names the typo
sug = client.suggest(exp.id, config={"strategy": "gp_ie", "seed": 1})
print("typo:", sug.provenance.strategy, "fallback_from", sug.provenance.fallback_from)
ladder: sobol fallback_from gp_ei
fail: 503 suggest_timeout - ask exceeded max_wait_s=1e-06 before gp_ei could run
caps: AskDeadlineExceeded point rung_boundary rung gp_ei
typo: gp_ei fallback_from gp_ie