metrik-core 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.
- metrik_core-0.1.0/.gitignore +89 -0
- metrik_core-0.1.0/PKG-INFO +68 -0
- metrik_core-0.1.0/README.md +46 -0
- metrik_core-0.1.0/pyproject.toml +43 -0
- metrik_core-0.1.0/src/metrik/core/__init__.py +239 -0
- metrik_core-0.1.0/src/metrik/core/artifacts/__init__.py +38 -0
- metrik_core-0.1.0/src/metrik/core/artifacts/artifact.py +177 -0
- metrik_core-0.1.0/src/metrik/core/artifacts/envelope.py +241 -0
- metrik_core-0.1.0/src/metrik/core/bench.py +335 -0
- metrik_core-0.1.0/src/metrik/core/env.py +45 -0
- metrik_core-0.1.0/src/metrik/core/errors.py +125 -0
- metrik_core-0.1.0/src/metrik/core/hashing.py +407 -0
- metrik_core-0.1.0/src/metrik/core/ir.py +431 -0
- metrik_core-0.1.0/src/metrik/core/lineage.py +206 -0
- metrik_core-0.1.0/src/metrik/core/plugins.py +381 -0
- metrik_core-0.1.0/src/metrik/core/py.typed +0 -0
- metrik_core-0.1.0/src/metrik/core/runs.py +300 -0
- metrik_core-0.1.0/src/metrik/core/store/__init__.py +14 -0
- metrik_core-0.1.0/src/metrik/core/store/_fs.py +102 -0
- metrik_core-0.1.0/src/metrik/core/store/artifacts.py +380 -0
- metrik_core-0.1.0/src/metrik/core/store/blobs.py +130 -0
- metrik_core-0.1.0/src/metrik/core/viz.py +353 -0
- metrik_core-0.1.0/tests/test_ascii_messages.py +67 -0
- metrik_core-0.1.0/tests/test_bench.py +195 -0
- metrik_core-0.1.0/tests/test_canonical.py +297 -0
- metrik_core-0.1.0/tests/test_canonical_properties.py +134 -0
- metrik_core-0.1.0/tests/test_envelope.py +174 -0
- metrik_core-0.1.0/tests/test_ir.py +270 -0
- metrik_core-0.1.0/tests/test_lineage.py +267 -0
- metrik_core-0.1.0/tests/test_plugins.py +316 -0
- metrik_core-0.1.0/tests/test_runs.py +287 -0
- metrik_core-0.1.0/tests/test_store.py +369 -0
- metrik_core-0.1.0/tests/test_viz.py +257 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Metrik — .gitignore
|
|
2
|
+
#
|
|
3
|
+
# NOTE: the previous version of this file contained `docs/*`, `.gitignore`, and `.claude`,
|
|
4
|
+
# which excluded the entire documentation tree — i.e. every Phase 0/1 deliverable — from
|
|
5
|
+
# version control. Replaced deliberately. docs/ is the project's primary output right now.
|
|
6
|
+
|
|
7
|
+
# --- Build output that must never be committed ---
|
|
8
|
+
# ADR 016: diagram sources live in docs/diagrams/*.mmd; rendered images are build output.
|
|
9
|
+
docs/static/diagrams/
|
|
10
|
+
# ADR 015: generated reference docs are committed on purpose (drift detection), so they
|
|
11
|
+
# are NOT ignored here — see docs/reference/.
|
|
12
|
+
|
|
13
|
+
# --- Python ---
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.py[cod]
|
|
16
|
+
*.so
|
|
17
|
+
.Python
|
|
18
|
+
build/
|
|
19
|
+
dist/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.eggs/
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
env/
|
|
25
|
+
|
|
26
|
+
# Tooling caches
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
.hypothesis/
|
|
31
|
+
.coverage
|
|
32
|
+
.coverage.*
|
|
33
|
+
htmlcov/
|
|
34
|
+
coverage.xml
|
|
35
|
+
.benchmarks/
|
|
36
|
+
.tox/
|
|
37
|
+
.nox/
|
|
38
|
+
|
|
39
|
+
# uv (ADR 013) — uv.lock IS committed; the cache is not.
|
|
40
|
+
.uv/
|
|
41
|
+
|
|
42
|
+
# --- Node / web (ADR 009) ---
|
|
43
|
+
node_modules/
|
|
44
|
+
.next/
|
|
45
|
+
out/
|
|
46
|
+
.turbo/
|
|
47
|
+
*.tsbuildinfo
|
|
48
|
+
.pnpm-store/
|
|
49
|
+
|
|
50
|
+
# --- Tauri (ADR 010) ---
|
|
51
|
+
apps/desktop/src-tauri/target/
|
|
52
|
+
apps/desktop/src-tauri/gen/
|
|
53
|
+
|
|
54
|
+
# --- Docs site (ADR 015) ---
|
|
55
|
+
docs/.docusaurus/
|
|
56
|
+
docs/build/
|
|
57
|
+
|
|
58
|
+
# --- Metrik runtime data ---
|
|
59
|
+
# The artifact store and blob store (ADR 006). Content-addressed, machine-generated,
|
|
60
|
+
# gigabytes. Never in git.
|
|
61
|
+
.metrik/
|
|
62
|
+
*.metrik-store/
|
|
63
|
+
# Local config may hold API keys (ADR 018).
|
|
64
|
+
metrik.local.toml
|
|
65
|
+
.env
|
|
66
|
+
.env.*
|
|
67
|
+
!.env.example
|
|
68
|
+
|
|
69
|
+
# Model weights and checkpoints — always too large, often license-encumbered (plan.md §5.7).
|
|
70
|
+
*.safetensors
|
|
71
|
+
*.gguf
|
|
72
|
+
*.onnx
|
|
73
|
+
*.pt
|
|
74
|
+
*.pth
|
|
75
|
+
*.bin
|
|
76
|
+
# ...except the tiny-random fixtures, which are committed on purpose (ADR 017).
|
|
77
|
+
!tests/fixtures/**
|
|
78
|
+
|
|
79
|
+
# --- Editors and OS ---
|
|
80
|
+
.vscode/
|
|
81
|
+
!.vscode/extensions.json
|
|
82
|
+
.idea/
|
|
83
|
+
*.swp
|
|
84
|
+
.DS_Store
|
|
85
|
+
Thumbs.db
|
|
86
|
+
desktop.ini
|
|
87
|
+
|
|
88
|
+
# --- Local agent/tooling settings ---
|
|
89
|
+
.claude/settings.local.json
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: metrik-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Metrik core — artifact envelope, canonical serialization, content addressing.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Asmodeus14/Metrik
|
|
6
|
+
Project-URL: Repository, https://github.com/Asmodeus14/Metrik
|
|
7
|
+
Project-URL: Issues, https://github.com/Asmodeus14/Metrik/issues
|
|
8
|
+
Author: The Metrik Authors
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: machine-learning,provenance,quantization,reproducibility
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: blake3>=1.0
|
|
19
|
+
Requires-Dist: packaging>=23.0
|
|
20
|
+
Requires-Dist: pydantic>=2.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# metrik-core
|
|
24
|
+
|
|
25
|
+
The vocabulary every other Metrik package agrees on: the artifact envelope, canonical
|
|
26
|
+
serialization, and content addressing.
|
|
27
|
+
|
|
28
|
+
**This package defines vocabulary, not behaviour** (`plan.md` §4). It has no ML dependencies —
|
|
29
|
+
no torch, no transformers — which is enforced in CI by `import-linter`, not by convention. That
|
|
30
|
+
constraint is what lets the artifact layer be installed and reasoned about without a multi-gigabyte
|
|
31
|
+
ML stack.
|
|
32
|
+
|
|
33
|
+
Not published yet. Phase 2 of [the roadmap](../../ROADMAP.md).
|
|
34
|
+
|
|
35
|
+
## What is here so far
|
|
36
|
+
|
|
37
|
+
| Module | Purpose |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `metrik.core.hashing` | Canonical JSON, `Digest`, `content_hash`, `derivation_hash` |
|
|
40
|
+
| `metrik.core.errors` | The error taxonomy plugins raise from |
|
|
41
|
+
| `metrik.core.artifacts` | The envelope every artifact carries |
|
|
42
|
+
|
|
43
|
+
## The two hashes
|
|
44
|
+
|
|
45
|
+
The subtlest part of the design, and getting it wrong makes both caching and reproducibility
|
|
46
|
+
silently unreliable. There are two questions, so there are two hashes:
|
|
47
|
+
|
|
48
|
+
| Question | Answered by |
|
|
49
|
+
|---|---|
|
|
50
|
+
| *Is this the same result as that?* | `content_hash` — over the payload only |
|
|
51
|
+
| *Have I already computed this, so can I skip the work?* | `derivation_hash` — over how it was asked for |
|
|
52
|
+
|
|
53
|
+
A single hash cannot do both. Caching needs a key computable **before** running, so it must cover
|
|
54
|
+
inputs and parameters rather than results. Dedup and citation need a key covering **only** the
|
|
55
|
+
value, so two runs producing identical numbers are recognised as identical even if asked for
|
|
56
|
+
differently.
|
|
57
|
+
|
|
58
|
+
Because both are recorded, the store can *check* a producer's determinism claim rather than
|
|
59
|
+
believe it: same `derivation_hash`, different `content_hash`, and a declared class of
|
|
60
|
+
`deterministic` means the declaration is false.
|
|
61
|
+
|
|
62
|
+
## Specification
|
|
63
|
+
|
|
64
|
+
The full contract is [`docs/planning/artifacts.md`](../../docs/planning/artifacts.md). This
|
|
65
|
+
package implements §2 (envelope), §3 (hashing and canonicalization), and §4 (blobs).
|
|
66
|
+
|
|
67
|
+
Canonicalization rules are normative and tested as properties, not examples — see
|
|
68
|
+
`tests/test_canonical_properties.py`.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# metrik-core
|
|
2
|
+
|
|
3
|
+
The vocabulary every other Metrik package agrees on: the artifact envelope, canonical
|
|
4
|
+
serialization, and content addressing.
|
|
5
|
+
|
|
6
|
+
**This package defines vocabulary, not behaviour** (`plan.md` §4). It has no ML dependencies —
|
|
7
|
+
no torch, no transformers — which is enforced in CI by `import-linter`, not by convention. That
|
|
8
|
+
constraint is what lets the artifact layer be installed and reasoned about without a multi-gigabyte
|
|
9
|
+
ML stack.
|
|
10
|
+
|
|
11
|
+
Not published yet. Phase 2 of [the roadmap](../../ROADMAP.md).
|
|
12
|
+
|
|
13
|
+
## What is here so far
|
|
14
|
+
|
|
15
|
+
| Module | Purpose |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `metrik.core.hashing` | Canonical JSON, `Digest`, `content_hash`, `derivation_hash` |
|
|
18
|
+
| `metrik.core.errors` | The error taxonomy plugins raise from |
|
|
19
|
+
| `metrik.core.artifacts` | The envelope every artifact carries |
|
|
20
|
+
|
|
21
|
+
## The two hashes
|
|
22
|
+
|
|
23
|
+
The subtlest part of the design, and getting it wrong makes both caching and reproducibility
|
|
24
|
+
silently unreliable. There are two questions, so there are two hashes:
|
|
25
|
+
|
|
26
|
+
| Question | Answered by |
|
|
27
|
+
|---|---|
|
|
28
|
+
| *Is this the same result as that?* | `content_hash` — over the payload only |
|
|
29
|
+
| *Have I already computed this, so can I skip the work?* | `derivation_hash` — over how it was asked for |
|
|
30
|
+
|
|
31
|
+
A single hash cannot do both. Caching needs a key computable **before** running, so it must cover
|
|
32
|
+
inputs and parameters rather than results. Dedup and citation need a key covering **only** the
|
|
33
|
+
value, so two runs producing identical numbers are recognised as identical even if asked for
|
|
34
|
+
differently.
|
|
35
|
+
|
|
36
|
+
Because both are recorded, the store can *check* a producer's determinism claim rather than
|
|
37
|
+
believe it: same `derivation_hash`, different `content_hash`, and a declared class of
|
|
38
|
+
`deterministic` means the declaration is false.
|
|
39
|
+
|
|
40
|
+
## Specification
|
|
41
|
+
|
|
42
|
+
The full contract is [`docs/planning/artifacts.md`](../../docs/planning/artifacts.md). This
|
|
43
|
+
package implements §2 (envelope), §3 (hashing and canonicalization), and §4 (blobs).
|
|
44
|
+
|
|
45
|
+
Canonicalization rules are normative and tested as properties, not examples — see
|
|
46
|
+
`tests/test_canonical_properties.py`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "metrik-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Metrik core — artifact envelope, canonical serialization, content addressing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "The Metrik Authors" }]
|
|
13
|
+
keywords = ["machine-learning", "quantization", "provenance", "reproducibility"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Deliberately tiny. metrik-core defines vocabulary, not behaviour (plan.md §4), and
|
|
24
|
+
# must be installable without a multi-GB ML stack:
|
|
25
|
+
# - NO torch, NO transformers — enforced by .importlinter (ADR 007).
|
|
26
|
+
# - blake3 is a compiled dependency, taken knowingly: buy native performance as a
|
|
27
|
+
# dependency rather than as a component (ADR 001, ADR 023).
|
|
28
|
+
dependencies = [
|
|
29
|
+
"pydantic>=2.7",
|
|
30
|
+
"blake3>=1.0",
|
|
31
|
+
# Semver range parsing for plugin API compatibility. Pure Python, no transitive deps,
|
|
32
|
+
# already present in every build environment -- the alternative is hand-rolling version
|
|
33
|
+
# comparison, which is a well-known way to be subtly wrong about pre-releases.
|
|
34
|
+
"packaging>=23.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/Asmodeus14/Metrik"
|
|
39
|
+
Repository = "https://github.com/Asmodeus14/Metrik"
|
|
40
|
+
Issues = "https://github.com/Asmodeus14/Metrik/issues"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/metrik"]
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Metrik core — the vocabulary every other package agrees on.
|
|
2
|
+
|
|
3
|
+
This package defines vocabulary, not behaviour (``plan.md`` §4). It has **no ML
|
|
4
|
+
dependencies** — no torch, no transformers — which is enforced in CI by ``import-linter``
|
|
5
|
+
rather than by convention, and is what lets the artifact layer be installed and reasoned
|
|
6
|
+
about without a multi-gigabyte stack.
|
|
7
|
+
|
|
8
|
+
Specification: ``docs/planning/artifacts.md``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from metrik.core.artifacts.artifact import MAX_PAYLOAD_BYTES, Artifact, build_artifact
|
|
14
|
+
from metrik.core.artifacts.envelope import (
|
|
15
|
+
ROOT_ARTIFACT_TYPES,
|
|
16
|
+
BlobRef,
|
|
17
|
+
DeterminismClass,
|
|
18
|
+
Envelope,
|
|
19
|
+
InputRef,
|
|
20
|
+
Omission,
|
|
21
|
+
PartialInfo,
|
|
22
|
+
ProducerRef,
|
|
23
|
+
Provenance,
|
|
24
|
+
ReasonCode,
|
|
25
|
+
)
|
|
26
|
+
from metrik.core.bench import (
|
|
27
|
+
BaselineComparison,
|
|
28
|
+
BenchResult,
|
|
29
|
+
ContaminationCheck,
|
|
30
|
+
CostRecord,
|
|
31
|
+
GenerationSpec,
|
|
32
|
+
HarnessName,
|
|
33
|
+
HarnessRef,
|
|
34
|
+
Isolation,
|
|
35
|
+
Metric,
|
|
36
|
+
SandboxAttestation,
|
|
37
|
+
SandboxLimits,
|
|
38
|
+
TaskRef,
|
|
39
|
+
)
|
|
40
|
+
from metrik.core.errors import (
|
|
41
|
+
BackendError,
|
|
42
|
+
CancelledError,
|
|
43
|
+
FailureKind,
|
|
44
|
+
InsufficientResourcesError,
|
|
45
|
+
LineageError,
|
|
46
|
+
MetrikError,
|
|
47
|
+
UnsupportedError,
|
|
48
|
+
UserError,
|
|
49
|
+
ValidationError,
|
|
50
|
+
)
|
|
51
|
+
from metrik.core.hashing import (
|
|
52
|
+
CanonicalizationError,
|
|
53
|
+
Digest,
|
|
54
|
+
HashAlgo,
|
|
55
|
+
canonical_bytes,
|
|
56
|
+
canonical_json,
|
|
57
|
+
content_hash,
|
|
58
|
+
derivation_hash,
|
|
59
|
+
hash_bytes,
|
|
60
|
+
hash_stream,
|
|
61
|
+
)
|
|
62
|
+
from metrik.core.ir import (
|
|
63
|
+
IR_VERSION,
|
|
64
|
+
AttentionKind,
|
|
65
|
+
Block,
|
|
66
|
+
Edge,
|
|
67
|
+
ExtractionInfo,
|
|
68
|
+
ExtractionMethod,
|
|
69
|
+
FfnKind,
|
|
70
|
+
Fidelity,
|
|
71
|
+
ModelGraph,
|
|
72
|
+
MoEInfo,
|
|
73
|
+
Node,
|
|
74
|
+
NodeKind,
|
|
75
|
+
NormKind,
|
|
76
|
+
PositionKind,
|
|
77
|
+
RoleTag,
|
|
78
|
+
RopeInfo,
|
|
79
|
+
TensorDecl,
|
|
80
|
+
TensorKind,
|
|
81
|
+
Topology,
|
|
82
|
+
op_node_id,
|
|
83
|
+
)
|
|
84
|
+
from metrik.core.lineage import Lineage, LineageEdge, lineage_of
|
|
85
|
+
from metrik.core.plugins import PluginManifest, PluginProblem, PluginRef, PluginRegistry
|
|
86
|
+
from metrik.core.runs import (
|
|
87
|
+
RUN_ARTIFACT_TYPE,
|
|
88
|
+
CacheEvent,
|
|
89
|
+
FailureInfo,
|
|
90
|
+
RunRecorder,
|
|
91
|
+
RunStatus,
|
|
92
|
+
Step,
|
|
93
|
+
StepStatus,
|
|
94
|
+
)
|
|
95
|
+
from metrik.core.store import ArtifactStore, BlobStore, DeterminismViolationWarning
|
|
96
|
+
from metrik.core.viz import (
|
|
97
|
+
APPROVED_COLOR_SCHEMES,
|
|
98
|
+
MAX_INLINE_POINTS,
|
|
99
|
+
VIZ_SPEC_VERSION,
|
|
100
|
+
A11yBlock,
|
|
101
|
+
ChannelType,
|
|
102
|
+
Downsample,
|
|
103
|
+
EncodingDef,
|
|
104
|
+
Fallbacks,
|
|
105
|
+
Interaction,
|
|
106
|
+
InteractionKind,
|
|
107
|
+
ProvenanceBlock,
|
|
108
|
+
RenderHints,
|
|
109
|
+
ScaleDef,
|
|
110
|
+
TableSpec,
|
|
111
|
+
TransformKind,
|
|
112
|
+
TransformStep,
|
|
113
|
+
ViewKind,
|
|
114
|
+
VizData,
|
|
115
|
+
VizSpec,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _distribution_version(name: str) -> str:
|
|
120
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
return version(name)
|
|
124
|
+
except PackageNotFoundError: # pragma: no cover - only in an uninstalled source tree
|
|
125
|
+
return "0+unknown"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
#: Read from the installed distribution rather than hardcoded.
|
|
129
|
+
#:
|
|
130
|
+
#: Both existed for a while and drifted: a 0.1.0 build reported 0.0.1 from `metrik --version`,
|
|
131
|
+
#: found by installing the actual wheel rather than by any test. A version string is exactly
|
|
132
|
+
#: the kind of duplicate that stays wrong quietly, because nothing imports it in anger.
|
|
133
|
+
#:
|
|
134
|
+
#: The fallback covers a source checkout with nothing installed, where there is no metadata to
|
|
135
|
+
#: read and no released version to name.
|
|
136
|
+
__version__ = _distribution_version("metrik-core")
|
|
137
|
+
|
|
138
|
+
__all__ = [
|
|
139
|
+
"APPROVED_COLOR_SCHEMES",
|
|
140
|
+
"IR_VERSION",
|
|
141
|
+
"MAX_INLINE_POINTS",
|
|
142
|
+
"MAX_PAYLOAD_BYTES",
|
|
143
|
+
"ROOT_ARTIFACT_TYPES",
|
|
144
|
+
"RUN_ARTIFACT_TYPE",
|
|
145
|
+
"VIZ_SPEC_VERSION",
|
|
146
|
+
"A11yBlock",
|
|
147
|
+
"Artifact",
|
|
148
|
+
"ArtifactStore",
|
|
149
|
+
"AttentionKind",
|
|
150
|
+
"BackendError",
|
|
151
|
+
"BaselineComparison",
|
|
152
|
+
"BenchResult",
|
|
153
|
+
"BlobRef",
|
|
154
|
+
"BlobStore",
|
|
155
|
+
"Block",
|
|
156
|
+
"CacheEvent",
|
|
157
|
+
"CancelledError",
|
|
158
|
+
"CanonicalizationError",
|
|
159
|
+
"ChannelType",
|
|
160
|
+
"ContaminationCheck",
|
|
161
|
+
"CostRecord",
|
|
162
|
+
"DeterminismClass",
|
|
163
|
+
"DeterminismViolationWarning",
|
|
164
|
+
"Digest",
|
|
165
|
+
"Downsample",
|
|
166
|
+
"Edge",
|
|
167
|
+
"EncodingDef",
|
|
168
|
+
"Envelope",
|
|
169
|
+
"ExtractionInfo",
|
|
170
|
+
"ExtractionMethod",
|
|
171
|
+
"FailureInfo",
|
|
172
|
+
"FailureKind",
|
|
173
|
+
"Fallbacks",
|
|
174
|
+
"FfnKind",
|
|
175
|
+
"Fidelity",
|
|
176
|
+
"GenerationSpec",
|
|
177
|
+
"HarnessName",
|
|
178
|
+
"HarnessRef",
|
|
179
|
+
"HashAlgo",
|
|
180
|
+
"InputRef",
|
|
181
|
+
"InsufficientResourcesError",
|
|
182
|
+
"Interaction",
|
|
183
|
+
"InteractionKind",
|
|
184
|
+
"Isolation",
|
|
185
|
+
"Lineage",
|
|
186
|
+
"LineageEdge",
|
|
187
|
+
"LineageError",
|
|
188
|
+
"Metric",
|
|
189
|
+
"MetrikError",
|
|
190
|
+
"MoEInfo",
|
|
191
|
+
"ModelGraph",
|
|
192
|
+
"Node",
|
|
193
|
+
"NodeKind",
|
|
194
|
+
"NormKind",
|
|
195
|
+
"Omission",
|
|
196
|
+
"PartialInfo",
|
|
197
|
+
"PluginManifest",
|
|
198
|
+
"PluginProblem",
|
|
199
|
+
"PluginRef",
|
|
200
|
+
"PluginRegistry",
|
|
201
|
+
"PositionKind",
|
|
202
|
+
"ProducerRef",
|
|
203
|
+
"Provenance",
|
|
204
|
+
"ProvenanceBlock",
|
|
205
|
+
"ReasonCode",
|
|
206
|
+
"RenderHints",
|
|
207
|
+
"RoleTag",
|
|
208
|
+
"RopeInfo",
|
|
209
|
+
"RunRecorder",
|
|
210
|
+
"RunStatus",
|
|
211
|
+
"SandboxAttestation",
|
|
212
|
+
"SandboxLimits",
|
|
213
|
+
"ScaleDef",
|
|
214
|
+
"Step",
|
|
215
|
+
"StepStatus",
|
|
216
|
+
"TableSpec",
|
|
217
|
+
"TaskRef",
|
|
218
|
+
"TensorDecl",
|
|
219
|
+
"TensorKind",
|
|
220
|
+
"Topology",
|
|
221
|
+
"TransformKind",
|
|
222
|
+
"TransformStep",
|
|
223
|
+
"UnsupportedError",
|
|
224
|
+
"UserError",
|
|
225
|
+
"ValidationError",
|
|
226
|
+
"ViewKind",
|
|
227
|
+
"VizData",
|
|
228
|
+
"VizSpec",
|
|
229
|
+
"__version__",
|
|
230
|
+
"build_artifact",
|
|
231
|
+
"canonical_bytes",
|
|
232
|
+
"canonical_json",
|
|
233
|
+
"content_hash",
|
|
234
|
+
"derivation_hash",
|
|
235
|
+
"hash_bytes",
|
|
236
|
+
"hash_stream",
|
|
237
|
+
"lineage_of",
|
|
238
|
+
"op_node_id",
|
|
239
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Artifact schemas.
|
|
2
|
+
|
|
3
|
+
The envelope lives here. Concrete artifact types (``ModelFingerprint``, ``ModelGraph``,
|
|
4
|
+
``ProfileRecord``, …) arrive alongside the producers that emit them; the catalogue is
|
|
5
|
+
specified in ``docs/planning/artifacts.md`` §6.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from metrik.core.artifacts.artifact import MAX_PAYLOAD_BYTES, Artifact, build_artifact
|
|
11
|
+
from metrik.core.artifacts.envelope import (
|
|
12
|
+
ROOT_ARTIFACT_TYPES,
|
|
13
|
+
BlobRef,
|
|
14
|
+
DeterminismClass,
|
|
15
|
+
Envelope,
|
|
16
|
+
InputRef,
|
|
17
|
+
Omission,
|
|
18
|
+
PartialInfo,
|
|
19
|
+
ProducerRef,
|
|
20
|
+
Provenance,
|
|
21
|
+
ReasonCode,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"MAX_PAYLOAD_BYTES",
|
|
26
|
+
"ROOT_ARTIFACT_TYPES",
|
|
27
|
+
"Artifact",
|
|
28
|
+
"BlobRef",
|
|
29
|
+
"DeterminismClass",
|
|
30
|
+
"Envelope",
|
|
31
|
+
"InputRef",
|
|
32
|
+
"Omission",
|
|
33
|
+
"PartialInfo",
|
|
34
|
+
"ProducerRef",
|
|
35
|
+
"Provenance",
|
|
36
|
+
"ReasonCode",
|
|
37
|
+
"build_artifact",
|
|
38
|
+
]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""An artifact: envelope plus payload.
|
|
2
|
+
|
|
3
|
+
The envelope is described in :mod:`metrik.core.artifacts.envelope`. This module is the pair,
|
|
4
|
+
plus the one constructor producers should use — :func:`build_artifact`, which computes
|
|
5
|
+
``content_hash`` from the payload rather than accepting it.
|
|
6
|
+
|
|
7
|
+
That is deliberate. A producer that can *pass in* a content hash can pass in a wrong one, and
|
|
8
|
+
a wrong content hash is undetectable by inspection and fatal to every citation that quotes it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from datetime import UTC, datetime
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
18
|
+
|
|
19
|
+
from metrik.core.artifacts.envelope import (
|
|
20
|
+
DeterminismClass,
|
|
21
|
+
Envelope,
|
|
22
|
+
InputRef,
|
|
23
|
+
PartialInfo,
|
|
24
|
+
ProducerRef,
|
|
25
|
+
)
|
|
26
|
+
from metrik.core.hashing import (
|
|
27
|
+
Digest,
|
|
28
|
+
canonical_bytes,
|
|
29
|
+
canonical_json,
|
|
30
|
+
content_hash,
|
|
31
|
+
derivation_hash,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = ["MAX_PAYLOAD_BYTES", "Artifact", "build_artifact"]
|
|
35
|
+
|
|
36
|
+
#: ``artifacts.md`` §4. A payload you cannot open in an editor is one nobody will review, and
|
|
37
|
+
#: unreviewed artifacts are how a schema quietly starts carrying per-tensor arrays that scale
|
|
38
|
+
#: with model size. Anything that scales goes to a blob and the payload keeps aggregates.
|
|
39
|
+
MAX_PAYLOAD_BYTES = 1024 * 1024
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Artifact(BaseModel):
|
|
43
|
+
"""A stored fact: what was computed, and everything needed to trace it."""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
46
|
+
|
|
47
|
+
envelope: Envelope
|
|
48
|
+
payload: dict[str, Any]
|
|
49
|
+
|
|
50
|
+
@field_validator("payload", mode="before")
|
|
51
|
+
@classmethod
|
|
52
|
+
def _canonicalize(cls, value: Any) -> Any:
|
|
53
|
+
"""Replace the payload with its canonical form at construction.
|
|
54
|
+
|
|
55
|
+
Without this, an artifact does not survive a round trip through its own store. Rule 6
|
|
56
|
+
omits null fields on write (``artifacts.md`` §3.1), so ``{"license_id": None}`` is
|
|
57
|
+
stored as ``{}`` and read back with the key *missing* -- and a consumer written against
|
|
58
|
+
the schema gets ``KeyError`` where it expected ``None``. The hashes agree either way,
|
|
59
|
+
which is exactly what makes the discrepancy easy to miss.
|
|
60
|
+
|
|
61
|
+
Normalizing here means ``artifact.payload`` is always precisely what is on disk.
|
|
62
|
+
Canonicalization is idempotent (asserted as a property test), so doing it early cannot
|
|
63
|
+
change any hash.
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
return json.loads(canonical_json(value))
|
|
67
|
+
return value
|
|
68
|
+
|
|
69
|
+
@model_validator(mode="after")
|
|
70
|
+
def _check_self_consistent(self) -> Artifact:
|
|
71
|
+
actual = content_hash(self.payload, algo=self.envelope.content_hash.algo)
|
|
72
|
+
if actual != self.envelope.content_hash:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"content_hash does not match payload: envelope claims "
|
|
75
|
+
f"{self.envelope.content_hash.short()}, payload hashes to {actual.short()}"
|
|
76
|
+
)
|
|
77
|
+
size = len(canonical_bytes(self.payload))
|
|
78
|
+
if size > MAX_PAYLOAD_BYTES:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"payload is {size} bytes, over the {MAX_PAYLOAD_BYTES}-byte limit "
|
|
81
|
+
f"(artifacts.md sec. 4). Move the field that scales with model size into a blob "
|
|
82
|
+
f"and keep aggregates in the payload."
|
|
83
|
+
)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def artifact_id(self) -> Digest:
|
|
88
|
+
"""The storage key: a digest over the envelope, **excluding ``created_at``**.
|
|
89
|
+
|
|
90
|
+
Not a third semantic hash. ``content_hash`` and ``derivation_hash`` answer the two
|
|
91
|
+
questions in ``artifacts.md`` §3; this one answers neither, it just names a file.
|
|
92
|
+
|
|
93
|
+
It has to exist because neither of the other two can serve as a primary key:
|
|
94
|
+
|
|
95
|
+
* ``content_hash`` cannot, because two producers can compute the same value by
|
|
96
|
+
different routes, and keying on it would silently discard the second one's
|
|
97
|
+
provenance — the exact information the spine exists to keep.
|
|
98
|
+
* ``derivation_hash`` cannot, because ``artifacts.md`` §3.3 requires the store to
|
|
99
|
+
hold two artifacts that share one and *differ* in ``content_hash``. That is how a
|
|
100
|
+
producer falsely claiming ``deterministic`` gets caught. Keying on it would
|
|
101
|
+
overwrite the evidence.
|
|
102
|
+
|
|
103
|
+
Git makes the same split for the same reason: a commit is not its tree.
|
|
104
|
+
|
|
105
|
+
Why ``created_at`` is excluded
|
|
106
|
+
------------------------------
|
|
107
|
+
|
|
108
|
+
It records when we *noticed* a fact, not the fact. Including it meant re-running a
|
|
109
|
+
deterministic producer wrote a brand-new record every time, identical in every respect
|
|
110
|
+
that matters. Two real costs: the store grew without bound on repeated runs, and --
|
|
111
|
+
because ``InputRef`` resolves by ``content_hash`` -- each duplicate added another
|
|
112
|
+
candidate parent, so lineage went ambiguous on the second run and N-way ambiguous
|
|
113
|
+
after N. ``metrik explore`` run twice already showed it.
|
|
114
|
+
|
|
115
|
+
Everything distinguishing two genuine facts is still in the key: producer, version,
|
|
116
|
+
derivation, content, ``env``, ``labels``, ``partial``. Only the clock is dropped.
|
|
117
|
+
"""
|
|
118
|
+
envelope = self.envelope.model_dump(exclude={"created_at"})
|
|
119
|
+
return content_hash(envelope, algo=self.envelope.content_hash.algo)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def build_artifact(
|
|
123
|
+
*,
|
|
124
|
+
artifact_type: str,
|
|
125
|
+
schema_version: int,
|
|
126
|
+
payload: dict[str, Any],
|
|
127
|
+
determinism: DeterminismClass,
|
|
128
|
+
produced_by: ProducerRef,
|
|
129
|
+
inputs: tuple[InputRef, ...] = (),
|
|
130
|
+
params: Any = None,
|
|
131
|
+
seed: int | None = None,
|
|
132
|
+
device_class: str | None = None,
|
|
133
|
+
env: dict[str, object] | None = None,
|
|
134
|
+
labels: dict[str, str] | None = None,
|
|
135
|
+
supersedes: Digest | None = None,
|
|
136
|
+
partial: PartialInfo | None = None,
|
|
137
|
+
created_at: datetime | None = None,
|
|
138
|
+
) -> Artifact:
|
|
139
|
+
"""Assemble an artifact, computing both hashes from the inputs given.
|
|
140
|
+
|
|
141
|
+
Neither hash is a parameter, so neither can be wrong. ``derivation_hash`` is built from
|
|
142
|
+
the same ``params``/``inputs``/``seed`` the producer actually used, which is what makes a
|
|
143
|
+
later cache probe on those values meaningful.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
params: The producer's typed parameter object. Untyped ``**kwargs`` have no canonical
|
|
147
|
+
form and therefore cannot be cached correctly (``sdk-contracts.md`` §4).
|
|
148
|
+
device_class: Pass only for producers declaring themselves device-sensitive.
|
|
149
|
+
Including it elsewhere fragments the cache for nothing.
|
|
150
|
+
"""
|
|
151
|
+
ch = content_hash(payload)
|
|
152
|
+
dh = derivation_hash(
|
|
153
|
+
artifact_type=artifact_type,
|
|
154
|
+
schema_version=schema_version,
|
|
155
|
+
producer_name=produced_by.name,
|
|
156
|
+
producer_version=produced_by.version,
|
|
157
|
+
api_version=produced_by.api_version,
|
|
158
|
+
params=params,
|
|
159
|
+
inputs=[(ref.role, ref.content_hash) for ref in inputs],
|
|
160
|
+
seed=seed,
|
|
161
|
+
device_class=device_class,
|
|
162
|
+
)
|
|
163
|
+
envelope = Envelope(
|
|
164
|
+
artifact_type=artifact_type,
|
|
165
|
+
schema_version=schema_version,
|
|
166
|
+
content_hash=ch,
|
|
167
|
+
derivation_hash=dh,
|
|
168
|
+
determinism=determinism,
|
|
169
|
+
created_at=created_at or datetime.now(UTC),
|
|
170
|
+
produced_by=produced_by,
|
|
171
|
+
inputs=inputs,
|
|
172
|
+
env=env,
|
|
173
|
+
labels=labels or {},
|
|
174
|
+
supersedes=supersedes,
|
|
175
|
+
partial=partial,
|
|
176
|
+
)
|
|
177
|
+
return Artifact(envelope=envelope, payload=payload)
|