Overriding Configs

In these examples, we show how tyro.cli() can be used to override values in pre-instantiated configuration objects.

Dataclasses + Defaults

The default= argument can be used to override default values in dataclass types.

Note

When default= is used, we advise against mutation of configuration objects from a dataclass’s __post_init__ method [1]. In the example below, __post_init__ would be called twice: once for the Args() object provided as a default value and another time for the Args() objected instantiated by tyro.cli(). This can cause confusing behavior! Instead, we show below one example of how derived fields can be defined immutably.

 1# 01_dataclasses_defaults.py
 2import dataclasses
 3
 4import tyro
 5
 6@dataclasses.dataclass
 7class Args:
 8    """Description.
 9    This should show up in the helptext!"""
10
11    string: str
12    """A string field."""
13
14    reps: int = 3
15    """A numeric field, with a default value."""
16
17    @property
18    def derived_field(self) -> str:
19        return ", ".join([self.string] * self.reps)
20
21if __name__ == "__main__":
22    args = tyro.cli(
23        Args,
24        default=Args(
25            string="default string",
26            reps=tyro.MISSING,
27        ),
28    )
29    print(args.derived_field)
$ python ./01_dataclasses_defaults.py --help
usage: 01_dataclasses_defaults.py [-h] [--string STR] --reps INT

Description. This should show up in the helptext!

╭─ options ─────────────────────────────────────────────────────────────╮
 -h, --help          show this help message and exit                   
 --string STR        A string field. (default: 'default string')       
 --reps INT          A numeric field, with a default value. (required) 
╰───────────────────────────────────────────────────────────────────────╯
$ python ./01_dataclasses_defaults.py --reps 3
default string, default string, default string
$ python ./01_dataclasses_defaults.py --string hello --reps 5
hello, hello, hello, hello, hello

Overriding YAML Configs

tyro understands a wide range of data structures, including standard dictionaries and lists.

If you have a library of existing YAML files that you want to use, tyro.cli() can help override values within them.

Note

We recommend dataclass configs for new projects.

 1# 02_overriding_yaml.py
 2import yaml
 3
 4import tyro
 5
 6# YAML configuration. This could also be loaded from a file! Environment
 7# variables are an easy way to select between different YAML files.
 8default_yaml = r"""
 9exp_name: test
10optimizer:
11    learning_rate: 0.0001
12    type: adam
13training:
14    batch_size: 32
15    num_steps: 10000
16    checkpoint_steps:
17    - 500
18    - 1000
19    - 1500
20""".strip()
21
22if __name__ == "__main__":
23    # Convert our YAML config into a nested dictionary.
24    default_config = yaml.safe_load(default_yaml)
25
26    # Override fields in the dictionary.
27    overridden_config = tyro.cli(dict, default=default_config)
28
29    # Print the overridden config.
30    overridden_yaml = yaml.safe_dump(overridden_config)
31    print(overridden_yaml)
$ python ./02_overriding_yaml.py --help
usage: 02_overriding_yaml.py [-h] [OPTIONS]

╭─ options ───────────────────────────────────────────────╮
 -h, --help              show this help message and exit 
 --exp-name STR          (default: test)                 
╰─────────────────────────────────────────────────────────╯
╭─ optimizer options ─────────────────────────────────────╮
 --optimizer.learning-rate FLOAT                         
                         (default: 0.0001)               
 --optimizer.type STR    (default: adam)                 
╰─────────────────────────────────────────────────────────╯
╭─ training options ──────────────────────────────────────╮
 --training.batch-size INT                               
                         (default: 32)                   
 --training.num-steps INT                                
                         (default: 10000)                
 --training.checkpoint-steps [INT [INT ...]]             
                         (default: 500 1000 1500)        
╰─────────────────────────────────────────────────────────╯
$ python ./02_overriding_yaml.py --training.checkpoint-steps 300 1000 9000
exp_name: test
optimizer:
  learning_rate: 0.0001
  type: adam
training:
  batch_size: 32
  checkpoint_steps:
  - 300
  - 1000
  - 9000
  num_steps: 10000

Choosing Base Configs

One common pattern is to have a set of “base” configurations, which can be selected from and then overridden.

This is often implemented with a set of configuration files (e.g., YAML files). With tyro, we can instead define each base configuration as a separate Python object.

After creating the base configurations, we can use the CLI to select one of them and then override (existing) or fill in (missing) values.

The helper function used here, tyro.extras.overridable_config_cli(), is a lightweight wrapper over tyro.cli() and its Union-based subcommand syntax.

 1# 03_choosing_base_configs.py
 2from dataclasses import dataclass
 3from typing import Callable, Literal
 4
 5from torch import nn
 6
 7import tyro
 8
 9@dataclass(frozen=True)
10class ExperimentConfig:
11    # Dataset to run experiment on.
12    dataset: Literal["mnist", "imagenet-50"]
13
14    # Model size.
15    num_layers: int
16    units: int
17
18    # Batch size.
19    batch_size: int
20
21    # Total number of training steps.
22    train_steps: int
23
24    # Random seed.
25    seed: int
26
27    # Not specifiable via the commandline.
28    activation: Callable[[], nn.Module]
29
30# We could also define this library using separate YAML files (similar to
31# `config_path`/`config_name` in Hydra), but staying in Python enables seamless
32# type checking + IDE support.
33default_configs = {
34    "small": (
35        "Small experiment.",
36        ExperimentConfig(
37            dataset="mnist",
38            batch_size=2048,
39            num_layers=4,
40            units=64,
41            train_steps=30_000,
42            seed=0,
43            activation=nn.ReLU,
44        ),
45    ),
46    "big": (
47        "Big experiment.",
48        ExperimentConfig(
49            dataset="imagenet-50",
50            batch_size=32,
51            num_layers=8,
52            units=256,
53            train_steps=100_000,
54            seed=0,
55            activation=nn.GELU,
56        ),
57    ),
58}
59if __name__ == "__main__":
60    config = tyro.extras.overridable_config_cli(default_configs)
61    print(config)

Overall helptext:

$ python ./03_choosing_base_configs.py --help
usage: 03_choosing_base_configs.py [-h] {small,big}

╭─ options ──────────────────────────────────────────╮
 -h, --help         show this help message and exit 
╰────────────────────────────────────────────────────╯
╭─ subcommands ──────────────────────────────────────╮
 {small,big}                                        
     small          Small experiment.               
     big            Big experiment.                 
╰────────────────────────────────────────────────────╯

The “small” subcommand:

$ python ./03_choosing_base_configs.py small --help
usage: 03_choosing_base_configs.py small [-h] [SMALL OPTIONS]

Small experiment.

╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help              show this help message and exit                    
 --dataset {mnist,imagenet-50}                                              
                         Dataset to run experiment on. (default: mnist)     
 --num-layers INT        Model size. (default: 4)                           
 --units INT             Model size. (default: 64)                          
 --batch-size INT        Batch size. (default: 2048)                        
 --train-steps INT       Total number of training steps. (default: 30000)   
 --seed INT              Random seed. (default: 0)                          
 --activation {fixed}    Not specifiable via the commandline. (fixed to:    
                         <class 'torch.nn.modules.activation.ReLU'>)        
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./03_choosing_base_configs.py small --seed 94720
ExperimentConfig(dataset='mnist', num_layers=4, units=64, batch_size=2048, train_steps=30000, seed=94720, activation=<class 'torch.nn.modules.activation.ReLU'>)

The “big” subcommand:

$ python ./03_choosing_base_configs.py big --help
usage: 03_choosing_base_configs.py big [-h] [BIG OPTIONS]

Big experiment.

╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help              show this help message and exit                    
 --dataset {mnist,imagenet-50}                                              
                         Dataset to run experiment on. (default:            
                         imagenet-50)                                       
 --num-layers INT        Model size. (default: 8)                           
 --units INT             Model size. (default: 256)                         
 --batch-size INT        Batch size. (default: 32)                          
 --train-steps INT       Total number of training steps. (default: 100000)  
 --seed INT              Random seed. (default: 0)                          
 --activation {fixed}    Not specifiable via the commandline. (fixed to:    
                         <class 'torch.nn.modules.activation.GELU'>)        
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./03_choosing_base_configs.py big --seed 94720
ExperimentConfig(dataset='imagenet-50', num_layers=8, units=256, batch_size=32, train_steps=100000, seed=94720, activation=<class 'torch.nn.modules.activation.GELU'>)