bio-pype-runtime-models 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. bio_pype_runtime_models-0.1.0/PKG-INFO +289 -0
  2. bio_pype_runtime_models-0.1.0/README.md +279 -0
  3. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/PKG-INFO +289 -0
  4. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/SOURCES.txt +23 -0
  5. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/dependency_links.txt +1 -0
  6. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/entry_points.txt +2 -0
  7. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/requires.txt +4 -0
  8. bio_pype_runtime_models-0.1.0/bio_pype_runtime_models.egg-info/top_level.txt +1 -0
  9. bio_pype_runtime_models-0.1.0/pype_runtime_models/__init__.py +57 -0
  10. bio_pype_runtime_models-0.1.0/pype_runtime_models/__main__.py +4 -0
  11. bio_pype_runtime_models-0.1.0/pype_runtime_models/cli.py +214 -0
  12. bio_pype_runtime_models-0.1.0/pype_runtime_models/evaluate.py +217 -0
  13. bio_pype_runtime_models-0.1.0/pype_runtime_models/features.py +132 -0
  14. bio_pype_runtime_models-0.1.0/pype_runtime_models/gpu.py +105 -0
  15. bio_pype_runtime_models-0.1.0/pype_runtime_models/ingest.py +276 -0
  16. bio_pype_runtime_models-0.1.0/pype_runtime_models/model.py +521 -0
  17. bio_pype_runtime_models-0.1.0/pype_runtime_models/power.py +133 -0
  18. bio_pype_runtime_models-0.1.0/pype_runtime_models/predict.py +187 -0
  19. bio_pype_runtime_models-0.1.0/pype_runtime_models/provenance.py +111 -0
  20. bio_pype_runtime_models-0.1.0/pype_runtime_models/regression.py +267 -0
  21. bio_pype_runtime_models-0.1.0/pype_runtime_models/train.py +196 -0
  22. bio_pype_runtime_models-0.1.0/pype_runtime_models/units.py +92 -0
  23. bio_pype_runtime_models-0.1.0/pype_runtime_models/workflow.py +89 -0
  24. bio_pype_runtime_models-0.1.0/pyproject.toml +22 -0
  25. bio_pype_runtime_models-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: bio-pype-runtime-models
3
+ Version: 0.1.0
4
+ Summary: Learn per-workflow resource, timeline and power-curve models from bio-pype runtime dumps.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: numpy>=1.23
8
+ Provides-Extra: pype
9
+ Requires-Dist: bio-pype; extra == "pype"
10
+
11
+ # bio-pype-runtime-models
12
+
13
+ Learn per-workflow **resource**, **timeline** and **power-curve** models from
14
+ [Bio-Pype](https://bio-pype.readthedocs.io) runtime data, then use them to
15
+ predict a new run's cost from a single input size, to grade a finished run
16
+ against its own baseline, and to suggest better default requirements.
17
+
18
+ Built to be imported into other projects ([compute.bio](https://compute.bio)
19
+ trains and serves these models from its database). Pure `numpy`; models
20
+ serialise to plain JSON — no pickle.
21
+
22
+ > **Status: alpha.** The modelling choices and the JSON model format will
23
+ > change between releases, and estimates from few runs are rough. Retrain
24
+ > after upgrading rather than loading models written by an older version.
25
+
26
+ The distribution is `bio-pype-runtime-models`; the import name and the command
27
+ are `pype_runtime_models` / `pype-runtime-models`.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install bio-pype-runtime-models
33
+ ```
34
+
35
+ Requires Python >= 3.9 and `numpy`. Bio-Pype itself is not needed: the package
36
+ only reads the runtime data Bio-Pype writes.
37
+
38
+ ## Input data
39
+
40
+ One *run* is the content of a Bio-Pype `pipeline_runtime.yaml`: a mapping of
41
+ job id to job record (command, status, timings, `requirements`,
42
+ `resource_consumption`, `data_sizes`), plus run metadata and environment. It
43
+ can come from:
44
+
45
+ - a directory of `*.json` files, one per run ("dumps");
46
+ - parsed `Run` objects, built with `Run.from_dict(mapping)`, when the runs live
47
+ somewhere else, such as a database.
48
+
49
+ ## What it does (mapped to the original goals)
50
+
51
+ | Goal | Where |
52
+ |------|-------|
53
+ | Detect which workflow a runtime object is | `workflow.detect_workflow` — `pipeline_name`, else a snippet-set signature match |
54
+ | One model per workflow, improving as more runs land | `train.train_models` rebuilds the whole registry each time; safe to schedule |
55
+ | Per-step resource consumption, spot deviations | `StepModel` per snippet; `evaluate_run` flags per-step anomalies |
56
+ | Predict timeline + resources of each step from input size | `predict.predict_run` |
57
+ | Flag steps whose memory/CPU is driven by input size | `StepModel.input_sensitive` / `sensitivity_report` |
58
+ | Recommend raising/lowering the default requirement | `StepModel.recommend` → increase / decrease / ok |
59
+ | Post-hoc performance eval, overall and per-step | `evaluate.evaluate_run` |
60
+ | Predict the power curve for a workflow | `predict.predict_power_curve` (experimental, see below) |
61
+
62
+ ## Workflow identity & provenance
63
+
64
+ Snippets live in `$PYPE_MODULES/snippets` and are **shared by every pipeline in a
65
+ module**, so the natural owner of a snippet's resource model is the module, not
66
+ the individual pipeline. Identity is therefore pluggable (`--identity` on train,
67
+ `identity_mode=` in the API):
68
+
69
+ - **`module`** (default) — key models by the `PYPE_MODULES` basename, pooling
70
+ every pipeline in the module. Shared snippets like `mosdepth` train on all
71
+ their samples at once (e.g. n=116 across the 5 `ensemble_sv` pipelines instead
72
+ of split five ways). Resource models are pooled; each pipeline keeps its own
73
+ timeline view (snippet set + start offsets), so `predict <module> --pipeline X`
74
+ gives X's schedule against the pooled models.
75
+ - **`pipeline`** — the older one-model-per-`pipeline_name` behaviour.
76
+ - **`registry`** — key by a versioned `namespace/name:version` parsed from a
77
+ registry-style module path (e.g. `.../registry/sci-bioinformatics/somatic:20260701`).
78
+ This is the collision-proof, version-aware identity the module registry will
79
+ provide; it falls back to the module basename when no registry info is present.
80
+
81
+ Every model records its provenance (`module_paths`, `namespaces`, `versions`,
82
+ `pipeline_names`, `profiles`), so the day the registry lands, switching to
83
+ version-aware keying — or sharing/federating models at the app level — is a
84
+ config change, not a rewrite. `predict`/`evaluate` accept either an identity key
85
+ or a `pipeline_name` (auto-resolved to its module).
86
+
87
+ Provenance includes module paths, which often contain user or group names.
88
+ Strip it before showing a model to anyone other than the people whose runs
89
+ trained it.
90
+
91
+ Caveat for `module` mode: two *unrelated* modules that happen to share a basename
92
+ (your `somatic` vs a third party's) would be merged — the registry namespace is
93
+ what ultimately disambiguates them.
94
+
95
+ ## Key modelling choices
96
+
97
+ - **Canonical step identity** is the *snippet name* parsed from the job command
98
+ (`ingest.snippet_name_from_command`), not the `name` field — the latter carries
99
+ per-sample suffixes (`mosdepth_EGAD2585`) that would fragment the models.
100
+ - **Driver size.** Each step is regressed against its own *driver size*:
101
+ `input_size_bytes`, or `output_size_bytes` when the input is 0 (generators /
102
+ aggregators). Because a caller supplies only one number at prediction time, we
103
+ also learn how each step's driver scales with the **run-level** driver
104
+ (`driver_scale`), so the whole chain is predictable from a single input size.
105
+ The run driver is the largest input among the earliest-submitted jobs — the
106
+ recorded `dependencies` are often partial in dumps, so we don't rely on a
107
+ strict topological root.
108
+ - **Per-step fit** picks the best of `constant` / `linear` / `power` (power fit
109
+ and scored in log space for numerical stability) by **leave-one-out R²**, and
110
+ only beats the constant baseline if it generalises — noisy/sparse steps stay
111
+ constant instead of overfitting.
112
+ - **Few samples means no size dependence.** A step needs at least 5 samples
113
+ with differing driver sizes before a size-dependent fit is even tried. Below
114
+ that the prediction is the median of what was seen, whatever the input size:
115
+ three runs of a workflow give the same expected duration for a small and a
116
+ large input.
117
+ - **Robust throughout**: medians, MAD, and Spearman rank correlation, because
118
+ runtime telemetry is heavy-tailed. Predictions are capped at 50× the largest
119
+ observed value so queries far outside the training range can't blow up.
120
+
121
+ ## How a run is graded
122
+
123
+ `evaluate_run` compares each completed step with the prediction for that
124
+ step's own driver size:
125
+
126
+ - A step is **flagged** when duration or peak memory is at least 3 robust
127
+ standard deviations above the prediction, or at least 2x the prediction.
128
+ Average CPU is reported for context and never flagged.
129
+ - The spread used for the z-score has a floor of 5% of the predicted value, so
130
+ a step whose training runs happened to be almost identical does not flag
131
+ every normal variation.
132
+ - Steps with no model are reported as `unmodelled`, not dropped.
133
+ - The overall `performance_score` (0-100) and `verdict` come from the duration
134
+ z-scores. The verdict is `unknown` when no step's duration could be graded.
135
+
136
+ ## Requirement recommendations
137
+
138
+ `StepModel.recommend` compares the default requirement recorded with the jobs
139
+ to what the step used: predicted peak memory with 1.3x headroom, predicted
140
+ duration with 1.5x headroom. It suggests lowering a requirement that is more
141
+ than 3x above the 95th percentile of use. Check a suggestion against the
142
+ underlying measurements before applying it; a bad measurement in the training
143
+ runs produces a bad suggestion.
144
+
145
+ ## GPU steps
146
+
147
+ Steps are also grouped by a vendor-neutral GPU class. Once a class has at
148
+ least 3 samples the model learns a duration factor for it relative to the
149
+ size-based prediction; `--gpu-class` / `gpu_class=` applies it, and omitting it
150
+ predicts for the mix of cards seen in training.
151
+
152
+ ## Data hygiene (split runs & mis-measured inputs)
153
+
154
+ These matter because of how runs are actually dumped, and are handled generally
155
+ — **no per-snippet logic is hardcoded anywhere**:
156
+
157
+ - **Split / partial runs are kept, not discarded.** A logical run is often
158
+ spread across several dump files: a failed run is dropped, a resumed run
159
+ completes the same file, and a *relaunched* run starts a fresh file missing
160
+ the steps that already succeeded. Every completed step in every file is a
161
+ valid per-step sample, so all of them train the per-step models. The
162
+ `quality` report shows how many runs per workflow are partial.
163
+ - **Run-level input size is only trusted when it's actually present.** Each
164
+ workflow's input-tier ("entry") snippets are learned from the data (the steps
165
+ whose driver size is largest by a wide margin). A partial file that lacks an
166
+ entry step contributes its per-step samples but no run-level driver, so it
167
+ can't feed the chain-scaling model a misleading input size.
168
+ - **Implausible driver sizes are dropped per step.** Some inputs are folders
169
+ historically mis-measured as a few bytes, or are overwritten in place with a
170
+ dummy value by convert/cleanup steps. A driver is dropped only when it is
171
+ *both* < 1 MB *and* > 10,000× smaller than that step's own largest driver —
172
+ a general statistical test that removes the bogus cluster while leaving
173
+ genuinely small-input steps (all a few KB) untouched. Steps left with no
174
+ size signal fall back to a constant (median) model, which is correct for
175
+ cleanup-style steps whose cost isn't driven by input size. Counts surface in
176
+ the `quality` report and per-step `n_driver_outliers`.
177
+
178
+ ```bash
179
+ python -m pype_runtime_models quality models/ # partial runs, dropped outliers, entry snippets
180
+ ```
181
+
182
+ ## CLI
183
+
184
+ ```bash
185
+ # train / retrain from a dumps directory
186
+ pype-runtime-models train dumps/ --out models/
187
+
188
+ # list what got trained
189
+ pype-runtime-models workflows models/
190
+
191
+ # predict per-step resources + timeline for a 50 GB input
192
+ pype-runtime-models predict models/ ensemble_sv_TO --input-size 50gb --full
193
+
194
+ # grade a finished run
195
+ pype-runtime-models evaluate models/ dumps/<hash>.json
196
+
197
+ # requirement changes worth making
198
+ pype-runtime-models recommend models/ ensemble_sv_TO --changes-only
199
+
200
+ # learned per-GPU-class duration factors
201
+ pype-runtime-models gpu models/ ensemble_sv_TO
202
+
203
+ # predict the power curve (30-min bins by default; experimental)
204
+ pype-runtime-models power models/ ensemble_sv_TO --input-size 50gb --bin-minutes 60
205
+ ```
206
+
207
+ `train` options: `--identity module|pipeline|registry` (default `module`),
208
+ `--min-workflow-runs N` (skip identities seen in fewer runs, default 1) and
209
+ `--min-step-samples N` (skip steps with fewer completed jobs, default 3).
210
+ `python -m pype_runtime_models ...` is equivalent to the `pype-runtime-models`
211
+ command.
212
+
213
+ ## Library
214
+
215
+ ```python
216
+ from pype_runtime_models import (
217
+ Run, train_models, load_models, predict_run, evaluate_run,
218
+ )
219
+
220
+ reg = train_models("dumps/", "models/") # or load_models("models/")
221
+ pred = predict_run(reg, "ensemble_sv_TO", input_size_bytes=50 * 1024**3)
222
+ report = evaluate_run(reg, "dumps/somehash.json")
223
+
224
+ print(report["verdict"], report["performance_score"])
225
+ for step in report["flagged_steps"]:
226
+ print(step["name"], step["flags"])
227
+ ```
228
+
229
+ Without files, for runs held in a database or loaded from
230
+ `pipeline_runtime.yaml`:
231
+
232
+ ```python
233
+ runs = [Run.from_dict(mapping) for mapping in runtime_mappings]
234
+ reg = train_models(runs, min_workflow_runs=3) # nothing written to disk
235
+ report = evaluate_run(reg, runs[0])
236
+
237
+ for snippet, recs in reg.get("ensemble_sv").all_recommendations().items():
238
+ ...
239
+ ```
240
+
241
+ `train_models` only writes to disk when given a models directory. A workflow
242
+ model round-trips through `WorkflowModel.to_json()`.
243
+
244
+ ## Power curve (experimental)
245
+
246
+ `predict_power_curve` turns a predicted run into a binned power curve with a
247
+ copy of Bio-Pype's power estimate that is bundled with this package.
248
+
249
+ **Known issue:** when Bio-Pype is importable in the same environment, the
250
+ package tries to use Bio-Pype's own `pipeline_power_curve` instead, and with
251
+ current Bio-Pype that call fails with a `TypeError`. Training, prediction of
252
+ resources and timeline, evaluation and recommendations are not affected.
253
+
254
+ ## Recurrent retraining
255
+
256
+ `train_models` is idempotent — point it at the growing dumps directory and it
257
+ rebuilds every workflow model from the full corpus, so new runs simply sharpen
258
+ the estimates. Schedule it every few hours, e.g. cron:
259
+
260
+ ```cron
261
+ 0 */4 * * * pype-runtime-models train /path/to/dumps --out /path/to/models \
262
+ >> /var/log/pype_models.log 2>&1
263
+ ```
264
+
265
+ ## Package layout
266
+
267
+ ```
268
+ pype_runtime_models/
269
+ ingest.py Run/Job parsing, snippet name parsing, driver sizes
270
+ units.py memory / time requirement parsing
271
+ provenance.py module/registry provenance + pluggable identity key
272
+ gpu.py vendor-neutral GPU-class key + display name guess
273
+ workflow.py identity detection (provenance + signature fallback)
274
+ features.py per-step training samples, metric list
275
+ regression.py numpy constant/linear/power fitting + driver cleaning
276
+ model.py StepModel / WorkflowModel / PipelineView / ModelRegistry
277
+ train.py train_models / load_models (module-level pooling)
278
+ predict.py predict_run / predict_power_curve (GPU band, pipeline view)
279
+ evaluate.py evaluate_run (per-step + overall, GPU-adjusted grading)
280
+ power.py power estimate (bundled copy of Bio-Pype's, see Power curve)
281
+ cli.py argparse entrypoint
282
+ tests/ self-contained smoke test (no pytest needed)
283
+ ```
284
+
285
+ The smoke test is not part of the published package. From a source checkout,
286
+ with a directory of runtime dumps:
287
+ `python -m pype_runtime_models.tests.test_smoke dumps/`
288
+
289
+ Source and issues: <https://codeberg.org/bio-pype/bio-pype-runtime-models>
@@ -0,0 +1,279 @@
1
+ # bio-pype-runtime-models
2
+
3
+ Learn per-workflow **resource**, **timeline** and **power-curve** models from
4
+ [Bio-Pype](https://bio-pype.readthedocs.io) runtime data, then use them to
5
+ predict a new run's cost from a single input size, to grade a finished run
6
+ against its own baseline, and to suggest better default requirements.
7
+
8
+ Built to be imported into other projects ([compute.bio](https://compute.bio)
9
+ trains and serves these models from its database). Pure `numpy`; models
10
+ serialise to plain JSON — no pickle.
11
+
12
+ > **Status: alpha.** The modelling choices and the JSON model format will
13
+ > change between releases, and estimates from few runs are rough. Retrain
14
+ > after upgrading rather than loading models written by an older version.
15
+
16
+ The distribution is `bio-pype-runtime-models`; the import name and the command
17
+ are `pype_runtime_models` / `pype-runtime-models`.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install bio-pype-runtime-models
23
+ ```
24
+
25
+ Requires Python >= 3.9 and `numpy`. Bio-Pype itself is not needed: the package
26
+ only reads the runtime data Bio-Pype writes.
27
+
28
+ ## Input data
29
+
30
+ One *run* is the content of a Bio-Pype `pipeline_runtime.yaml`: a mapping of
31
+ job id to job record (command, status, timings, `requirements`,
32
+ `resource_consumption`, `data_sizes`), plus run metadata and environment. It
33
+ can come from:
34
+
35
+ - a directory of `*.json` files, one per run ("dumps");
36
+ - parsed `Run` objects, built with `Run.from_dict(mapping)`, when the runs live
37
+ somewhere else, such as a database.
38
+
39
+ ## What it does (mapped to the original goals)
40
+
41
+ | Goal | Where |
42
+ |------|-------|
43
+ | Detect which workflow a runtime object is | `workflow.detect_workflow` — `pipeline_name`, else a snippet-set signature match |
44
+ | One model per workflow, improving as more runs land | `train.train_models` rebuilds the whole registry each time; safe to schedule |
45
+ | Per-step resource consumption, spot deviations | `StepModel` per snippet; `evaluate_run` flags per-step anomalies |
46
+ | Predict timeline + resources of each step from input size | `predict.predict_run` |
47
+ | Flag steps whose memory/CPU is driven by input size | `StepModel.input_sensitive` / `sensitivity_report` |
48
+ | Recommend raising/lowering the default requirement | `StepModel.recommend` → increase / decrease / ok |
49
+ | Post-hoc performance eval, overall and per-step | `evaluate.evaluate_run` |
50
+ | Predict the power curve for a workflow | `predict.predict_power_curve` (experimental, see below) |
51
+
52
+ ## Workflow identity & provenance
53
+
54
+ Snippets live in `$PYPE_MODULES/snippets` and are **shared by every pipeline in a
55
+ module**, so the natural owner of a snippet's resource model is the module, not
56
+ the individual pipeline. Identity is therefore pluggable (`--identity` on train,
57
+ `identity_mode=` in the API):
58
+
59
+ - **`module`** (default) — key models by the `PYPE_MODULES` basename, pooling
60
+ every pipeline in the module. Shared snippets like `mosdepth` train on all
61
+ their samples at once (e.g. n=116 across the 5 `ensemble_sv` pipelines instead
62
+ of split five ways). Resource models are pooled; each pipeline keeps its own
63
+ timeline view (snippet set + start offsets), so `predict <module> --pipeline X`
64
+ gives X's schedule against the pooled models.
65
+ - **`pipeline`** — the older one-model-per-`pipeline_name` behaviour.
66
+ - **`registry`** — key by a versioned `namespace/name:version` parsed from a
67
+ registry-style module path (e.g. `.../registry/sci-bioinformatics/somatic:20260701`).
68
+ This is the collision-proof, version-aware identity the module registry will
69
+ provide; it falls back to the module basename when no registry info is present.
70
+
71
+ Every model records its provenance (`module_paths`, `namespaces`, `versions`,
72
+ `pipeline_names`, `profiles`), so the day the registry lands, switching to
73
+ version-aware keying — or sharing/federating models at the app level — is a
74
+ config change, not a rewrite. `predict`/`evaluate` accept either an identity key
75
+ or a `pipeline_name` (auto-resolved to its module).
76
+
77
+ Provenance includes module paths, which often contain user or group names.
78
+ Strip it before showing a model to anyone other than the people whose runs
79
+ trained it.
80
+
81
+ Caveat for `module` mode: two *unrelated* modules that happen to share a basename
82
+ (your `somatic` vs a third party's) would be merged — the registry namespace is
83
+ what ultimately disambiguates them.
84
+
85
+ ## Key modelling choices
86
+
87
+ - **Canonical step identity** is the *snippet name* parsed from the job command
88
+ (`ingest.snippet_name_from_command`), not the `name` field — the latter carries
89
+ per-sample suffixes (`mosdepth_EGAD2585`) that would fragment the models.
90
+ - **Driver size.** Each step is regressed against its own *driver size*:
91
+ `input_size_bytes`, or `output_size_bytes` when the input is 0 (generators /
92
+ aggregators). Because a caller supplies only one number at prediction time, we
93
+ also learn how each step's driver scales with the **run-level** driver
94
+ (`driver_scale`), so the whole chain is predictable from a single input size.
95
+ The run driver is the largest input among the earliest-submitted jobs — the
96
+ recorded `dependencies` are often partial in dumps, so we don't rely on a
97
+ strict topological root.
98
+ - **Per-step fit** picks the best of `constant` / `linear` / `power` (power fit
99
+ and scored in log space for numerical stability) by **leave-one-out R²**, and
100
+ only beats the constant baseline if it generalises — noisy/sparse steps stay
101
+ constant instead of overfitting.
102
+ - **Few samples means no size dependence.** A step needs at least 5 samples
103
+ with differing driver sizes before a size-dependent fit is even tried. Below
104
+ that the prediction is the median of what was seen, whatever the input size:
105
+ three runs of a workflow give the same expected duration for a small and a
106
+ large input.
107
+ - **Robust throughout**: medians, MAD, and Spearman rank correlation, because
108
+ runtime telemetry is heavy-tailed. Predictions are capped at 50× the largest
109
+ observed value so queries far outside the training range can't blow up.
110
+
111
+ ## How a run is graded
112
+
113
+ `evaluate_run` compares each completed step with the prediction for that
114
+ step's own driver size:
115
+
116
+ - A step is **flagged** when duration or peak memory is at least 3 robust
117
+ standard deviations above the prediction, or at least 2x the prediction.
118
+ Average CPU is reported for context and never flagged.
119
+ - The spread used for the z-score has a floor of 5% of the predicted value, so
120
+ a step whose training runs happened to be almost identical does not flag
121
+ every normal variation.
122
+ - Steps with no model are reported as `unmodelled`, not dropped.
123
+ - The overall `performance_score` (0-100) and `verdict` come from the duration
124
+ z-scores. The verdict is `unknown` when no step's duration could be graded.
125
+
126
+ ## Requirement recommendations
127
+
128
+ `StepModel.recommend` compares the default requirement recorded with the jobs
129
+ to what the step used: predicted peak memory with 1.3x headroom, predicted
130
+ duration with 1.5x headroom. It suggests lowering a requirement that is more
131
+ than 3x above the 95th percentile of use. Check a suggestion against the
132
+ underlying measurements before applying it; a bad measurement in the training
133
+ runs produces a bad suggestion.
134
+
135
+ ## GPU steps
136
+
137
+ Steps are also grouped by a vendor-neutral GPU class. Once a class has at
138
+ least 3 samples the model learns a duration factor for it relative to the
139
+ size-based prediction; `--gpu-class` / `gpu_class=` applies it, and omitting it
140
+ predicts for the mix of cards seen in training.
141
+
142
+ ## Data hygiene (split runs & mis-measured inputs)
143
+
144
+ These matter because of how runs are actually dumped, and are handled generally
145
+ — **no per-snippet logic is hardcoded anywhere**:
146
+
147
+ - **Split / partial runs are kept, not discarded.** A logical run is often
148
+ spread across several dump files: a failed run is dropped, a resumed run
149
+ completes the same file, and a *relaunched* run starts a fresh file missing
150
+ the steps that already succeeded. Every completed step in every file is a
151
+ valid per-step sample, so all of them train the per-step models. The
152
+ `quality` report shows how many runs per workflow are partial.
153
+ - **Run-level input size is only trusted when it's actually present.** Each
154
+ workflow's input-tier ("entry") snippets are learned from the data (the steps
155
+ whose driver size is largest by a wide margin). A partial file that lacks an
156
+ entry step contributes its per-step samples but no run-level driver, so it
157
+ can't feed the chain-scaling model a misleading input size.
158
+ - **Implausible driver sizes are dropped per step.** Some inputs are folders
159
+ historically mis-measured as a few bytes, or are overwritten in place with a
160
+ dummy value by convert/cleanup steps. A driver is dropped only when it is
161
+ *both* < 1 MB *and* > 10,000× smaller than that step's own largest driver —
162
+ a general statistical test that removes the bogus cluster while leaving
163
+ genuinely small-input steps (all a few KB) untouched. Steps left with no
164
+ size signal fall back to a constant (median) model, which is correct for
165
+ cleanup-style steps whose cost isn't driven by input size. Counts surface in
166
+ the `quality` report and per-step `n_driver_outliers`.
167
+
168
+ ```bash
169
+ python -m pype_runtime_models quality models/ # partial runs, dropped outliers, entry snippets
170
+ ```
171
+
172
+ ## CLI
173
+
174
+ ```bash
175
+ # train / retrain from a dumps directory
176
+ pype-runtime-models train dumps/ --out models/
177
+
178
+ # list what got trained
179
+ pype-runtime-models workflows models/
180
+
181
+ # predict per-step resources + timeline for a 50 GB input
182
+ pype-runtime-models predict models/ ensemble_sv_TO --input-size 50gb --full
183
+
184
+ # grade a finished run
185
+ pype-runtime-models evaluate models/ dumps/<hash>.json
186
+
187
+ # requirement changes worth making
188
+ pype-runtime-models recommend models/ ensemble_sv_TO --changes-only
189
+
190
+ # learned per-GPU-class duration factors
191
+ pype-runtime-models gpu models/ ensemble_sv_TO
192
+
193
+ # predict the power curve (30-min bins by default; experimental)
194
+ pype-runtime-models power models/ ensemble_sv_TO --input-size 50gb --bin-minutes 60
195
+ ```
196
+
197
+ `train` options: `--identity module|pipeline|registry` (default `module`),
198
+ `--min-workflow-runs N` (skip identities seen in fewer runs, default 1) and
199
+ `--min-step-samples N` (skip steps with fewer completed jobs, default 3).
200
+ `python -m pype_runtime_models ...` is equivalent to the `pype-runtime-models`
201
+ command.
202
+
203
+ ## Library
204
+
205
+ ```python
206
+ from pype_runtime_models import (
207
+ Run, train_models, load_models, predict_run, evaluate_run,
208
+ )
209
+
210
+ reg = train_models("dumps/", "models/") # or load_models("models/")
211
+ pred = predict_run(reg, "ensemble_sv_TO", input_size_bytes=50 * 1024**3)
212
+ report = evaluate_run(reg, "dumps/somehash.json")
213
+
214
+ print(report["verdict"], report["performance_score"])
215
+ for step in report["flagged_steps"]:
216
+ print(step["name"], step["flags"])
217
+ ```
218
+
219
+ Without files, for runs held in a database or loaded from
220
+ `pipeline_runtime.yaml`:
221
+
222
+ ```python
223
+ runs = [Run.from_dict(mapping) for mapping in runtime_mappings]
224
+ reg = train_models(runs, min_workflow_runs=3) # nothing written to disk
225
+ report = evaluate_run(reg, runs[0])
226
+
227
+ for snippet, recs in reg.get("ensemble_sv").all_recommendations().items():
228
+ ...
229
+ ```
230
+
231
+ `train_models` only writes to disk when given a models directory. A workflow
232
+ model round-trips through `WorkflowModel.to_json()`.
233
+
234
+ ## Power curve (experimental)
235
+
236
+ `predict_power_curve` turns a predicted run into a binned power curve with a
237
+ copy of Bio-Pype's power estimate that is bundled with this package.
238
+
239
+ **Known issue:** when Bio-Pype is importable in the same environment, the
240
+ package tries to use Bio-Pype's own `pipeline_power_curve` instead, and with
241
+ current Bio-Pype that call fails with a `TypeError`. Training, prediction of
242
+ resources and timeline, evaluation and recommendations are not affected.
243
+
244
+ ## Recurrent retraining
245
+
246
+ `train_models` is idempotent — point it at the growing dumps directory and it
247
+ rebuilds every workflow model from the full corpus, so new runs simply sharpen
248
+ the estimates. Schedule it every few hours, e.g. cron:
249
+
250
+ ```cron
251
+ 0 */4 * * * pype-runtime-models train /path/to/dumps --out /path/to/models \
252
+ >> /var/log/pype_models.log 2>&1
253
+ ```
254
+
255
+ ## Package layout
256
+
257
+ ```
258
+ pype_runtime_models/
259
+ ingest.py Run/Job parsing, snippet name parsing, driver sizes
260
+ units.py memory / time requirement parsing
261
+ provenance.py module/registry provenance + pluggable identity key
262
+ gpu.py vendor-neutral GPU-class key + display name guess
263
+ workflow.py identity detection (provenance + signature fallback)
264
+ features.py per-step training samples, metric list
265
+ regression.py numpy constant/linear/power fitting + driver cleaning
266
+ model.py StepModel / WorkflowModel / PipelineView / ModelRegistry
267
+ train.py train_models / load_models (module-level pooling)
268
+ predict.py predict_run / predict_power_curve (GPU band, pipeline view)
269
+ evaluate.py evaluate_run (per-step + overall, GPU-adjusted grading)
270
+ power.py power estimate (bundled copy of Bio-Pype's, see Power curve)
271
+ cli.py argparse entrypoint
272
+ tests/ self-contained smoke test (no pytest needed)
273
+ ```
274
+
275
+ The smoke test is not part of the published package. From a source checkout,
276
+ with a directory of runtime dumps:
277
+ `python -m pype_runtime_models.tests.test_smoke dumps/`
278
+
279
+ Source and issues: <https://codeberg.org/bio-pype/bio-pype-runtime-models>