CONCEPTS

Typed Groups

Typed Groups

Tutorial: Typed GroupsSee also: Examples: Typed Variants, API: language

Problem Statement

@L.params lets you parameterize a single config variant. It doesn't enforce that you've chosen a valid alternative (e.g. a valid optimizer), or catch typos in alternative names. Typed groups solve both.

L.Group[T]

Define a group by subclassing L.Group[T], where T is the runtime type all entries produce:

class OptimGroup(L.Group[torch.optim.Optimizer]):
    sgd  = L.call(torch.optim.SGD)(lr=1e-2, momentum=0.9)
    adam = L.call(torch.optim.Adam)(lr=1e-3)

L.Group.__init_subclass__ registers each class attribute as a named entry in Hydra's ConfigStore under the group name optim.

@L.config

Annotate the schema (the config dataclass) with @L.config:

@L.config
class TrainSchema:
    optimizer: torch.optim.Optimizer = L.slot(OptimGroup)
    model: nn.Module = L.call(nn.Linear)(784, 10)
    lr: float = 1e-3

@L.config applies @dataclass and dataclass_transform so IDE autocomplete works on TrainSchema instances. Fields annotated with group types are type-checked: assigning a non-OptimGroup entry raises at config time.

L.slot(G)

L.slot(G) creates a placeholder for a group entry: a _SlotSpec that resolves at load time to the ${package} OmegaConf interpolation. The field name is used as the package key by default:

optimizer: OptimGroup = L.slot(OptimGroup)  # package = "optimizer"

L.bind and L.Defaults

L.bind(slot, entry) wires a specific entry into a slot at config definition time:

defaults = L.Defaults(
    L.self_,
    L.bind(TrainSchema.optimizer, OptimGroup.adam),
)

L.Defaults produces the Hydra defaults list. L.self_ is the _self_ sentinel for Hydra's composition order. L.delete(G) removes a default.

L.bind_strict(slot, L.use(entry)) is the type-safe variant, verified at schema definition time.

L.chosen(G)

Use L.chosen(G) inside a config body to get a typed reference to whatever entry was composed into that group slot:

@L.config
class TrainSchema:
    optimizer: torch.optim.Optimizer = L.slot(OptimGroup)
    # chosen resolves to the actual composed optimizer config
    effective_lr: float = L.chosen(OptimGroup).lr

Switching Entries

From Python:

cfg = laco.load("configs://examples/typed/mlp.py?activation=gelu")

From CLI:

laco run configs://examples/typed/mlp.py optimizer=adam

Laco validates that adam is a registered entry in OptimGroup and raises ValueError if not.