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-buildwrites both sets toregistrations/, 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.