Configuration System¶
This page covers the configuration system — how to define your entire experiment in YAML so your experiments are fully reproducible and easy to modify.
SR-Forge uses a YAML-based configuration system that lets you define your entire experiment — model, optimizer, loss, dataset, transforms, runners, trainer, and hooks — in a single file. The ConfigResolver reads that YAML and recursively instantiates every Python object it describes, wiring them together automatically.
For how this config is used inside training scripts, see Writing Scripts.
The result: you can change your model, loss function, or preprocessing pipeline without touching a single line of Python.
Quick Reference¶
Config syntax — the two magic keys:
_target: my_project.models.MyModel # What class to create
params: # What arguments to pass
hidden_dim: 128
Equivalent Python:
Cheat sheet:
| Feature | Syntax | Purpose |
|---|---|---|
| Instantiate | _target + params |
Create any Python object |
| Full path | _target: my_project.models.MyModel |
Use full module path |
| IO binding | io: key |
Bind parameters to Entry fields |
| Reference | ${ref:path.to.object} |
Reuse an already-instantiated object |
| Interpolation | ${path.to.value} |
Copy a raw config value |
| Nesting | _target inside params |
Recursive instantiation |
Don't confuse ${path} with ${ref:path}
These look similar but behave very differently:
${loss}copies the raw YAML config — each use creates a separate Python object.${ref:loss}returns the same Python object (shared instance).
Use ${} to reuse config templates. Use ${ref:} to share a live object.
The Problem: Why Config-Driven?¶
Consider a typical training setup:
# Without config — everything hardcoded
model = MyModel(hidden_dim=128, num_layers=4, dropout=0.1)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 20])
loss = LossCombiner(losses=[L1(weight=1.0), SSIM(weight=0.01)])
dataset = LazyDataset(root="/data/experiment/train", depth=0)
transforms = [Divide(255.0).set_io({"inputs": {"image": "lr"}}), ToFloat().set_io({"inputs": {"image": "lr"}})]
Every time you want to try a different model, loss, or dataset, you have to edit Python code. That means:
- You risk introducing bugs in training scripts
- You can't easily compare experiments (which parameters changed?)
- You can't reproduce a colleague's run without their exact code
The SR-Forge config system solves this:
# Everything in one YAML file — change it, run it, track it
model:
_target: MyModel
params:
hidden_dim: 128
num_layers: 4
dropout: 0.1
optimizer:
_target: torch.optim.Adam
params:
lr: 0.001
Golden Rule
The YAML config is the single source of truth for your experiment. If it's not in the config, it didn't happen.
How It Works: The _target / params Pattern¶
Every object in SR-Forge is described by two keys:
_target: module.path.ClassName # Which class to instantiate
params: # Constructor arguments (kwargs)
arg1: value1
arg2: value2
The ConfigResolver reads this and calls:
Using Full Module Paths¶
You can specify any importable Python class:
# Your project classes
model:
_target: my_project.models.MyModel
params:
hidden_dim: 128
# PyTorch classes work too
optimizer:
_target: torch.optim.AdamW
params:
lr: 0.0005
weight_decay: 0
# Scheduler
lr_scheduler:
_target: torch.optim.lr_scheduler.ReduceLROnPlateau
params:
mode: "min"
factor: 0.5
patience: 10
When ConfigResolver encounters a _target value, it splits the string to derive the module path and class name — for example, my_project.models.MyModel becomes module my_project.models and class MyModel. The class is then imported and instantiated automatically.
Recursive Instantiation¶
The real power of ConfigResolver is recursion. When a parameter value itself contains _target, it gets instantiated first:
loss:
_target: srforge.loss.schedule.LossScheduler
params:
schedule:
0: # At epoch 0, use this loss:
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.L1
params:
weight: 1.0
- _target: srforge.loss.metrics.SSIM
params:
weight: 0.01
50: # At epoch 50, switch to this:
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.L1
params:
weight: 1.0
- _target: srforge.loss.metrics.SSIM
params:
weight: 0.05
The resolver resolves this bottom-up:
- Creates
L1(weight=1.0)andSSIM(weight=0.01) - Creates
LossCombiner(losses=[L1_instance, SSIM_instance])for epoch 0 - Creates another
LossCombinerfor epoch 50 - Creates
LossScheduler(schedule={0: combiner_0, 50: combiner_50})
You can nest as deeply as you need — each _target + params block is resolved recursively.
Params: None, Empty, and Missing¶
# No parameters needed — use empty dict
model:
_target: Identity
params: {}
# Also valid: params can be null
model:
_target: Identity
params:
Both produce the same result: Identity() with no arguments.
Config Interpolation: ${path}¶
YAML configs support OmegaConf interpolation — referencing other config values using ${path}:
loss:
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.L1
params:
weight: 1.0
# Reuse the exact same config structure for validation metrics
validation_metrics: ${loss}
This copies the raw config value — both loss and validation_metrics produce the same object structure when instantiated. Common patterns:
preprocessing:
training:
- _target: srforge.transform.data.Multiply
params:
value: 2.0
io:
inputs: [{image: x}, {image: y}]
# Reuse training preprocessing for validation
validation: ${preprocessing.training}
postprocessing:
training: []
# Same postprocessing for validation
validation: ${postprocessing.training}
Key point:
${path}resolves at config-load time (before any objects are created). It copies the raw YAML structure, not instantiated objects.
Separate instances
Because ${path} copies config, each copy is instantiated independently.
validation_metrics: ${loss} creates a second loss object — it does
not reuse the training loss. If you need the same instance, use
${ref:loss} instead.
Object References: ${ref:path}¶
Sometimes you need to reference an already-instantiated object, not just copy config. The ${ref:path} syntax does this:
shared_backbone:
_target: srforge.models.SomeBackbone
params:
features: 64
model:
_target: srforge.models.SomeModel
params:
backbone: ${ref:shared_backbone} # Same Python object, not a copy
When the resolver encounters ${ref:shared_backbone}:
- It checks if
shared_backbonewas already instantiated - If yes, it returns the same Python object (same memory reference)
- If no, it instantiates it first, caches it, then returns it
This ensures object identity — both references point to the exact same instance:
resolved = resolver.resolve_all()
assert resolved["model"].backbone is resolved["shared_backbone"] # True — same object
Method Chains¶
You can call no-arg methods on referenced objects:
optimizer:
_target: torch.optim.AdamW
params:
params: ${ref:model}.trainable_params() # resolves model, then calls method
lr: 0.0005
Chained calls work too: ${ref:obj}.foo().bar().
Reference vs Interpolation¶
| Feature | ${path} |
${ref:path} |
|---|---|---|
| Resolves | At config load time | At instantiation time |
| Returns | Raw config value (YAML) | Instantiated Python object |
| Identity | Creates separate objects | Shares the same object |
| Use when | Reusing config structure | Sharing runtime objects |
Nested References¶
References can point to nested paths using dot notation:
group:
inner:
_target: srforge.data.Entry
params:
name: "inner"
alias: ${ref:group.inner} # Resolves to the Entry instance
Circular Reference Detection¶
The resolver detects circular references and raises a clear error:
Legacy syntax
The older "%{path}" form still works (requires YAML quoting) but ${ref:path} is preferred because it needs no quoting.
Which Should I Use?¶
| You want to… | Use | Why |
|---|---|---|
| Reuse a config block (e.g., same transforms for train and val) | ${path} |
Each dataset gets its own independent pipeline |
| Share one object (e.g., optimizer needs the model's params) | ${ref:path} |
Must be the exact same Python object |
| Call a method on another object | ${ref:path}.method() |
Only references support method chains |
| Copy a scalar value (number, string) | ${path} |
${ref:} raises TypeError on primitives |
IO Binding in Config¶
Models, transforms, and losses can have their IO bound directly in the config using the io: key. This is one of the IO Binding methods:
model:
_target: Identity
params: {}
io:
inputs:
input: x # Model param 'input' reads from entry.x
outputs: y # Writes model output to entry.y
For transforms:
# EntryTransform — field keys in params, no io:
transform:
_target: srforge.transform.entry.CopyFields
params:
field_key: y
output_key: z
# DataTransform — param name maps to Entry field
transform:
_target: srforge.transform.data.Multiply
params:
value: 2.0
io:
inputs:
image: x
outputs: y
The ConfigResolver handles this automatically — after creating the object, it calls instance.set_io(io_config) if the io: key is present. This works for any IOModule subclass (Models, Transforms, and Losses).
See IO Binding for all binding methods and detailed examples.
Full Config Structure¶
A complete training config has these top-level sections:
# ═══════════════════════════════════════════════
# System settings
# ═══════════════════════════════════════════════
system:
cache_dir: # Dataset cache directory (null = no caching)
device: 0 # Single GPU by index; use [0, 1] for multi-GPU, or "cpu"
recache: True # Force re-cache even if cache exists
mixed_precision: False # Enable automatic mixed precision (AMP)
debug_level: INFO # Logging: DEBUG, INFO, WARNING, ERROR, CRITICAL
# ═══════════════════════════════════════════════
# Experiment tracking
# ═══════════════════════════════════════════════
tracker:
_target: srforge.tracking.WandbTracker # or srforge.tracking.NullTracker for offline runs
params:
mode: online # online, offline, or disabled
entity: my-team # W&B team
project: my-project # W&B project name
name: # Auto-generated if null
run_id: # Set to resume a previous run
group: # Group related runs
tags: # List of tags
notes: # Description
job_type: training
resume: allow
# ═══════════════════════════════════════════════
# Training hyperparameters
# ═══════════════════════════════════════════════
training:
epochs: 1000
batch_size: 8
num_workers: 0
gradient_accumulation_steps: 1
# ═══════════════════════════════════════════════
# Model
# ═══════════════════════════════════════════════
model:
_target: MyModel
params:
hidden_dim: 128
# ═══════════════════════════════════════════════
# Optimizer
# ═══════════════════════════════════════════════
optimizer:
_target: torch.optim.AdamW
params:
lr: 0.0005
weight_decay: 0
# ═══════════════════════════════════════════════
# Learning rate scheduler (null = no scheduler)
# ═══════════════════════════════════════════════
lr_scheduler:
_target: torch.optim.lr_scheduler.ReduceLROnPlateau
params:
mode: "min"
factor: 0.5
patience: 10
# ═══════════════════════════════════════════════
# Loss function
# ═══════════════════════════════════════════════
loss:
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.L1
params:
weight: 1.0
io: # map L1's x,y params to Entry fields
inputs: {x: sr, y: hr}
# ═══════════════════════════════════════════════
# Validation metrics (often same as loss)
# ═══════════════════════════════════════════════
validation_metrics: ${loss}
# ═══════════════════════════════════════════════
# Datasets
# ═══════════════════════════════════════════════
dataset:
training:
_target: srforge.dataset.LazyDataset
params:
root: /path/to/train/data
validation:
_target: srforge.dataset.LazyDataset
params:
root: /path/to/val/data
# ═══════════════════════════════════════════════
# Pre- and post-processing (lists of transforms)
# ═══════════════════════════════════════════════
preprocessing:
training:
- _target: srforge.transform.data.ToFloat
io:
inputs: [{image: target}, {image: images}]
- _target: srforge.transform.data.Divide
params:
value: 16383.0
io:
inputs: [{image: target}, {image: images}]
validation: ${preprocessing.training}
postprocessing:
training: []
validation: ${postprocessing.training}
# ═══════════════════════════════════════════════
# Training runner (with its hooks)
# ═══════════════════════════════════════════════
training_runner:
_target: srforge.training.runners.TrainingEpochRunner
params:
optimizer: ${ref:optimizer} # Reference to instantiated optimizer
postprocessor: ${ref:postprocessing.training} # Transforms applied after model output
mixed_precision: ${system.mixed_precision} # Raw config value (bool)
gradient_accumulation_steps: ${training.gradient_accumulation_steps}
hooks:
- _target: srforge.training.hooks.ProgressBar # Progress bar for the training loop
params: { name: "T" }
# ═══════════════════════════════════════════════
# Validation runner (with its hooks)
# ═══════════════════════════════════════════════
validation_runner:
_target: srforge.training.runners.ValidationEpochRunner
params:
postprocessor: ${ref:postprocessing.validation}
mixed_precision: ${system.mixed_precision}
hooks:
- _target: srforge.training.hooks.ProgressBar # Progress bar for the validation loop
params: { name: "V" }
- _target: srforge.training.hooks.BatchImageLogger # Log sample predictions to tracker
params:
batch_id: 0
img_key: sr
log_dir: val_pred
tracker: ${ref:tracker}
# ═══════════════════════════════════════════════
# Trainer (orchestrates the training loop; carries trainer-level hooks)
# ═══════════════════════════════════════════════
trainer:
_target: srforge.training.trainers.PyTorchTrainer
params:
training_epoch_runner: ${ref:training_runner} # References instantiated runner
validation_epoch_runner: ${ref:validation_runner}
training_criterion: ${ref:loss} # Loss function for training
validation_criterion: ${ref:validation_metrics} # Metrics for validation
lr_scheduler: ${ref:lr_scheduler}
# stop_condition: # Optional early stopping
# _target: srforge.training.stop.ValidationLossDidNotImprove
# params:
# patience: 50
# min_delta: 0.0001
hooks:
- _target: srforge.training.hooks.LossLogger # Log losses each epoch
params: { tracker: ${ref:tracker} }
- _target: srforge.training.hooks.PyTorchModelSaver # Save checkpoints (best + last)
params: { tracker: ${ref:tracker} }
Hooks attach where they fire
Notice there is no global observers: section and no scope:
filters. A hook goes into the hooks: list of the component whose
lifecycle it follows — runner-level hooks (progress bars, image
loggers) on the runner, trainer-level hooks (loss logging,
checkpointing) on the trainer. Attachment is the scope.
Trainer, Runners, and Hooks¶
The config sections above define three layers of the training architecture:
PyTorchTrainer¶
The trainer owns the epoch loop. Each epoch it:
- Calls
training_runner.run_epoch()to train on the full dataset - Calls
validation_runner.run_epoch()to evaluate - Steps the LR scheduler
- Checks the stop condition (if configured)
Parameters:
| Parameter | What It Does |
|---|---|
training_epoch_runner |
Runner that performs the training pass |
validation_epoch_runner |
Runner that performs the validation pass |
training_criterion |
Loss function (or LossScheduler) for training |
validation_criterion |
Loss/metrics for validation |
lr_scheduler |
PyTorch LR scheduler (null = constant LR) |
stop_condition |
Optional early stopping (e.g., ValidationLossDidNotImprove) |
Runners¶
Runners execute a single pass through a dataset. Each runner moves data to the device, runs the model, applies postprocessing transforms, and computes loss/metrics.
| Runner | Use Case | Key Params |
|---|---|---|
TrainingEpochRunner |
Training with backprop | optimizer, gradient_accumulation_steps, mixed_precision, postprocessor |
ValidationEpochRunner |
Validation (no gradients) | mixed_precision, postprocessor |
BenchmarkRunner |
Testing/inference | Same as validation, but criterion is optional |
Hooks¶
Hooks are pluggable components that fire at defined points of the training lifecycle. Each hook goes in the hooks: list of the component whose lifecycle it follows:
Hook (srforge.training.hooks.*) |
Attach to | What It Does |
|---|---|---|
ProgressBar |
Runner | CLI progress bar during the epoch |
BatchImageLogger |
Runner | Logs sample prediction images to the tracker |
BatchImageSaver |
Runner | Saves prediction images to disk |
LossLogger |
Trainer | Logs training/validation losses + LR to the tracker each epoch |
PyTorchModelSaver |
Trainer | Saves best and latest model checkpoints |
GradientClip |
Runner | Clips gradients after backward |
There is no scope filter — a hook in the training runner's list fires only during training epochs; a hook in the validation runner's list fires only during validation. See Hooks for the full guide, including writing your own.
Legacy observers: sections
Configs written for older versions used a top-level observers:
block with scope: filters, subscribed to a global EventBus by the
script. That mechanism still works via a compatibility adapter but
is deprecated — removal in v0.16.0. Migrate each observer into
the appropriate hooks: list (the classes have Hook counterparts
with the same names under srforge.training.hooks). See the
porting guide.
Stop Conditions¶
The optional stop_condition on the trainer controls early stopping:
stop_condition:
_target: srforge.training.stop.ValidationLossDidNotImprove
params:
patience: 50 # Epochs to wait for improvement
min_delta: 0.0001 # Minimum change to count as improvement
If omitted, training runs for the full number of epochs.
How the Config Gets Used¶
The training script uses srforge.init() to create a ConfigResolver, then calls it to instantiate each component:
from srforge import GlobalSettings, init
from srforge.utils.checkpoint import resume_from_checkpoint
resolve = init(cfg)
# 1. Core components
model = resolve(cfg.model)
loss = resolve(cfg.loss)
optimizer = resolve(cfg.optimizer)
scheduler = resolve(cfg.lr_scheduler)
# 2. Datasets
train_dataset = resolve(cfg.dataset.training)
val_dataset = resolve(cfg.dataset.validation)
# 3. Trainer — must exist before resume (resume needs the runner)
trainer = resolve(cfg.trainer)
# 4. Tracker
tracker = resolve(cfg.tracker)
tracker.log_config(omegaconf.OmegaConf.to_container(cfg, resolve=False))
# 5. Resume from checkpoint — pass the trainer's runner, not the
# raw optimizer. Optimizer + scaler state live inside the runner.
ckpt = resume_from_checkpoint(
model, trainer.training_runner, scheduler, tracker=tracker
)
trainer.restore(ckpt) # None-safe: no-op for fresh runs
# 6. Train — hooks were already attached when the trainer/runners
# were resolved (they live in the runners' and trainer's hooks: lists)
trainer.train(cfg.training.epochs, train_loader, val_loader)
tracker.finish(0)
The resolve() callable is the general-purpose instantiator. Pass any config subtree to it, and it will recursively instantiate whatever it finds. If the subtree has no _target key, it's returned as a plain dictionary (or list).
See Writing Scripts for a complete walkthrough.
Runtime Injection¶
Some values are not known at config time — they depend on runtime setup. These are passed as keyword arguments to resolve():
The kwarg is forwarded to the constructor alongside the YAML-defined params.
Checkpoint state (epoch, best losses, scaler) is handled separately via trainer.restore(ckpt) — see Resuming Training.
Instance Caching¶
The resolver caches every instantiated object by its config path. This means:
# These return the same Python object
model1 = resolve(cfg.model)
model2 = resolve(cfg.model)
assert model1 is model2 # True — cached
This is critical for the ${ref:path} reference system to work correctly.
Caching edge cases
Extra kwargs are ignored on cache hit. If you call resolve(cfg.model, device="cuda") and later resolve(cfg.model, device="cpu"), the second call returns the cached instance from the first call — the device="cpu" argument is silently ignored.
${path} interpolation creates separate instances. See Which Should I Use? above.
Detached config nodes bypass caching. If you build a DictConfig manually (not from the resolver's config tree), resolve() cannot determine its path and will not cache the result. Calling it twice creates two separate instances.
CLI Overrides¶
SR-Forge uses Hydra for config loading, which gives you powerful CLI overrides for free:
# Change a single parameter
train model.params.hidden_dim=256
# Change multiple parameters
train training.batch_size=32 optimizer.params.lr=0.001
# Change the model entirely
train model._target=MyModel model.params.hidden_dim=128 model.params.num_layers=6
# Multi-GPU training
train 'system.device=[0,1]'
# Disable experiment tracking
train tracker.params.mode=disabled
The train and test commands are console entry points defined in pyproject.toml. They automatically load train-cfg.yaml or test-cfg.yaml respectively.
SequentialModel in Config¶
The SequentialModel can be fully defined in YAML, including all sub-models, transforms, and the flow DSL:
model:
_target: srforge.models.SequentialModel
params:
modules:
m1:
_target: MyModel
params: {}
copy:
_target: srforge.transform.entry.CopyFields
params:
field_key: y
output_key: z
mul:
_target: srforge.transform.data.Multiply
params:
value: 2.0
flow:
- "x -> m1 -> y" # Flow DSL strings
- " -> copy -> " # EntryTransform: opaque step
- "z -> mul -> out"
Each module under modules: is recursively instantiated. Models and DataTransforms get their IO binding from the flow DSL. EntryTransforms are opaque steps — their field keys go in params:, and the flow line is just -> name ->. See SequentialModel for the full syntax.
Testing Config¶
The test config is simpler — no optimizer, scheduler, runners, or trainer:
system:
device: 0
output_dir: ./results # Where to save output images
model:
# Load a trained model from Weights & Biases
_target: srforge.utils.model.get_model_from_wandb
params:
model_name: MyModel
project: my-project
entity: my-team
run_id: abc123xyz # W&B run to load from
load_best_model: True # Best checkpoint (vs. last)
test_metrics:
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.PSNR
params:
weight: 1.0
dataset:
_target: srforge.dataset.LazyDataset
params:
root: /path/to/test/data
postprocessing: []
Key differences from training:
- No
optimizer,lr_scheduler,training, or runner/trainer sections - Model is typically loaded from W&B rather than defined fresh
- Single dataset (no training/validation split)
test_metricsinstead ofloss+validation_metrics
The test script creates a BenchmarkRunner — hooks (e.g. a progress bar or image saver) go in its hooks: list, in YAML or programmatically:
runner = BenchmarkRunner(metrics, device, postprocessor=postprocessor,
hooks=[ProgressBar(name="test")])
runner.run_epoch(model=model, data_loader=test_loader, epoch=0)
BenchmarkRunner works like ValidationEpochRunner but with an optional criterion — it can run pure inference without computing loss.
See Writing Scripts for complete training and test script examples.
Resuming Training¶
When the tracker detects a resumed run (tracker.is_resumed), the resume_from_checkpoint() utility handles everything:
- Downloads the last checkpoint from the tracker
- Restores runner state (optimizer + AMP scaler) via
runner.load_training_state() - Restores LR-scheduler state via
lr_scheduler.load_state_dict() - Loads model weights for all sub-models
- Restores RNG states for exact reproducibility
tracker:
_target: srforge.tracking.WandbTracker
params:
run_id: abc123xyz # Setting this triggers resume
resume: allow
from srforge.utils.checkpoint import resume_from_checkpoint
# Create the trainer first — its `training_runner` owns the optimizer +
# AMP scaler state that resume_from_checkpoint needs to restore.
trainer = resolve(cfg.trainer)
ckpt = resume_from_checkpoint(
model, trainer.training_runner, scheduler, tracker=tracker
)
trainer.restore(ckpt) # None-safe: no-op for fresh runs
See Experiment Tracking for more details.
Common Patterns¶
Loss Scheduling¶
Change the loss function at specific epochs:
loss:
_target: srforge.loss.schedule.LossScheduler
params:
schedule:
0: # Epochs 0-49: L1 only
_target: srforge.loss.metrics.L1
params:
weight: 1.0
50: # Epochs 50+: L1 + SSIM
_target: srforge.loss.combiner.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.L1
params:
weight: 1.0
- _target: srforge.loss.metrics.SSIM
params:
weight: 0.05
Multi-GPU Training¶
The DataLoaderFactory detects the list and configures distributed data loading automatically. The training script wraps the model in DataParallel.
Dataset Caching¶
When cache_dir is set, raw dataset entries are cached to disk as pickle files for fast subsequent loading.
Common Errors¶
Missing _target¶
Without _target, the resolver treats this as a plain dictionary, not an object to instantiate. It will pass it through as-is, which usually causes a TypeError downstream.
Class Not Found¶
Check:
- Is the module path correct?
- Is there a typo in
_target?
IO Binding on Non-IOModule¶
Only IOModule subclasses (Models, Transforms, and Losses) support the io: key. Regular Python classes cannot have IO bindings.
Invalid Reference¶
The ${ref:path} must point to an existing config key. Check for typos in the reference path.
How It All Fits Together¶
train-cfg.yaml
│
├─ model, optimizer, loss, lr_scheduler, dataset
├─ preprocessing / postprocessing
├─ training_runner (hooks: ProgressBar "T")
├─ validation_runner (hooks: ProgressBar "V", BatchImageLogger)
└─ trainer (hooks: LossLogger, PyTorchModelSaver)
│
▼ resolve = init(cfg)
resolve() instantiates all objects, resolves ${ref:...}
hooks bind to their runner / trainer at construction
│
▼
PyTorchTrainer.train(epochs, train_loader, val_loader)
│
├─ Each epoch:
│ ├─ TrainingEpochRunner.run_epoch(model, train_loader)
│ │ ├─ Forward pass → postprocessor → loss → backward
│ │ └─ its ProgressBar updates after each batch
│ │
│ ├─ ValidationEpochRunner.run_epoch(model, val_loader)
│ │ ├─ Forward pass → postprocessor → metrics (no gradients)
│ │ └─ its ProgressBar + BatchImageLogger update after each batch
│ │
│ ├─ LossLogger (trainer hook) logs train/val losses to tracker
│ ├─ PyTorchModelSaver (trainer hook) saves best + last checkpoints
│ ├─ LR scheduler steps
│ └─ Stop condition checked
│
└─ Training complete
Golden Rule:
_target+params= the object you want. Nest them as deep as you need — the resolver handles the rest.
Summary¶
| Concept | What It Does |
|---|---|
_target |
Specifies which class to instantiate |
params |
Constructor arguments (keyword) |
${path} |
Copies raw config values (OmegaConf interpolation) |
${ref:path} |
References an already-instantiated Python object |
io: key |
Binds IO for Models, Transforms, and Losses |
init(cfg) |
Framework setup, returns a ConfigResolver |
resolve(cfg.section) |
Instantiates any config section |
resolve(cfg.section, key=val) |
Injects runtime-only values |
resume_from_checkpoint(model, runner, lr_scheduler, tracker=) |
One-call checkpoint resume (runner owns optimizer+scaler) |
hooks: list on runner/trainer params |
Attach hooks (progress bars, loggers, checkpointing) |
PyTorchTrainer |
Orchestrates the epoch loop with runners |
TrainingEpochRunner |
Runs training pass (forward, loss, backward, optimizer step) |
ValidationEpochRunner |
Runs validation pass (forward, metrics, no gradients) |
BenchmarkRunner |
Runs inference with optional metrics (used by test script) |
| CLI overrides | train key=value changes any config parameter |
Next: Experiment Tracking — Logging, checkpointing, and run resumption