metria 0.1.0__py3-none-any.whl

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.
metria/__init__.py ADDED
@@ -0,0 +1,142 @@
1
+ """Metria core models for reproducible inference-systems studies."""
2
+
3
+ from .capabilities import (
4
+ GeometryInspection,
5
+ ModelGeometry,
6
+ evaluate_turboquant_kv_capability,
7
+ inspect_model_geometry,
8
+ )
9
+ from .comparison import compare_runs
10
+ from .execution import execute_run
11
+ from .hardware import capture_hardware_fingerprint
12
+ from .identity import (
13
+ ArtifactManifest,
14
+ Capability,
15
+ CapabilitySet,
16
+ HardwareFingerprint,
17
+ ModelRef,
18
+ RuntimeConfig,
19
+ SupportLevel,
20
+ WorkloadSpec,
21
+ )
22
+ from .identity_evidence import (
23
+ IdentityStatus,
24
+ RuntimeIdentityEvidence,
25
+ aggregate_identity_status,
26
+ )
27
+ from .inspection import (
28
+ PreflightCapabilityResult,
29
+ capability_inspection_to_mapping,
30
+ inspect_run_capabilities,
31
+ )
32
+ from .models import (
33
+ ComparisonPlan,
34
+ CompatibilityIssue,
35
+ CompatibilityReport,
36
+ MetricDefinition,
37
+ MetricDirection,
38
+ MetricSample,
39
+ MetricSummary,
40
+ RunRecord,
41
+ RunSpec,
42
+ RunStatus,
43
+ StudySpec,
44
+ TreatmentSpec,
45
+ TreatmentType,
46
+ )
47
+ from .protocols import MeasurementResult
48
+ from .recipes import (
49
+ StudyRecipe,
50
+ dump_study_recipe,
51
+ load_study_recipe,
52
+ run_spec_from_data,
53
+ run_spec_to_data,
54
+ study_recipe_digest,
55
+ study_recipe_from_data,
56
+ study_recipe_to_data,
57
+ study_recipe_to_json,
58
+ )
59
+ from .records import (
60
+ RUN_RECORD_SCHEMA,
61
+ dump_run_record,
62
+ load_run_record,
63
+ run_evidence_digest,
64
+ run_record_digest,
65
+ run_record_from_data,
66
+ run_record_to_data,
67
+ run_record_to_json,
68
+ )
69
+ from .study_execution import (
70
+ PairwiseAnalysisStatus,
71
+ StudyExecutionResult,
72
+ StudyPairAnalysis,
73
+ StudyPairComparison,
74
+ execute_study,
75
+ )
76
+
77
+ __version__ = "0.1.0"
78
+
79
+ from .verification import VerificationResult, VerificationVerdict, verify_recipe
80
+
81
+ __all__ = [
82
+ "RUN_RECORD_SCHEMA",
83
+ "ArtifactManifest",
84
+ "Capability",
85
+ "CapabilitySet",
86
+ "ComparisonPlan",
87
+ "CompatibilityIssue",
88
+ "CompatibilityReport",
89
+ "GeometryInspection",
90
+ "HardwareFingerprint",
91
+ "IdentityStatus",
92
+ "MeasurementResult",
93
+ "MetricDefinition",
94
+ "MetricDirection",
95
+ "MetricSample",
96
+ "MetricSummary",
97
+ "ModelGeometry",
98
+ "ModelRef",
99
+ "PairwiseAnalysisStatus",
100
+ "PreflightCapabilityResult",
101
+ "RunRecord",
102
+ "RunSpec",
103
+ "RunStatus",
104
+ "RuntimeConfig",
105
+ "RuntimeIdentityEvidence",
106
+ "StudyExecutionResult",
107
+ "StudyPairAnalysis",
108
+ "StudyPairComparison",
109
+ "StudyRecipe",
110
+ "StudySpec",
111
+ "SupportLevel",
112
+ "TreatmentSpec",
113
+ "TreatmentType",
114
+ "WorkloadSpec",
115
+ "VerificationResult",
116
+ "VerificationVerdict",
117
+ "aggregate_identity_status",
118
+ "capability_inspection_to_mapping",
119
+ "capture_hardware_fingerprint",
120
+ "compare_runs",
121
+ "dump_run_record",
122
+ "dump_study_recipe",
123
+ "evaluate_turboquant_kv_capability",
124
+ "execute_run",
125
+ "execute_study",
126
+ "inspect_model_geometry",
127
+ "inspect_run_capabilities",
128
+ "load_run_record",
129
+ "load_study_recipe",
130
+ "run_evidence_digest",
131
+ "run_record_digest",
132
+ "run_record_from_data",
133
+ "run_record_to_data",
134
+ "run_record_to_json",
135
+ "run_spec_from_data",
136
+ "run_spec_to_data",
137
+ "study_recipe_digest",
138
+ "study_recipe_from_data",
139
+ "study_recipe_to_data",
140
+ "study_recipe_to_json",
141
+ "verify_recipe",
142
+ ]
metria/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Module entry point for ``python -m metria``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ raise SystemExit(main())
metria/_freeze.py ADDED
@@ -0,0 +1,89 @@
1
+ """Recursive normalization helpers for immutable Metria evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping, Sequence, Set
7
+ from enum import Enum
8
+ from types import MappingProxyType
9
+ from typing import Any, TypeVar, cast
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ def freeze_value(value: Any, *, _active: set[int] | None = None) -> Any:
15
+ """Return an immutable, detached representation of an evidence value.
16
+
17
+ Metria's core records intentionally retain JSON-like configuration and
18
+ evidence rather than arbitrary live runtime objects. Mappings are copied
19
+ into read-only proxies, sequences become tuples, sets become frozensets,
20
+ bytearrays become bytes, and path-like values become their filesystem
21
+ representation. Cyclic containers and unsupported object types are
22
+ rejected so a recorded value cannot keep a mutable alias into caller state.
23
+ """
24
+
25
+ if value is None or isinstance(value, (str, bytes, bool, int, float, Enum)):
26
+ return value
27
+ if isinstance(value, bytearray):
28
+ return bytes(value)
29
+ if isinstance(value, os.PathLike):
30
+ return os.fspath(value)
31
+
32
+ active = _active if _active is not None else set()
33
+ if isinstance(value, Mapping):
34
+ marker = id(value)
35
+ if marker in active:
36
+ raise ValueError("cyclic evidence mappings are not supported")
37
+ active.add(marker)
38
+ try:
39
+ frozen: dict[str, Any] = {}
40
+ for key, item in value.items():
41
+ if not isinstance(key, str):
42
+ raise TypeError("evidence mapping keys must be strings")
43
+ frozen[key] = freeze_value(item, _active=active)
44
+ return MappingProxyType(frozen)
45
+ finally:
46
+ active.remove(marker)
47
+
48
+ if isinstance(value, Sequence):
49
+ marker = id(value)
50
+ if marker in active:
51
+ raise ValueError("cyclic evidence sequences are not supported")
52
+ active.add(marker)
53
+ try:
54
+ return tuple(freeze_value(item, _active=active) for item in value)
55
+ finally:
56
+ active.remove(marker)
57
+
58
+ if isinstance(value, Set):
59
+ marker = id(value)
60
+ if marker in active:
61
+ raise ValueError("cyclic evidence sets are not supported")
62
+ active.add(marker)
63
+ try:
64
+ return frozenset(freeze_value(item, _active=active) for item in value)
65
+ finally:
66
+ active.remove(marker)
67
+
68
+ raise TypeError(
69
+ "unsupported evidence value type "
70
+ f"{type(value).__module__}.{type(value).__qualname__}; "
71
+ "store a serializable value or an artifact reference instead"
72
+ )
73
+
74
+
75
+ def freeze_mapping(value: Mapping[str, Any]) -> Mapping[str, Any]:
76
+ """Copy and deeply freeze a string-keyed evidence mapping."""
77
+
78
+ return cast(Mapping[str, Any], freeze_value(value))
79
+
80
+
81
+ def freeze_typed_mapping(value: Mapping[str, T]) -> Mapping[str, T]:
82
+ """Copy a mapping whose values are already immutable typed objects."""
83
+
84
+ copied: dict[str, T] = {}
85
+ for key, item in value.items():
86
+ if not isinstance(key, str):
87
+ raise TypeError("mapping keys must be strings")
88
+ copied[key] = item
89
+ return MappingProxyType(copied)
metria/capabilities.py ADDED
@@ -0,0 +1,320 @@
1
+ """Evidence-backed model geometry and capability inspection for Metria."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
8
+
9
+ from ._freeze import freeze_mapping
10
+ from .identity import Capability, SupportLevel
11
+
12
+ _GEOMETRY_ALIASES: Mapping[str, tuple[str, ...]] = {
13
+ "hidden_size": ("hidden_size", "n_embd"),
14
+ "num_attention_heads": ("num_attention_heads", "n_head"),
15
+ "num_key_value_heads": ("num_key_value_heads", "n_head_kv"),
16
+ "head_dim": ("head_dim",),
17
+ "num_hidden_layers": ("num_hidden_layers", "n_layer"),
18
+ "context_length": ("context_length", "max_position_embeddings", "n_ctx"),
19
+ }
20
+ _VALIDATED_TURBO_HEAD_DIMS = frozenset({128, 256})
21
+
22
+
23
+ def _positive_int(value: Any, *, name: str) -> int:
24
+ if isinstance(value, bool) or not isinstance(value, int):
25
+ raise TypeError(f"{name} must be an integer")
26
+ if value <= 0:
27
+ raise ValueError(f"{name} must be positive")
28
+ return value
29
+
30
+
31
+ def _text(value: Any, *, name: str) -> str:
32
+ if not isinstance(value, str) or not value.strip():
33
+ raise ValueError(f"{name} must be a non-empty string")
34
+ return value
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ModelGeometry:
39
+ """Consistent model-geometry evidence used by capability rules.
40
+
41
+ The type stores observations; it does not infer model-family properties from
42
+ names. ``head_dim`` may be derived only from a self-consistent
43
+ ``hidden_size / num_attention_heads`` pair.
44
+ """
45
+
46
+ hidden_size: int | None = None
47
+ num_attention_heads: int | None = None
48
+ num_key_value_heads: int | None = None
49
+ head_dim: int | None = None
50
+ num_hidden_layers: int | None = None
51
+ context_length: int | None = None
52
+ attention_layout: str | None = None
53
+ evidence: Mapping[str, Any] = field(default_factory=dict)
54
+
55
+ def __post_init__(self) -> None:
56
+ for name in (
57
+ "hidden_size",
58
+ "num_attention_heads",
59
+ "num_key_value_heads",
60
+ "head_dim",
61
+ "num_hidden_layers",
62
+ "context_length",
63
+ ):
64
+ value = getattr(self, name)
65
+ if value is not None:
66
+ object.__setattr__(self, name, _positive_int(value, name=name))
67
+ if self.attention_layout is not None:
68
+ object.__setattr__(
69
+ self,
70
+ "attention_layout",
71
+ _text(self.attention_layout, name="attention_layout"),
72
+ )
73
+ if (
74
+ self.hidden_size is not None
75
+ and self.num_attention_heads is not None
76
+ and self.hidden_size % self.num_attention_heads != 0
77
+ ):
78
+ raise ValueError(
79
+ "hidden_size must be divisible by num_attention_heads to derive head_dim"
80
+ )
81
+ derived = (
82
+ self.hidden_size // self.num_attention_heads
83
+ if self.hidden_size is not None and self.num_attention_heads is not None
84
+ else None
85
+ )
86
+ if (
87
+ self.head_dim is not None
88
+ and derived is not None
89
+ and self.head_dim != derived
90
+ ):
91
+ raise ValueError(
92
+ "head_dim contradicts hidden_size / num_attention_heads "
93
+ f"({self.head_dim} != {derived})"
94
+ )
95
+ if self.head_dim is None and derived is not None:
96
+ object.__setattr__(self, "head_dim", derived)
97
+ if (
98
+ self.num_key_value_heads is not None
99
+ and self.num_attention_heads is not None
100
+ and self.num_key_value_heads > self.num_attention_heads
101
+ ):
102
+ raise ValueError("num_key_value_heads cannot exceed num_attention_heads")
103
+ object.__setattr__(self, "evidence", freeze_mapping(self.evidence))
104
+
105
+ def to_mapping(self) -> Mapping[str, Any]:
106
+ """Return immutable normalized geometry evidence."""
107
+
108
+ result: dict[str, Any] = {}
109
+ for name in (
110
+ "hidden_size",
111
+ "num_attention_heads",
112
+ "num_key_value_heads",
113
+ "head_dim",
114
+ "num_hidden_layers",
115
+ "context_length",
116
+ "attention_layout",
117
+ ):
118
+ value = getattr(self, name)
119
+ if value is not None:
120
+ result[name] = value
121
+ if self.evidence:
122
+ result["evidence"] = self.evidence
123
+ return freeze_mapping(result)
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class GeometryInspection:
128
+ """Normalized geometry plus the evidence-backed inspection conclusion."""
129
+
130
+ geometry: ModelGeometry | None
131
+ capability: Capability
132
+
133
+
134
+ def _read_alias(
135
+ raw: Mapping[str, Any],
136
+ canonical: str,
137
+ ) -> tuple[Any | None, tuple[str, ...]]:
138
+ aliases = _GEOMETRY_ALIASES[canonical]
139
+ observed = [(name, raw[name]) for name in aliases if name in raw]
140
+ if not observed:
141
+ return None, ()
142
+ values = {repr(value) for _, value in observed}
143
+ if len(values) > 1:
144
+ names = ", ".join(name for name, _ in observed)
145
+ raise ValueError(f"conflicting {canonical} aliases: {names}")
146
+ return observed[0][1], tuple(name for name, _ in observed)
147
+
148
+
149
+ def inspect_model_geometry(model: Mapping[str, Any]) -> GeometryInspection:
150
+ """Normalize explicit model geometry without guessing from model-family names."""
151
+
152
+ raw = model.get("geometry")
153
+ if raw is None:
154
+ return GeometryInspection(
155
+ geometry=None,
156
+ capability=Capability(
157
+ name="model.geometry",
158
+ status=SupportLevel.UNKNOWN,
159
+ reasons=("model.geometry was not provided",),
160
+ evidence={"source": "requested_model_metadata"},
161
+ ),
162
+ )
163
+ if not isinstance(raw, Mapping):
164
+ return GeometryInspection(
165
+ geometry=None,
166
+ capability=Capability(
167
+ name="model.geometry",
168
+ status=SupportLevel.UNKNOWN,
169
+ reasons=("model.geometry must be a mapping",),
170
+ evidence={"source": "requested_model_metadata"},
171
+ ),
172
+ )
173
+
174
+ try:
175
+ values: dict[str, Any] = {}
176
+ source_keys: dict[str, tuple[str, ...]] = {}
177
+ for canonical in _GEOMETRY_ALIASES:
178
+ value, keys = _read_alias(raw, canonical)
179
+ if value is not None:
180
+ values[canonical] = value
181
+ source_keys[canonical] = keys
182
+ if "attention_layout" in raw:
183
+ values["attention_layout"] = raw["attention_layout"]
184
+ source_keys["attention_layout"] = ("attention_layout",)
185
+ geometry = ModelGeometry(
186
+ **values,
187
+ evidence={
188
+ "source": "requested_model_metadata",
189
+ "source_keys": source_keys,
190
+ },
191
+ )
192
+ except (TypeError, ValueError) as exc:
193
+ return GeometryInspection(
194
+ geometry=None,
195
+ capability=Capability(
196
+ name="model.geometry",
197
+ status=SupportLevel.UNKNOWN,
198
+ reasons=(str(exc),),
199
+ evidence={
200
+ "source": "requested_model_metadata",
201
+ "provided_keys": tuple(sorted(str(key) for key in raw)),
202
+ },
203
+ ),
204
+ )
205
+
206
+ normalized = geometry.to_mapping()
207
+ if not normalized or set(normalized) == {"evidence"}:
208
+ return GeometryInspection(
209
+ geometry=None,
210
+ capability=Capability(
211
+ name="model.geometry",
212
+ status=SupportLevel.UNKNOWN,
213
+ reasons=("model.geometry contains no recognized geometry fields",),
214
+ evidence={
215
+ "source": "requested_model_metadata",
216
+ "provided_keys": tuple(sorted(str(key) for key in raw)),
217
+ },
218
+ ),
219
+ )
220
+
221
+ return GeometryInspection(
222
+ geometry=geometry,
223
+ capability=Capability(
224
+ name="model.geometry",
225
+ status=SupportLevel.SUPPORTED,
226
+ reasons=("geometry metadata is internally consistent",),
227
+ evidence=normalized,
228
+ ),
229
+ )
230
+
231
+
232
+ def _uses_turboquant(kv_config: Mapping[str, Any]) -> bool:
233
+ for key in ("key_dtype", "value_dtype"):
234
+ value = kv_config.get(key)
235
+ if isinstance(value, str) and value.lower().startswith("turbo"):
236
+ return True
237
+ return False
238
+
239
+
240
+ def evaluate_turboquant_kv_capability(
241
+ model: Mapping[str, Any],
242
+ kv_config: Mapping[str, Any],
243
+ *,
244
+ experimental_override: bool = False,
245
+ ) -> Capability:
246
+ """Evaluate the documented TurboQuant KV-cache head-dimension guardrail."""
247
+
248
+ if not _uses_turboquant(kv_config):
249
+ return Capability(
250
+ name="turboquant.kv_cache.geometry",
251
+ status=SupportLevel.SUPPORTED,
252
+ reasons=("requested KV cache does not use a TurboQuant dtype",),
253
+ evidence={"active": False},
254
+ )
255
+
256
+ inspection = inspect_model_geometry(model)
257
+ if inspection.geometry is None or inspection.geometry.head_dim is None:
258
+ reasons = inspection.capability.reasons
259
+ if inspection.geometry is not None:
260
+ reasons = ("head_dim is unavailable from consistent geometry metadata",)
261
+ return Capability(
262
+ name="turboquant.kv_cache.geometry",
263
+ status=SupportLevel.UNKNOWN,
264
+ reasons=reasons,
265
+ evidence={
266
+ "active": True,
267
+ "experimental_override": experimental_override,
268
+ "geometry": (
269
+ inspection.geometry.to_mapping()
270
+ if inspection.geometry is not None
271
+ else None
272
+ ),
273
+ "geometry_status": SupportLevel(inspection.capability.status).value,
274
+ },
275
+ )
276
+
277
+ head_dim = inspection.geometry.head_dim
278
+ evidence = {
279
+ "active": True,
280
+ "head_dim": head_dim,
281
+ "validated_head_dims": tuple(sorted(_VALIDATED_TURBO_HEAD_DIMS)),
282
+ "experimental_override": experimental_override,
283
+ "geometry": inspection.geometry.to_mapping(),
284
+ }
285
+ if head_dim <= 64:
286
+ if experimental_override:
287
+ return Capability(
288
+ name="turboquant.kv_cache.geometry",
289
+ status=SupportLevel.EXPERIMENTAL,
290
+ reasons=(
291
+ "head_dim <= 64 is at/below the documented TurboQuant boundary; "
292
+ "explicit experimental override requested",
293
+ ),
294
+ evidence=evidence,
295
+ )
296
+ return Capability(
297
+ name="turboquant.kv_cache.geometry",
298
+ status=SupportLevel.UNSUPPORTED,
299
+ reasons=(
300
+ "head_dim <= 64 is at/below the documented TurboQuant boundary; "
301
+ "use a non-Turbo KV dtype or opt into an explicit experimental override",
302
+ ),
303
+ evidence=evidence,
304
+ )
305
+ if head_dim in _VALIDATED_TURBO_HEAD_DIMS:
306
+ return Capability(
307
+ name="turboquant.kv_cache.geometry",
308
+ status=SupportLevel.SUPPORTED,
309
+ reasons=(f"head_dim={head_dim} is in the documented validated set",),
310
+ evidence=evidence,
311
+ )
312
+ return Capability(
313
+ name="turboquant.kv_cache.geometry",
314
+ status=SupportLevel.EXPERIMENTAL,
315
+ reasons=(
316
+ f"head_dim={head_dim} is internally consistent but outside the documented "
317
+ "validated set {128, 256}",
318
+ ),
319
+ evidence=evidence,
320
+ )