6. Conditions#

A sweep generates combinations mechanically, and some of them make no sense — a decoder wider than its encoder, a batch too large for the accumulation steps. Conditions let a configuration reject itself.

python examples/tutorial/06_conditions.py
class AutoencoderConfig(Configuration):
    encoder_width: int = Param(128, variants=[64])
    decoder_width: int = Param(64, variants=[128])

    def model_post_init(self, context) -> None:
        super().model_post_init(context)
        self.add_condition(
            name="decoder_not_wider_than_encoder",
            condition=lambda config: config.decoder_width <= config.encoder_width,
            description="A decoder wider than its encoder is not a bottleneck",
        )

Two two-valued axes give four combinations; one of them has a decoder wider than its encoder, and the condition removes it.

What to notice#

  • Resolution returns two sets rather than raising. Invalid keys are not errors to fix; they are the combinations a sweep should skip, and each carries the reason it was excluded.

  • That is the difference between a condition and a pydantic constraint. A constraint says this value is impossible; a condition says this combination is not worth running. The first should raise, the second should not.

  • cmn-build writes both sets to registrations/, so the excluded combinations and their reasons are on disk rather than in someone’s memory.

The whole file#

 1"""
 26. Conditions, and the split between valid and invalid.
 3
 4    python examples/tutorial/06_conditions.py
 5
 6A sweep generates combinations mechanically, and some of them make no sense --
 7a decoder wider than the encoder, a batch too large for the accumulation steps.
 8Conditions let a configuration reject itself.
 9
10Resolution then returns two sets rather than raising. Invalid keys are not
11errors to fix; they are the combinations a sweep should skip, and each one
12carries the reason it was excluded.
13"""
14
15from cinnamon.configuration import Configuration, Param
16from cinnamon.registry import Registry
17
18
19class Autoencoder:
20    def __init__(self, encoder_width: int, decoder_width: int):
21        self.encoder_width = encoder_width
22        self.decoder_width = decoder_width
23
24
25class AutoencoderConfig(Configuration):
26    encoder_width: int = Param(128, variants=[64])
27    decoder_width: int = Param(64, variants=[128])
28
29    def model_post_init(self, context) -> None:
30        super().model_post_init(context)
31        self.add_condition(
32            name="decoder_not_wider_than_encoder",
33            condition=lambda config: config.decoder_width <= config.encoder_width,
34            description="A decoder wider than its encoder is not a bottleneck",
35        )
36
37
38def main() -> None:
39    Registry.initialize()
40    Registry.register_configuration(
41        AutoencoderConfig(),
42        name="autoencoder",
43        namespace="tutorial",
44        component=f"{__name__}.Autoencoder",
45    )
46
47    valid_keys, invalid_keys = Registry.dag_resolution()
48
49    print(f"{len(valid_keys)} valid, {len(invalid_keys)} invalid\n")
50    for key in sorted(valid_keys, key=str):
51        built = Registry.from_key(key)
52        print(f"  valid    {built.encoder_width:>4} -> {built.decoder_width:<4}")
53    for key in sorted(invalid_keys, key=str):
54        reason = (key.metadata or "").split("Message:")[-1].strip()
55        print(f"  invalid  {', '.join(sorted(key.tags)):32s} {reason}")
56
57    print(
58        "\nOnly the valid keys stay in the registry, so `cmn-run` will not offer"
59        "\nyou a combination that cannot work. The invalid ones are returned so"
60        "\nthat a sweep can report what it skipped and why."
61    )
62
63
64if __name__ == "__main__":
65    main()

Next: 7. A real project — how all of this looks in a real project.