Core Concepts¶
You've seen why a training needs each piece in Anatomy of a Training; this page is the precise vocabulary and code for each one, plus the big-picture diagram the rest of the guide builds on. The terms below (Entry, IO binding, Hook, Runner, …) show up everywhere — read it once now for the general shape, and come back anytime a term needs a refresher.
The Big Picture¶
Every concept below plays one role in this flow:
flowchart LR
subgraph data ["Data side"]
DS[Dataset] -->|produces| E1[Entry]
E1 --> T[Transforms]
T --> E2[Entry]
end
subgraph model ["Model side"]
E2 --> M[Model]
M -->|writes output<br/>into| E3[Entry]
E3 --> L[Metric]
L --> MS[MetricScores]
end
subgraph training ["Training side"]
TR[Trainer] -->|runs epochs via| R[EpochRunner]
R -.->|iterates batches,<br/>calls| M
R -.->|computes| L
H[Hooks] -.->|attach to| TR
H -.->|attach to| R
H -->|log via| TK[Experiment Tracker]
end
CFG[YAML Configuration] -.->|instantiates & wires<br/>everything| DS & T & M & L & TR
Three things to notice:
- Entry is the bloodstream. Every component reads from and writes to Entry objects — that's what makes components swappable.
- IO Binding is the wiring. It tells each component which Entry fields to read/write, so the same component works with any data layout.
- YAML instantiates everything. The dashed lines from Configuration mean "built from config" — you change the experiment by editing YAML, not Python.
Code or config — your choice
Each concept below is shown twice: as Python and as the equivalent YAML config. The tabs are linked — pick the view you prefer once and the whole page follows it. Python is shown by default.
The snippets share one running example — a super-resolution pipeline — so they connect: a dataset produces lr and hr fields; an Upscaler model reads lr and writes sr; an L1 loss compares sr against hr; the trainer wires them together. Follow the same names from one concept to the next.
Entry¶
A specialized dictionary that carries all data for a single sample through the pipeline. It can hold tensors, nested structures, metadata — anything your experiment needs. Every component in SR-Forge reads from and writes to Entry.
Detailed guide: Entry
Dataset¶
A Dataset loads raw data (images, time series, point clouds, etc.) and wraps each sample in an Entry. It extends PyTorch's Dataset class, so it works directly with PyTorch's DataLoader for batching and shuffling. Datasets can also apply transforms at load time and cache the preprocessed results so subsequent epochs skip recomputation.
Detailed guide: Datasets
Fields¶
Named slots inside an Entry. Think of them as columns in a spreadsheet — each field has a name (like "lr", "hr", "sr") and holds one piece of data. Components read from specific fields and write results to other fields.
Transform¶
A reusable processing step that modifies data. SR-Forge has two kinds:
- DataTransform — processes the values inside Entry fields. You write a function that takes values in (tensors, strings, dicts, etc.) and returns values out — the framework extracts them from the Entry and stores results back. The Entry structure stays the same; only field content changes. Example: normalizing pixel values, resizing an image, converting types.
- EntryTransform — operates on the Entry itself. Has full access to add, remove, rename, or inspect fields, and can even change the Entry type. Example: splitting a multispectral image into separate band fields, removing temporary metadata, renaming fields.
from srforge.transform import DataTransform, EntryTransform
class Normalize(DataTransform): # transforms field VALUES
def transform(self, x):
return (x - x.mean()) / x.std()
class DropField(EntryTransform): # transforms the Entry STRUCTURE
def __init__(self, *, field_key):
self.field_key = field_key; super().__init__()
def transform_unbatched(self, entry):
del entry[self.field_key]; return entry
Detailed guide: Transforms
IO Binding¶
The process of connecting method parameter names to Entry fields. A model with forward(self, image) might be bound so that image reads from entry["lr"] — same model, different data. This separation lets you write a component once and reuse it with different field names in different contexts. There are three ways to bind: in Python code, in YAML configuration, or automatically through the flow DSL.
from srforge.models.basic import Bicubic
model = Bicubic(scale=3) # a simple built-in model
model.set_io({"inputs": {"image": "lr"}, "outputs": "sr"}) # bind it to the entry
# forward param 'image' reads entry["lr"]; the result writes entry["sr"]
result = model(entry) # result.sr now holds the upscaled image
Detailed guide: IO Binding
Model¶
A neural network component that participates in SR-Forge's pipeline. It extends PyTorch's nn.Module with IO binding, so it can read inputs from Entry fields and write outputs back. You implement forward() with your computation logic — the framework handles data routing.
import torch
from srforge.models import Model
class Upscaler(Model):
def __init__(self, channels: int = 64): # ← the 'channels' param the YAML passes
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Conv2d(3, channels, 3, padding=1),
torch.nn.ReLU(),
torch.nn.Conv2d(channels, 3, 3, padding=1),
)
def forward(self, image): # parameter names drive IO binding
return self.net(image)
Detailed guide: Model
SequentialModel¶
A way to chain multiple models and transforms into a multi-stage pipeline. Instead of writing glue code to pass data between components, you describe the flow declaratively: "take this field, pass it through this module, store the result there." SequentialModel handles all the wiring.
Detailed guide: SequentialModel
Flow DSL¶
A small arrow-based syntax for defining pipelines inside SequentialModel. DSL stands for Domain-Specific Language. Every line follows the same pattern — three parts separated by ->:
For example: lr -> upscale -> sr means "read the lr field, pass it through the module named upscale, store the result as sr." Multiple fields are separated by commas: (lr, ref) -> fusion -> (sr, confidence).
Metric¶
A component that scores model output. The same class serves as a training loss (its score drives the gradient) and as an evaluation metric (its score is only logged) — its weight decides which. Like a Model, a Metric uses IO binding to read the fields it needs from the Entry (e.g. prediction and target). Several metrics are combined and accumulated into a MetricScores object — a container of weighted, named score entries. The training loop reduces it (total_weighted().mean().backward()) to drive optimization, and the same object carries validation metrics that hooks log and checkpointing uses to decide "best so far."
Usually you just pick a built-in metric, bind its parameters to Entry fields, and call it:
from srforge.metrics.regression import L1
loss = L1().set_io({"inputs": {"x": "sr", "y": "hr"}})
scores = loss(entry) # → MetricScores
scores.total_weighted().mean().backward() # the training signal
Writing your own is tiny — no __init__ needed unless you add
parameters. Return a per-element error map and the framework handles
masking and batch reduction. This is exactly how the built-in L1 above
is defined:
Detailed guide: Metrics
Trainer¶
The component that owns the training lifecycle. It runs the epoch loop, alternates training and validation, steps the learning-rate scheduler, checks stop conditions, and exposes hook points that drive checkpointing and logging. You configure it once and call train(...); it coordinates everything else. PyTorchTrainer is the standard implementation.
trainer = PyTorchTrainer(
training_epoch_runner=train_runner,
validation_epoch_runner=val_runner,
training_criterion=loss, # the L1 from the Metric concept
validation_criterion=metrics,
lr_scheduler=sched, model=model, # the Upscaler from the Model concept
)
trainer.train(100, train_loader, val_loader) # epochs, then the two loaders
# ${ref:...} points at objects defined elsewhere in the config —
# this is how the trainer connects to the model, loss, and runners.
trainer:
_target: srforge.training.trainers.PyTorchTrainer
params:
training_epoch_runner: ${ref:training_runner}
validation_epoch_runner: ${ref:validation_runner}
training_criterion: ${ref:loss}
validation_criterion: ${ref:validation_metrics}
lr_scheduler: ${ref:lr_scheduler}
model: ${ref:model}
Detailed guide: Trainers & Runners
EpochRunner¶
Where one epoch actually executes. The Trainer delegates each epoch to a runner that iterates over a DataLoader, calls the model, computes the loss, and (when training) runs the backward pass and optimizer step. Different runners specialize the behavior: TrainingEpochRunner does backprop with gradient accumulation and AMP; ValidationEpochRunner and BenchmarkRunner run without backward; GANTrainingRunner alternates generator and discriminator updates. Each returns a MetricScores summarizing the epoch.
Detailed guide: Trainers & Runners
Hook¶
The mechanism for plugging side behavior into the training loop — progress bars, loss logging, checkpoint saving, image previews, gradient clipping, auxiliary penalties. A Hook attaches directly to the component whose lifecycle it should follow: runner-level hooks (e.g. ProgressBar) go in the runner's hooks: list, trainer-level hooks (e.g. LossLogger, PyTorchModelSaver) in the trainer's. Hooks receive a mutable Context at each stage, so they can also influence training (add losses, clip gradients, skip steps) — not just observe it.
Detailed guide: Hooks
Experiment Tracker¶
A backend-agnostic interface for recording metrics, images, and files. WandbTracker logs to Weights & Biases; NullTracker silently does nothing (handy for debugging or offline runs). Hooks and scripts log through the tracker without knowing which backend is active, so switching from W&B to nothing — or to a custom backend — is a config change.
Detailed guide: Experiment Tracking
Configuration¶
YAML files that define your entire experiment — model architecture, optimizer, loss function, data pipeline, everything. SR-Forge reads the YAML and automatically builds all the Python objects it describes. This means you can change your experiment without touching code.
Detailed guide: Configuration
Next: the Guide takes each piece in depth, starting with Entry — the container every component reads from and writes to.