embedflow 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.
- embedflow/__init__.py +25 -0
- embedflow/__main__.py +3 -0
- embedflow/analysis.py +192 -0
- embedflow/cache/__init__.py +4 -0
- embedflow/cache/base.py +28 -0
- embedflow/cache/persistent_cache.py +198 -0
- embedflow/cli.py +1200 -0
- embedflow/compatibility/__init__.py +28 -0
- embedflow/compatibility/candidate_gap.py +105 -0
- embedflow/compatibility/containment.py +17 -0
- embedflow/compatibility/evaluate.py +319 -0
- embedflow/compatibility/metrics.py +75 -0
- embedflow/compatibility/migration_depth.py +67 -0
- embedflow/compatibility/probe.py +34 -0
- embedflow/compatibility/report.py +102 -0
- embedflow/compatibility/t2.py +64 -0
- embedflow/config.py +455 -0
- embedflow/data/__init__.py +1 -0
- embedflow/data/registry/__init__.py +1 -0
- embedflow/data/registry/benchmark_profiles.jsonl +3 -0
- embedflow/data/registry/checksums.sha256 +4 -0
- embedflow/data/registry/migrations.jsonl +15 -0
- embedflow/data/registry/registry_manifest.json +16 -0
- embedflow/data/registry/research_summaries.json +55 -0
- embedflow/data/registry/schema_version.json +5 -0
- embedflow/frozen/T2_V1_FROZEN_SPEC.md +71 -0
- embedflow/frozen/T2_V1_FROZEN_SPEC.sha256 +1 -0
- embedflow/indexes/__init__.py +5 -0
- embedflow/indexes/base.py +60 -0
- embedflow/indexes/faiss_backend.py +240 -0
- embedflow/indexes/qdrant_backend.py +225 -0
- embedflow/metrics/__init__.py +3 -0
- embedflow/metrics/latency.py +50 -0
- embedflow/migration/__init__.py +3 -0
- embedflow/migration/compatibility.py +156 -0
- embedflow/migration/facade.py +312 -0
- embedflow/migration/materializer.py +190 -0
- embedflow/migration/planner.py +78 -0
- embedflow/migration/state.py +81 -0
- embedflow/models/__init__.py +4 -0
- embedflow/models/base.py +31 -0
- embedflow/models/huggingface.py +226 -0
- embedflow/registry/__init__.py +47 -0
- embedflow/registry/loader.py +785 -0
- embedflow/registry/matcher.py +197 -0
- embedflow/registry/schema.py +266 -0
- embedflow/runtime.py +115 -0
- embedflow/serving/__init__.py +3 -0
- embedflow/serving/api.py +161 -0
- embedflow/serving/engine.py +222 -0
- embedflow/serving/factory.py +3 -0
- embedflow/serving/schemas.py +39 -0
- embedflow-0.1.0.dist-info/METADATA +210 -0
- embedflow-0.1.0.dist-info/RECORD +64 -0
- embedflow-0.1.0.dist-info/WHEEL +5 -0
- embedflow-0.1.0.dist-info/entry_points.txt +2 -0
- embedflow-0.1.0.dist-info/licenses/LICENSE +178 -0
- embedflow-0.1.0.dist-info/top_level.txt +2 -0
- src/__init__.py +1 -0
- src/embed.py +123 -0
- src/probe_features.py +24 -0
- src/storage.py +51 -0
- src/t2_v1.py +21 -0
- src/utils.py +53 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .candidate_gap import CandidateGapCurve, observed_k_epsilon
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def write_curve_csv(path: str | Path, curve: Sequence[CandidateGapCurve], *, field: str = "candidate_gap") -> None:
|
|
13
|
+
path = Path(path)
|
|
14
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
rows = [point.to_dict() for point in curve]
|
|
16
|
+
with path.open("w", newline="") as handle:
|
|
17
|
+
writer = csv.DictWriter(handle, fieldnames=list(rows[0]) if rows else ["k", field])
|
|
18
|
+
writer.writeheader()
|
|
19
|
+
writer.writerows(rows)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def migration_report(
|
|
23
|
+
*,
|
|
24
|
+
source_model: str,
|
|
25
|
+
target_model: str,
|
|
26
|
+
corpus_size: int,
|
|
27
|
+
curve: Sequence[CandidateGapCurve],
|
|
28
|
+
diagnostic: str | None = None,
|
|
29
|
+
recommended_k: int | None = None,
|
|
30
|
+
ann_status: str = "UNKNOWN",
|
|
31
|
+
t2_warning: str = "T2-v1 is an empirical finite-tail diagnostic, not a compatibility guarantee.",
|
|
32
|
+
epsilon: float = 0.01,
|
|
33
|
+
native_target_index_used: bool = True,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
observed = observed_k_epsilon(curve, epsilon=epsilon) if native_target_index_used else None
|
|
36
|
+
if str(diagnostic or "").upper() == "SAFE":
|
|
37
|
+
recommendation = "Progressive migration is a reasonable candidate for further deployment validation."
|
|
38
|
+
elif str(diagnostic or "").upper() == "EXPAND":
|
|
39
|
+
recommendation = "Expand source retrieval depth and validate manually before deployment."
|
|
40
|
+
else:
|
|
41
|
+
recommendation = "Prefer manual validation, a larger probe, or a full target backfill before relying on progressive migration."
|
|
42
|
+
return {
|
|
43
|
+
"schema_version": "0.1",
|
|
44
|
+
"source_model": source_model,
|
|
45
|
+
"target_model": target_model,
|
|
46
|
+
"corpus_documents": int(corpus_size),
|
|
47
|
+
"diagnostic": diagnostic,
|
|
48
|
+
"recommended_initial_k": recommended_k,
|
|
49
|
+
"observed_k_epsilon": observed,
|
|
50
|
+
"epsilon": float(epsilon),
|
|
51
|
+
"ann_status": ann_status,
|
|
52
|
+
"recommendation": recommendation,
|
|
53
|
+
"native_target_index_used": bool(native_target_index_used),
|
|
54
|
+
"t2_warning": t2_warning,
|
|
55
|
+
"candidate_gap_curve": [point.to_dict() for point in curve],
|
|
56
|
+
"limitations": [
|
|
57
|
+
"Candidate gap and containment are separate metrics.",
|
|
58
|
+
"A SAFE T2-v1 diagnostic is empirical and is not a compatibility guarantee.",
|
|
59
|
+
"ANN fidelity is UNKNOWN unless an exact/reference source comparison was supplied.",
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def write_report(path: str | Path, report: Mapping[str, Any]) -> None:
|
|
65
|
+
path = Path(path)
|
|
66
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
path.write_text(json.dumps(dict(report), indent=2, ensure_ascii=False, default=float) + "\n")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def report_markdown(report: Mapping[str, Any]) -> str:
|
|
71
|
+
curve = list(report.get("candidate_gap_curve", []))
|
|
72
|
+
corpus_value = report.get("corpus_documents", "unknown")
|
|
73
|
+
try:
|
|
74
|
+
corpus_display = f"{int(corpus_value):,}"
|
|
75
|
+
except (TypeError, ValueError):
|
|
76
|
+
corpus_display = str(corpus_value)
|
|
77
|
+
lines = [
|
|
78
|
+
"# EmbedFlow Migration Report", "", "## Models", "",
|
|
79
|
+
f"- Source: `{report.get('source_model', 'unknown')}`",
|
|
80
|
+
f"- Target: `{report.get('target_model', 'unknown')}`",
|
|
81
|
+
f"- Corpus documents: `{corpus_display}`",
|
|
82
|
+
"", "## Decision", "",
|
|
83
|
+
f"- T2-v1 diagnostic: **{report.get('diagnostic') or 'not run'}**",
|
|
84
|
+
f"- Recommended initial candidate depth: **K={report.get('recommended_initial_k') or 'n/a'}**",
|
|
85
|
+
f"- Observed K*_epsilon: **{report.get('observed_k_epsilon') or 'not computed'}** (epsilon={report.get('epsilon', 0.01)})",
|
|
86
|
+
f"- Finite-tail behavior: **{report.get('finite_tail_behavior', 'not established')}**",
|
|
87
|
+
f"- ANN health: **{report.get('ann_status', 'UNKNOWN')}**",
|
|
88
|
+
f"- Recommendation: {report.get('recommendation', 'Further validation required.')}",
|
|
89
|
+
"", "> SAFE is an empirical finite-tail diagnostic, not a compatibility guarantee.",
|
|
90
|
+
"", "## Candidate gap curve", "", "| K | native target | target within source candidates | G(K) | containment |", "|---:|---:|---:|---:|---:|",
|
|
91
|
+
]
|
|
92
|
+
for row in curve:
|
|
93
|
+
lines.append(f"| {row.get('k')} | {float(row.get('native_target_quality', 0)):.4f} | {float(row.get('restricted_target_quality', 0)):.4f} | {float(row.get('candidate_gap', 0)):.4f} | {float(row.get('containment', 0)):.4f} |")
|
|
94
|
+
reused = report.get("registry_reused_values") or []
|
|
95
|
+
if reused:
|
|
96
|
+
lines.extend(["", "## Reused registry evidence", "", "> These are canonical prior measurements reused because the source/target contracts and corpus construction matched exactly. They are not a new-corpus probe result.", "", "| Evidence | Dataset | G(50) | Observed K* | CI-certified K* |", "|---|---|---:|---:|---:|"])
|
|
97
|
+
for item in reused:
|
|
98
|
+
gaps = item.get("candidate_gap") or {}
|
|
99
|
+
gap_50 = gaps.get("50", gaps.get(50, "unavailable"))
|
|
100
|
+
lines.append(f"| `{item.get('evidence_id', 'unknown')}` | {item.get('dataset', 'unknown')} | {float(gap_50):.5f} | {item.get('observed_migration_depth') or 'unavailable'} | {item.get('ci_certified_migration_depth') or 'unavailable'} |" if gap_50 != "unavailable" else f"| `{item.get('evidence_id', 'unknown')}` | {item.get('dataset', 'unknown')} | unavailable | {item.get('observed_migration_depth') or 'unavailable'} | {item.get('ci_certified_migration_depth') or 'unavailable'} |")
|
|
101
|
+
lines.extend(["", "## Limitations", "", *[f"- {item}" for item in report.get("limitations", [])], ""])
|
|
102
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class T2Diagnostic:
|
|
11
|
+
"""A frozen finite-tail diagnostic, never a compatibility guarantee."""
|
|
12
|
+
|
|
13
|
+
diagnostic: str
|
|
14
|
+
features: dict[str, float]
|
|
15
|
+
implementation: str
|
|
16
|
+
warning: str = "T2-v1 is an empirical finite-tail diagnostic, not a compatibility guarantee."
|
|
17
|
+
|
|
18
|
+
def to_dict(self) -> dict[str, Any]:
|
|
19
|
+
return {
|
|
20
|
+
"diagnostic": self.diagnostic,
|
|
21
|
+
"features": self.features,
|
|
22
|
+
"implementation": self.implementation,
|
|
23
|
+
"warning": self.warning,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def diagnose_t2(features: Mapping[str, Any], root: str | Path | None = None) -> T2Diagnostic:
|
|
28
|
+
"""Run the repository's frozen T2-v1 rule without reimplementing it."""
|
|
29
|
+
from src.t2_v1 import decide, verify_t2_hash
|
|
30
|
+
|
|
31
|
+
if not isinstance(features, Mapping):
|
|
32
|
+
raise TypeError("T2-v1 features must be a mapping")
|
|
33
|
+
forbidden = ("qrel", "native", "ndcg", "candidate_gap", "g(", "target_rank", "label", "observed_k")
|
|
34
|
+
leaked = [str(key) for key in features if any(token in str(key).lower() for token in forbidden)]
|
|
35
|
+
if leaked:
|
|
36
|
+
raise ValueError(f"T2-v1 refuses label/native-target data in features: {leaked}")
|
|
37
|
+
required = {
|
|
38
|
+
"probe_residual_tail_50_mean", "deepest_p90", "late_tail_area",
|
|
39
|
+
"stability_to_500_50_mean", "last_shell_any_rate", "fraction_margin_nonpositive",
|
|
40
|
+
}
|
|
41
|
+
missing = sorted(required.difference(features))
|
|
42
|
+
if missing:
|
|
43
|
+
raise ValueError(f"T2-v1 feature row is missing required fields: {missing}")
|
|
44
|
+
|
|
45
|
+
if root is None:
|
|
46
|
+
root = Path(__file__).resolve().parents[2]
|
|
47
|
+
if not (Path(root) / "frozen").exists():
|
|
48
|
+
root = Path(__file__).resolve().parents[1]
|
|
49
|
+
root = Path(root)
|
|
50
|
+
frozen = root / "frozen" / "T2_V1_FROZEN_SPEC.md"
|
|
51
|
+
if frozen.exists():
|
|
52
|
+
verify_t2_hash(root)
|
|
53
|
+
scalar = {}
|
|
54
|
+
for key, value in features.items():
|
|
55
|
+
if isinstance(value, bool):
|
|
56
|
+
raise ValueError(f"T2-v1 feature {key!r} must be numeric, not boolean")
|
|
57
|
+
try:
|
|
58
|
+
scalar[str(key)] = float(value)
|
|
59
|
+
except (TypeError, ValueError) as exc:
|
|
60
|
+
raise ValueError(f"T2-v1 feature {key!r} must be numeric") from exc
|
|
61
|
+
import math
|
|
62
|
+
if not all(math.isfinite(value) for value in scalar.values()):
|
|
63
|
+
raise ValueError("T2-v1 features must be finite")
|
|
64
|
+
return T2Diagnostic(decide(scalar), scalar, "src.t2_v1.decide")
|
embedflow/config.py
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import asdict, dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
_UNSET = object()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _integer(value: Any, label: str, *, minimum: int | None = None) -> int:
|
|
16
|
+
"""Validate integer-valued YAML fields without lossy coercion."""
|
|
17
|
+
if isinstance(value, bool):
|
|
18
|
+
raise ValueError(f"{label} must be an integer")
|
|
19
|
+
try:
|
|
20
|
+
parsed = int(value)
|
|
21
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
22
|
+
raise ValueError(f"{label} must be an integer") from exc
|
|
23
|
+
try:
|
|
24
|
+
exact = float(value) == parsed
|
|
25
|
+
except (TypeError, ValueError, OverflowError):
|
|
26
|
+
exact = False
|
|
27
|
+
if not exact or (minimum is not None and parsed < minimum):
|
|
28
|
+
qualifier = f" >= {minimum}" if minimum is not None else ""
|
|
29
|
+
raise ValueError(f"{label} must be an integer{qualifier}")
|
|
30
|
+
return parsed
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(init=False)
|
|
34
|
+
class ModelConfig:
|
|
35
|
+
model: str
|
|
36
|
+
revision: str | None = None
|
|
37
|
+
dimension: int | None = None
|
|
38
|
+
max_length: int = 512
|
|
39
|
+
pooling: str = "mean_tokens"
|
|
40
|
+
padding_side: str = "right"
|
|
41
|
+
truncation_side: str = "right"
|
|
42
|
+
query_instruction: str = ""
|
|
43
|
+
document_instruction: str = ""
|
|
44
|
+
normalization: str = "l2"
|
|
45
|
+
dtype: str = "float32"
|
|
46
|
+
local_path: str | None = None
|
|
47
|
+
device: str | None = None
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
model: str,
|
|
52
|
+
revision: str | None | object = _UNSET,
|
|
53
|
+
dimension: int | None | object = _UNSET,
|
|
54
|
+
max_length: int | object = _UNSET,
|
|
55
|
+
pooling: str | object = _UNSET,
|
|
56
|
+
padding_side: str | object = _UNSET,
|
|
57
|
+
truncation_side: str | object = _UNSET,
|
|
58
|
+
query_instruction: str | object = _UNSET,
|
|
59
|
+
document_instruction: str | object = _UNSET,
|
|
60
|
+
normalization: str | object = _UNSET,
|
|
61
|
+
dtype: str | object = _UNSET,
|
|
62
|
+
local_path: str | None | object = _UNSET,
|
|
63
|
+
device: str | None | object = _UNSET,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Construct a model contract while remembering explicit overrides.
|
|
66
|
+
|
|
67
|
+
The research contracts have non-generic defaults (for example,
|
|
68
|
+
Qwen3-8B uses length 8192 while the product's generic default is
|
|
69
|
+
512). A normal dataclass cannot distinguish an omitted value from a
|
|
70
|
+
caller explicitly requesting that generic default. The sentinel
|
|
71
|
+
arguments let ``hydrate_research_contract`` fill only omitted fields,
|
|
72
|
+
preserving deliberate prompt/pooling/max-length changes for matching
|
|
73
|
+
and cache safety.
|
|
74
|
+
"""
|
|
75
|
+
defaults = {
|
|
76
|
+
"revision": None,
|
|
77
|
+
"dimension": None,
|
|
78
|
+
"max_length": 512,
|
|
79
|
+
"pooling": "mean_tokens",
|
|
80
|
+
"padding_side": "right",
|
|
81
|
+
"truncation_side": "right",
|
|
82
|
+
"query_instruction": "",
|
|
83
|
+
"document_instruction": "",
|
|
84
|
+
"normalization": "l2",
|
|
85
|
+
"dtype": "float32",
|
|
86
|
+
"local_path": None,
|
|
87
|
+
"device": None,
|
|
88
|
+
}
|
|
89
|
+
values = {
|
|
90
|
+
"revision": revision,
|
|
91
|
+
"dimension": dimension,
|
|
92
|
+
"max_length": max_length,
|
|
93
|
+
"pooling": pooling,
|
|
94
|
+
"padding_side": padding_side,
|
|
95
|
+
"truncation_side": truncation_side,
|
|
96
|
+
"query_instruction": query_instruction,
|
|
97
|
+
"document_instruction": document_instruction,
|
|
98
|
+
"normalization": normalization,
|
|
99
|
+
"dtype": dtype,
|
|
100
|
+
"local_path": local_path,
|
|
101
|
+
"device": device,
|
|
102
|
+
}
|
|
103
|
+
self.model = str(model)
|
|
104
|
+
self._explicit_fields = {key for key, value in values.items() if value is not _UNSET}
|
|
105
|
+
for key, value in values.items():
|
|
106
|
+
setattr(self, key, defaults[key] if value is _UNSET else value)
|
|
107
|
+
|
|
108
|
+
def contract(self) -> dict[str, Any]:
|
|
109
|
+
return asdict(self)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def fingerprint(self) -> str:
|
|
113
|
+
# Paths and execution devices are deployment details, not embedding
|
|
114
|
+
# semantics. Excluding them keeps a cache/index portable across
|
|
115
|
+
# machines while retaining model revision, prompts, pooling, padding,
|
|
116
|
+
# truncation, normalization, dtype, and dimension in the contract.
|
|
117
|
+
semantic = {key: value for key, value in self.contract().items() if key not in {"local_path", "device"}}
|
|
118
|
+
payload = json.dumps(semantic, sort_keys=True, separators=(",", ":"))
|
|
119
|
+
return hashlib.sha256(payload.encode()).hexdigest()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class IndexConfig:
|
|
124
|
+
backend: str = "faiss"
|
|
125
|
+
path: str = "./legacy.index"
|
|
126
|
+
metric: str = "cosine"
|
|
127
|
+
nprobe: int = 64
|
|
128
|
+
collection: str = "embedflow"
|
|
129
|
+
url: str | None = None
|
|
130
|
+
ids: str | None = None
|
|
131
|
+
vector_name: str | None = None
|
|
132
|
+
api_key_env: str | None = "QDRANT_API_KEY"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class DocumentsConfig:
|
|
137
|
+
path: str = "./documents.jsonl"
|
|
138
|
+
id_field: str = "id"
|
|
139
|
+
text_field: str = "text"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class MigrationConfig:
|
|
144
|
+
candidate_depth: int | str = 50
|
|
145
|
+
kmax_probe: int = 500
|
|
146
|
+
probe_queries: int = 100
|
|
147
|
+
max_sync_misses: int = 4
|
|
148
|
+
background_batch_size: int = 32
|
|
149
|
+
max_retries: int = 3
|
|
150
|
+
worker_count: int = 1
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class CacheConfig:
|
|
155
|
+
path: str = "./embedflow_cache"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class EconomicsConfig:
|
|
160
|
+
gpu_price_per_hour: float | None = None
|
|
161
|
+
target_docs_per_second: float | None = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass
|
|
165
|
+
class ProbeConfig:
|
|
166
|
+
"""Settings for the leakage-safe finite-pool compatibility probe."""
|
|
167
|
+
|
|
168
|
+
queries: str | None = None
|
|
169
|
+
k_values: list[int] = field(default_factory=lambda: [10, 20, 50, 100, 200, 500])
|
|
170
|
+
kmax: int = 500
|
|
171
|
+
epsilon: float = 0.01
|
|
172
|
+
seed: int = 42
|
|
173
|
+
limit: int | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class TelemetryConfig:
|
|
178
|
+
latency_log: str = "./logs/latency.jsonl"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class EmbedFlowConfig:
|
|
183
|
+
source: ModelConfig
|
|
184
|
+
target: ModelConfig
|
|
185
|
+
index: IndexConfig = field(default_factory=IndexConfig)
|
|
186
|
+
documents: DocumentsConfig = field(default_factory=DocumentsConfig)
|
|
187
|
+
migration: MigrationConfig = field(default_factory=MigrationConfig)
|
|
188
|
+
cache: CacheConfig = field(default_factory=CacheConfig)
|
|
189
|
+
economics: EconomicsConfig = field(default_factory=EconomicsConfig)
|
|
190
|
+
probe: ProbeConfig = field(default_factory=ProbeConfig)
|
|
191
|
+
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
|
192
|
+
state_path: str = "./embedflow_state.json"
|
|
193
|
+
dashboard_title: str = "EmbedFlow — Progressive Embedding Migration"
|
|
194
|
+
|
|
195
|
+
def resolve_paths(self, base: Path) -> EmbedFlowConfig:
|
|
196
|
+
"""Resolve relative paths against the configuration file directory."""
|
|
197
|
+
for obj, attr in ((self.index, "path"), (self.documents, "path"),
|
|
198
|
+
(self.cache, "path"), (self, "state_path"),
|
|
199
|
+
(self.telemetry, "latency_log")):
|
|
200
|
+
raw_value = str(getattr(obj, attr))
|
|
201
|
+
# A Qdrant URL is a connection endpoint, not a filesystem path.
|
|
202
|
+
# Leave it untouched so ``index: {backend: qdrant, path: https://…}``
|
|
203
|
+
# works even when no separate ``url`` field is supplied.
|
|
204
|
+
if "://" in raw_value:
|
|
205
|
+
continue
|
|
206
|
+
value = Path(raw_value)
|
|
207
|
+
if not value.is_absolute():
|
|
208
|
+
setattr(obj, attr, str((base / value).resolve()))
|
|
209
|
+
if self.source.local_path:
|
|
210
|
+
p = Path(self.source.local_path)
|
|
211
|
+
self.source.local_path = str((base / p).resolve()) if not p.is_absolute() else str(p)
|
|
212
|
+
if self.target.local_path:
|
|
213
|
+
p = Path(self.target.local_path)
|
|
214
|
+
self.target.local_path = str((base / p).resolve()) if not p.is_absolute() else str(p)
|
|
215
|
+
if self.probe.queries:
|
|
216
|
+
p = Path(self.probe.queries)
|
|
217
|
+
self.probe.queries = str((base / p).resolve()) if not p.is_absolute() else str(p)
|
|
218
|
+
if self.index.ids:
|
|
219
|
+
p = Path(self.index.ids)
|
|
220
|
+
self.index.ids = str((base / p).resolve()) if not p.is_absolute() else str(p)
|
|
221
|
+
return self
|
|
222
|
+
|
|
223
|
+
def to_dict(self) -> dict[str, Any]:
|
|
224
|
+
return asdict(self)
|
|
225
|
+
|
|
226
|
+
def validate(self) -> None:
|
|
227
|
+
for label, model in (("source", self.source), ("target", self.target)):
|
|
228
|
+
if not model.model.strip():
|
|
229
|
+
raise ValueError(f"{label}.model is required")
|
|
230
|
+
if model.dimension is not None:
|
|
231
|
+
model.dimension = _integer(model.dimension, f"{label}.dimension", minimum=1)
|
|
232
|
+
model.max_length = _integer(model.max_length, f"{label}.max_length", minimum=1)
|
|
233
|
+
if not isinstance(model.normalization, str) or model.normalization.lower() not in {"l2", "none", "identity"}:
|
|
234
|
+
raise ValueError(f"{label}.normalization must be l2, none, or identity")
|
|
235
|
+
if self.source.fingerprint == self.target.fingerprint:
|
|
236
|
+
raise ValueError("source and target embedding contracts must differ for migration")
|
|
237
|
+
if not isinstance(self.index.backend, str) or self.index.backend.lower() not in {"faiss", "qdrant"}:
|
|
238
|
+
raise ValueError("index.backend must be faiss or qdrant")
|
|
239
|
+
if not isinstance(self.index.metric, str) or self.index.metric.lower() not in {"cosine", "dot", "inner_product"}:
|
|
240
|
+
raise ValueError("index.metric must be cosine, dot, or inner_product")
|
|
241
|
+
candidate_depth = self.migration.candidate_depth
|
|
242
|
+
if isinstance(candidate_depth, str) and candidate_depth.strip().lower() == "auto":
|
|
243
|
+
candidate_depth = "auto"
|
|
244
|
+
self.migration.candidate_depth = "auto"
|
|
245
|
+
if candidate_depth != "auto":
|
|
246
|
+
candidate_depth = _integer(candidate_depth, "migration.candidate_depth", minimum=1)
|
|
247
|
+
self.migration.candidate_depth = candidate_depth
|
|
248
|
+
self.migration.kmax_probe = _integer(self.migration.kmax_probe, "migration.kmax_probe", minimum=1)
|
|
249
|
+
self.migration.probe_queries = _integer(self.migration.probe_queries, "migration.probe_queries", minimum=1)
|
|
250
|
+
if candidate_depth != "auto" and self.migration.kmax_probe < int(candidate_depth):
|
|
251
|
+
raise ValueError("migration.kmax_probe must be >= candidate_depth")
|
|
252
|
+
self.migration.max_sync_misses = _integer(self.migration.max_sync_misses, "migration.max_sync_misses", minimum=0)
|
|
253
|
+
self.migration.background_batch_size = _integer(self.migration.background_batch_size, "migration.background_batch_size", minimum=1)
|
|
254
|
+
self.migration.max_retries = _integer(self.migration.max_retries, "migration.max_retries", minimum=1)
|
|
255
|
+
self.migration.worker_count = _integer(self.migration.worker_count, "migration.worker_count", minimum=1)
|
|
256
|
+
self.index.nprobe = _integer(self.index.nprobe, "index.nprobe", minimum=1)
|
|
257
|
+
self.probe.kmax = _integer(self.probe.kmax, "probe.kmax", minimum=10)
|
|
258
|
+
if self.probe.kmax < 10:
|
|
259
|
+
raise ValueError("probe.kmax must be at least 10")
|
|
260
|
+
if not self.probe.k_values:
|
|
261
|
+
raise ValueError("probe.k_values must contain positive integers")
|
|
262
|
+
try:
|
|
263
|
+
self.probe.k_values = [_integer(k, "probe.k_values item", minimum=1) for k in self.probe.k_values]
|
|
264
|
+
except ValueError as exc:
|
|
265
|
+
raise ValueError("probe.k_values must contain positive integers") from exc
|
|
266
|
+
if len(set(self.probe.k_values)) != len(self.probe.k_values):
|
|
267
|
+
raise ValueError("probe.k_values must not contain duplicates")
|
|
268
|
+
if any(k > int(self.probe.kmax) for k in self.probe.k_values):
|
|
269
|
+
raise ValueError("probe.k_values cannot exceed probe.kmax")
|
|
270
|
+
self.probe.epsilon = float(self.probe.epsilon)
|
|
271
|
+
if not math.isfinite(self.probe.epsilon) or self.probe.epsilon < 0:
|
|
272
|
+
raise ValueError("probe.epsilon must be finite and non-negative")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _model(raw: dict[str, Any], fallback: str) -> ModelConfig:
|
|
276
|
+
raw = dict(raw or {})
|
|
277
|
+
model = raw.pop("model", raw.pop("model_id", fallback))
|
|
278
|
+
# Accept the research project's names and hydrate their exact contracts.
|
|
279
|
+
return ModelConfig(model=str(model), **raw)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def from_dict(raw: dict[str, Any]) -> EmbedFlowConfig:
|
|
283
|
+
if not isinstance(raw, Mapping):
|
|
284
|
+
raise ValueError("configuration root must be a YAML object")
|
|
285
|
+
raw = dict(raw or {})
|
|
286
|
+
source = hydrate_research_contract(_model(raw.get("source", {}), ""))
|
|
287
|
+
target = hydrate_research_contract(_model(raw.get("target", {}), ""))
|
|
288
|
+
migration_raw = dict(raw.get("migration", {}))
|
|
289
|
+
if isinstance(migration_raw.get("candidate_depth"), str) and migration_raw["candidate_depth"].strip().lower() == "auto":
|
|
290
|
+
migration_raw["candidate_depth"] = "auto"
|
|
291
|
+
cfg = EmbedFlowConfig(
|
|
292
|
+
source=source,
|
|
293
|
+
target=target,
|
|
294
|
+
index=IndexConfig(**dict(raw.get("index", {}))),
|
|
295
|
+
documents=DocumentsConfig(**dict(raw.get("documents", {}))),
|
|
296
|
+
migration=MigrationConfig(**migration_raw),
|
|
297
|
+
cache=CacheConfig(**dict(raw.get("cache", {}))),
|
|
298
|
+
economics=EconomicsConfig(**dict(raw.get("economics", {}))),
|
|
299
|
+
probe=ProbeConfig(**dict(raw.get("probe", {}))),
|
|
300
|
+
telemetry=TelemetryConfig(**dict(raw.get("telemetry", {}))),
|
|
301
|
+
state_path=str(raw.get("state_path", "./embedflow_state.json")),
|
|
302
|
+
dashboard_title=str(raw.get("dashboard_title", EmbedFlowConfig.__dataclass_fields__["dashboard_title"].default)),
|
|
303
|
+
)
|
|
304
|
+
cfg.validate()
|
|
305
|
+
return cfg
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def hydrate_research_contract(model: ModelConfig, project_root: Path | None = None) -> ModelConfig:
|
|
309
|
+
"""Use the frozen research contract for one of its registered model keys."""
|
|
310
|
+
# Keep the public package self-contained. The research checkout also has
|
|
311
|
+
# these contracts in ``config/nq_5090_1m.yaml``; the built-ins ensure that
|
|
312
|
+
# installing EmbedFlow from a clean Git clone still preserves the exact
|
|
313
|
+
# pooling, prompt, padding, and revision semantics for known checkpoints.
|
|
314
|
+
builtin = {
|
|
315
|
+
"sentence-transformers/all-MiniLM-L6-v2": {
|
|
316
|
+
"revision": "1110a243fdf4706b3f48f1d95db1a4f5529b4d41",
|
|
317
|
+
"dimension": 384, "max_length": 512, "pooling": "mean_tokens",
|
|
318
|
+
"padding_side": "right", "truncation_side": "right",
|
|
319
|
+
"query_instruction": "", "document_instruction": "", "normalization": "l2",
|
|
320
|
+
},
|
|
321
|
+
"Qwen/Qwen3-Embedding-0.6B": {
|
|
322
|
+
"revision": "97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3",
|
|
323
|
+
"dimension": 1024, "max_length": 512, "pooling": "last_non_padding_token",
|
|
324
|
+
"padding_side": "left", "truncation_side": "right",
|
|
325
|
+
"query_instruction": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery: {text}",
|
|
326
|
+
"document_instruction": "", "normalization": "l2",
|
|
327
|
+
},
|
|
328
|
+
"Qwen/Qwen3-Embedding-4B": {
|
|
329
|
+
"revision": "5cf2132abc99cad020ac570b19d031efec650f2b",
|
|
330
|
+
"dimension": 2560, "max_length": 8192, "pooling": "last_non_padding_token",
|
|
331
|
+
"padding_side": "left", "truncation_side": "right",
|
|
332
|
+
"query_instruction": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:{text}",
|
|
333
|
+
"document_instruction": "", "normalization": "l2",
|
|
334
|
+
},
|
|
335
|
+
"Qwen/Qwen3-Embedding-8B": {
|
|
336
|
+
"revision": "1d8ad4ca9b3dd8059ad90a75d4983776a23d44af",
|
|
337
|
+
"dimension": 4096, "max_length": 8192, "pooling": "last_non_padding_token",
|
|
338
|
+
"padding_side": "left", "truncation_side": "right",
|
|
339
|
+
"query_instruction": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:{text}",
|
|
340
|
+
"document_instruction": "", "normalization": "l2",
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
if model.model in builtin:
|
|
344
|
+
for field_name, value in builtin[model.model].items():
|
|
345
|
+
# An explicit user value wins, while dataclass defaults are filled
|
|
346
|
+
# from the frozen contract. ``revision``/dimension are especially
|
|
347
|
+
# important for cache and index safety.
|
|
348
|
+
current = getattr(model, field_name)
|
|
349
|
+
default = ModelConfig.__dataclass_fields__[field_name].default
|
|
350
|
+
explicit = field_name in getattr(model, "_explicit_fields", set())
|
|
351
|
+
if not explicit and (current is None or current == default or field_name in {"query_instruction", "document_instruction"} and not current):
|
|
352
|
+
setattr(model, field_name, value)
|
|
353
|
+
try:
|
|
354
|
+
import yaml
|
|
355
|
+
root = project_root or Path(__file__).resolve().parents[1]
|
|
356
|
+
config_path = root / "config" / "nq_5090_1m.yaml"
|
|
357
|
+
if config_path.exists():
|
|
358
|
+
raw = yaml.safe_load(config_path.read_text()) or {}
|
|
359
|
+
for key, item in (raw.get("models") or {}).items():
|
|
360
|
+
if item.get("model_id") == model.model or key == model.model:
|
|
361
|
+
known = dict(item)
|
|
362
|
+
known.pop("min_vram_gib", None)
|
|
363
|
+
known.pop("dtype_preference", None)
|
|
364
|
+
known["model"] = known.pop("model_id", model.model)
|
|
365
|
+
# The research YAML calls Qwen pooling `last_token`; the
|
|
366
|
+
# product encoder accepts that as last_non_padding_token.
|
|
367
|
+
if known.get("pooling") == "last_token":
|
|
368
|
+
known["pooling"] = "last_non_padding_token"
|
|
369
|
+
for field_name in ModelConfig.__dataclass_fields__:
|
|
370
|
+
if field_name in known and field_name not in getattr(model, "_explicit_fields", set()):
|
|
371
|
+
setattr(model, field_name, known[field_name])
|
|
372
|
+
return model
|
|
373
|
+
except Exception:
|
|
374
|
+
# Generic Hugging Face models remain usable when the research config is
|
|
375
|
+
# not present. The caller can still inspect the resulting fingerprint.
|
|
376
|
+
pass
|
|
377
|
+
return model
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def load_config(path: str | Path) -> EmbedFlowConfig:
|
|
381
|
+
path = Path(path)
|
|
382
|
+
if not path.exists():
|
|
383
|
+
raise FileNotFoundError(path)
|
|
384
|
+
import yaml
|
|
385
|
+
try:
|
|
386
|
+
parsed = yaml.safe_load(path.read_text())
|
|
387
|
+
except yaml.YAMLError as exc:
|
|
388
|
+
raise ValueError(f"invalid YAML configuration: {path}: {exc}") from exc
|
|
389
|
+
cfg = from_dict(parsed or {})
|
|
390
|
+
_apply_environment_overrides(cfg)
|
|
391
|
+
cfg.validate()
|
|
392
|
+
return cfg.resolve_paths(path.parent.resolve())
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _apply_environment_overrides(cfg: EmbedFlowConfig) -> None:
|
|
396
|
+
"""Apply deliberately small, documented deployment overrides.
|
|
397
|
+
|
|
398
|
+
YAML remains the source of truth for experiments. These variables are
|
|
399
|
+
useful for containerized serving and secrets-free deployment manifests;
|
|
400
|
+
unset variables leave the file untouched.
|
|
401
|
+
"""
|
|
402
|
+
paths: dict[str, tuple[Any, str]] = {
|
|
403
|
+
"EMBEDFLOW_SOURCE_MODEL": (cfg.source, "model"),
|
|
404
|
+
"EMBEDFLOW_TARGET_MODEL": (cfg.target, "model"),
|
|
405
|
+
"EMBEDFLOW_SOURCE_DEVICE": (cfg.source, "device"),
|
|
406
|
+
"EMBEDFLOW_TARGET_DEVICE": (cfg.target, "device"),
|
|
407
|
+
"EMBEDFLOW_INDEX_BACKEND": (cfg.index, "backend"),
|
|
408
|
+
"EMBEDFLOW_INDEX_PATH": (cfg.index, "path"),
|
|
409
|
+
"EMBEDFLOW_INDEX_URL": (cfg.index, "url"),
|
|
410
|
+
"EMBEDFLOW_INDEX_COLLECTION": (cfg.index, "collection"),
|
|
411
|
+
"EMBEDFLOW_INDEX_VECTOR_NAME": (cfg.index, "vector_name"),
|
|
412
|
+
"EMBEDFLOW_QDRANT_API_KEY_ENV": (cfg.index, "api_key_env"),
|
|
413
|
+
"EMBEDFLOW_DOCUMENTS_PATH": (cfg.documents, "path"),
|
|
414
|
+
"EMBEDFLOW_CACHE_PATH": (cfg.cache, "path"),
|
|
415
|
+
"EMBEDFLOW_STATE_PATH": (cfg, "state_path"),
|
|
416
|
+
"EMBEDFLOW_LATENCY_LOG": (cfg.telemetry, "latency_log"),
|
|
417
|
+
}
|
|
418
|
+
for variable, (target, field_name) in paths.items():
|
|
419
|
+
value = os.environ.get(variable)
|
|
420
|
+
if value is not None and value.strip():
|
|
421
|
+
setattr(target, field_name, value.strip())
|
|
422
|
+
candidate = os.environ.get("EMBEDFLOW_CANDIDATE_DEPTH")
|
|
423
|
+
if candidate:
|
|
424
|
+
if candidate.strip().lower() == "auto":
|
|
425
|
+
cfg.migration.candidate_depth = "auto"
|
|
426
|
+
else:
|
|
427
|
+
try:
|
|
428
|
+
parsed = int(candidate)
|
|
429
|
+
if float(candidate) != parsed:
|
|
430
|
+
raise ValueError
|
|
431
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
432
|
+
raise ValueError("EMBEDFLOW_CANDIDATE_DEPTH must be auto or an integer") from exc
|
|
433
|
+
cfg.migration.candidate_depth = parsed
|
|
434
|
+
for variable, target, field_name in (
|
|
435
|
+
("EMBEDFLOW_MAX_SYNC_MISSES", cfg.migration, "max_sync_misses"),
|
|
436
|
+
("EMBEDFLOW_BACKGROUND_BATCH_SIZE", cfg.migration, "background_batch_size"),
|
|
437
|
+
("EMBEDFLOW_PROBE_KMAX", cfg.probe, "kmax"),
|
|
438
|
+
("EMBEDFLOW_INDEX_NPROBE", cfg.index, "nprobe"),
|
|
439
|
+
):
|
|
440
|
+
value = os.environ.get(variable)
|
|
441
|
+
if value is not None and value.strip():
|
|
442
|
+
try:
|
|
443
|
+
parsed = int(value)
|
|
444
|
+
if float(value) != parsed:
|
|
445
|
+
raise ValueError
|
|
446
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
447
|
+
raise ValueError(f"{variable} must be an integer") from exc
|
|
448
|
+
setattr(target, field_name, parsed)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def save_config(cfg: EmbedFlowConfig, path: str | Path) -> None:
|
|
452
|
+
import yaml
|
|
453
|
+
path = Path(path)
|
|
454
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
455
|
+
path.write_text(yaml.safe_dump(cfg.to_dict(), sort_keys=False))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged, small data assets for EmbedFlow."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core migration evidence shipped with EmbedFlow."""
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"registry_version":"0.1.0","profile_id":"latency_25k_qwen3_4b_to_8b_k50_nprobe64_warm","kind":"serving_latency","status":"measured_workload_specific","scope":"25K-document serving benchmark","source":{"canonical_model_id":"Qwen/Qwen3-Embedding-4B","contract_fingerprint":"0607b87cc200f80bfc498e595891fb533f42677355b18279be42d7d988896c44"},"target":{"canonical_model_id":"Qwen/Qwen3-Embedding-8B","contract_fingerprint":"11d6d071a07a73b0bb3588a49447bb8bf58cb5426af732b534d374581ac4b018"},"dataset":{"name":"Natural Questions canonical 25K prefix","corpus_size":25000,"query_count":1000,"split":"latency_25k","fingerprint":null},"configuration":{"K":50,"nprobe":64,"target_nprobe":64,"cache_state":"warm","index_type":"ivf_flat"},"measurements":{"p50_ms":38.792666,"p95_ms":42.68700545,"added_vs_native_p50_ms":19.8086975,"added_vs_native_p95_ms":21.12569415,"mean_stage_ms":{"source_query_encode":19.48547872,"source_ann":0.401102655,"target_query_encode":18.742604425,"candidate_cache_lookup":0.213127613,"target_score":0.061910947,"topk":0.06689177}},"hardware":{"gpu":"unspecified in retained CSV","peak_gpu_memory_mb":37917.7666015625},"provenance":{"artifact":"remote_scale_experiment/experiments/latency/results_25k/latency_summary.csv","artifact_sha256":"13136c9efa90a569d8a771237a3f8536934ea94111a05701872a7c0ba3674416","table":"latency_summary.csv","status":"retained_external","git_commit":null},"notes":["Measured on a 25K-document workload; the retained CSV does not identify the GPU, so this row must not be labeled an H100 benchmark.","Latency is hardware- and workload-dependent; it is not a million-document latency claim."]}
|
|
2
|
+
{"registry_version":"0.1.0","profile_id":"latency_25k_native_qwen3_8b","kind":"serving_latency","status":"measured_workload_specific","scope":"25K-document serving benchmark","source":{"canonical_model_id":"native_target","contract_fingerprint":null},"target":{"canonical_model_id":"Qwen/Qwen3-Embedding-8B","contract_fingerprint":"11d6d071a07a73b0bb3588a49447bb8bf58cb5426af732b534d374581ac4b018"},"dataset":{"name":"Natural Questions canonical 25K prefix","corpus_size":25000,"query_count":1000,"split":"latency_25k","fingerprint":null},"configuration":{"cache_state":"not_applicable","index_type":"ivf_flat","target_nprobe":64},"measurements":{"p50_ms":18.9839685,"p95_ms":21.5613113},"hardware":{"gpu":"unspecified in retained CSV","peak_gpu_memory_mb":37917.7666015625},"provenance":{"artifact":"remote_scale_experiment/experiments/latency/results_25k/latency_summary.csv","artifact_sha256":"13136c9efa90a569d8a771237a3f8536934ea94111a05701872a7c0ba3674416","table":"latency_summary.csv","status":"retained_external","git_commit":null},"notes":["Native target baseline for the same 25K workload; hardware identity is not retained in this CSV."]}
|
|
3
|
+
{"registry_version":"0.1.0","profile_id":"throughput_qwen3_8b_document_encoding_profile","kind":"document_encoding_throughput","status":"measured_workload_specific","scope":"retained model-profile benchmark","source":{"canonical_model_id":"Qwen/Qwen3-Embedding-8B","contract_fingerprint":"11d6d071a07a73b0bb3588a49447bb8bf58cb5426af732b534d374581ac4b018"},"target":null,"dataset":{"name":"profile fixture (dataset details not retained)","corpus_size":null,"query_count":null,"split":null,"fingerprint":null},"configuration":{"repetitions":5,"hardware":"not recorded in profile JSON"},"measurements":{"best_rows_per_second":331.19314884980446,"best_batch_size":40,"best_mean_seconds":0.1207754452014342,"all_batch_rows_per_second":{"1":27.222915766054818,"2":54.38365517199809,"4":109.39658538220068,"8":193.1738374840373,"12":247.2968027380519,"16":262.2610551452983,"20":301.8062592513787,"24":270.37271409519764,"32":317.50042263060874,"40":331.19314884980446,"50":311.4730361008796}},"hardware":{"gpu":"not recorded in profile JSON"},"provenance":{"artifact":"iclr_candidate_compatibility/remote_outputs/profiles/qwen3_8b_latency.json","artifact_sha256":"f46dc680f87cf0d0a9ea96fcd25c3ddf20a92afd84acda72f7ca72a918950a30","table":"batch_sizes","status":"retained_external","git_commit":null},"notes":["Measured workload-specific model-profile throughput. The often-used 106.98 docs/s value was not traceable to a retained measured artifact and is intentionally not included.","Do not use this profile as a universal H100 rate; supply your own measured throughput for economics projections."]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
c3d851bcfe66d9cc1aabc4ec8b61512110d8802ac0bcc7af3239a9ac6cec14a9 schema_version.json
|
|
2
|
+
1471e7a937d87eeba58d19a801fb8c147f6982d2c1522b5da43f6986b92800d5 migrations.jsonl
|
|
3
|
+
fcbf2fa0456fc3fc2e30d1b945b05e7d7dcfa11b7187910ced8a3fa0e69f6720 research_summaries.json
|
|
4
|
+
4ef2bdfc9f1803ef108400986be6a06915879d302e14a2757fc091c32b5b6e54 benchmark_profiles.jsonl
|