Config Management¶
Config management controls live hyperparameters, experiment identity, and runtime paths during training.
Hyperparameter wrapper parameters¶
wl.watch_or_edit(..., flag="hyperparameters", ...) key parameters:
Parameter |
Default |
Behavior |
|---|---|---|
|
|
Values registered before the YAML is first read. They seed the in-memory config only — the watcher reads the file, it never writes it, so write the YAML yourself if you want it editable from the start. |
|
|
Reload period (seconds) for file-based config updates. |
|
|
Checkpoint load/save behavior override for config state. |
Registration patterns¶
Dict-based:
import weightslab as wl
hp = wl.watch_or_edit(
{
"experiment_name": "exp_a",
"root_log_dir": "./logs/exp_a",
"optimizer": {"lr": 1e-3},
"data": {"train_loader": {"batch_size": 16}},
},
flag="hyperparameters",
)
YAML-based with polling:
hp = wl.watch_or_edit(
"./config.yaml",
flag="hyperparameters",
defaults={"optimizer": {"lr": 1e-3}},
poll_interval=1.0,
)
watch_or_edit rebinds the caller’s variable to the returned proxy, so pass the
path as a fresh string (str(config_path)) when you still need the path
afterwards.
Runtime SDK operations¶
# Read
lr = hp["optimizer"]["lr"]
# Write (in-place)
hp["optimizer"]["lr"] = 5e-4
hp["data"]["train_loader"]["batch_size"] = 32
root_log_dir behavior¶
root_log_dir determines where experiment artifacts are stored:
checkpoints and version states
logger history
generated reports
notebook artifacts
Example:
experiment_name: classifier_v1
root_log_dir: ./logs/classifier_v1
Standalone config-only integration (UI + CLI ready)¶
A complete, runnable script with nothing but the configuration registered: no
model, no data, no signals. Its loop only reads the config each step and prints
what changed, so you can watch a value propagate from any of the three places it
can be edited — the YAML file, set_hp in the CLI, or the studio panel.
Bundled example: weightslab/examples/PyTorch/wl-standalone-config/main.py
weightslab start example --config # writes config.yaml on first run
weightslab cli # attach a terminal, in another shell
weightslab start # open Weights Studio, in a third shell
def main(argv=None) -> int:
args = parse_args(argv)
config_path = Path(args.config).resolve()
config_path.parent.mkdir(parents=True, exist_ok=True)
if not config_path.exists():
# `defaults=` seeds the in-memory config; the watcher only *reads* the
# file, so write it once here to make it editable from the start.
config_path.write_text(yaml.safe_dump(DEFAULT_CONFIG, sort_keys=False),
encoding="utf-8")
print(f"[config-level] wrote {config_path}")
import weightslab as wl
# --- the only WeightsLab registration in this file ------------------------
# str(...) is deliberate: watch_or_edit rebinds the caller's variable to the
# returned proxy, and passing a fresh string keeps `config_path` intact.
hp = wl.watch_or_edit(
str(config_path),
flag="hyperparameters",
defaults=copy.deepcopy(DEFAULT_CONFIG),
poll_interval=args.poll_interval,
)
# --------------------------------------------------------------------------
wl.serve(serving_grpc=not args.no_grpc, serving_cli=not args.no_cli,
grpc_port=args.grpc_port)
print("=" * 70)
print(" CONFIG-ONLY standalone — attach with `weightslab cli`, UI with `weightslab start`")
print(f" config={config_path}")
# Each reload makes the FILE authoritative, so a root_log_dir that was only
# injected at registration does not survive it; ask for the directory in use.
print(f" experiment dir={experiment_dir()}")
print(" try: set_hp optimizer.lr 0.0005 (or edit the YAML)")
print("=" * 70)
wl.start_training(timeout=3)
total = args.steps or int(read_path(hp, "training_steps_to_do", 300) or 300)
last = {key: read_path(hp, key) for key in WATCHED}
print(f"[config-level] step 0: " + ", ".join(f"{k}={v}" for k, v in last.items()))
for step in range(1, total + 1):
# A real loop would use these values (lr on the optimizer, batch size on
# the loader). Here we only observe them, so the config level stands alone.
current = {key: read_path(hp, key) for key in WATCHED}
changed = {k: v for k, v in current.items() if v != last[k]}
if changed:
print(f"[config-level] step {step}: changed -> "
+ ", ".join(f"{k}: {last[k]} -> {v}" for k, v in changed.items()))
last = current
if not current.get("is_training", True):
print(f"[config-level] step {step}: is_training=False — idling")
time.sleep(args.step_delay)
print(f"[config-level] loop finished after {total} steps; final config:")
print(yaml.safe_dump({k: last[k] for k in WATCHED}, sort_keys=False).strip())
wl.keep_serving(timeout=args.serve_timeout)
return 0
Then, from the attached CLI:
hp # -> ['main']
hp main # the whole config
set_hp optimizer.lr 0.0005 # the loop prints the change
set_hp data.train_loader.batch_size 64
Note
The file watcher is one-way: it loads the YAML when its mtime changes, and
set_hp / studio edits change the live config without writing the file back.
Saving the YAML after an in-memory edit therefore reinstates the file’s values.
CLI and UI surfaces¶
CLI:
hp/hp <name>set_hp [hp_name] <key.path> <value>statusfor current registered configuration
UI:
Hyperparameters panel runtime edits
Agent-driven config changes (for example “set batch size to 32”)