Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configuration framework for complex applications (Hydra). Dynamic hierarchical configuration by composition and override via CLI, YAML, and structured configs. Use for ML experiment management, multi-environment deployment, hyperparameter sweeps, and reproducible research workflows. Integrates with PyTorch Lightning, Weights & Biases, MLflow, and Optuna.
.claude/skills/mkurman-hydra/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 111% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 194% | 0% |
| case-17 | ✓→✗ | ▼ Worse | 29% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 125% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 268% | 0% |
Hydra is a configuration framework that dynamically creates hierarchical configurations through composition and override. It eliminates hardcoded paths and config files scattered across projects. Use this skill for managing complex ML experiment configurations, multi-environment deployments, hyperparameter sweeps, and reproducible research workflows.
This skill should be used when:
bashpip install hydra-core --upgrade
Directory structure:
conf/
config.yaml
db/
mysql.yaml
postgresql.yaml
my_app.pyconf/config.yaml:
yamldefaults: - db: mysql - _self_ db: driver: mysql host: localhost port: 3306 user: root
my_app.py:
pythonimport hydra from omegaconf import DictConfig, OmegaConf @hydra.main(version_base=None, config_path="conf", config_name="config") def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg)) print(f"Connecting to {cfg.db.host}:{cfg.db.port}") if __name__ == "__main__": my_app()
CLI overrides:
bashpython my_app.py # Uses mysql python my_app.py db=postgresql # Switch to postgresql python my_app.py db.host=prod-server # Override specific value python my_app.py db=postgresql db.port=5432 # Multiple overrides
pythonfrom dataclasses import dataclass, field from typing import List, Optional import hydra from hydra.core.config_store import ConfigStore @dataclass class ModelConfig: name: str = "resnet50" pretrained: bool = True num_classes: int = 1000 @dataclass class TrainingConfig: learning_rate: float = 0.001 batch_size: int = 32 max_epochs: int = 100 optimizer: str = "adam" @dataclass class DataConfig: dataset_path: str = "./data" num_workers: int = 4 image_size: int = 224 augmentations: List[str] = field(default_factory=lambda: ["flip", "rotate"]) @dataclass class ExperimentConfig: model: ModelConfig = ModelConfig() training: TrainingConfig = TrainingConfig() data: DataConfig = DataConfig() seed: int = 42 experiment_name: str = "baseline" tags: List[str] = field(default_factory=list) # Register config cs = ConfigStore.instance() cs.store(name="base_config", node=ExperimentConfig) @hydra.main(version_base=None, config_path=None, config_name="base_config") def run_experiment(cfg: ExperimentConfig) -> None: print(f"Model: {cfg.model.name}") print(f"LR: {cfg.training.learning_rate}") print(f"Batch size: {cfg.training.batch_size}") if __name__ == "__main__": run_experiment()
CLI overrides with structured configs:
bashpython experiment.py \ model=resnet101 \ training.learning_rate=0.0001 \ training.batch_size=64 \ data.image_size=256 \ experiment_name=experiment_1
Directory:
conf/
config.yaml
model/
resnet50.yaml
vit_base.yaml
efficientnet.yaml
optimizer/
adam.yaml
adamw.yaml
sgd.yaml
dataset/
imagenet.yaml
cifar10.yamlconf/config.yaml:
yamldefaults: - model: resnet50 - optimizer: adamw - dataset: imagenet - _self_ training: epochs: 100 mixed_precision: true
conf/model/vit_base.yaml:
yamlname: vit_base_patch16_224 pretrained: true num_classes: 1000 patch_size: 16 hidden_dim: 768 num_heads: 12 num_layers: 12
Usage:
bashpython train.py model=vit_base # Switch model python train.py model=efficientnet optimizer=sgd # Switch both python train.py model.vit_base.patch_size=32 # Nested override
bash# Grid sweep: try all combinations python train.py --multirun \ training.learning_rate=0.001,0.0001,0.00001 \ training.batch_size=32,64,128 # Specific combinations python train.py --multirun \ model=resnet50,vit_base \ optimizer=adamw,sgd # Range sweep python train.py --multirun \ seed=1,2,3,4,5 # From a sweep config python train.py --multirun --config-name=sweep_config
Hydra automatically creates timestamped output directories:
outputs/
2024-01-15/
10-30-45/
.hydra/ # Hydra config metadata
train.log # Application logs
checkpoints/ # Your artifactsAccess output directory in code:
pythonimport hydra from hydra.utils import get_original_cwd, to_absolute_path @hydra.main(...) def my_app(cfg): # Hydra changes working directory to output dir print(os.getcwd()) # .../outputs/2024-01-15/10-30-45/ print(get_original_cwd()) # Original working directory
Config:
yamldefaults: - model: resnet50 - trainer: default - data: imagenet - _self_ seed: 42
Training script:
python@hydra.main(version_base=None, config_path="conf", config_name="config") def train(cfg: DictConfig): pl.seed_everything(cfg.seed) model = MyLightningModule(cfg.model) datamodule = MyDataModule(cfg.data) trainer = pl.Trainer(**cfg.trainer) trainer.fit(model, datamodule)
python@hydra.main(...) def train(cfg: DictConfig): # W&B import wandb wandb.init(project=cfg.wandb.project, config=OmegaConf.to_container(cfg)) # MLflow import mlflow mlflow.log_params(OmegaConf.to_container(cfg))
python# Config # model: # _target_: torch.optim.AdamW # lr: 0.001 # weight_decay: 0.01 from hydra.utils import instantiate @hydra.main(...) def train(cfg): optimizer = instantiate(cfg.optimizer) # Creates AdamW(lr=0.001, weight_decay=0.01) model = instantiate(cfg.model) scheduler = instantiate(cfg.scheduler, optimizer=optimizer)
Recursive instantiation:
yamlmodel: _target_: mylib.models.ResNetClassifier backbone: _target_: torchvision.models.resnet50 pretrained: true num_classes: 1000
python# Register a custom resolver from omegaconf import OmegaConf OmegaConf.register_new_resolver("sum", lambda x, y: x + y) OmegaConf.register_new_resolver("eval", eval) # Use in YAML # total_steps: ${sum:${train.epochs},${train.warmup_epochs}} # batch_size_gb: ${eval:'int(${batch_size} * ${image_size}**2 * 3 * 4 / 1e9)'}
Built-in resolvers:
yamloutput_dir: ${hydra:runtime.output_dir} now: ${now:%Y-%m-%d_%H-%M-%S} # Path relative to config file data_path: ${oc.env:DATA_PATH,/default/path}
instantiate() for object creation from config — reduces boilerplate--multirun plus comma-separated values| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 13,158 | 13,752 | +5% | 1 | 1 | 0% | 2,646 | 5,577 | +111% | 0 | 0 | — |
case-02 | pass→pass | 8,754 | 6,232 | -29% | 1 | 1 | 0% | 1,611 | 3,632 | +125% | 0 | 0 | — |
case-03 | pass→pass | 4,633 | 4,557 | -2% | 1 | 1 | 0% | 901 | 3,317 | +268% | 0 | 0 | — |
case-04 | pass→pass | 6,285 | 4,848 | -23% | 1 | 1 | 0% | 1,156 | 3,280 | +184% | 0 | 0 | — |
case-05 | pass→pass | 7,807 | 4,917 | -37% | 1 | 1 | 0% | 1,551 | 3,465 | +123% | 0 | 0 | — |
case-06 | pass→pass | 9,811 | 6,207 | -37% | 1 | 1 | 0% | 1,621 | 3,559 | +120% | 0 | 0 | — |
case-07 | pass→pass | 6,767 | 4,324 | -36% | 1 | 1 | 0% | 1,235 | 3,380 | +174% | 0 | 0 | — |
case-08 | pass→pass | 8,751 | 5,525 | -37% | 1 | 1 | 0% | 1,490 | 3,470 | +133% | 0 | 0 | — |
case-09 | pass→pass | 6,979 | 3,428 | -51% | 1 | 1 | 0% | 1,127 | 3,122 | +177% | 0 | 0 | — |
case-10 | fail→pass | 5,620 | 2,530 | -55% | 1 | 1 | 0% | 992 | 2,914 | +194% | 0 | 0 | — |
case-11 | pass→pass | 2,968 | 2,377 | -20% | 1 | 1 | 0% | 531 | 2,914 | +449% | 0 | 0 | — |
case-12 | pass→pass | 3,769 | 2,964 | -21% | 1 | 1 | 0% | 547 | 2,980 | +445% | 0 | 0 | — |
case-13 | pass→pass | 4,968 | 2,984 | -40% | 1 | 1 | 0% | 899 | 2,985 | +232% | 0 | 0 | — |
case-14 | pass→pass | 9,392 | 5,515 | -41% | 1 | 1 | 0% | 1,717 | 3,505 | +104% | 0 | 0 | — |
case-15 | pass→pass | 3,897 | 2,828 | -27% | 1 | 1 | 0% | 587 | 2,874 | +390% | 0 | 0 | — |
case-16 | pass→pass | 4,400 | 3,554 | -19% | 1 | 1 | 0% | 790 | 3,136 | +297% | 0 | 0 | — |
case-17 | pass→fail | 16,011 | 6,806 | -57% | 1 | 1 | 0% | 2,869 | 3,694 | +29% | 0 | 0 | — |
case-18 | pass→pass | 5,165 | 3,870 | -25% | 1 | 1 | 0% | 943 | 3,190 | +238% | 0 | 0 | — |
case-19 | pass→pass | 3,516 | 4,345 | +24% | 1 | 1 | 0% | 581 | 3,275 | +464% | 0 | 0 | — |
case-20 | pass→pass | 6,758 | 4,343 | -36% | 1 | 1 | 0% | 1,117 | 3,206 | +187% | 0 | 0 | — |
case-21 | pass→pass | 6,217 | 4,246 | -32% | 1 | 1 | 0% | 1,065 | 3,223 | +203% | 0 | 0 | — |
case-22 | pass→pass | 7,177 | 4,799 | -33% | 1 | 1 | 0% | 1,331 | 3,477 | +161% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.