Let every harness work on a copy of the frozen store
copy_store lived in dense_scale, so the two harnesses that also needed it reached back into a measurement module for a plumbing helper. It now sits in systems beside the product path it protects, and bench.run passes a scratch directory to the live check: the product path migrates whatever database it opens, and the frozen baseline is cited evidence.
This commit is contained in:
1 parent
b8f83f422a
commit
91fbff34ee
5 files changed
+39
-22
No files matched your search
+1
-16
@@ -10,7 +10,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import shutil
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
@@ -22,7 +21,7 @@ import numpy as np
|
|||||||
from bench.query_set import load_queries, scale_probes
|
from bench.query_set import load_queries, scale_probes
|
||||||
from bench.report import git_commit
|
from bench.report import git_commit
|
||||||
from bench.run import latency
|
from bench.run import latency
|
||||||
from bench.systems import DENSE_MODEL, MODELS, machine_facts
|
from bench.systems import DENSE_MODEL, MODELS, copy_store, machine_facts
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ely_eye.db import Database
|
from ely_eye.db import Database
|
||||||
@@ -65,20 +64,6 @@ def vector_footprint(database: Any, table: str) -> tuple[int, str]:
|
|||||||
return int(payload), "vector bytes only, this sqlite3 has no dbstat"
|
return int(payload), "vector bytes only, this sqlite3 has no dbstat"
|
||||||
|
|
||||||
|
|
||||||
def copy_store(home: Path, scratch: Path) -> tuple[Path, float]:
|
|
||||||
"""Work on a copy: the baseline store is frozen evidence and must not be migrated in place."""
|
|
||||||
source = home / "data" / "ely_eye.sqlite"
|
|
||||||
if not source.is_file():
|
|
||||||
raise SystemExit(f"{source}: no product database, build it with python -m bench.ingest_baseline")
|
|
||||||
data = scratch / "data"
|
|
||||||
data.mkdir(parents=True, exist_ok=True)
|
|
||||||
began = time.perf_counter()
|
|
||||||
for suffix in ("", "-wal", "-shm"):
|
|
||||||
if (path := Path(str(source) + suffix)).is_file():
|
|
||||||
shutil.copyfile(path, data / (source.name + suffix))
|
|
||||||
return scratch, time.perf_counter() - began
|
|
||||||
|
|
||||||
|
|
||||||
def stored_vectors(database: Database) -> tuple[list[str], np.ndarray]:
|
def stored_vectors(database: Database) -> tuple[list[str], np.ndarray]:
|
||||||
"""Every vector the engine holds, for a scan it can be checked against."""
|
"""Every vector the engine holds, for a scan it can be checked against."""
|
||||||
with database.connect() as connection:
|
with database.connect() as connection:
|
||||||
|
|||||||
+15
-2
@@ -73,6 +73,7 @@ def live_check(
|
|||||||
fast_rankings: dict[str, list[str]],
|
fast_rankings: dict[str, list[str]],
|
||||||
top_k: int = 10,
|
top_k: int = 10,
|
||||||
live_queries: int = LIVE_QUERIES,
|
live_queries: int = LIVE_QUERIES,
|
||||||
|
scratch: Path | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Score the unmodified RetrievalService on its own, as its holder would ask it.
|
"""Score the unmodified RetrievalService on its own, as its holder would ask it.
|
||||||
|
|
||||||
@@ -85,7 +86,11 @@ def live_check(
|
|||||||
if not chosen:
|
if not chosen:
|
||||||
raise SystemExit("live check needs answerable queries")
|
raise SystemExit("live check needs answerable queries")
|
||||||
rankings, distinct, timings = live_product_rankings(
|
rankings, distinct, timings = live_product_rankings(
|
||||||
DB_HOME, [query.text for query in chosen], top_k, as_holders=[query.holder for query in chosen]
|
DB_HOME,
|
||||||
|
[query.text for query in chosen],
|
||||||
|
top_k,
|
||||||
|
as_holders=[query.holder for query in chosen],
|
||||||
|
scratch=scratch,
|
||||||
)
|
)
|
||||||
overlaps = [
|
overlaps = [
|
||||||
len(set(fast_rankings[query.query_id]) & set(ranking)) / top_k
|
len(set(fast_rankings[query.query_id]) & set(ranking)) / top_k
|
||||||
@@ -229,6 +234,12 @@ def main() -> int:
|
|||||||
parser.add_argument("--limit", type=int, default=None, help="score at most this many queries per modality")
|
parser.add_argument("--limit", type=int, default=None, help="score at most this many queries per modality")
|
||||||
parser.add_argument("--no-live", action="store_true", help="skip the unmodified RetrievalService path")
|
parser.add_argument("--no-live", action="store_true", help="skip the unmodified RetrievalService path")
|
||||||
parser.add_argument("--live-queries", type=int, default=LIVE_QUERIES)
|
parser.add_argument("--live-queries", type=int, default=LIVE_QUERIES)
|
||||||
|
parser.add_argument(
|
||||||
|
"--live-scratch",
|
||||||
|
type=Path,
|
||||||
|
default=ROOT / "runs" / "live",
|
||||||
|
help="copy the store here before the product path opens it, so the frozen baseline stays frozen",
|
||||||
|
)
|
||||||
parser.add_argument("--no-scale", action="store_true", help="skip the latency/scale curve")
|
parser.add_argument("--no-scale", action="store_true", help="skip the latency/scale curve")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--remeasure-latency",
|
"--remeasure-latency",
|
||||||
@@ -325,7 +336,9 @@ def main() -> int:
|
|||||||
# A crash in the live or scale phase should not cost every finished arm.
|
# A crash in the live or scale phase should not cost every finished arm.
|
||||||
(DB_HOME / "partial_report.json").write_text(json.dumps(report, indent=1, sort_keys=True), encoding="utf-8")
|
(DB_HOME / "partial_report.json").write_text(json.dumps(report, indent=1, sort_keys=True), encoding="utf-8")
|
||||||
if not args.no_live:
|
if not args.no_live:
|
||||||
report["live_product"] = live_check(queries, fast_rankings, live_queries=args.live_queries)
|
report["live_product"] = live_check(
|
||||||
|
queries, fast_rankings, live_queries=args.live_queries, scratch=args.live_scratch
|
||||||
|
)
|
||||||
if not args.no_scale:
|
if not args.no_scale:
|
||||||
report["scale"] = scale_curve(queries, atoms, [arm for arm in arms_requested if arm in ("bm25_only", "dense_only")])
|
report["scale"] = scale_curve(queries, atoms, [arm for arm in arms_requested if arm in ("bm25_only", "dense_only")])
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bench.dense_scale import copy_store
|
|
||||||
from bench.query_set import load_queries, scale_probes
|
from bench.query_set import load_queries, scale_probes
|
||||||
from bench.report import git_commit
|
from bench.report import git_commit
|
||||||
from bench.run import latency
|
from bench.run import latency
|
||||||
from bench.systems import machine_facts
|
from bench.systems import copy_store, machine_facts
|
||||||
|
|
||||||
DEFAULT_HOME = Path("bench/runs/baseline")
|
DEFAULT_HOME = Path("bench/runs/baseline")
|
||||||
DEFAULT_SCRATCH = Path("bench/runs/scale")
|
DEFAULT_SCRATCH = Path("bench/runs/scale")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
@@ -445,11 +446,26 @@ def _corpus_ids(database: Database) -> dict[str, str]:
|
|||||||
return ids
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def copy_store(home: Path, scratch: Path) -> tuple[Path, float]:
|
||||||
|
"""Work on a copy: the baseline store is frozen evidence and must not be migrated in place."""
|
||||||
|
source = home / "data" / "ely_eye.sqlite"
|
||||||
|
if not source.is_file():
|
||||||
|
raise SystemExit(f"{source}: no product database, build it with python -m bench.ingest_baseline")
|
||||||
|
data = scratch / "data"
|
||||||
|
data.mkdir(parents=True, exist_ok=True)
|
||||||
|
began = time.perf_counter()
|
||||||
|
for suffix in ("", "-wal", "-shm"):
|
||||||
|
if (path := Path(str(source) + suffix)).is_file():
|
||||||
|
shutil.copyfile(path, data / (source.name + suffix))
|
||||||
|
return scratch, time.perf_counter() - began
|
||||||
|
|
||||||
|
|
||||||
def live_product_rankings(
|
def live_product_rankings(
|
||||||
db_home: Path,
|
db_home: Path,
|
||||||
queries: Sequence[str],
|
queries: Sequence[str],
|
||||||
top_k: int = 10,
|
top_k: int = 10,
|
||||||
as_holders: Sequence[str | None] | None = None,
|
as_holders: Sequence[str | None] | None = None,
|
||||||
|
scratch: Path | None = None,
|
||||||
) -> tuple[list[list[str]], list[int], list[float]]:
|
) -> tuple[list[list[str]], list[int], list[float]]:
|
||||||
"""Rankings from the unmodified RetrievalService, with per-query wall time.
|
"""Rankings from the unmodified RetrievalService, with per-query wall time.
|
||||||
|
|
||||||
@@ -460,6 +476,9 @@ def live_product_rankings(
|
|||||||
The second list counts how many distinct sources the product's top-k atoms came from: the
|
The second list counts how many distinct sources the product's top-k atoms came from: the
|
||||||
product ranks atoms and never diversifies, so ten slots can be filled by fragments of one
|
product ranks atoms and never diversifies, so ten slots can be filled by fragments of one
|
||||||
document, and that is invisible once the hits are collapsed.
|
document, and that is invisible once the hits are collapsed.
|
||||||
|
|
||||||
|
`scratch` copies the store before opening it. The product path migrates whatever it opens, and
|
||||||
|
the baseline store is cited evidence, so a benchmark that measures the product must not edit it.
|
||||||
"""
|
"""
|
||||||
from ely_eye.config import Settings
|
from ely_eye.config import Settings
|
||||||
from ely_eye.db import Database
|
from ely_eye.db import Database
|
||||||
@@ -468,6 +487,8 @@ def live_product_rankings(
|
|||||||
|
|
||||||
if as_holders is not None and len(as_holders) != len(queries):
|
if as_holders is not None and len(as_holders) != len(queries):
|
||||||
raise SystemExit(f"{len(as_holders)} holders for {len(queries)} queries")
|
raise SystemExit(f"{len(as_holders)} holders for {len(queries)} queries")
|
||||||
|
if scratch is not None:
|
||||||
|
db_home, _ = copy_store(db_home, scratch)
|
||||||
settings = Settings(home=db_home)
|
settings = Settings(home=db_home)
|
||||||
database = Database(settings)
|
database = Database(settings)
|
||||||
service = RetrievalService(settings=settings, database=database)
|
service = RetrievalService(settings=settings, database=database)
|
||||||
|
|||||||
+1
-2
@@ -16,12 +16,11 @@ import statistics
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bench.dense_scale import copy_store
|
|
||||||
from bench.metrics import aggregate, score_query
|
from bench.metrics import aggregate, score_query
|
||||||
from bench.query_set import load_queries, scale_probes
|
from bench.query_set import load_queries, scale_probes
|
||||||
from bench.report import git_commit
|
from bench.report import git_commit
|
||||||
from bench.run import latency
|
from bench.run import latency
|
||||||
from bench.systems import live_product_rankings, machine_facts
|
from bench.systems import copy_store, live_product_rankings, machine_facts
|
||||||
|
|
||||||
FRACTIONS = [0.20, 0.10, 0.05, 0.02, 0.01, 0.005]
|
FRACTIONS = [0.20, 0.10, 0.05, 0.02, 0.01, 0.005]
|
||||||
TOP_K = 10
|
TOP_K = 10
|
||||||
|
|||||||
Reference in new issue
Block a user