diff --git a/.gitignore b/.gitignore index 506ff87..37c8bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ data/* *.png !docs/images/*.png *.txt +!docs/requirements.txt *.csv !tests/references/*.csv *.pdf @@ -162,6 +163,7 @@ celerybeat.pid .env .envrc .venv +.venv-docs/ env/ venv/ ENV/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..25d3706 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# Read the Docs build configuration. +# https://docs.readthedocs.io/en/stable/config-file/v2.html +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.13" + +sphinx: + configuration: docs/conf.py + # The build is warning-clean; keep it that way. + fail_on_warning: true + +# Only the light docs dependencies are installed; torch, river, evidently, wandb +# and friends are mocked in docs/conf.py via autodoc_mock_imports. +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/README.md b/docs/README.md index 5f78d58..77dc4b7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,25 +1,49 @@ -# BaseSim Documentation +# Apeiron Documentation -This directory contains the detailed reference docs for the framework's three main extension points and configurations: +This directory is a [Sphinx](https://www.sphinx-doc.org/) project written in +[MyST-Markdown](https://myst-parser.readthedocs.io/) and published on +Read the Docs. Every `.md` file here is a page in that site; `conf.py` and +`../.readthedocs.yaml` configure the build. -- `configurations.md`: required and optional configuration settings -- `model_harness.md`: model + data-stream integration contract -- `drift_detectors.md`: detector classes, detector config, and detector wiring -- `continuous_learning.md`: continual-learning trainer, updater modes, and training config -- `tracking.md`: enabling the W&B or MLflow backend, the logged metric namespace, and reading run charts +## Building locally -## Read Order +Heavy runtime dependencies (torch, river, evidently, wandb, ...) are mocked in +`conf.py`, so a docs build does **not** need the full project environment: -1. Start with `configurations.md` to learn on the required and optional configuration parameters used by Apeiron. -2. Continue with `model_harness.md` to understand how models and stream loaders are exposed. -3. Read `drift_detectors.md` to see how monitoring decisions are made. -4. Read `continuous_learning.md` to understand what happens after drift is detected. +```bash +python -m venv .venv-docs && source .venv-docs/bin/activate +pip install -r docs/requirements.txt +sphinx-build -b html docs docs/_build/html +open docs/_build/html/index.html +``` -## Runtime Flow +Add `-W` to turn warnings into errors, and `-a -E` to force a full rebuild after +changing `conf.py`. -1. `src/main.py` builds `Config` from TOML, env vars, and CLI overrides. -2. `examples/utils.py` selects a concrete `BaseModelHarness` by `cfg.data.name`. -3. `src/driver/continuous_monitor.py` evaluates streaming batches and calls a detector at intervals. -4. On drift, `src/training/continuous_trainer.py` runs a CL loop with an updater from `src/training/updater/create_updater.py`. -5. Logging is stage-aware (`eval`, `drift`, `cl`) via `src/logger/`. +## Page map +| Page | Contents | +| --- | --- | +| `index.md` | Landing page and the toctrees that define site navigation. | +| `installation.md` | Python/Poetry setup, using Apeiron as a dependency, dev commands. | +| `quickstart.md` | First run, reading the metrics CSV, config overrides. | +| `architecture.md` | Runtime flow, module map, the four extension points. | +| `configurations.md` | Every TOML section and key the config parser accepts. | +| `model_harness.md` | Model + data-stream integration contract. | +| `drift_detectors.md` | Detector classes, options, and wiring. | +| `choosing_a_detector.md` | Decision guide for picking and tuning a detector. | +| `continuous_learning.md` | CL trainer, updater modes, training config. | +| `tracking.md` | W&B / MLflow backends, logged metric namespace, reading charts. | +| `profiler.md` | FLOPS profiler and the `cperf_*` metrics. | +| `deployment.md` | Frontier / Perlmutter HPC setup (included from the deployment READMEs). | +| `agent_skills.md` | The Claude Code and Codex skills shipped with the repo. | +| `api/` | Autodoc API reference generated from `src/apeiron/` docstrings. | + +## Conventions + +- `profiler.md` and `deployment.md` use `{include}` to pull in READMEs that live + next to the code, so those pages stay in sync with the scripts they document. +- Prefer `{doc}` / `{ref}` cross-references over raw relative links so Sphinx + can validate them at build time. +- New pages must be added to a toctree in `index.md`, otherwise Sphinx warns + that the document is not included in any toctree. diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..d19bf42 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,26 @@ +/* Apeiron docs -- small refinements on top of the Furo theme. */ + +/* Keep wide config/metric tables scrollable instead of overflowing the page. */ +.rst-content table, +article table { + display: block; + overflow-x: auto; + max-width: 100%; +} + +/* Mermaid diagrams: center and constrain. */ +.mermaid { + display: flex; + justify-content: center; + margin: 1.5rem 0; +} + +.mermaid svg { + max-width: 100%; + height: auto; +} + +/* Slightly tighter grid cards from sphinx-design. */ +.sd-card { + border-radius: 0.5rem; +} diff --git a/docs/agent_skills.md b/docs/agent_skills.md new file mode 100644 index 0000000..e61dbc0 --- /dev/null +++ b/docs/agent_skills.md @@ -0,0 +1,76 @@ +# Agent Skills + +The repository ships task-oriented **agent skills** that walk an AI coding agent +through the common Apeiron workflows. Each skill is maintained for both tools: + +- **Claude Code** — `.claude/skills//SKILL.md` +- **Codex** — `.codex/skills//SKILL.md` + +```{important} +Keep the two trees in sync: a change to a workflow should be reflected in both +`.claude/skills//SKILL.md` and `.codex/skills//SKILL.md`. +``` + +## Available skills + +| Skill | What it does | +| --- | --- | +| `install-apeiron` | Add Apeiron as a dependency to **another** project (path/git), verify `import apeiron`, pick CPU vs CUDA PyTorch. | +| `explore-examples` | Run a bundled example (MNIST/CIFAR) to see drift detection + CL in action; picks a config and reports the metrics CSV. | +| `custom-experiment` | Scaffold a harness, data utilities, and TOML for **your own** dataset/model, register it in the example factory, smoke-test, and run. | +| `integrate-apeiron` | Add Apeiron's drift detection / CL to an **existing** training loop; inspects your repo and writes the lightest adapter that fits. | +| `choose-detector` | Pick a drift detector (including whether to combine several into an `EnsembleDetector` and which voting rule to use), tune its settings, then emit or patch a validated `[drift_detection]` block. | + +## Choosing between them + +```{mermaid} +flowchart TD + A{What do you want to do?} --> B[Try the framework
on shipped data] + A --> C[Run on my own
data + model] + A --> D[Keep my own
training loop] + A --> E[Just configure
drift detection] + B --> B1[explore-examples] + C --> C1[custom-experiment] + D --> D1[install-apeiron
then integrate-apeiron] + E --> E1[choose-detector] +``` + +- **`explore-examples`** vs **`custom-experiment`** — the former runs a bundled + config, the latter scaffolds everything for your dataset and architecture. +- **`custom-experiment`** vs **`integrate-apeiron`** — use `custom-experiment` + for a self-contained Apeiron run; use `integrate-apeiron` when you already have + a PyTorch / Lightning / HF Trainer loop and want to bolt drift detection onto it. +- **`install-apeiron`** is only for adding Apeiron to a *separate* project. + Developing inside this repo is just `poetry install`. +- **`choose-detector`** stops at a validated config block — it does not run an + experiment. + +## Using them + +### Claude Code + +The skills are exposed as slash commands. Type `/` and the skill name: + +```text +/explore-examples +/install-apeiron ../my-project +/choose-detector examples/mnist/mnist.toml +``` + +You can also just describe the task in plain language ("add apeiron to my +training loop") and the matching skill triggers from its description. + +### Codex + +The equivalent skills live under `.codex/skills/`. Invoke a skill by name or +describe the task; Codex selects the skill whose description matches the +request. The skills are tool-agnostic in intent — only the file format differs +between the two trees. + +## Authoring notes + +Skills should defer to these docs rather than restating numbers that can drift. +For example, `choose-detector` names {doc}`drift_detectors` as its authoritative +reference for detector behavior and options, and re-checks +`src/apeiron/drift_detection/load_drift_detector.py` before relying on which +detectors are wired up. diff --git a/docs/api/config.md b/docs/api/config.md new file mode 100644 index 0000000..216259d --- /dev/null +++ b/docs/api/config.md @@ -0,0 +1,11 @@ +# Configuration + +The frozen dataclasses produced by the TOML/env/CLI parser. For the meaning of +each field, see {doc}`../configurations`. + +```{eval-rst} +.. automodule:: apeiron.config.configuration + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/drift_detection.md b/docs/api/drift_detection.md new file mode 100644 index 0000000..d2f3eb6 --- /dev/null +++ b/docs/api/drift_detection.md @@ -0,0 +1,40 @@ +# Drift Detection + +See {doc}`../drift_detectors` for behavior and options, and +{doc}`../choosing_a_detector` for picking one. + +## Core types + +```{eval-rst} +.. automodule:: apeiron.drift_detection.detectors.base + :members: + :undoc-members: + :show-inheritance: +``` + +## Statistical detectors + +```{eval-rst} +.. automodule:: apeiron.drift_detection.detectors.statistical_detectors + :members: + :undoc-members: + :show-inheritance: +``` + +## Model performance detector + +```{eval-rst} +.. automodule:: apeiron.drift_detection.detectors.model_performance_detector + :members: + :undoc-members: + :show-inheritance: +``` + +## Factory + +```{eval-rst} +.. automodule:: apeiron.drift_detection.load_drift_detector + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/driver.md b/docs/api/driver.md new file mode 100644 index 0000000..86517a2 --- /dev/null +++ b/docs/api/driver.md @@ -0,0 +1,11 @@ +# Driver + +`ContinuousMonitor` orchestrates the monitoring loop: evaluate batches, check +drift at intervals, dispatch continual learning on drift. + +```{eval-rst} +.. automodule:: apeiron.driver.continuous_monitor + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/evaluation.md b/docs/api/evaluation.md new file mode 100644 index 0000000..7eed98e --- /dev/null +++ b/docs/api/evaluation.md @@ -0,0 +1,16 @@ +# Evaluation + +Metric functions used to populate a harness's `eval_metrics` map. Their order in +that map is what `drift_detection.metric_index` indexes into. + +```{eval-rst} +.. automodule:: apeiron.evaluation.metrics + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.evaluation.evaluation + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..780d880 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,34 @@ +# API Reference + +Generated from the docstrings in `src/apeiron/`. The heavy runtime dependencies +are mocked during the docs build, so signatures involving `torch` types render +as plain names. + +## Top-level package + +Everything below is re-exported from `apeiron` itself: + +```python +from apeiron import ( + Config, ModelCfg, DataCfg, TrainCfg, ContinualLearningCfg, + DriftDetectionCfg, VisualizationCfg, LoggingCfg, build_config, + BaseModelHarness, ContinuousMonitor, ContinuousTrainer, BaseUpdater, + BaseDriftDetector, DriftSignal, LearningRegime, + ADWINDetector, KSWINDetector, PageHinkleyDetector, + ModelPerformanceDetector, ModelEvalDetector, EnsembleDetector, + Logger, get_logger, +) +``` + +```{toctree} +:maxdepth: 2 + +config +model +driver +drift_detection +training +evaluation +logger +profilers +``` diff --git a/docs/api/logger.md b/docs/api/logger.md new file mode 100644 index 0000000..6b8e839 --- /dev/null +++ b/docs/api/logger.md @@ -0,0 +1,36 @@ +# Logger + +Stage-aware logging (`eval`, `drift`, `cl`) with pluggable metrics backends, +selected by `[logging] backend`. + +## Logger + +```{eval-rst} +.. automodule:: apeiron.logger.logger + :members: + :undoc-members: + :show-inheritance: +``` + +## Console output + +```{eval-rst} +.. automodule:: apeiron.logger.console_logger + :members: + :undoc-members: + :show-inheritance: +``` + +## Metrics backends + +```{eval-rst} +.. automodule:: apeiron.logger.wandb_logger + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.logger.mlflow_logger + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/model.md b/docs/api/model.md new file mode 100644 index 0000000..ed337c8 --- /dev/null +++ b/docs/api/model.md @@ -0,0 +1,11 @@ +# Model Harness + +The contract every model + data-stream integration implements. See +{doc}`../model_harness` for the narrative version. + +```{eval-rst} +.. automodule:: apeiron.model.torch_model_harness + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/profilers.md b/docs/api/profilers.md new file mode 100644 index 0000000..3be56c1 --- /dev/null +++ b/docs/api/profilers.md @@ -0,0 +1,11 @@ +# Profilers + +FLOP and wall-time measurement built on PyTorch's `FlopCounterMode`. See +{doc}`../profiler` for usage. + +```{eval-rst} +.. automodule:: apeiron.profilers.count_flops + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/api/training.md b/docs/api/training.md new file mode 100644 index 0000000..1d2bc08 --- /dev/null +++ b/docs/api/training.md @@ -0,0 +1,51 @@ +# Training + +See {doc}`../continuous_learning` for the loop structure and the config keys +that drive it. + +## Continuous trainer + +```{eval-rst} +.. automodule:: apeiron.training.continuous_trainer + :members: + :undoc-members: + :show-inheritance: +``` + +## Updater factory + +```{eval-rst} +.. automodule:: apeiron.training.updater.create_updater + :members: + :undoc-members: + :show-inheritance: +``` + +## Updaters + +```{eval-rst} +.. automodule:: apeiron.training.updater.base + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.training.updater.jvp_reg + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.training.updater.ewc + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.training.updater.kfac + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: apeiron.training.updater.no_updater + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..e53a98b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,190 @@ +# Architecture + +Apeiron is built around four extension points — configuration, a **model +harness**, a **drift detector**, and an **updater** — wired together by a +monitoring driver. Everything else is plumbing. + +The driver is the default way to run the workflow, not the only one: each +component keeps a standalone API, so detection and adaptation can be used +separately or embedded in someone else's loop. See +{ref}`running-without-the-driver`. + +## Runtime flow + +```{mermaid} +flowchart TD + A[src/main.py
build_config] --> B[examples/utils.py
get_example -> BaseModelHarness] + B --> C[ContinuousMonitor.run] + C --> D[evaluate stream batch
buffer eval_metrics] + D --> E{every
detection_interval?} + E -- no --> D + E -- yes --> F[aggregate metric
mean / median / last] + F --> G[detector.update value] + G --> H{drift_detected?} + H -- no --> D + H -- yes --> I[ContinuousTrainer
outer_cl_training_loop] + I --> J[updater hooks
base / jvp_reg / ewc / kfac] + J --> K[optional checkpoint
optional detector.reset] + K --> D +``` + +Step by step: + +1. `src/main.py` builds a `Config` from TOML, `APP_` environment variables, and + `--set` CLI overrides. +2. `examples/utils.py:get_example` selects a concrete `BaseModelHarness` from + `cfg.data.name`. +3. `apeiron/driver/continuous_monitor.py` evaluates streaming batches and calls + the detector every `detection_interval` batches. +4. On drift, `apeiron/training/continuous_trainer.py` runs a CL loop using an + updater from `apeiron/training/updater/create_updater.py`. +5. Logging is stage-aware (`eval`, `drift`, `cl`) via `apeiron/logger/`. + +(running-without-the-driver)= + +## Running without the driver + +`ContinuousMonitor` is a convenience: it wires detection and adaptation together +into the loop above and is what `src/main.py` runs. Nothing else depends on it. +The detector, the trainer, the updater, and the harness are each constructed +independently and can be driven on their own — half the pipeline, or none of it, +embedded in a training loop you already have. + +Two half-pipeline entry points ship with the repo. Both take the same flags as +`main.py` (`--config`, `--set key=val`, `--device`, `--multi-gpu`) and emit the +same CSV schema, so their runs are directly comparable to a full run. + +| Entry point | Runs | Bypasses | +| --- | --- | --- | +| `python -m src.main` | `ContinuousMonitor` — detect, then adapt | — | +| `python -m src.drift_only` | `DriftOnlyMonitor` — detection trace over the stream, weights frozen | `ContinuousTrainer` | +| `python -m src.cl_only` | `ScheduledCLRunner` — CL fired by a `TriggerSchedule` | the drift detector | + +**Detection only** (`src/drift_only.py`): streams data past a frozen model and +records every detector firing without adapting. Use it to tune a detector +offline — every check lands in the metrics CSV under the `drift/` stage with its +score, regime and confidence. + +```{code-block} bash +:caption: Detection trace, no adaptation + +poetry run python -m src.drift_only --config examples/mnist/mnist.toml +``` + +**Adaptation only** (`src/cl_only.py`): triggers the CL loop on a fixed schedule +instead of on detected drift — `periodic`, `random` (rate- or budget-matched), +`fixed` (explicit window indices), or `never` (the frozen-model lower bound). +This is the control arm for judging whether a detector's firing points were +actually worth their cost. + +```{code-block} bash +:caption: Budget-matched control — 3 triggers over the run + +poetry run python -m src.cl_only --config examples/mnist/mnist.toml \ + --schedule periodic --period 14 +``` + +Both expose a function form (`run_drift_only(cfg, modelHarness)` and +`run_manual_cl(cfg, modelHarness, schedule)`) that returns a summary dict, so +they can be called from a sweep script rather than the shell. + +### Component APIs + +Below the entry points, each piece stands alone. + +```{code-block} python +:caption: A detector on any scalar stream — no harness, no config + +from apeiron.drift_detection import ADWINDetector + +detector = ADWINDetector(delta=0.002) +for value in my_metric_stream: # any float you already compute + signal = detector.update(value) + if signal.drift_detected: + print(signal.regime, signal.drift_score) +``` + +`load_drift_detector(cfg)` is the config-driven equivalent when you already have +a `Config`; detectors themselves take plain constructor arguments. + +```{code-block} python +:caption: The CL loop on its own, triggered by whatever you like + +from apeiron.logger import get_logger +from apeiron.training import ContinuousTrainer + +trainer = ContinuousTrainer( + cfg=cfg, modelHarness=harness, logger=get_logger(), profiler=None +) +trainer.outer_cl_training_loop(drift_event_id=1) +``` + +And a single updater can be built with `create_updater(cfg, modelHarness)` and +its hooks called from your own training step, without `ContinuousTrainer` at +all. Adding Apeiron to an existing training loop this way is what the +`integrate-apeiron` skill automates — see {doc}`agent_skills`. + +## Modules + +| Module | Role | +| --- | --- | +| `apeiron/config/configuration.py` | TOML/env/CLI config assembly into frozen dataclasses. | +| `apeiron/model/torch_model_harness.py` | `BaseModelHarness` — the model + data-stream contract. | +| `apeiron/driver/continuous_monitor.py` | `ContinuousMonitor` — the monitoring and drift loop. | +| `apeiron/drift_detection/` | Detector classes and the `load_drift_detector` factory. | +| `apeiron/training/continuous_trainer.py` | `ContinuousTrainer` — outer/inner CL loops with gradient accumulation. | +| `apeiron/training/updater/` | CL update strategies behind the `BaseUpdater` hooks. | +| `apeiron/evaluation/metrics.py` | `accuracy()` and `accuracy_topk()`. | +| `apeiron/logger/` | Console output plus W&B / MLflow metrics backends. | +| `apeiron/profilers/` | `FLOPSProfiler` built on PyTorch `FlopCounterMode`. | + +The installable package lives under `src/apeiron/` and is imported as `apeiron` +(see `packages = [{ include = "apeiron", from = "src" }]` in `pyproject.toml`). + +## Extension points + +Each extension point is an ABC with a factory in front of it, so adding a new +implementation means subclassing and registering — not editing the driver. + +::::{grid} 1 1 3 3 +:gutter: 2 + +:::{grid-item-card} `BaseModelHarness` +:link: model_harness +:link-type: doc + +Exposes your model, optimizer, criterion, stream loader, train/val loaders, and +historical replay loaders. Selected by `data.name`. +::: + +:::{grid-item-card} `BaseDriftDetector` +:link: drift_detectors +:link-type: doc + +`update(value) -> DriftSignal`. Selected by `drift_detection.detector_name` via +`load_drift_detector`. +::: + +:::{grid-item-card} `BaseUpdater` +:link: continuous_learning +:link-type: doc + +Hooks around forward/backward and the optimizer step. Selected by +`continual_learning.update_mode` via `create_updater`. +::: + +:::: + +## Example harnesses + +The bundled examples double as reference implementations of the harness +contract: + +| Harness | File | Notes | +| --- | --- | --- | +| `MNIST_CNN` | `examples/mnist/model.py` | CNN on MNIST with affine drift simulation. | +| `CIFAR_VISION` | `examples/cifar/model.py` | ViT/VGG on CIFAR-10 with affine drift. | +| `IMAGENET_VISION` | `examples/imagenet/model.py` | ViT on ImageNet with affine drift. | + +`examples/utils.py:get_example(cfg)` is the factory that dispatches on +`cfg.data.name`. diff --git a/docs/choosing_a_detector.md b/docs/choosing_a_detector.md new file mode 100644 index 0000000..9df715b --- /dev/null +++ b/docs/choosing_a_detector.md @@ -0,0 +1,245 @@ +# Choosing a Drift Detector + +{doc}`drift_detectors` is the authoritative reference for what each detector +does and every option it takes. This page is the decision guide: which one to +pick, and how to scale its knobs to your metric. + +## Start here: what is actually plug-and-play + +`ContinuousMonitor._check_drift()` calls `detector.update(agg_metric)` with a +**single aggregated scalar**. Three detectors work with that signature out of +the box: + +- `ADWINDetector` +- `KSWINDetector` +- `PageHinkleyDetector` + +`EnsembleDetector` is also drop-in **as long as every sub-detector is one of the +three above** — it forwards the same scalar to each of them and votes on the +results. See [Combine detectors with an ensemble](#combine-detectors-with-an-ensemble). + +The remaining two need extra wiring and should not be treated as defaults: + +| Detector | Why it is not drop-in | +| --- | --- | +| `ModelPerformanceDetector` | Needs reference data plus batch `DataFrame`s, which the monitor does not pass. | +| `EvalDetector` (`ModelEvalDetector`) | Needs `modelHarness`, `reference_validation_metrics`, and `higher_is_better` kwargs the monitor does not send. | + +Putting either of those inside an ensemble fails the same way, because +`EnsembleDetector.update()` passes each sub-detector exactly the arguments it +received from the monitor. + +```{important} +The three scalar detectors fire on **change in either direction** — they do not +know "better" from "worse". If you only care about degradation, that is what +`EvalDetector` is for, and it requires the extra wiring described in +{doc}`drift_detectors`. +``` + +## Pick a detector + +| Your situation | Use | Why | +| --- | --- | --- | +| Distribution / variance / shape changes with little mean movement | **KSWIN** | A two-sample KS test compares full distributions, not just means. | +| Abrupt mean shifts; you want fast and cheap detection | **PageHinkley** | A cumulative-sum test, low memory, quick to react. | +| Gradual drift, mixed drift, or "not sure — give me a sane default" | **ADWIN** | Adaptive windowing handles gradual *and* abrupt shifts with no fixed window size. | +| Several drift shapes at once, or you want to trade sensitivity against false alarms explicitly | **Ensemble** | Runs the above in parallel and votes; see below. | + +## Combine detectors with an ensemble + +`EnsembleDetector` runs several sub-detectors on the same aggregated scalar and +combines their verdicts. Use it when no single test covers the drift you expect, +or when you want an explicit sensitivity dial that is coarser than any one +detector's threshold. + +```toml +[drift_detection] +detector_name = "EnsembleDetector" +ensemble_detectors = ["ADWINDetector", "KSWINDetector", "PageHinkleyDetector"] +ensemble_voting = "majority" +``` + +| `ensemble_voting` | Fires when | Use it for | +| --- | --- | --- | +| `any` (alias `or`) | At least one sub-detector fires | Highest recall; catches drift early at the cost of false alarms. | +| `majority` (default) | Strictly more than half fire | Balanced; the sane starting point. | +| `unanimous` (aliases `all`, `and`) | Every sub-detector fires | Highest precision; use when a CL update is expensive. | + +Voting names are case-insensitive. An unrecognized name, or an empty +`ensemble_detectors` list, raises `ValueError` at load time rather than +silently falling back. + +Things to know before reaching for it: + +- **Each sub-detector is built from the same `[drift_detection]` block**, so a + detector type can appear at most once — you cannot ensemble two ADWINs with + different `adwin_delta`. Ensembles cannot be nested either. +- **Warm-up is governed by the slowest member.** With `majority` over + ADWIN + KSWIN + PageHinkley, a verdict is only meaningful once KSWIN's + `kswin_window_size` samples have accumulated — at `detection_interval = 10` + that is 1000 monitored batches. +- **Every sub-detector is updated on every call**, with no short-circuiting, so + their internal windows stay aligned and `reset_after_learning` applies + uniformly. The cost is the sum of the members' costs. +- The reported `drift_score` is the **mean** of the sub-detector scores and the + regime is a **plurality vote** over their regimes — both independent of the + voting rule, which only decides `drift_detected`. Per-detector verdicts are in + the signal's `metadata`. + +## Scale the knobs to your metric + +### ADWIN + +`adwin_delta` is the main sensitivity knob. + +- Lower (`~0.001`) — stricter test, fewer and later detections, fewer false alarms. +- Higher (`~0.01`) — more sensitive. +- Default `0.002`; typical range `0.001 – 0.01`. + +Leave `adwin_minor_threshold` / `adwin_moderate_threshold` at `0.3` / `0.6` +unless you want to steer the regime split (`continual_learning` → +`fine_tuning` → `retrain`). + +### KSWIN + +- `kswin_alpha` — KS significance level; lower is stricter. Default `0.005`. +- `kswin_window_size` — total retained samples; the reference window is + `window_size - stat_size`. Larger is a more stable baseline but slower to + adapt. Default `100`. +- `kswin_stat_size` — most-recent samples tested against the reference; must be + `< window_size`. Default `30`. + +### Page-Hinkley + +```{warning} +`ph_threshold` scale **depends on your metric**. For a bounded metric in +`[0, 1]` (accuracy, error rate) the default of `50` is enormous and will +essentially never fire — start around `1 – 10` and tune. For larger-magnitude +losses, larger thresholds are appropriate. +``` + +- `ph_min_instances` — warm-up samples before detection can fire. Default `30`. +- `ph_delta` — slack per deviation, i.e. the smallest change treated as real. + Higher ignores small fluctuations. Default `0.005`. +- `ph_alpha` — forgetting factor for the running mean; closer to `1` weights + history more. Default `0.9999`. + +## Set the cadence + +These keys are detector-independent and control *how often* the detector sees a +value: + +- `detection_interval` — check drift every N monitored batches. `<= 0` disables + checks entirely (and therefore disables CL dispatch). +- `aggregation` — `mean`, `median`, or `last` over the buffered values. +- `metric_index` — index into the harness's `eval_metrics` ordering. +- `max_stream_updates` — stop monitoring after this many stream extensions. + +Warm-up matters: Page-Hinkley needs `ph_min_instances` updates and KSWIN needs +`kswin_window_size` samples before detection is meaningful. With +`detection_interval = 10`, KSWIN's default window of `100` means 1000 monitored +batches before the reference window is full. + +## Paste-ready blocks + +::::{tab-set} + +:::{tab-item} ADWIN (general default) +```toml +[drift_detection] +detector_name = "ADWINDetector" +detection_interval = 10 +aggregation = "mean" +metric_index = 0 +reset_after_learning = false +max_stream_updates = 20 + +adwin_delta = 0.002 +adwin_minor_threshold = 0.3 +adwin_moderate_threshold = 0.6 +``` +::: + +:::{tab-item} KSWIN (distribution shift) +```toml +[drift_detection] +detector_name = "KSWINDetector" +detection_interval = 10 +aggregation = "mean" +metric_index = 0 +reset_after_learning = false +max_stream_updates = 20 + +kswin_alpha = 0.005 +kswin_window_size = 100 +kswin_stat_size = 30 +``` +::: + +:::{tab-item} Page-Hinkley (abrupt mean shift) +```toml +[drift_detection] +detector_name = "PageHinkleyDetector" +detection_interval = 10 +aggregation = "mean" +metric_index = 0 +reset_after_learning = false +max_stream_updates = 20 + +ph_min_instances = 30 +ph_delta = 0.005 +# Bounded metric in [0, 1]: start small, not at the default 50. +ph_threshold = 5 +ph_alpha = 0.9999 +``` +::: + +:::{tab-item} Ensemble (vote across detectors) +```toml +[drift_detection] +detector_name = "EnsembleDetector" +ensemble_detectors = ["ADWINDetector", "KSWINDetector", "PageHinkleyDetector"] +ensemble_voting = "majority" +detection_interval = 10 +aggregation = "mean" +metric_index = 0 +reset_after_learning = false +max_stream_updates = 20 + +# Sub-detectors are built from these same keys. +adwin_delta = 0.002 +adwin_minor_threshold = 0.3 +adwin_moderate_threshold = 0.6 + +kswin_alpha = 0.005 +kswin_window_size = 100 +kswin_stat_size = 30 + +ph_min_instances = 30 +ph_delta = 0.005 +ph_threshold = 5 +ph_alpha = 0.9999 +``` +::: + +:::: + +## Validate before a full run + +Build the config and instantiate the detector to confirm the TOML parses and the +detector name is accepted — no training required: + +```bash +poetry run python -c " +from apeiron import build_config +from apeiron.drift_detection.load_drift_detector import load_drift_detector +cfg = build_config(['--config', 'examples/mnist/mnist.toml']) +d = load_drift_detector(cfg) +print(type(d).__name__, d.update(0.5)) +" +``` + +```{seealso} +The repository ships a `choose-detector` agent skill that runs this decision +process interactively and patches your config file. See {doc}`agent_skills`. +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5fd2d66 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,134 @@ +"""Sphinx configuration for the Apeiron documentation. + +The heavy runtime dependencies (torch, river, evidently, wandb, ...) are mocked +via ``autodoc_mock_imports`` so the docs build stays fast and does not need a +GPU-specific PyTorch wheel. Only ``docs/requirements.txt`` is installed. +""" + +from __future__ import annotations + +import sys +from datetime import date +from pathlib import Path + +# Make the installable package under src/ importable for autodoc. +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) + +# -- Project information ----------------------------------------------------- + +project = "Apeiron" +author = "AI-ModCon" +copyright = f"{date.today().year}, AI-ModCon" +release = "0.1.0" +version = "0.1.0" + +# -- General configuration --------------------------------------------------- + +extensions = [ + "myst_parser", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_copybutton", + "sphinx_design", + "sphinxcontrib.mermaid", +] + +exclude_patterns = [ + "_build", + "README.md", + "requirements.txt", + "Thumbs.db", + ".DS_Store", +] + +source_suffix = { + ".md": "markdown", + ".rst": "restructuredtext", +} + +# -- MyST --------------------------------------------------------------------- + +myst_enable_extensions = [ + "attrs_inline", + "colon_fence", + "deflist", + "fieldlist", + "substitution", + "tasklist", +] +# Auto-generate anchors for headings so `file.md#some-heading` links resolve. +myst_heading_anchors = 3 + +# -- autodoc ------------------------------------------------------------------ + +autodoc_mock_imports = [ + "torch", + "torchvision", + "transformers", + "river", + "evidently", + "wandb", + "mlflow", + "matplotlib", + "psutil", + "pynvml", + "nvidia_ml_py", +] + +autodoc_default_options = { + "member-order": "bysource", +} +autodoc_typehints = "description" +autodoc_class_signature = "separated" +napoleon_google_docstring = True +napoleon_numpy_docstring = True + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "pandas": ("https://pandas.pydata.org/docs/", None), +} + +# Mocked modules produce unresolvable type targets; do not fail the build on them. +nitpicky = False + +# -- HTML output -------------------------------------------------------------- + +html_theme = "furo" +html_title = f"{project} {release}" +html_static_path = ["_static"] +html_css_files = ["custom.css"] + +html_theme_options = { + "light_css_variables": { + "color-brand-primary": "#0a4bff", + "color-brand-content": "#2757dd", + }, + "dark_css_variables": { + "color-brand-primary": "#3d94ff", + "color-brand-content": "#5ca5ff", + }, + "source_repository": "https://github.com/AI-ModCon/BaseSIM_APEIRON/", + "source_branch": "main", + "source_directory": "docs/", + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/AI-ModCon/BaseSIM_APEIRON", + "html": ( + '' + ), + "class": "", + }, + ], +} diff --git a/docs/configurations.md b/docs/configurations.md index 5c7833f..a43bd91 100644 --- a/docs/configurations.md +++ b/docs/configurations.md @@ -200,6 +200,7 @@ ensemble_voting = "majority" Details about the drift detection algorithms available can be found in [docs/drift_detectors.md](drift_detectors.md) +(visualization)= ## [visualization] The visualization configuration options are optional and are used to store the results of the metrics captured during the run. @@ -258,7 +259,7 @@ eval/test_hist_acc ``` Example output file: -```csv +```text step,metric,value 10,eval/accuracy,62.5 10,eval/loss,2.0406203269958496 diff --git a/docs/continuous_learning.md b/docs/continuous_learning.md index e4f9add..aa0d7fb 100644 --- a/docs/continuous_learning.md +++ b/docs/continuous_learning.md @@ -4,9 +4,9 @@ This document describes the continual-learning path triggered after drift detect ## Main Components -- `ContinuousTrainer` in `src/training/continuous_trainer.py` -- Updater factory `create_updater(...)` in `src/training/updater/create_updater.py` -- Updater implementations in `src/training/updater/` +- `ContinuousTrainer` in `src/apeiron/training/continuous_trainer.py` +- Updater factory `create_updater(...)` in `src/apeiron/training/updater/create_updater.py` +- Updater implementations in `src/apeiron/training/updater/` ## Training Loop Flow @@ -27,7 +27,7 @@ When `ContinuousMonitor` detects drift: ## `train` Config Keys Used By CL -Defined in `TrainCfg` (`src/config/configuration.py`): +Defined in `TrainCfg` (`src/apeiron/config/configuration.py`): | Key | Default | Meaning | | --- | --- | --- | @@ -57,7 +57,7 @@ Defined in `ContinualLearningCfg`: ### `base` -> `BaseUpdater` -- File: `src/training/updater/base.py` +- File: `src/apeiron/training/updater/base.py` - Behavior: plain supervised forward/backward on current batch only. - Extra config: none. @@ -87,7 +87,7 @@ Defined in `ContinualLearningCfg`: ### `ewc_online` -> `OnlineEWCUpdater` -- File: `src/training/updater/ewc.py` +- File: `src/apeiron/training/updater/ewc.py` - Keeps running parameter anchor (`theta_star`) and diagonal Fisher estimate. - Adds EWC gradient penalty before optimizer step. - Updates Fisher/anchor once per CL event in `cl_postprocessing()`. @@ -97,7 +97,7 @@ Defined in `ContinualLearningCfg`: ### `kfac_online` -> `OnlineKFACUpdater` -- File: `src/training/updater/kfac.py` +- File: `src/apeiron/training/updater/kfac.py` - Tracks layer-wise activation/gradient statistics via hooks. - Applies KFAC-structured EWC-like penalty. - Supports modules: @@ -109,7 +109,7 @@ Defined in `ContinualLearningCfg`: ### `none` -> `NoUpdater` -- File: `src/training/updater/no_updater.py` +- File: `src/apeiron/training/updater/no_updater.py` - `fwd_bwd(...)` is a no-op and returns `-1.0`. - Useful for disabling CL gradient updates while keeping monitoring flow intact. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..95c7376 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,13 @@ +# Deployment + +Platform-specific setup and job-submission guides for HPC systems. Both pages +are rendered from the READMEs that live next to the install scripts, so they +stay in sync with the scripts themselves. + +```{include} ../src/apeiron/deployment/frontier/README.md +:start-line: 2 +``` + +```{include} ../src/apeiron/deployment/perlmutter/README.md +:start-line: 2 +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..fd16ccb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,107 @@ +# Apeiron + +**A PyTorch framework for continual learning that detects concept drift in a live +data stream and adapts the model in place.** + +Apeiron runs a monitoring loop over a changing data stream, watches an evaluation +metric, hands that metric to a drift detector, and — when the detector fires — +pauses monitoring to run a continual-learning update on the model before +resuming. + +```{code-block} bash +:caption: Run a bundled example + +poetry run python -m src.main --config examples/mnist/mnist.toml +``` + +## The loop + +1. Evaluate the current model on stream batches. +2. Aggregate the monitored metric at a configured interval. +3. Run a drift detector on the aggregated metric. +4. On drift, pause monitoring and run a continual-learning update loop. +5. Resume monitoring on the updated model until stream limits are reached. + +::::{grid} 1 1 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`rocket` Get started +:link: quickstart +:link-type: doc + +Install Apeiron and run your first drift-detection experiment. +::: + +:::{grid-item-card} {octicon}`gear` Configuration reference +:link: configurations +:link-type: doc + +Every TOML section and key the config parser accepts, with defaults. +::: + +:::{grid-item-card} {octicon}`pulse` Drift detectors +:link: drift_detectors +:link-type: doc + +Detector classes, their options, and how detector output drives training. +::: + +:::{grid-item-card} {octicon}`sync` Continual learning +:link: continuous_learning +:link-type: doc + +The CL trainer, the updater modes, and what runs after drift is detected. +::: + +:::: + +## Suggested reading order + +1. {doc}`installation` and {doc}`quickstart` — get a run going. +2. {doc}`architecture` — how the pieces fit together at runtime. +3. {doc}`configurations` — the required and optional configuration parameters. +4. {doc}`model_harness` — how your model and stream loaders are exposed to the framework. +5. {doc}`drift_detectors` — how monitoring decisions are made. +6. {doc}`continuous_learning` — what happens after drift is detected. +7. {doc}`tracking` — sending run metrics to Weights & Biases or MLflow. + +```{toctree} +:maxdepth: 2 +:caption: Getting started +:hidden: + +installation +quickstart +architecture +``` + +```{toctree} +:maxdepth: 2 +:caption: User guide +:hidden: + +configurations +model_harness +drift_detectors +choosing_a_detector +continuous_learning +tracking +``` + +```{toctree} +:maxdepth: 2 +:caption: Operations +:hidden: + +profiler +deployment +agent_skills +``` + +```{toctree} +:maxdepth: 2 +:caption: API reference +:hidden: + +api/index +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..c016a0e --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,75 @@ +# Installation + +Apeiron requires **Python `>=3.13,<3.14`** and uses [Poetry](https://python-poetry.org/) +for dependency management. + +## Developing inside this repository + +Clone the repository and install the full environment, including the dev tools +(pytest, ruff, mypy): + +```bash +git clone https://github.com/AI-ModCon/BaseSIM_APEIRON.git +cd BaseSIM_APEIRON +poetry install +``` + +Verify the install: + +```bash +poetry run pytest -m "not slow" +poetry run python -c "import apeiron; print(apeiron.__doc__)" +``` + +## Using Apeiron as a dependency in your own project + +The installable package lives under `src/apeiron/` and is imported as `apeiron`. + +```toml +# pyproject.toml +[tool.poetry.dependencies] +apeiron = "^0.1.0" # once published to PyPI + +# Or as a path dependency during development +apeiron = { path = "../BaseSIM_APEIRON/", develop = true } + +# Or straight from git +apeiron = { git = "https://github.com/AI-ModCon/BaseSIM_APEIRON.git", branch = "main" } +``` + +Then import the public API: + +```python +from apeiron import BaseModelHarness, ContinuousMonitor, build_config +from apeiron.drift_detection import ADWINDetector +from apeiron.training.updater import BaseUpdater +``` + +See {doc}`api/index` for everything the package exports. + +```{note} +PyTorch resolution differs between CPU-only and CUDA/ROCm machines. If Poetry +picks the wrong wheel, install the matching `torch` build first (following the +[PyTorch install matrix](https://pytorch.org/get-started/locally/)) and then run +`poetry install`. For HPC systems see {doc}`deployment`. +``` + +## Development commands + +```bash +poetry run pytest # tests +poetry run ruff check . # lint +poetry run ruff format --check . # formatting +poetry run mypy . # type checks +``` + +## Building these docs locally + +The docs are built with Sphinx and MyST-Markdown. Heavy runtime dependencies are +mocked, so a docs build does not need torch installed: + +```bash +pip install -r docs/requirements.txt +sphinx-build -b html docs docs/_build/html +open docs/_build/html/index.html +``` diff --git a/docs/model_harness.md b/docs/model_harness.md index 7acad4c..42126fb 100644 --- a/docs/model_harness.md +++ b/docs/model_harness.md @@ -4,7 +4,7 @@ This document describes the model harness contract and the concrete harness clas ## Base Class Contract -All harnesses inherit from `BaseModelHarness` in `src/model/torch_model_harness.py`. +All harnesses inherit from `BaseModelHarness` in `src/apeiron/model/torch_model_harness.py`. Required methods: diff --git a/docs/profiler.md b/docs/profiler.md new file mode 100644 index 0000000..da663a4 --- /dev/null +++ b/docs/profiler.md @@ -0,0 +1,30 @@ +# FLOPS Profiler + +```{include} ../src/apeiron/profilers/README.md +:start-line: 2 +``` + +## Where the profiler shows up in a run + +`ContinuousMonitor` and `ContinuousTrainer` are constructed with a +`FLOPSProfiler` and emit its measurements as stage-namespaced metrics, so they +land in the CSV and in your metrics backend alongside accuracy and loss: + +| Metric family | Emitted during | Meaning | +| --- | --- | --- | +| `*/cperf_infer_flop`, `_time`, `_flops` | eval, drift | Forward pass over stream batches. | +| `*/cperf_detector_flop`, `_time`, `_flops` | drift | Cost of the detector `update(...)` call itself. | +| `*/cperf_update_fwd_bwd_flop`, `_time`, `_flops` | cl | Forward + backward inside the CL loop. | +| `*/cperf_optimizer_flop`, `_time`, `_flops` | cl | The optimizer step. | + +See {doc}`configurations` for the full metric list written to the CSV. + +```{note} +GPU measurements need a warm-up. `FLOPSProfiler(warmup_iters=N)` skips the first +`N` iterations so kernel autotuning and allocator warm-up do not distort the +timings. +``` + +## API + +Full class and method documentation lives in {doc}`api/profilers`. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..ac4adf8 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,121 @@ +# Quickstart + +This page takes you from a fresh checkout to a running drift-detection +experiment. If you have not installed Apeiron yet, start with {doc}`installation`. + +## 1. Run a bundled example + +Every experiment is driven by a TOML config file passed to `src/main.py`: + +```bash +poetry run python -m src.main --config examples/mnist/mnist.toml +poetry run python -m src.main --config examples/cifar/cifar10_vit.toml +poetry run python -m src.main --config examples/imagenet/imagenet_vit.toml # needs ImageNet at data.path +``` + +MNIST is the fastest way to see the whole loop: the harness applies a random +affine transform to each new task, which drives the monitored accuracy down and +triggers the detector. + +## 2. Read the output + +Per-batch metrics are written to the CSV at `visualization.input` +(default `output/output.csv`) in long form: + +```text +step,metric,value +10,eval/accuracy,62.5 +10,eval/loss,2.0406203269958496 +10,drift/score,0.0 +10,drift/regime,stable +10,cl/jvp_reg_total_loss,3.4211268424987793 +``` + +Metric names are namespaced by stage — `eval/`, `drift/`, and `cl/`. The full +list of emitted metrics is in {ref}`the visualization section ` +of the configuration reference. + +```{note} +`[visualization]` is parsed and the CSV is written, but the package does not +bundle a dashboard or renderer — plot the CSV with your tool of choice. +``` + +## 3. Turn on metrics logging + +Apeiron ships two metrics backends, Weights & Biases and MLflow, selected with +`[logging] backend`. Setting it to `none` disables remote logging (console +output is unaffected). + +```bash +poetry run python -m src.main \ + --config examples/mnist/mnist.toml \ + --set logging.backend=mlflow \ + --set logging.experiment_name="My Experiment" +``` + +For MLflow, run `mlflow ui` in another terminal and open +. The MNIST example sets `backend = "wandb"` in its TOML; +the other examples leave it unset, which also defaults to W&B. + +## 4. Override config without editing files + +Values resolve in this order, later winning over earlier: + +1. Base TOML from `--config` +2. Environment variables prefixed with `APP_` +3. Repeated `--set key=value` CLI flags + +```bash +poetry run python -m src.main \ + --config examples/mnist/mnist.toml \ + --set drift_detection.detector_name=\"KSWINDetector\" \ + --set train.max_iter=200 +``` + +```{tip} +String values passed to `--set` need TOML quoting, hence the escaped quotes +above. Numbers and booleans do not. +``` + +## 5. A minimal config of your own + +```toml +seed = 1337 +device = "auto" + +[model] +name = "mnist" +pretrained_path = "examples/mnist/mnist.pth" + +[data] +name = "mnist" +path = "" +batch_size = 32 + +[train] +batch_size = 64 +num_workers = 4 +init_lr = 0.001 + +[continual_learning] +update_mode = "base" + +[drift_detection] +detector_name = "ADWINDetector" + +[logging] +backend = "none" + +[visualization] +input = "output/results.csv" +``` + +See {doc}`configurations` for every key, and {doc}`choosing_a_detector` for +picking and tuning the detector. + +## Where to go next + +- Bringing your own model and dataset → {doc}`model_harness` +- Choosing a drift detector and its thresholds → {doc}`choosing_a_detector` +- Changing what happens on drift → {doc}`continuous_learning` +- Measuring FLOPs and wall time → {doc}`profiler` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..eb329c3 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,16 @@ +# Documentation build dependencies (used by Read the Docs and local builds). +# +# The heavy runtime dependencies (torch, river, evidently, wandb, ...) are NOT +# installed here -- they are mocked in conf.py via `autodoc_mock_imports`, which +# keeps the docs build fast and CPU/GPU agnostic. +sphinx>=8.1 +myst-parser>=4.0 +furo>=2024.8.6 +sphinx-copybutton>=0.5.2 +sphinx-design>=0.6.1 +sphinxcontrib-mermaid>=1.0.0 + +# Light imports that autodoc resolves for real rather than mocking. +numpy>=2.3 +pandas>=2.3 +tqdm>=4.67 diff --git a/docs/tracking.md b/docs/tracking.md index 5e72735..b89bb2b 100644 --- a/docs/tracking.md +++ b/docs/tracking.md @@ -209,4 +209,5 @@ than the auto-generated ones. tracker entirely. That is what the `examples/mnist/sweep/configs/` files use. - MNIST's committed `mnist.pth` is deliberately under-trained (~50% on clean MNIST), so absolute accuracy in these charts is lower than a converged MNIST - CNN would show. See [`../examples/mnist/README.md`](../examples/mnist/README.md). + CNN would show. See the + [MNIST example README](https://github.com/AI-ModCon/BaseSIM_APEIRON/blob/main/examples/mnist/README.md). diff --git a/src/apeiron/config/configuration.py b/src/apeiron/config/configuration.py index 6d229d9..41561d5 100644 --- a/src/apeiron/config/configuration.py +++ b/src/apeiron/config/configuration.py @@ -32,14 +32,18 @@ def get_available_device(multi_gpu: bool = False) -> torch.device: """ - Returns a torch.device with sensible fallbacks: - - CPU-only hosts: 'cpu' - - CUDA hosts: - * multi_gpu=True -> 'cuda' (let caller handle DDP/DataParallel) - * multi_gpu=False -> choose GPU with most free memory, then restrict - CUDA_VISIBLE_DEVICES so only that GPU is visible. - - Apple Silicon with PyTorch MPS: 'mps' if CUDA is unavailable - Never raises if nvidia-smi is missing. + Return a ``torch.device`` with sensible fallbacks. + + - CPU-only hosts: ``cpu``. + - CUDA hosts: + + - ``multi_gpu=True`` -> ``cuda`` (let the caller handle DDP/DataParallel). + - ``multi_gpu=False`` -> choose the GPU with the most free memory, then + restrict ``CUDA_VISIBLE_DEVICES`` so only that GPU is visible. + + - Apple Silicon with PyTorch MPS: ``mps`` if CUDA is unavailable. + + Never raises if ``nvidia-smi`` is missing. """ # Single-GPU mode: must set CUDA_VISIBLE_DEVICES *before* CUDA init if not multi_gpu and "CUDA_VISIBLE_DEVICES" not in os.environ: @@ -318,7 +322,7 @@ def env_overrides(prefix="APP_") -> dict[str, Any]: Parameters ---------- prefix : str, optional - The prefix to filter environment variables with. Defaults to "APP_". + The prefix to filter environment variables with. Defaults to ``APP_``. Returns -------