Architecture
flowchart LR
C[Config YAML + CLI] --> R[Runner / scheduler]
R --> B[Benchmark → Stream of Tasks]
B --> S1[Sequential CL: Ensemble + Strategy + TrainStep]
B --> S2[Train-then-consolidate: Experts → Consolidator]
S1 --> K[Collectors]
S2 --> K
K --> L[(RawLog per run)]
L --> M[Metrics: pure functions]
M --> T[results.csv + statistics]
T --> P[report.md / report.html]
Packages
| Package | Responsibility |
|---|---|
contilab.core | TensorSplit, Task, Stream, Batch, RunContext, config dataclasses, scenario masks |
contilab.data | Benchmark builders, on-disk tensor cache, TrainPool (all tasks’ training data addressed by global index), DataLoader-free batching |
contilab.models | MLP, ReducedResNet18 (GroupNorm), HFClassifier (any Hugging Face causal LM + last-token head), in-house LoRA |
contilab.engine | Ensemble (stacked parameters), TrainStep (vmapped / compiled loss, autograd, element-wise update), trainer loop, evaluator, device and precision selection |
contilab.strategies | CL methods as plugins |
contilab.consolidate | Expert training and caching, Task Arithmetic, TIES, multi-teacher and teacher → student continual distillation, smaller students |
contilab.metrics | RawLog, collectors, metric registry, metric families, statistics |
contilab.report | Tables, figures, Markdown/HTML rendering |
contilab.runner | Config loading and matrix expansion, execution, checkpoint resume, scheduling, CLI, throughput bench |
The ensemble axis E
Every training tensor carries a leading member axis E. Runs that differ only by their seed form one group:
- parameters are stacked with
torch.func.stack_module_stateinto[E, ...]leaf tensors; - the model is evaluated with
vmap(functional_call(template, ...))over members (E = 1 skips vmap); - the loss is the sum of per-member losses, so gradients never mix between members;
- optimisers are element-wise (SGD, AdamW) and gradient clipping is per member, so member i of a group is numerically the same run as seed i trained alone (tested to
atol=1e-5); - per-member state — data order, replay buffers, Fisher information, snapshots — is stacked or kept per member generator.
A member whose loss becomes NaN or explodes is marked diverged, its gradients are zeroed and its parameters restored after each optimiser step, so it cannot disturb the others.
Strategies: hot path vs host side
A strategy implements a per-member loss(p, s, batch, replay, f) that the engine vmaps and compiles, and host-side hooks (before_task, replay_batch, after_step, after_task, eval_setup) that run outside the compiled region. See Extending.
Raw-log-first evaluation
Collectors record only raw data during training (per tier, see Metrics). After the experiment, finalize_experiment computes every metric from the stored records, so a new metric can be added with contilab metrics <exp> --add <name> without re-training.