Config Schema¶
An echelon3 run is a single YAML file. Every top-level key is one section of the
run, and every component inside it is the same module / type / config
triple resolved by echelon3.creator.get_attr_from_module (see
How It Works). This page documents each section:
what goes in it, which keys are required, and which create_* function in
echelon3.creator reads it.
config is just constructor kwargs
For nearly every component, the config: block is splatted into the
constructor as **config. Required keys are therefore the required
arguments of whatever class or function module/type names — echelon3
does not validate them itself; the constructor does.
Section overview¶
| Section | Read by | Purpose |
|---|---|---|
device / device_ids |
cli.train |
Target device and DataParallel GPU ids |
net |
create_net |
The network (nested triples for backbone/head/neck) |
weights_loader |
create_universal |
How to load net.weights (optional) |
data |
create_datasets |
Train dataset + one or many test datasets |
transform |
create_augments, create_preprocesses |
Albumentations augment + tensor preprocess |
loss |
create_loss |
List of weighted loss terms |
metrics |
create_metrics |
List of validation metrics |
dataloaders |
create_dataloaders |
Train + test loaders (global batch size) |
optimizer |
create_optimizer |
Optimizer over net.parameters() |
scheduler |
create_scheduler |
LR scheduler |
trainer |
create_trainer |
The training loop |
target |
create_checkpoint_manager |
Checkpoint directory |
mlops |
create_mlops_logger |
Metric logger (optional, defaults to TensorBoard) |
export |
create_exporters |
ONNX/graph export (optional) |
runner |
create_universal |
Batch inference over images/video (optional) |
evaluator |
create_evaluator |
Post-training evaluation (optional) |
init_from / finetune |
cli.finetune |
Warm-start, freezing, param groups (optional) |
device / device_ids¶
Plain scalars, not triples. device defaults to cuda (falls back to cpu
when CUDA is unavailable). device_ids is a list of GPU indices passed to
torch.nn.DataParallel.
device: cuda
device_ids: [0, 1]
Ignored under DDP
When launched with torchrun, both keys are ignored — one process owns one
GPU chosen by LOCAL_RANK, and visibility comes from CUDA_VISIBLE_DEVICES.
See Multi-GPU Training.
net¶
create_net resolves the triple and calls type(**config). config is
required. Container nets (ClassifierNet, Segmenter, HeatmapDetector)
receive further triples as their config values and resolve them recursively.
net:
module: echelon3.nets.classifier
type: ClassifierNet
config:
backbone:
module: timm
type: create_model
config: { model_name: mobilenetv3_small_100, pretrained: false, num_classes: 0 }
head:
module: echelon3.nets.heads.classifier_heads
type: DenseClassifierHead
config:
num_classes: 2
num_features: 1024
dropout: 0.1
activation: { module: torch.nn, type: Identity }
Loading pretrained weights¶
Add a weights key (a checkpoint path) to net, plus a top-level
weights_loader section. When net.weights is present, cli.train builds the
loader with create_universal(cfg.weights_loader) and calls
loader.load(net, cfg.net.weights, device).
net:
module: echelon3.nets.classifier
type: ClassifierNet
weights: ./pretrained/backbone.tar
config: { ... }
weights_loader:
module: echelon3.weightloaders.partial
type: PartialWeightsLoader
config: { strip_prefix: "module." } # optional
echelon3.weightloaders.basic.WeightsLoader does a strict load_state_dict;
PartialWeightsLoader loads only tensors whose name and shape match,
skipping the rest — the way to warm-start a changed architecture.
data¶
Read by create_datasets. Requires a train entry (always a single dataset)
and a test entry, which may be either a single dataset or a mapping of
named datasets. Each dataset triple is instantiated with
type(**config, augment=..., preprocess=...), where the augment/preprocess come
from the transform section (train transforms for train, test transforms for
every test dataset).
Single test dataset:
data:
train:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ./data/train, wildcards: ['*.png'] }
test:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ./data/test, wildcards: ['*.png'] }
Multiple named test datasets — create_datasets returns a dict[name ->
Dataset], which the Trainer validates independently:
data:
train: { ... }
test:
incidents:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ./data/incidents, wildcards: ['*.png'] }
control:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ./data/control, wildcards: ['*.png'] }
A test dataset is single vs. named by shape
create_datasets treats test as a single dataset if it has module and
type keys at the top level, otherwise as a mapping of named datasets. With
named datasets, route metrics per dataset with trainer.config.metrics_on
(see below).
transform¶
Two independent pipelines keyed by purpose (train / test):
augment— an ordered map of albumentations transforms applied to the raw HWC uint8 image.create_augmentswraps them inA.Compose([...] + [ToTensorV2()]), soToTensorV2is appended for you and the pipeline always yields a CHW tensor. Ifaugmentis omitted for a purpose, that purpose gets a bareToTensorV2.preprocess— an ordered map oftorch.nn.Modules assembled bycreate_preprocessesinto atorch.nn.Sequential. Runs on the tensor after augmentation.
transform:
train:
augment:
flip:
module: albumentations
type: HorizontalFlip
config: { p: 0.5 }
jitter:
module: albumentations
type: ColorJitter
config: { p: 0.3 }
preprocess:
to01:
name: to01
module: echelon3.transforms.preprocess.basic
type: To01
test:
preprocess:
to01:
name: to01
module: echelon3.transforms.preprocess.basic
type: To01
preprocess entries need a name
create_single_preprocess builds an OrderedDict keyed by each entry's
name field, so every preprocess step must carry name:. Augment entries
do not — their map key is only for readability.
For detection, bbox-aware Compose is enabled by putting bbox_params under the
purpose's config: transform.train.config.bbox_params: { format: yolo,
label_fields: [] }.
loss¶
A list — each item is a single-key mapping name -> triple. Beyond the
usual module/type/config, each item requires a weight. create_loss
returns {name: (loss_instance, weight)}; the trainer computes
sum(weight * loss(pred, target)).
loss:
- ce:
module: echelon3.losses.classification
type: WeightedCrossEntropyLoss
weight: 1.0
config: { weight: [1.0, 1.0] }
- margin:
module: echelon3.losses.classification
type: FixedMarginCrossEntropyLoss
weight: 0.5
config: { m: 0.35, idx: 1 }
config is optional (a loss with no constructor args can omit it); weight is
not.
metrics¶
A list of name -> triple items, like loss but without weights. Built by
create_metrics via create_universal. Any object exposing torchmetrics-style
update/compute/reset works — including torchmetrics classes directly.
metrics:
- accuracy:
module: torchmetrics
type: Accuracy
config: { task: multiclass, num_classes: 2 }
- eer:
module: echelon3.metrics.classification
type: EER
dataloaders¶
train (single) and test (single or named, mirroring data). The dataset is
injected as dataset=...; everything under config is passed through to the
loader constructor.
dataloaders:
train:
module: torch.utils.data
type: DataLoader
config: { batch_size: 32, shuffle: true, num_workers: 4, drop_last: true }
test:
module: torch.utils.data
type: DataLoader
config: { batch_size: 32, shuffle: false, num_workers: 4 }
batch_size is the global batch size
Under DDP, create_dataloaders divides dataloaders.train.config.batch_size
by world_size and installs a DistributedSampler, so the number you write
is the total across all GPUs. See Multi-GPU Training.
echelon3 also ships custom loaders you can drop in here — e.g.
echelon3.dataloaders.balance.BalancedDataLoader for class-balanced batches.
optimizer¶
create_optimizer calls type(params=net.parameters(), **config). config is
optional. A special trainable_only: true key (popped before construction)
restricts the optimizer to parameters with requires_grad=True.
optimizer:
module: torch.optim
type: AdamW
config: { lr: 0.001, weight_decay: 0.0001 }
scheduler¶
create_scheduler calls type(optimizer=optimizer, **config). config is
required (even if empty for a schedule with defaults). scheduler.step() is
called once per epoch by the trainer.
scheduler:
module: torch.optim.lr_scheduler
type: CosineAnnealingLR
config: { T_max: 50 }
trainer¶
create_trainer injects the assembled net, optimizer, loaders, losses, metrics,
scheduler, checkpoint manager, logger and device, then splats config. For the
built-in echelon3.trainers.baseline.Trainer the useful config keys are:
| Key | Meaning |
|---|---|
epochs |
Number of epochs (required). |
keep_best_on |
Which metric(s) gate checkpoint saving (see below). |
times_to_validate_per_epoch |
Validation passes per epoch (default 1). |
high_is_better |
Default direction when keep_best_on is a bare name (default true). |
float_labels |
Cast labels to float before the loss (default false). |
reset |
On resume, restart epoch/step counters instead of continuing. |
metrics_on |
Map metric_name -> test_dataset_name for named test sets. |
keep_best_on accepts a bare metric name, a list, or a directional/tolerance
mapping:
trainer:
module: echelon3.trainers.baseline
type: Trainer
config:
epochs: 50
times_to_validate_per_epoch: 1
high_is_better: true
keep_best_on:
accuracy:
mode: directional # save when this metric moves the right way
value: high # high | low
accuracy_control:
mode: tolerance # allow small regressions within a band
value: 0.1% # absolute (0.001) or percent ("0.1%")
direction: high
metrics_on:
accuracy: incidents
accuracy_control: control
A checkpoint is saved only when all listed metrics satisfy their condition
simultaneously. For dict-shaped (multi-head) predictions, use
echelon3.trainers.multihead.MultiHeadTrainer instead.
target¶
Passed verbatim to CheckpointManager(**config).
target:
path: ./targets/my_experiment
checkpoints_to_keep: 2
path is the directory for checkpoint-N.tar files and TensorBoard events; it
is also where the default logger writes. Training resumes automatically from the
highest-numbered checkpoint found there.
mlops (optional)¶
Omit it and echelon3 uses echelon3.mlops.tensorboard.TensorboardLogger, whose
folder is set to target.path. Provide the section to swap loggers; the
folder is injected for you.
mlops:
module: echelon3.mlops.tensorboard
type: SegmentationTensorboardLogger
export (optional)¶
Read by create_exporters for echelon3-export. Optional preprocess /
postprocess maps (same shape as transform.*.preprocess) are fused around
the network so the exported graph is preprocess -> net -> postprocess; then one
or more named exporters run. See Exporting to ONNX.
export:
preprocess:
to01: { name: to01, module: echelon3.transforms.preprocess.basic, type: To01 }
exporters:
onnx:
module: echelon3.exporters.onnx
type: OnnxExporter
config:
target: ./targets/my_experiment/model.onnx
input_names: [images]
output_names: [logits]
input_shape: [1, 3, 64, 64]
opset: 18
use_tracing: true
runner (optional)¶
Read by echelon3-run via create_universal(cfg.runner). The runner processes
a folder of images or a video through preprocess -> net -> postprocess. It
reuses export.preprocess / export.postprocess, and an optional
export.wrapper triple (e.g. echelon3.wrappers.spatial.PadToMultiplier).
runner:
module: echelon3.runners.segmenter
type: ImagesSegmenter
config: { source: ./inputs, target: ./outputs, colormap: {1: [255, 0, 0]}, opacity: 0.5 }
evaluator (optional)¶
Read by echelon3-evaluate. create_evaluator builds the evaluator with the
network, the test dataloader, and the single metric named by evaluator.metric
(looked up in metrics).
evaluator:
module: echelon3.evaluators.classifier
type: ClassifierEvaluator
metric: accuracy
config: { scores_and_labels: ./targets/my_experiment/errors }
init_from / finetune (optional)¶
Only echelon3-finetune reads these; a stock training config runs unchanged
under that entry point. See Extending for the workflow.
init_from:
checkpoint: ./targets/base/checkpoint-40.tar
strict: false # tolerate missing/extra keys (default false)
finetune:
freeze_patterns: ['backbone\.'] # freeze params whose dotted name matches
head_only: false # or: train only the head
param_groups: # per-layer LR multipliers
- { pattern: 'backbone\.', lr_mult: 0.1 }
- { pattern: 'head\.', lr_mult: 1.0 }
init_from.checkpointwarm-starts the network'sstate_dict(themodule.prefix is stripped automatically).finetune.freeze_patternsfreezes tensors whose dotted name matches any regex.finetune.param_groupsbuilds per-group learning rates (lr_multtimes the optimizer's baselr);head_only: truetrains only the head.
A complete config¶
This is examples/configs/smoke.yaml — a full classifier run with an export
section, runnable end to end (see First Run):
device: cuda
device_ids: [0]
net:
module: echelon3.nets.classifier
type: ClassifierNet
config:
backbone:
module: timm
type: create_model
config: { model_name: mobilenetv3_small_100, pretrained: false, num_classes: 0 }
head:
module: echelon3.nets.heads.classifier_heads
type: DenseClassifierHead
config:
num_classes: 2
num_features: 1024
dropout: 0.1
activation: { module: torch.nn, type: Identity }
data:
train:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ${oc.env:SMOKE_DATA_ROOT,./smoke_data}/train, wildcards: ['*.png'] }
test:
module: echelon3.data.imageclassifier
type: FoldersHiveImageClassifierDataset
config: { classes: 2, folder: ${oc.env:SMOKE_DATA_ROOT,./smoke_data}/test, wildcards: ['*.png'] }
transform:
train:
preprocess:
to01: { name: to01, module: echelon3.transforms.preprocess.basic, type: To01 }
test:
preprocess:
to01: { name: to01, module: echelon3.transforms.preprocess.basic, type: To01 }
loss:
- ce:
module: echelon3.losses.classification
type: WeightedCrossEntropyLoss
weight: 1.0
config: { weight: [1.0, 1.0] }
metrics:
- accuracy:
module: torchmetrics
type: Accuracy
config: { task: multiclass, num_classes: 2 }
dataloaders:
train:
module: torch.utils.data
type: DataLoader
config: { batch_size: 16, shuffle: true, num_workers: 2, drop_last: true }
test:
module: torch.utils.data
type: DataLoader
config: { batch_size: 16, shuffle: false, num_workers: 2 }
optimizer:
module: torch.optim
type: AdamW
config: { lr: 0.001 }
scheduler:
module: torch.optim.lr_scheduler
type: LinearLR
config: { start_factor: 1.0, end_factor: 1.0, total_iters: 1 }
trainer:
module: echelon3.trainers.baseline
type: Trainer
config:
epochs: 6
keep_best_on:
accuracy: { mode: directional, value: high }
times_to_validate_per_epoch: 1
high_is_better: true
target:
path: ${oc.env:SMOKE_TARGET,./targets/smoke}
checkpoints_to_keep: 2
export:
preprocess:
to01: { name: to01, module: echelon3.transforms.preprocess.basic, type: To01 }
exporters:
onnx:
module: echelon3.exporters.onnx
type: OnnxExporter
config:
target: ${oc.env:SMOKE_TARGET,./targets/smoke}/smoke.onnx
input_names: [images]
output_names: [logits]
input_shape: [1, 3, 64, 64]
use_tracing: true
opset: 18
Next¶
- Built-in Components — the catalog of
module/typevalues. - Extending — plug in your own classes.