Publishing a reproducible app with laco app
Publishing a reproducible app with laco app
laco app lets project authors expose a simple argparse-style CLI for
their configs so that collaborators and external users can reproduce or
extend experiments without needing to understand Hydra.
How it works
- The author annotates their config class with
@L.paramsand marks individual hyperparameters withL.param()/L.Param[T]/L.Hidden[T]. - The project's
pyproject.tomlregisters alaco.appsentry point pointing to the entry function. - Users run
laco app <dist> <entry> --flag value: laco translates the flags into Hydra overrides and runs the task.
Author setup
1. Declare hyperparameters with @L.params
# configs/train.py
import laco.language as L
@L.params # default: prefix="", so flags are flat (--lr, --epochs)
class hps:
"""Hyperparameters.
Parameters
----------
lr : float
Learning rate.
epochs : int
Number of training epochs.
"""
lr: float = 0.001
epochs: int = 100
# Hidden from the CLI but still in the config:
_internal_state: L.Hidden[str] = "init"
seed = L.param(42, help="Global random seed")
2. Write the entry point
# my_project/train.py
import laco
import laco.language as L
@laco.main(config_name="train", config_path="configs")
@L.task
def run(hps, seed: int):
print(f"Training with lr={hps.lr}, seed={seed}")
...
if __name__ == "__main__":
run()
3. Register the entry point in pyproject.toml
[project.entry-points."laco.apps"]
train = "my_project.train:run"
After pip install -e . (or any install that registers the entry points),
users can invoke:
laco app my-project train --lr 0.01 --epochs 200 --seed 7
User experience
Help
laco app my-project train --help
usage: laco app my-project train [--lr LR] [--epochs EPOCHS] [--seed SEED]
[--dry-run] [--save-dir DIR]
Run my-project:train with a simple flag-based interface.
options:
--lr LR Learning rate. (default: 0.001)
--epochs EPOCHS Number of training epochs. (default: 100)
--seed SEED Global random seed (default: 42)
--dry-run Print the resolved config as YAML and exit without running.
--save-dir DIR Save resolved config.yaml into DIR before running.
Running with overrides
# Override a single value
laco app my-project train --lr 1e-4
# Override several values
laco app my-project train --lr 1e-4 --epochs 500 --seed 0
# Inspect the resolved config without running
laco app my-project train --lr 1e-4 --dry-run
# Save the resolved config to disk
laco app my-project train --lr 1e-4 --save-dir ./outputs/run-001
Fuzzy suggestions on typos
If you mistype a flag, laco app shows the closest match:
laco app my-project train --learing-rate 0.01
# error: argument --learing-rate: unrecognized arguments
# Unknown flag '--learing-rate'. Did you mean: --lr?
Flag prefix rules
The prefix argument to @L.params controls how flags are named:
prefix= | Config key | CLI flag |
|---|---|---|
"" (default) | hps.lr | --lr |
None | hps.lr | --hps-lr |
"model" | hps.lr | --model-lr |
Underscores in field names and prefixes are converted to hyphens in flags.
Hiding parameters
Use L.Hidden[T] as the annotation, or pass hidden=[...] to @L.params,
to keep fields in the config without exposing them as CLI flags:
@L.params(hidden=["weight_decay"])
class hps:
lr: float = 0.001
weight_decay: float = 1e-4 # not exposed as --weight-decay
Or use the type annotation directly:
@L.params
class hps:
lr: float = 0.001
weight_decay: L.Hidden[float] = 1e-4
To hide an entire @L.params block from laco app (for example, a block
used only internally), pass exclude=True:
@L.params(exclude=True)
class internal:
debug: bool = False
One-off parameters with L.param()
For values that don't belong in a @L.params class, use L.param() at
module level:
seed = L.param(42, help="Random seed", validator=L.Range(0, 2**31))
opt = L.param("adam", choices=["adam", "sgd", "adamw"],
help="Optimizer name")
These appear as --seed and --opt in the CLI alongside the @L.params
flags.
Validators
import laco.language as L
lr = L.param(1e-3, validator=L.Range(1e-7, 1.0))
opt = L.param("adam", validator=L.Choices("adam", "sgd", "adamw"))
| Validator | Effect |
|---|---|
L.Range(min, max) | Value must be in [min, max] |
L.Choices(*opts) | Value must be one of opts |
| Any callable | Called with the value; raise ValueError to reject |
Boolean flags
Boolean parameters get --flag / --no-flag pairs automatically:
@L.params
class hps:
use_amp: bool = True
laco app my-project train --no-use-amp