Process reference: ml
Generated from the docstrings of vaft.process.ml:
24 public functions. Each entry is what
vaft.process.describe("ml.<name>") prints.
All process categories
Overview
Training is a VAFT processing stage like any other. This package owns the
whole path – dataset construction, group-aware splitting, windowing,
training, evaluation, threshold calibration, artifact packaging, export,
resolution of published versions and inference – while trained weights are
distributed through the separate VEST-Tokamak/vaft-nn registry.
Importing it loads NumPy only. Frameworks are backends chosen by name in a
ModelSpec and imported on first use: "numpy" (ridge, PCA
autoencoder, k-NN distance – no dependency), "sklearn" (one-class SVM,
stored as ONNX) and "torch" (MLP, autoencoder, and any architecture task
code registers), the last two installed with pip install 'vaft[ml]'. No
public signature takes or returns a framework object. Published models come
from VEST-Tokamak/vaft-nn, an external dependency configured with
$VAFT_NN_HOME like the external codes’ $XHOME.
A split assigns groups – the independent scientific unit, a shot or a simulation case – to partitions, and is made before windowing or augmentation; it then applies unchanged to every derived dataset. Normalisation moments come from the train partition only. Every inference result carries the immutable identity of the model that produced it: exact version and manifest SHA-256, never only a stage alias.
Functions
build_dataset | Assemble a fingerprinted dataset from arrays. |
build_structured_dataset | Validate and fingerprint named scientific samples without retaining a corpus. Convention |
calibrate_threshold | Set a decision threshold on a per-sample model output from one partition. |
dataset_fingerprint | SHA-256 content fingerprint of a dataset. |
evaluate_model | Standard ML metrics of a model on one partition, overall and per group. |
export_model | Export the network to an inference format and prove it agrees. |
fetch_model | Download a published model’s release assets into the cache, verified. |
import_data_roles | Import exact group roles without recomputing a scientific split. Convention |
load_dataset | Load a dataset written by save_dataset, verifying its fingerprint.
|
load_model | Resolve a published model and load it, ready for predict.
|
load_model_artifact | Load an artifact directory, verifying every file against its manifest. |
load_model_bundle | Load a bundle directory, verifying every file and member manifest. |
predict | Run a model on new data and stamp the result with its exact identity. |
record_data_use | Check a declared use and return its provenance receipt. Convention |
register_architecture | Make a task-defined network available to a backend by name. |
register_augmentation | Make a train-time augmentation available by name. |
register_loss | Make a task-defined training loss available by name. |
resolve_model | Resolve a published model to one exact, hash-verified local version. Convention |
save_dataset | Materialise a dataset on disk so training can run elsewhere. |
save_model_artifact | Write a model as an immutable, hash-pinned artifact directory. |
save_model_bundle | Write a multi-model bundle as one immutable, hash-pinned release. |
split_groups | Assign independent groups to train/validation/test partitions. |
train_model | Train a model on the train partition and package it with its provenance. Convention |
window_dataset | Cut sliding windows that never cross a group boundary. |
No function matches.
build_dataset
Machine-independent
source
build_dataset(inputs, groups, *, spec, targets=None, sample_meta=None, feature_spec=None, available=None)
Assemble a fingerprinted dataset from arrays.
The generic entry point for every ML task: an engineered-feature table
(pass feature_spec), a waveform or image tensor, or paired
input-target supervision (pass targets).
| Name | Type | Unit | Description |
|---|---|---|---|
inputs | array_like | any | Samples along the first axis, shape |
groups | array_like | - | Independent unit of each sample (shot number, simulation case), shape |
spec | DatasetSpec | - | Name, task, group key and column names of the dataset. |
targets | array_like or None, optional | any | Paired targets, shape |
sample_meta | mapping of str to array_like or None, optional | any | Per-sample arrays that travel with every subset, such as window times. |
feature_spec | FeatureSpec or None, optional | - | Column schema of a 2-D engineered-feature table; returns a |
available | array_like of bool or None, optional | - | Per-sample availability: |
| Returns | |||
| — | DatasetArtifact | - | The dataset with its |
Raises
ModelContractErrorOn mismatched lengths, a non-finite input in an available sample, or a feature table whose width differs fromfeature_spec.
Applicability · Limitations
Applicability
Machine-independent.
Limitations
Available inputs must be finite: imputation is a scientific choice
(train-partition medians, a flag, a drop) that belongs to the caller.
Unavailability is per sample, not per feature: a two-stage model whose
second stage lacks a diagnostic builds that stage’s dataset with its own
available.
build_structured_dataset
Machine-independentConvention-sensitive
source
build_structured_dataset(source, groups, *, input_specs, target_specs, spec, sample_meta=None, available=None)
Show source vaft/process/ml/structured.py, lines 199–261
def build_structured_dataset(source, groups, *, input_specs, target_specs, spec, sample_meta=None, available=None):
"""Validate and fingerprint named scientific samples without retaining a corpus.
Parameters
----------
source : sequence
Stable provider of StructuredSample objects; lazy file access is allowed [-].
groups : array_like
Independent scientific group identity for every sample [-].
input_specs : mapping
Named input TensorSpec records with shape, dtype, units and masks [-].
target_specs : mapping
Named fixed-shape target TensorSpec records [-].
spec : DatasetSpec
Task, group convention and source-role/preprocessing provenance [-].
sample_meta : mapping or None, optional
Per-sample machine, time or identity arrays [any].
available : array_like or None, optional
Boolean sample-level input availability; True means available [-].
Returns
-------
StructuredDatasetArtifact
Validated source and schema with a content SHA-256 fingerprint [-].
Processing steps
----------------
1. Validate names, shapes, mask broadcasts and finite valid elements.
2. Stream metadata and every input, target and validity array into SHA-256.
3. Retain the stable source and fingerprint, without retaining sampled tensors.
Convention
----------
True means valid. Missing observations differ from physical zero. Only a
leading input axis may be variable; target availability uses separate masks.
Applicability
-------------
Machine-independent.
Limitations
-----------
A source must have stable contents. DataLoader workers require a picklable
source. Fixed output shapes are required; this is not an IMAS data model.
Provenance
----------
.. [vaft2016] VEST-Tokamak/vaft#2016, structured observation and provenance
extension of the existing #669 grouped ML lifecycle.
"""
data = StructuredDatasetArtifact(source=source, groups=groups, input_specs=input_specs,
target_specs=target_specs, spec=spec, sample_meta=sample_meta or {}, available=available)
digest = hashlib.sha256(canonical_json({"spec": spec.to_dict(), "schema": data.schema(),
"groups": data.group_labels.tolist(), "metadata": dict(data.sample_meta),
"available": data.available_mask}).encode())
for i in range(data.n_samples):
sample = data.sample(i)
for values in (sample.inputs, sample.targets, sample.target_masks):
for name, a in sorted(values.items()):
digest.update(canonical_json([name, a.shape, a.dtype.str]).encode())
digest.update(np.ascontiguousarray(a).tobytes())
from dataclasses import replace
return replace(data, fingerprint=digest.hexdigest())Validate and fingerprint named scientific samples without retaining a corpus.
| Name | Type | Unit | Description |
|---|---|---|---|
source | sequence | - | Stable provider of StructuredSample objects; lazy file access is allowed. |
groups | array_like | - | Independent scientific group identity for every sample. |
input_specs | mapping | - | Named input TensorSpec records with shape, dtype, units and masks. |
target_specs | mapping | - | Named fixed-shape target TensorSpec records. |
spec | DatasetSpec | - | Task, group convention and source-role/preprocessing provenance. |
sample_meta | mapping or None, optional | any | Per-sample machine, time or identity arrays. |
available | array_like or None, optional | - | Boolean sample-level input availability; True means available. |
| Returns | |||
| — | StructuredDatasetArtifact | - | Validated source and schema with a content SHA-256 fingerprint. |
Convention
True means valid. Missing observations differ from physical zero. Only a leading input axis may be variable; target availability uses separate masks.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Validate names, shapes, mask broadcasts and finite valid elements.
- Stream metadata and every input, target and validity array into SHA-256.
- Retain the stable source and fingerprint, without retaining sampled tensors.
Applicability
Machine-independent.
Limitations
A source must have stable contents. DataLoader workers require a picklable source. Fixed output shapes are required; this is not an IMAS data model.
Provenance
- VEST-Tokamak/vaft#2016, structured observation and provenance extension of the existing #669 grouped ML lifecycle.
calibrate_threshold
Machine-independent
source
calibrate_threshold(model, dataset, split, *, partition='validation', output='score', quantile=0.99, name=None, mask=None, population=None)
Set a decision threshold on a per-sample model output from one partition.
The second half of a two-stage detector: a score is only an event
candidate once a threshold, fixed on data the network did not train on,
has been applied. Attach the result with
model.with_calibration(result); predict then reports
<name>_exceeds for every sample.
| Name | Type | Unit | Description |
|---|---|---|---|
model | ModelArtifact | - | Trained or loaded model. |
dataset | DatasetArtifact | - | Data with the model’s input shape. |
split | Split | - | Group assignment selecting the partition. |
partition | str, optional | - | Partition the quantile is taken over. |
output | str, optional | - | Per-sample scalar output to threshold. |
quantile | float, optional | - | Quantile of that output on the partition, in |
name | str or None, optional | - | Calibration name; |
mask | array_like of bool or None, optional | - | Further restricts the population within the partition – a second stage calibrated only on the first stage’s candidates. |
population | str or None, optional | - | Description of |
| Returns | |||
| — | CalibrationResult | any | Threshold with the partition, population, quantile, sample count and groups it was set on. |
Raises
ModelContractErrorWhen the output is not a per-sample scalar, the population is empty,quantileis outside(0, 1),maskis given without apopulationdescription, the dataset’s columns differ from the model’s, or the partition holds groups the model was fitted on – a model scores its own training samples in-sample (a k-NN against themselves), so that threshold would be too low; useTrainingResult.train_scoresfor a training-population statistic.
Defaults · Applicability
Defaults
quantile = 0.99 is numerical convenience; a detector’s quantile is a
task decision (the VEST IRE prototype used 0.85-0.90).
Applicability
Machine-independent.
dataset_fingerprint
Machine-independent
source
dataset_fingerprint(dataset)
Show source vaft/process/ml/dataset.py, lines 46–87
def dataset_fingerprint(dataset):
"""SHA-256 content fingerprint of a dataset.
Two datasets share a fingerprint only when their spec, feature schema,
inputs, targets, groups and per-sample metadata are identical in value,
dtype and shape. A model records this fingerprint as part of its identity,
so "which data trained it" is answerable from the artifact alone.
Parameters
----------
dataset : DatasetArtifact
The dataset to fingerprint; its own ``fingerprint`` field is ignored [-].
Returns
-------
str
64-character lowercase hex digest [-].
Processing steps
----------------
1. Hash the canonical JSON of the spec (and the feature spec, if any).
2. Hash inputs, targets, string group labels, the availability mask (when
one is set) and each ``sample_meta`` array in sorted key order, each
prefixed by its name, dtype and shape.
Applicability
-------------
Machine-independent.
"""
digest = hashlib.sha256()
digest.update(canonical_json(dataset.spec.to_dict()).encode())
if isinstance(dataset, FeatureDataset):
digest.update(canonical_json(dataset.feature_spec.to_dict()).encode())
_hash_array(digest, "inputs", np.asarray(dataset.inputs))
if dataset.targets is not None:
_hash_array(digest, "targets", np.asarray(dataset.targets))
_hash_array(digest, "groups", dataset.group_labels.astype(str))
if dataset.available is not None:
_hash_array(digest, "available", np.asarray(dataset.available))
for key in sorted(dataset.sample_meta):
_hash_array(digest, f"meta:{key}", np.asarray(dataset.sample_meta[key]))
return digest.hexdigest()SHA-256 content fingerprint of a dataset.
Two datasets share a fingerprint only when their spec, feature schema, inputs, targets, groups and per-sample metadata are identical in value, dtype and shape. A model records this fingerprint as part of its identity, so “which data trained it” is answerable from the artifact alone.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact | - | The dataset to fingerprint; its own |
| Returns | |||
| — | str | - | 64-character lowercase hex digest. |
Processing steps · Applicability
Processing steps
- Hash the canonical JSON of the spec (and the feature spec, if any).
- Hash inputs, targets, string group labels, the availability mask (when
one is set) and each
sample_metaarray in sorted key order, each prefixed by its name, dtype and shape.
Applicability
Machine-independent.
evaluate_model
Machine-independent
source
evaluate_model(model, dataset, split, partition='test', *, metrics=None)
Standard ML metrics of a model on one partition, overall and per group.
This is training evaluation. Whether a model is scientifically valid –
campaign robustness, negative controls, cross-diagnostic timing – is a
task-specific verdict for vaft.validation, not a number here.
| Name | Type | Unit | Description |
|---|---|---|---|
model | ModelArtifact | - | Trained or loaded model. |
dataset | DatasetArtifact | - | Data with the model’s input shape. |
split | Split | - | Group assignment selecting the partition. |
partition | str, optional | - | Partition to evaluate. |
metrics | callable or None, optional | - | Task metrics, |
| Returns | |||
| — | EvaluationResult | any | Aggregate and per-group metrics over the available samples: |
Raises
ModelContractErrorWhen the partition holds groups the model was fitted on (other than evaluating the training partition itself, which reports training metrics), or the dataset’s columns differ from the model’s.
Applicability
Applicability
Machine-independent.
export_model
Machine-independent
source
export_model(model, directory, *, format='onnx')
Show source vaft/process/ml/artifact.py, lines 295–356
def export_model(model, directory, *, format="onnx"):
"""Export the network to an inference format and prove it agrees.
The training artifact and the deployment artifact need not be the same
thing: a model trained in PyTorch can be served by ONNX Runtime. The
export is accepted only after the exported graph reproduces the source
model on a probe batch.
Parameters
----------
model : ModelArtifact
Trained or loaded model [-].
directory : str or path-like
Output directory; must not exist or be empty [-].
format : str, optional
Target format; ``"onnx"`` is the only one implemented [-].
Returns
-------
dict
Export record written to ``export.json``: format, opset, parity
difference and tolerance, runtimes, and the source model identity [-].
Raises
------
ModelContractError
When the backend cannot export to ``format`` or the parity check fails.
BackendUnavailableError
When the exporter or runtime is not installed.
Output semantics
----------------
The exported graph maps *normalised* inputs to *normalised* outputs;
``normalization.npz`` is written beside it and must be applied around it,
exactly as :func:`predict` does. A ``sklearn`` one-class SVM's graph
returns ``scores``, the estimator's decision function; the model's
``score`` is its negative.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [vaft669] VEST-Tokamak/vaft#669, section 12: training and inference
artifacts are separate, with numerical validation between them.
"""
if model.tensor_schema:
raise ModelContractError("structured ONNX export is not qualified; use the native artifact")
directory = Path(directory)
if directory.exists() and any(directory.iterdir()):
raise ModelContractError(f"{directory} is not empty")
directory.mkdir(parents=True, exist_ok=True)
backend = get_backend(model.model_spec.backend)
output_shape = model.target_shape if model.supervised else model.input_shape
record = backend.export(
model.model_spec, model.state, model.input_shape, output_shape, directory / f"model.{format}", format
)
(directory / NORMALIZATION).write_bytes(_npz(model.normalization))
record = {**record, "source": model.resolved_identity().to_dict()}
(directory / "export.json").write_text(json.dumps(_jsonable(record), indent=2, sort_keys=True) + "\n")
return recordExport the network to an inference format and prove it agrees.
The training artifact and the deployment artifact need not be the same thing: a model trained in PyTorch can be served by ONNX Runtime. The export is accepted only after the exported graph reproduces the source model on a probe batch.
| Name | Type | Unit | Description |
|---|---|---|---|
model | ModelArtifact | - | Trained or loaded model. |
directory | str or path-like | - | Output directory; must not exist or be empty. |
format | str, optional | - | Target format; |
| Returns | |||
| — | dict | - | Export record written to |
Raises
ModelContractErrorWhen the backend cannot export toformator the parity check fails.BackendUnavailableErrorWhen the exporter or runtime is not installed.
Output semantics · Applicability · Provenance
Output semantics
The exported graph maps normalised inputs to normalised outputs;
normalization.npz is written beside it and must be applied around it,
exactly as predict does. A sklearn one-class SVM’s graph
returns scores, the estimator’s decision function; the model’s
score is its negative.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#669, section 12: training and inference artifacts are separate, with numerical validation between them.
fetch_model
Machine-independent
source
fetch_model(name, *, version=None, stage=None, registry=None, cache=None, env=None)
Download a published model’s release assets into the cache, verified.
The registry checkout says which files a version is (their hashes); the GitHub Release holds the bytes. This copies the bytes into the cache and accepts them only if they are exactly the files the registry pins.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Registry model name. |
version | str or None, optional | - | Exact version; give this or |
stage | str or None, optional | - | Lifecycle alias, resolved to an exact version first. |
registry | str or path-like or None, optional | - |
|
cache | str or path-like or None, optional | - | Artifact cache root; |
env | mapping or None, optional | - | Environment to read the variables from; |
| Returns | |||
| — | ModelIdentity | - | The identity |
Raises
ModelResolutionErrorWhenghis missing or cannot read the release, or a downloaded file is missing or differs from the registry manifest; the cache is then left as it was.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Resolve the exact version and its manifest from the registry.
- Download the release
<name>-v<version>(or the tag the registry records) withgh release downloadinto a temporary directory inside the cache root; the repository is the registry checkout’s GitHub origin. - Verify every file the registry manifest lists, then replace
<cache>/<name>/<version>with the verified directory.
Applicability
Machine-independent.
Limitations
Needs the GitHub CLI logged in to an account that can read the private repository; VAFT reads no token itself.
Provenance
- VEST-Tokamak/vaft#669, sections 10 and 13: weights are release artifacts outside git history, verified against the registry on resolution.
import_data_roles
Machine-independentConvention-sensitive
source
import_data_roles(dataset, assignments, allowed_operations, *, role_to_partition=None, provenance=None)
Show source vaft/process/ml/usage.py, lines 47–113
def import_data_roles(dataset, assignments, allowed_operations, *, role_to_partition=None, provenance=None):
"""Import exact group roles without recomputing a scientific split.
Parameters
----------
dataset : DatasetArtifact or StructuredDatasetArtifact
Existing stable, fingerprinted samples [-].
assignments : mapping of str to str
Actual independent group-to-role assignments, including absent groups [-].
allowed_operations : mapping of str to sequence of str
Explicit role permissions: train, preprocess, calibrate, select,
evaluate or infer [-].
role_to_partition : mapping or None, optional
Explicit mapping to execution partitions; None preserves role names [-].
provenance : mapping or None, optional
Split/protocol revisions, origins, label methods and dependencies [-].
Returns
-------
tuple
Re-fingerprinted dataset and exact Split, without random reassignment [-].
Processing steps
----------------
1. Copy the declared assignments, permissions, mapping and provenance.
2. Validate role coverage and record the full assignment digest.
3. Recompute dataset identity and construct a fixed split for its groups.
Convention
----------
Roles are owner-defined; neither machine restrictions nor salts are inferred.
Applicability
-------------
Machine-independent.
Limitations
-----------
Re-fingerprinting streams structured samples. Sources must remain stable.
Supplied historical provenance is a declaration, not retrospective proof.
Provenance
----------
.. [vaft2047] VEST-Tokamak/vaft#2047, exact assignment and usage provenance.
"""
roles = {str(k): str(v) for k, v in assignments.items()}
mapping = dict(role_to_partition) if role_to_partition is not None else {v: v for v in roles.values()}
policy = {"schema_version": 1, "group_roles": roles,
"allowed_operations": {str(k): sorted(set(v)) for k, v in allowed_operations.items()},
"role_to_partition": mapping, "assignment_sha256": _hash(roles),
"provenance": dict(provenance or {}), "history_basis": "owner_declaration"}
spec = replace(dataset.spec, metadata={**dataset.spec.metadata, "data_use_policy": policy, "data_use_policy_sha256": _hash(policy)})
data = replace(dataset, spec=spec)
_policy(data)
from .structured import StructuredDatasetArtifact, build_structured_dataset
if isinstance(data, StructuredDatasetArtifact):
data = build_structured_dataset(data.source, data.groups, input_specs=data.input_specs,
target_specs=data.target_specs, spec=spec, sample_meta=data.sample_meta, available=data.available)
else:
from .dataset import dataset_fingerprint
data = replace(data, fingerprint=dataset_fingerprint(data))
fixed = {g: mapping[roles[g]] for g in sorted(set(data.group_labels))}
if not fixed or any(not isinstance(v, str) or not v for v in fixed.values()):
raise ModelContractError("execution partitions must be nonempty strings")
names = sorted(set(fixed.values()))
split = Split(spec=SplitSpec(fractions={p: 1 / len(names) for p in names}, fixed=fixed), assignment=fixed)
return data, splitImport exact group roles without recomputing a scientific split.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact or StructuredDatasetArtifact | - | Existing stable, fingerprinted samples. |
assignments | mapping of str to str | - | Actual independent group-to-role assignments, including absent groups. |
allowed_operations | mapping of str to sequence of str | - | Explicit role permissions: train, preprocess, calibrate, select, evaluate or infer. |
role_to_partition | mapping or None, optional | - | Explicit mapping to execution partitions; None preserves role names. |
provenance | mapping or None, optional | - | Split/protocol revisions, origins, label methods and dependencies. |
| Returns | |||
| — | tuple | - | Re-fingerprinted dataset and exact Split, without random reassignment. |
Convention
Roles are owner-defined; neither machine restrictions nor salts are inferred.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Copy the declared assignments, permissions, mapping and provenance.
- Validate role coverage and record the full assignment digest.
- Recompute dataset identity and construct a fixed split for its groups.
Applicability
Machine-independent.
Limitations
Re-fingerprinting streams structured samples. Sources must remain stable. Supplied historical provenance is a declaration, not retrospective proof.
Provenance
- VEST-Tokamak/vaft#2047, exact assignment and usage provenance.
load_dataset
Machine-independent
source
load_dataset(directory)
Show source vaft/process/ml/dataset.py, lines 447–515
def load_dataset(directory):
"""Load a dataset written by :func:`save_dataset`, verifying its fingerprint.
Parameters
----------
directory : str or path-like
Directory holding ``arrays.npz`` and ``dataset.json`` [-].
Returns
-------
DatasetArtifact
The dataset, a :class:`FeatureDataset` when one was saved [-].
Raises
------
ModelContractError
When the files are missing or the recomputed fingerprint differs from
the recorded one -- the arrays changed after they were saved.
Applicability
-------------
Machine-independent.
"""
directory = Path(directory)
record_path = directory / _DATASET_RECORD
if not record_path.is_file() or not (directory / _DATASET_ARRAYS).is_file():
raise ModelContractError(f"{directory} does not hold a saved dataset")
record = json.loads(record_path.read_text(encoding="utf-8"))
with np.load(directory / _DATASET_ARRAYS, allow_pickle=False) as payload:
arrays = {key: payload[key] for key in payload.files}
spec_record = record["spec"]
spec = DatasetSpec(
name=spec_record["name"],
task=spec_record["task"],
group_key=spec_record.get("group_key", "group"),
input_names=tuple(spec_record.get("input_names", ())),
target_names=tuple(spec_record.get("target_names", ())),
description=spec_record.get("description", ""),
metadata=spec_record.get("metadata", {}),
)
groups = arrays["groups"]
if np.dtype(record.get("group_dtype", groups.dtype)).kind in "iu":
groups = groups.astype(record["group_dtype"])
fields = dict(
spec=spec,
inputs=arrays["inputs"],
groups=groups,
targets=arrays.get("targets"),
sample_meta={k[len("meta:"):]: v for k, v in arrays.items() if k.startswith("meta:")},
available=arrays.get("available"),
)
fs = record.get("feature_spec")
if fs is not None:
dataset = FeatureDataset(
feature_spec=FeatureSpec(
feature_names=tuple(fs["feature_names"]),
blocks={k: tuple(v) for k, v in fs.get("blocks", {}).items()},
version=fs.get("version", ""),
),
**fields,
)
else:
dataset = DatasetArtifact(**fields)
fingerprint = dataset_fingerprint(dataset)
if fingerprint != record["fingerprint"]:
raise ModelContractError(
f"dataset in {directory} has fingerprint {fingerprint}, but was saved as {record['fingerprint']}"
)
return dataset.replace(fingerprint=fingerprint)Load a dataset written by save_dataset, verifying its fingerprint.
| Name | Type | Unit | Description |
|---|---|---|---|
directory | str or path-like | - | Directory holding |
| Returns | |||
| — | DatasetArtifact | - | The dataset, a |
Raises
ModelContractErrorWhen the files are missing or the recomputed fingerprint differs from the recorded one – the arrays changed after they were saved.
Applicability
Applicability
Machine-independent.
load_model
Machine-independent
source
load_model(name, *, version=None, stage=None, registry=None, cache=None, env=None, fetch=False)
Show source vaft/process/ml/resolver.py, lines 353–409
def load_model(name, *, version=None, stage=None, registry=None, cache=None, env=None, fetch=False):
"""Resolve a published model and load it, ready for :func:`predict`.
Parameters
----------
name : str
Registry model name [-].
version : str or None, optional
Exact version; give this or ``stage`` [-].
stage : str or None, optional
Lifecycle alias such as ``"production"`` [-].
registry : str or path-like or None, optional
``vaft-nn`` checkout; ``$VAFT_NN_HOME`` when ``None`` [-].
cache : str or path-like or None, optional
Artifact cache root; ``$VAFT_NN_CACHE``, else the platform cache
directory [-].
env : mapping or None, optional
Environment to read the variables from [-].
fetch : bool, optional
On a cache miss or mismatch, run :func:`fetch_model` and retry [-].
Returns
-------
ModelArtifact or ModelBundle
The model (or bundle of models), carrying the identity
:func:`resolve_model` returned [-].
Applicability
-------------
Machine-independent.
"""
from .artifact import load_model_artifact
from .bundle import _with_identity, load_model_bundle
env = os.environ if env is None else env
try:
identity = resolve_model(name, version=version, stage=stage, registry=registry, cache=cache, env=env)
except ModelResolutionError:
if not fetch:
raise
fetched = fetch_model(name, version=version, stage=stage, registry=registry, cache=cache, env=env)
identity = ModelIdentity(**{**fetched.to_dict(), "stage": stage})
root = _registry_root(registry, env)
manifest = root / "models" / name / "versions" / identity.version / "manifest.json"
kind = json.loads(manifest.read_bytes()).get("kind", "model")
# verify=False skips only the size/presence pass resolve_model just made;
# every byte loaded is still hashed, and the manifest re-read here must be
# the one whose hash the registry pins.
if kind == "bundle":
loaded = load_model_bundle(identity.source, manifest=manifest, verify=False)
else:
loaded = load_model_artifact(identity.source, manifest=manifest, verify=False)
if loaded.identity.manifest_sha256 != identity.manifest_sha256:
raise ModelResolutionError(f"the registry manifest of {name} {identity.version} changed after verification")
if kind == "bundle":
return _with_identity(loaded, identity)
return loaded.replace(identity=identity)Resolve a published model and load it, ready for predict.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Registry model name. |
version | str or None, optional | - | Exact version; give this or |
stage | str or None, optional | - | Lifecycle alias such as |
registry | str or path-like or None, optional | - |
|
cache | str or path-like or None, optional | - | Artifact cache root; |
env | mapping or None, optional | - | Environment to read the variables from. |
fetch | bool, optional | - | On a cache miss or mismatch, run |
| Returns | |||
| — | ModelArtifact or ModelBundle | - | The model (or bundle of models), carrying the identity |
Applicability
Applicability
Machine-independent.
load_model_artifact
Machine-independent
source
load_model_artifact(directory, *, manifest=None, verify=True)
Show source vaft/process/ml/artifact.py, lines 190–243
def load_model_artifact(directory, *, manifest=None, verify=True):
"""Load an artifact directory, verifying every file against its manifest.
Parameters
----------
directory : str or path-like
Directory holding the weights and normalisation files [-].
manifest : str or path-like or None, optional
Manifest to trust; ``directory/manifest.json`` when ``None``. The
resolver passes the registry's reviewed copy here, so a tampered cache
cannot vouch for itself [-].
verify : bool, optional
Check the SHA-256 and size of every file before loading [-].
Returns
-------
ModelArtifact
The model with an identity naming the directory, version and manifest
hash [-].
Raises
------
ModelResolutionError
When the manifest is missing or a file is missing or differs.
ModelContractError
When the manifest's schema version is not one this VAFT reads.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [vaft669] VEST-Tokamak/vaft#669, section 13: resolution verifies identity,
version and artifact hash.
"""
directory = Path(directory)
manifest_path = Path(manifest) if manifest is not None else directory / MANIFEST
if not manifest_path.is_file():
raise ModelResolutionError(f"no manifest at {manifest_path}")
raw = manifest_path.read_bytes()
record = json.loads(raw)
if record.get("kind", "model") != "model":
raise ModelContractError(f"{manifest_path} describes a {record.get('kind')!r}; use load_model_bundle")
model = _load_artifact(directory, record, verify=verify)
identity = ModelIdentity(
name=model.name,
state_sha256=model.state_sha256,
version=record["version"],
manifest_sha256=sha256_bytes(raw),
resolved_by="path",
source=str(directory),
)
return model.replace(identity=identity)Load an artifact directory, verifying every file against its manifest.
| Name | Type | Unit | Description |
|---|---|---|---|
directory | str or path-like | - | Directory holding the weights and normalisation files. |
manifest | str or path-like or None, optional | - | Manifest to trust; |
verify | bool, optional | - | Check the SHA-256 and size of every file before loading. |
| Returns | |||
| — | ModelArtifact | - | The model with an identity naming the directory, version and manifest hash. |
Raises
ModelResolutionErrorWhen the manifest is missing or a file is missing or differs.ModelContractErrorWhen the manifest’s schema version is not one this VAFT reads.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#669, section 13: resolution verifies identity, version and artifact hash.
load_model_bundle
Machine-independent
source
load_model_bundle(directory, *, manifest=None, verify=True)
Show source vaft/process/ml/bundle.py, lines 137–195
def load_model_bundle(directory, *, manifest=None, verify=True):
"""Load a bundle directory, verifying every file and member manifest.
Parameters
----------
directory : str or path-like
Directory holding the bundle's files [-].
manifest : str or path-like or None, optional
Bundle manifest to trust; ``directory/manifest.json`` when ``None``.
The resolver passes the registry's reviewed copy [-].
verify : bool, optional
Check every file's SHA-256 and size first [-].
Returns
-------
ModelBundle
The members, each with an identity naming the bundle, and the
composition record [-].
Raises
------
ModelResolutionError
When a file is missing or differs, or a member manifest differs from
the hash the bundle pins.
ModelContractError
When the manifest is not a bundle manifest this VAFT reads.
Applicability
-------------
Machine-independent.
"""
directory = Path(directory)
manifest_path = Path(manifest) if manifest is not None else directory / MANIFEST
if not manifest_path.is_file():
raise ModelResolutionError(f"no manifest at {manifest_path}")
raw = manifest_path.read_bytes()
record = json.loads(raw)
if record.get("schema_version") != MANIFEST_SCHEMA_VERSION or record.get("kind") != "bundle":
raise ModelContractError(f"{manifest_path} is not a schema-{MANIFEST_SCHEMA_VERSION} bundle manifest")
if verify:
_verify_files(record, directory)
identity = ModelIdentity(
name=record["model"],
state_sha256=sha256_bytes(raw),
version=record["version"],
manifest_sha256=sha256_bytes(raw),
resolved_by="path",
source=str(directory),
)
members = {}
for role, entry in record["members"].items():
member_raw = (directory / entry["manifest"]).read_bytes()
if sha256_bytes(member_raw) != entry["manifest_sha256"]:
raise ModelResolutionError(f"member {role!r} manifest differs from the hash the bundle pins")
member = _load_artifact(directory, json.loads(member_raw), verify=verify, prefix=f"{role}.")
members[role] = member.replace(identity=_member_identity(member, role, entry["manifest_sha256"], identity))
return ModelBundle(
name=record["model"], members=members, composition=record.get("composition") or {}, identity=identity
)Load a bundle directory, verifying every file and member manifest.
| Name | Type | Unit | Description |
|---|---|---|---|
directory | str or path-like | - | Directory holding the bundle’s files. |
manifest | str or path-like or None, optional | - | Bundle manifest to trust; |
verify | bool, optional | - | Check every file’s SHA-256 and size first. |
| Returns | |||
| — | ModelBundle | - | The members, each with an identity naming the bundle, and the composition record. |
Raises
ModelResolutionErrorWhen a file is missing or differs, or a member manifest differs from the hash the bundle pins.ModelContractErrorWhen the manifest is not a bundle manifest this VAFT reads.
Applicability
Applicability
Machine-independent.
predict
Machine-independent
source
predict(model, data)
Show source vaft/process/ml/inference.py, lines 69–146
def predict(model, data):
"""Run a model on new data and stamp the result with its exact identity.
Accepts a dataset (checked against the model's feature schema) or a bare
array. The result records the model's immutable identity -- name, exact
version, manifest and weight hashes -- never an alias such as
``production``, so a stored result keeps meaning what it meant when it was
produced.
Parameters
----------
model : ModelArtifact
Trained, loaded or resolved model [-].
data : DatasetArtifact or array_like
Inputs of shape ``(n, *model.input_shape)`` [any].
Returns
-------
InferenceResult
Outputs aligned with the samples (``prediction``; ``reconstruction``
and a per-sample ``score``; or ``score`` alone; plus
``<name>_exceeds`` for every calibration and, for a dataset with an
availability mask, ``available``), the model identity, and the groups
and per-sample metadata of a dataset input [any].
Raises
------
ModelContractError
When the input shape, or a dataset's column names, differ from the
model's.
Output semantics
----------------
``prediction`` and ``reconstruction`` are in the units of the targets and
inputs; an autoencoder's ``score`` is the mean squared reconstruction
error in the model's normalised units, and a score model's is the
detector's own anomaly measure (larger is more anomalous). An
unavailable sample has NaN outputs and exceeds no threshold: missing
data is not "no anomaly". A score or an exceedance is a model output, not a
physical event: an ``IRE-like candidate``, never a reconnection onset
(#669 section 16).
Applicability
-------------
Machine-independent.
"""
from .usage import _inference_use
usage = _inference_use(data) if hasattr(data, "spec") else None
from .structured import StructuredDatasetArtifact
if isinstance(data, StructuredDatasetArtifact):
from ._structured_lifecycle import predict_structured
result = predict_structured(model, data)
from dataclasses import replace
return replace(result, provenance={**result.provenance, "data_use": usage}) if usage else result
if model.tensor_schema:
raise ModelContractError("structured models require a StructuredDatasetArtifact")
if isinstance(data, DatasetArtifact):
_check_columns(model, data)
x = np.asarray(data.inputs)
available = data.available
groups, meta = data.groups, dict(data.sample_meta)
provenance = {"input": "dataset", "dataset_fingerprint": data.fingerprint}
else:
x = np.asarray(data)
available = None
groups, meta = None, {}
digest = hashlib.sha256(np.ascontiguousarray(x).tobytes()).hexdigest()
provenance = {"input": "array", "input_sha256": digest}
if usage:
provenance["data_use"] = usage
outputs = _forward(model, x, available)
return InferenceResult(
outputs=outputs,
model_identity=model.resolved_identity(),
groups=groups,
sample_meta=meta,
provenance=provenance,
)Run a model on new data and stamp the result with its exact identity.
Accepts a dataset (checked against the model’s feature schema) or a bare
array. The result records the model’s immutable identity – name, exact
version, manifest and weight hashes – never an alias such as
production, so a stored result keeps meaning what it meant when it was
produced.
| Name | Type | Unit | Description |
|---|---|---|---|
model | ModelArtifact | - | Trained, loaded or resolved model. |
data | DatasetArtifact or array_like | any | Inputs of shape |
| Returns | |||
| — | InferenceResult | any | Outputs aligned with the samples ( |
Raises
ModelContractErrorWhen the input shape, or a dataset’s column names, differ from the model’s.
Output semantics · Applicability
Output semantics
prediction and reconstruction are in the units of the targets and
inputs; an autoencoder’s score is the mean squared reconstruction
error in the model’s normalised units, and a score model’s is the
detector’s own anomaly measure (larger is more anomalous). An
unavailable sample has NaN outputs and exceeds no threshold: missing
data is not “no anomaly”. A score or an exceedance is a model output, not a
physical event: an IRE-like candidate, never a reconnection onset
(#669 section 16).
Applicability
Machine-independent.
record_data_use
Machine-independentConvention-sensitive
source
record_data_use(dataset, split, operation, *, groups=None)
Show source vaft/process/ml/usage.py, lines 116–173
def record_data_use(dataset, split, operation, *, groups=None):
"""Check a declared use and return its provenance receipt.
Parameters
----------
dataset : DatasetArtifact or StructuredDatasetArtifact
Dataset with an optional data_use_policy [-].
split : Split
Realized execution assignment to verify against the declaration [-].
operation : str
Requested train, preprocess, calibrate, select, evaluate or infer [-].
groups : iterable of str or None, optional
Groups actually consumed; None selects every dataset group [-].
Returns
-------
dict or None
Usage receipt; None means no policy was supplied, not validated history [-].
Processing steps
----------------
1. Verify the dataset's execution assignment matches its declared mapping.
2. Check explicit permissions for every selected group.
3. Return dataset, policy and assignment identities with observed group roles.
Convention
----------
Calling this reports a requested entrypoint use; it cannot inspect external
training history or intercept scientific operations outside this API.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [vaft2047] VEST-Tokamak/vaft#2047, opt-in operation-role checks.
"""
policy = _policy(dataset)
if policy is None:
return None
if operation not in _OPERATIONS:
raise ModelContractError("unknown data-use operation")
roles = policy["group_roles"]
if _hash(roles) != policy["assignment_sha256"]:
raise ModelContractError("data assignment digest does not match declaration")
for g in set(dataset.group_labels):
if split.assignment.get(g) != policy["role_to_partition"][roles[g]]:
raise ModelContractError("split differs from declared data roles")
selected = sorted(set(dataset.group_labels if groups is None else map(str, groups)))
if set(selected) - set(dataset.group_labels):
raise ModelContractError("requested use contains groups outside the dataset")
for g in selected:
if operation not in policy["allowed_operations"][roles[g]]:
raise ModelContractError(f"{operation} is forbidden for role {roles[g]!r}, group {g!r}")
return {"operation": operation, "groups": selected, "group_roles": {g: roles[g] for g in selected},
"dataset_fingerprint": dataset.fingerprint, "assignment_sha256": policy["assignment_sha256"],
"policy_sha256": _hash(policy), "history_basis": "declared_use_at_entrypoint"}Check a declared use and return its provenance receipt.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact or StructuredDatasetArtifact | - | Dataset with an optional data_use_policy. |
split | Split | - | Realized execution assignment to verify against the declaration. |
operation | str | - | Requested train, preprocess, calibrate, select, evaluate or infer. |
groups | iterable of str or None, optional | - | Groups actually consumed; None selects every dataset group. |
| Returns | |||
| — | dict or None | - | Usage receipt; None means no policy was supplied, not validated history. |
Convention
Calling this reports a requested entrypoint use; it cannot inspect external training history or intercept scientific operations outside this API.
Processing steps · Applicability · Provenance
Processing steps
- Verify the dataset’s execution assignment matches its declared mapping.
- Check explicit permissions for every selected group.
- Return dataset, policy and assignment identities with observed group roles.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#2047, opt-in operation-role checks.
register_architecture
Machine-independent
source
register_architecture(name, factory, *, kind, backend='torch')
Show source vaft/process/ml/_backends/__init__.py, lines 142–201
def register_architecture(name, factory, *, kind, backend="torch"):
"""Make a task-defined network available to a backend by name.
Task code (the SXR reconstruction transformer, a sequence model) defines
its network next to its science and registers it here; a
:class:`ModelSpec` then selects it with ``architecture=name``. The
factory is called only when a model is built, so registering imports no
ML framework.
Parameters
----------
name : str
Architecture name a ``ModelSpec`` refers to [-].
factory : callable
``factory(spec, input_shape, output_shape)`` returning a network that
maps a batch of shape ``(n, *input_shape)`` to ``(n, *output_shape)``
without flattening it first [-].
kind : {"supervised", "reconstruction", "score"}
What the network learns [-].
backend : str, optional
Backend the factory builds for; only ``"torch"`` accepts registrations [-].
Returns
-------
None
The architecture is registered for this process [-].
Raises
------
ModelContractError
On an unknown kind, a backend that does not accept registrations, a
name that shadows a built-in, or a second registration of one name
with a different factory.
Applicability
-------------
Machine-independent.
Limitations
-----------
The registry is per process. A published model records its
architecture's name, not its code; loading it needs the same task module
imported, and the code version is traced through the VAFT revision in the
manifest.
"""
if kind not in KINDS:
raise ModelContractError(f"kind must be one of {KINDS}; got {kind!r}")
_check_name("architecture", name)
target = get_backend(backend)
if not target.extensible:
raise ModelContractError(f"backend {backend!r} does not accept registered architectures")
if kind not in target.trainable_kinds:
raise ModelContractError(f"backend {backend!r} trains {target.trainable_kinds}, not {kind!r}")
if name in target.builtin:
raise ModelContractError(f"{name!r} is a built-in {backend} architecture")
registered = _REGISTERED.setdefault(backend, {})
existing = registered.get(name)
if existing is not None and (existing.factory is not factory or existing.kind != kind):
raise ModelContractError(f"architecture {name!r} is already registered for {backend!r}")
registered[name] = Architecture(name=name, kind=kind, factory=factory)Make a task-defined network available to a backend by name.
Task code (the SXR reconstruction transformer, a sequence model) defines
its network next to its science and registers it here; a
ModelSpec then selects it with architecture=name. The
factory is called only when a model is built, so registering imports no
ML framework.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Architecture name a |
factory | callable | - |
|
kind | {"supervised", "reconstruction", "score"} | - | What the network learns. |
backend | str, optional | - | Backend the factory builds for; only |
| Returns | |||
| — | None | - | The architecture is registered for this process. |
Raises
ModelContractErrorOn an unknown kind, a backend that does not accept registrations, a name that shadows a built-in, or a second registration of one name with a different factory.
Applicability · Limitations
Applicability
Machine-independent.
Limitations
The registry is per process. A published model records its architecture’s name, not its code; loading it needs the same task module imported, and the code version is traced through the VAFT revision in the manifest.
register_augmentation
Machine-independent
source
register_augmentation(name, function)
Show source vaft/process/ml/_backends/__init__.py, lines 253–287
def register_augmentation(name, function):
"""Make a train-time augmentation available by name.
Selected with ``TrainingConfig(augmentation=name)``. It is applied to the
**train partition only**, once per epoch, after the group split -- so an
augmented copy of a sample can never reach validation or test.
Parameters
----------
name : str
Augmentation name [-].
function : callable
``function(inputs, targets, rng)`` returning ``(inputs, targets)`` of
the same shapes, in physical (un-normalised) units; ``targets`` is
``None`` for an unsupervised model and ``rng`` is a seeded
``numpy.random.Generator`` [-].
Returns
-------
None
The augmentation is registered for this process [-].
Raises
------
ModelContractError
On a name already registered with a different function.
Applicability
-------------
Machine-independent.
"""
_check_name("augmentation", name)
if AUGMENTATIONS.get(name, function) is not function:
raise ModelContractError(f"augmentation {name!r} is already registered")
AUGMENTATIONS[name] = functionMake a train-time augmentation available by name.
Selected with TrainingConfig(augmentation=name). It is applied to the
train partition only, once per epoch, after the group split – so an
augmented copy of a sample can never reach validation or test.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Augmentation name. |
function | callable | - |
|
| Returns | |||
| — | None | - | The augmentation is registered for this process. |
Raises
ModelContractErrorOn a name already registered with a different function.
Applicability
Applicability
Machine-independent.
register_loss
Machine-independent
source
register_loss(name, factory)
Show source vaft/process/ml/_backends/__init__.py, lines 204–250
def register_loss(name, factory):
"""Make a task-defined training loss available by name.
Selected with ``ModelSpec(hyperparameters={"loss": name})``. The factory
receives the model spec and the train-partition normalisation, so a
physics loss -- a line-of-sight residual through a projector acting on
emissivity in physical units -- can undo the normalisation the network
works in.
Parameters
----------
name : str
Loss name [-].
factory : callable
``factory(spec, normalization)`` returning ``loss(prediction, target,
inputs)``, a differentiable scalar in the backend's tensor type.
``prediction``, ``target`` and ``inputs`` are normalised and keep
their sample shapes; ``normalization`` maps ``input_mean``,
``input_std`` and, for a supervised model, ``target_mean`` and
``target_std`` to NumPy arrays that broadcast against one sample [-].
Returns
-------
None
The loss is registered for this process [-].
Raises
------
ModelContractError
On a name that is already registered with a different factory, or
that shadows the built-in ``"mse"``.
Applicability
-------------
Machine-independent.
Limitations
-----------
Only the iterative ``torch`` backend trains with a loss; the closed-form
and scikit-learn backends refuse a ``loss`` hyperparameter.
"""
_check_name("loss", name)
if name == "mse":
raise ModelContractError("'mse' is the built-in loss")
if LOSSES.get(name, factory) is not factory:
raise ModelContractError(f"loss {name!r} is already registered")
LOSSES[name] = factoryMake a task-defined training loss available by name.
Selected with ModelSpec(hyperparameters={"loss": name}). The factory
receives the model spec and the train-partition normalisation, so a
physics loss – a line-of-sight residual through a projector acting on
emissivity in physical units – can undo the normalisation the network
works in.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Loss name. |
factory | callable | - |
|
| Returns | |||
| — | None | - | The loss is registered for this process. |
Raises
ModelContractErrorOn a name that is already registered with a different factory, or that shadows the built-in"mse".
Applicability · Limitations
Applicability
Machine-independent.
Limitations
Only the iterative torch backend trains with a loss; the closed-form
and scikit-learn backends refuse a loss hyperparameter.
resolve_model
Machine-independentConvention-sensitive
source
resolve_model(name, *, version=None, stage=None, registry=None, cache=None, env=None)
Show source vaft/process/ml/resolver.py, lines 139–207
def resolve_model(name, *, version=None, stage=None, registry=None, cache=None, env=None):
"""Resolve a published model to one exact, hash-verified local version.
Parameters
----------
name : str
Registry model name, e.g. ``"ire_ae_vaft_v1"`` [-].
version : str or None, optional
Exact version; give this or ``stage`` [-].
stage : str or None, optional
Lifecycle alias such as ``"production"``, resolved to an exact version
before anything is loaded [-].
registry : str or path-like or None, optional
``vaft-nn`` checkout; ``$VAFT_NN_HOME`` when ``None`` [-].
cache : str or path-like or None, optional
Artifact cache root; ``$VAFT_NN_CACHE``, else the platform cache
directory [-].
env : mapping or None, optional
Environment to read the two variables from; ``os.environ`` when ``None`` [-].
Returns
-------
ModelIdentity
Name, exact version, manifest SHA-256, requested stage, lifecycle status
and the verified artifact directory (``source``) [-].
Raises
------
ModelResolutionError
When the registry, model, version, stage, manifest or a cached file is
missing, or a hash differs from the one the registry pins.
ModelContractError
When both or neither of ``version`` and ``stage`` are given.
Processing steps
----------------
1. Read ``models/<name>/releases.yaml`` from the registry.
2. Map ``stage`` to its version, or take ``version``; refuse a stage that
points at a deprecated version.
3. Check the registry manifest's SHA-256 against the pinned one.
4. Check every file the manifest lists in ``<cache>/<name>/<version>/``
for presence, size and SHA-256.
Convention
----------
A stage alias is mutable and a version is not. The returned identity
always carries the exact version and manifest hash; ``stage`` only records
what was asked for.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [vaft669] VEST-Tokamak/vaft#669, sections 9 and 13: lifecycle aliases
resolve to immutable artifacts, and resolution verifies the hash.
"""
env = os.environ if env is None else env
_, version, entry, _, manifest, manifest_sha = _registry_entry(name, version, stage, registry, env)
directory = _cache_root(cache, env) / name / version
problems = _cache_problems(directory, manifest)
if problems:
raise ModelResolutionError(
f"cached artifact {directory} is not {name} {version}: " + "; ".join(problems)
+ f". Fetch it with vaft.process.ml.fetch_model({name!r}, version={version!r}), or: "
+ f"gh release download {entry.get('release') or f'{name}-v{version}'} -R {DEFAULT_REPOSITORY} -D {directory}"
)
return _identity(name, version, stage, entry, manifest, manifest_sha, directory)Resolve a published model to one exact, hash-verified local version.
| Name | Type | Unit | Description |
|---|---|---|---|
name | str | - | Registry model name, e.g. |
version | str or None, optional | - | Exact version; give this or |
stage | str or None, optional | - | Lifecycle alias such as |
registry | str or path-like or None, optional | - |
|
cache | str or path-like or None, optional | - | Artifact cache root; |
env | mapping or None, optional | - | Environment to read the two variables from; |
| Returns | |||
| — | ModelIdentity | - | Name, exact version, manifest SHA-256, requested stage, lifecycle status and the verified artifact directory ( |
Convention
A stage alias is mutable and a version is not. The returned identity
always carries the exact version and manifest hash; stage only records
what was asked for.
Raises
ModelResolutionErrorWhen the registry, model, version, stage, manifest or a cached file is missing, or a hash differs from the one the registry pins.ModelContractErrorWhen both or neither ofversionandstageare given.
Processing steps · Applicability · Provenance
Processing steps
- Read
models/<name>/releases.yamlfrom the registry. - Map
stageto its version, or takeversion; refuse a stage that points at a deprecated version. - Check the registry manifest’s SHA-256 against the pinned one.
- Check every file the manifest lists in
<cache>/<name>/<version>/for presence, size and SHA-256.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#669, sections 9 and 13: lifecycle aliases resolve to immutable artifacts, and resolution verifies the hash.
save_dataset
Machine-independent
source
save_dataset(dataset, directory)
Show source vaft/process/ml/dataset.py, lines 392–444
def save_dataset(dataset, directory):
"""Materialise a dataset on disk so training can run elsewhere.
Building a corpus-scale dataset (reading thousands of shots) and training
on it are separate steps, often on separate machines; this is the hand-off.
Parameters
----------
dataset : DatasetArtifact
Fingerprinted dataset from :func:`build_dataset` [-].
directory : str or path-like
Target directory; must not exist or be empty [-].
Returns
-------
str
The dataset fingerprint, which :func:`load_dataset` re-checks [-].
Raises
------
ModelContractError
When the dataset has no fingerprint, the directory is not empty, or an
array has dtype ``object``.
Applicability
-------------
Machine-independent.
"""
if not dataset.fingerprint:
raise ModelContractError("dataset has no fingerprint; build it with build_dataset")
directory = Path(directory)
if directory.exists() and any(directory.iterdir()):
raise ModelContractError(f"{directory} is not empty")
directory.mkdir(parents=True, exist_ok=True)
arrays = {"inputs": np.asarray(dataset.inputs), "groups": dataset.group_labels.astype(str)}
if dataset.targets is not None:
arrays["targets"] = np.asarray(dataset.targets)
if dataset.available is not None:
arrays["available"] = np.asarray(dataset.available)
for key, value in dataset.sample_meta.items():
arrays[f"meta:{key}"] = np.asarray(value)
for key, value in arrays.items():
if value.dtype == object:
raise ModelContractError(f"{key} has dtype object and cannot be stored without pickle")
np.savez(directory / _DATASET_ARRAYS, **arrays)
record = {
"fingerprint": dataset.fingerprint,
"spec": dataset.spec.to_dict(),
"feature_spec": dataset.feature_spec.to_dict() if isinstance(dataset, FeatureDataset) else None,
"group_dtype": str(np.asarray(dataset.groups).dtype),
}
(directory / _DATASET_RECORD).write_text(json.dumps(record, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return dataset.fingerprintMaterialise a dataset on disk so training can run elsewhere.
Building a corpus-scale dataset (reading thousands of shots) and training on it are separate steps, often on separate machines; this is the hand-off.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact | - | Fingerprinted dataset from |
directory | str or path-like | - | Target directory; must not exist or be empty. |
| Returns | |||
| — | str | - | The dataset fingerprint, which |
Raises
ModelContractErrorWhen the dataset has no fingerprint, the directory is not empty, or an array has dtypeobject.
Applicability
Applicability
Machine-independent.
save_model_artifact
Machine-independent
source
save_model_artifact(model, directory, *, version, model_card=None, applicability=None)
Show source vaft/process/ml/artifact.py, lines 114–173
def save_model_artifact(model, directory, *, version, model_card=None, applicability=None):
"""Write a model as an immutable, hash-pinned artifact directory.
The directory is what gets attached to a ``vaft-nn`` release; its
``manifest.json`` is what gets committed to the registry.
Parameters
----------
model : ModelArtifact
Trained model; ``model.name`` must be lower_snake_case [-].
directory : str or path-like
Target directory; must not exist or be empty [-].
version : str
Semantic version of this artifact, e.g. ``"0.1.0"`` [-].
model_card : mapping or None, optional
Intended use, training population, validation evidence and known
limits, stored in the manifest [-].
applicability : mapping or None, optional
The input domain the model was trained and validated on (parameter
ranges, shot ranges, diagnostics), stored in the manifest [-].
Returns
-------
ModelArtifact
The same model carrying its saved identity: version, manifest SHA-256
and weight SHA-256 [-].
Raises
------
ModelContractError
On an invalid name or version, or a non-empty target directory.
Processing steps
----------------
1. Write the backend weights and the normalisation moments.
2. Hash each file and write ``manifest.json`` (sorted keys, two-space
indent) pinning every hash and size.
3. Hash the manifest; that hash is the artifact's identity.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [vaft669] VEST-Tokamak/vaft#669, sections 10-11: the ``vaft-nn`` release
layout and manifest contract.
"""
_check_name_version(model.name, version)
directory = _empty_directory(directory)
raw = _write_artifact(model, directory, str(version), "", model_card, applicability)
identity = ModelIdentity(
name=model.name,
state_sha256=model.state_sha256,
version=str(version),
manifest_sha256=sha256_bytes(raw),
resolved_by="saved",
source=str(directory),
)
return model.replace(identity=identity)Write a model as an immutable, hash-pinned artifact directory.
The directory is what gets attached to a vaft-nn release; its
manifest.json is what gets committed to the registry.
| Name | Type | Unit | Description |
|---|---|---|---|
model | ModelArtifact | - | Trained model; |
directory | str or path-like | - | Target directory; must not exist or be empty. |
version | str | - | Semantic version of this artifact, e.g. |
model_card | mapping or None, optional | - | Intended use, training population, validation evidence and known limits, stored in the manifest. |
applicability | mapping or None, optional | - | The input domain the model was trained and validated on (parameter ranges, shot ranges, diagnostics), stored in the manifest. |
| Returns | |||
| — | ModelArtifact | - | The same model carrying its saved identity: version, manifest SHA-256 and weight SHA-256. |
Raises
ModelContractErrorOn an invalid name or version, or a non-empty target directory.
Processing steps · Applicability · Provenance
Processing steps
- Write the backend weights and the normalisation moments.
- Hash each file and write
manifest.json(sorted keys, two-space indent) pinning every hash and size. - Hash the manifest; that hash is the artifact’s identity.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#669, sections 10-11: the
vaft-nnrelease layout and manifest contract.
save_model_bundle
Machine-independent
source
save_model_bundle(bundle, directory, *, version, model_card=None, applicability=None)
Write a multi-model bundle as one immutable, hash-pinned release.
| Name | Type | Unit | Description |
|---|---|---|---|
bundle | ModelBundle | - | Members by role and the composition record. |
directory | str or path-like | - | Target directory; must not exist or be empty. |
version | str | - | Semantic version shared by the bundle and every member. |
model_card | mapping or None, optional | - | Intended use, population, validation evidence and limits. |
applicability | mapping or None, optional | - | Input domain the bundle was trained and validated on. |
| Returns | |||
| — | ModelBundle | - | The bundle carrying its saved identity; every member carries its own identity and the bundle’s. |
Raises
ModelContractErrorOn an invalid bundle or member name or version, or a non-empty directory.
Processing steps · Applicability · Provenance
Processing steps
- Write each member as an ordinary artifact whose file names are prefixed by its role.
- Write
manifest.jsonwithkind: "bundle", every file’s SHA-256 and size, each member manifest’s SHA-256 and the composition record.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#973, section 24: one logical model release may contain two fitted stage artifacts.
split_groups
Machine-independent
source
split_groups(groups, spec=None)
Assign independent groups to train/validation/test partitions.
Splits the groups – never the samples – so correlated samples of one
shot or one simulation case can never sit on both sides of a holdout. Do
this on the record-level data, before any windowing or augmentation; the
returned Split then applies unchanged to everything derived.
| Name | Type | Unit | Description |
|---|---|---|---|
groups | array_like or DatasetArtifact | - | Group label of every sample, or a dataset whose groups are used. |
spec | SplitSpec or None, optional | - | Partition fractions (of groups), seed and pinned groups; |
| Returns | |||
| — | Split | - | Group-to-partition assignment. |
Raises
ModelContractErrorWhen a pinned group is absent, or when there are fewer groups than partitions with a positive fraction.
Processing steps · Defaults · Applicability · Provenance
Processing steps
- Take the unique groups as strings, sorted, so the result depends on the set of groups and the seed only – not on sample order.
- Assign the groups
spec.fixedpins. - With
method="permutation", permute the remaining groups withnumpy.random.default_rng(seed)and cut the permutation at the cumulative fractions, rounding so every partition with a positive fraction receives at least one group. Withmethod="hash", map each group tou = int(sha256("seed:group")) / 2**256in[0, 1)and place it in the partition whose cumulative-fraction interval holdsu; no partition is guaranteed a group.
Defaults
The 70/15/15 fractions are numerical convenience, the same shares the VEST IRE prototype used [ire].
Applicability
Machine-independent.
Provenance
- S. Kaufman, S. Rosset, C. Perlich, O. Stitelman, “Leakage in data mining: formulation, detection, and avoidance”, ACM TKDD 6, 15 (2012).
- VEST IRE two-stage anomaly-detection notebook (v4), shot-level split; reviewed in issue #669 section 15A.
train_model
Machine-independentConvention-sensitive
source
train_model(dataset, split, model_spec, config=None, *, name=None, train_mask=None, train_mask_record=None)
Train a model on the train partition and package it with its provenance.
The one training entry point for every backend and architecture. What
comes back is a ModelArtifact, not a framework object: its
trained state is opaque bytes and everything that identifies it – data,
split, configuration, software – travels with it.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact | - | Fingerprinted training data from |
split | Split | - | Group assignment from |
model_spec | ModelSpec | - | Backend, architecture, task and hyperparameters. |
config | TrainingConfig or None, optional | - | Optimisation, normalisation and augmentation settings; |
name | str or None, optional | - | Model name used when publishing; |
train_mask | array_like of bool or None, optional | - | Per-sample mask further restricting the fitted samples within the train partition – training-set cleaning; |
train_mask_record | mapping or None, optional | - | How |
| Returns | |||
| — | TrainingResult | - | The artifact, the per-epoch history, the split, the epoch whose weights were kept, the fitted sample indices and, for a score model that can leave a sample out (k-NN), the leave-self-out training scores. |
Convention
Normalisation is a z-score, (x - mean) / std, with a constant element
given std = 1; the reconstruction score of an autoencoder is
measured in these normalised units. A score model’s score is larger
for more anomalous samples.
Raises
ModelContractErrorWhen the dataset has no fingerprint, a group is missing from the split, no sample is left to fit, a supervised architecture has no targets, or a named augmentation or loss is not registered.BackendUnavailableErrorWhen the backend’s framework is not installed.
Processing steps · Defaults · Applicability · Provenance
Processing steps
- Select the fitted samples: train partition, available, and
train_mask; select the available validation samples. - Compute input (and target) normalisation moments on the fitted samples
only, per element or pooled over
config.normalize_axes. - Fit the backend on the normalised arrays; a reconstruction architecture learns to reproduce its input. A registered augmentation is applied to the fitted samples only, once per epoch. The validation loss drives early stopping and the best epoch’s weights are kept.
- Record dataset fingerprint and spec, split groups, the train-mask record, model spec, configuration, software revision and runtimes, and the loss history.
Defaults
The TrainingConfig defaults (Adam, learning rate 1e-3, batch 64,
patience 10, per-element normalisation) are numerical convenience, not
tuned for any task.
Applicability
Machine-independent.
Provenance
- VEST-Tokamak/vaft#669, sections 6 and 7: training as a VAFT processing stage, dataset provenance as part of model identity.
window_dataset
Machine-independent
source
window_dataset(dataset, window, step, *, target_mode=None)
Cut sliding windows that never cross a group boundary.
Turns a record-level dataset – samples in time order, one row per time point – into a window dataset for a sequence model or a feature extractor.
| Name | Type | Unit | Description |
|---|---|---|---|
dataset | DatasetArtifact | - | Record-level samples in time order within each group. |
window | int | - | Samples per window. |
step | int | - | Samples between consecutive window starts. |
target_mode | {"window", "last", "none"} or None, optional | - | What each window’s target is: the target rows over the window, the target at its last sample, or none; |
| Returns | |||
| — | DatasetArtifact | - | Inputs of shape |
Raises
ModelContractErrorWhenwindoworstepis not a positive integer, or a target mode asks for targets the dataset does not have.
Processing steps · Input semantics · Output semantics · Applicability · Provenance
Processing steps
- Find the maximal contiguous runs of one group label.
- Within each run, start a window every
stepsamples while a fullwindowfits; a shorter tail is dropped. - Recompute the fingerprint of the result.
Input semantics
One row per sample time, grouped and time-ordered within each group.
Output semantics
One row per window. The group of every window is the group of all of its
samples, so a Split made on the source applies unchanged. A
window is available only when every sample in it is.
Applicability
Machine-independent.
Provenance
- S. Kaufman et al., “Leakage in data mining”, ACM TKDD 6, 15 (2012): windows from one unit on both sides of a holdout leak.