Hydra (Python)

Hydra-zen

One problem with hydra is that when using the _target_ key in some configuration file foo.yaml for calling functions, we can only see cfg.foo in the Python code, where cfg refers to the OmegaConf dictionary object loaded for hydra. This makes it hard to go to function definitions, and see documentation for the function. hydra-zen is one solution for this.

The following is a modified example from this talk, where we use(/nest) one configured object (function_foo) into the configuration of another (some_class_object).

from hydra_zen import make_custom_builds_fn

from some_module import some_function, some_class

builds = make_custom_builds_fn(populate_full_signature=True)

some_function_conf = builds(some_function)

function_foo = some_function_conf(bar="baz", another_arg=True)

some_class_object = builds(some_class, some_function=function_foo)

The top level configuration is them created via the make_config function. Below, we are creating a configuration object which has one field, arg, which is populated by the object configured above. We can then use it in some task function (e.g. a function that actually runs the experiment) as follows:

from hydra_zen import make_config, instantiate

Config = make_config(some_field=some_class_object)

def task_function(cfg: Config):
    obj = instantiate(cfg)

    field = obj.some_field #Instiated object from the class some_class

To run programs, we can do the following:

from hydra_zen import launch

job = launch(Config, task_function, ["some_field.some_attribute_from_some_class=some_value"]

We can configure nested fields within the list easily.

To use the CLI to run the code above:

import hydra
from hydra.core.config_store import ConfigStore
from hydra_zen import make_config, instantiate

Config = make_config(some_field=some_class_object)

cs = ConfigStore.instance()
cs.store(name="some_task", node=Config)

@hydra.main(config_path=None, config_name="some_task")
def task_function(cfg: Config):
    obj = instantiate(cfg)

    field = obj.some_field #Instiated object from the class some_class

if __name__ == "__main__":
    task_function()

To run this from the CLI:

python some_task.py some_field.some_attribute_from_some_class=some_value

Hydra will save the config which was used in the code above, assuming the output directory is the default, running the following will give identical results to the code above:

python some_task.py -cp outputs/YYYY-MM-DD/HH-MM-SS/.hydra/ -cn config

(#ASK Is the YAML config saved via hydra-zen a monolithic file? Or is it monolithic even without hydra-zen? If it is, then it is actually helpful when debugging).

We can register configs under groups:

some_function_conf = builds(some_function)

some_function_conf_dataset_1 = some_function_conf(name="MNIST")
some_function_conf_dataset_2 = some_function_conf(name="CIFAR10")

cs.store(group="dataset", name="MNIST_simple", node=some_function_conf_dataset_1)
cs.store(group="dataset", name="CIFAR10_simple", node=some_function_conf_dataset_2)

We can then use:

python task_function.py dataset=MNIST_simple dataset.some_field=some_value

To use runtime type checking:

from beartype.vale import Is

from typing_extensions import Annotated

from hydra_zen import make_custom_builds_fn
from hydra_zen.third_party.beartype import validates_with_beartype
from hydra_zen import instantiate

PositiveInt = Annotated[int, Is[lambda x: x>= 0]]

def process_age(age: PositiveInt):
    return age

builds = make_custom_builds_fn(populate_full_signature=True
                               zen_wrappers=validates_with_beartype
                               hydra_convert="all")

ConfAge = builds(process_age)

instantiate(ConfAge, age=12) # Works
instantiate(ConfAge, age=-1) # Does not work

Emacs 29.4 (Org mode 9.6.15)