Jupyter / scripts /bench_local.py
SmartDataAlex's picture
Deploy vectorization service from GitHub 41b71f6b9ba674ba7ca60eeed5d2193ec224391c
d289270 verified
Raw History Blame Contribute Delete
4.69 kB
"""Opt-in on-host three-resident-model CPU/RSS benchmark.
Run from service root with model snapshots downloaded and API_KEY configured:
BENCH_LONG=1 python scripts/bench_local.py
Reports are checkpointed after every case to survive failures/OOM.
"""
from __future__ import annotations
import json
import os
import threading
import time
from datetime import datetime, timezone
from pathlib import Path
from app.embedding import MODEL_ID as E5_ID
from app.models import ModelRegistry, QWEN_ID, COLBERT_ID, memory_mb
REPORT = Path(os.getenv("BENCH_REPORT", "bench-result.json"))
LONG = os.getenv("BENCH_LONG", "0") == "1"
rows = []
report = {"time": datetime.now(timezone.utc).isoformat(),
"long_context": LONG, "startup": None, "cases": rows}
def save():
REPORT.write_text(json.dumps(report, indent=2, ensure_ascii=False),
encoding="utf-8")
def repeated_text(tokenizer, max_tokens: int, prefix: str = "") -> str:
"""Find a repetition count whose true tokenizer length is <= target.
Model-specific input overhead (query instructions/markers) is counted.
A result slightly shorter than target is reported, not mislabeled.
"""
base = "Planet earth history. "
def length(n):
return len(tokenizer(prefix + base * n, add_special_tokens=True,
truncation=False)["input_ids"])
lo, hi = 0, max_tokens
while lo < hi:
mid = (lo + hi + 1) // 2
if length(mid) <= max_tokens:
lo = mid
else:
hi = mid - 1
if lo == 0:
raise RuntimeError("Model prefix exceeds the test target")
return base * lo
def measure(name, runner, token_count=None):
readings = []
finish = threading.Event()
def monitor():
while not finish.wait(0.05):
value = memory_mb()["rss_mb"]
if value is not None:
readings.append(value)
sampler = threading.Thread(target=monitor, daemon=True)
sampler.start()
started = time.perf_counter()
result = {"case": name, "requested_tokens": token_count}
try:
payload = runner()
result["status"] = "pass"
result["output_tokens"] = payload.tokens_count
if hasattr(payload, "embedding"):
result["dimensions"] = len(payload.embedding)
else:
result["dimensions"] = len(payload.token_embeddings[0])
result["vector_count"] = len(payload.token_embeddings)
except Exception as error:
result["status"] = "fail"
result["error"] = f"{type(error).__name__}: {error}"
finally:
finish.set()
sampler.join()
result["elapsed_seconds"] = round(time.perf_counter() - started, 3)
result["rss_mb"] = memory_mb()["rss_mb"]
result["peak_sampled_rss_mb"] = max(readings, default=None)
result["peak_process_rss_mb"] = memory_mb()["peak_rss_mb"]
rows.append(result)
save()
print(result, flush=True)
def main():
started = time.perf_counter()
registry = ModelRegistry.load() # all 3 weights resident, once
report["startup"] = {
"seconds": round(time.perf_counter() - started, 3),
"per_model_rss": registry.startup_memory,
"post_load_ram": memory_mb(),
}
save()
for kind in ("query", "passage"):
for language, text in (
("en", "A long voyage through the forest."),
("ru", "Долгое путешествие сквозь лес."),
("he", "מסע ארוך דרך היער."),
):
measure(f"e5-{kind}-{language}",
lambda t=text, k=kind: registry.e5.encode(t, k))
measure(f"qwen-{kind}-{language}",
lambda t=text, k=kind: registry.qwen.encode(t, k))
measure(f"colbert-{kind}-{language}",
lambda t=text, k=kind: registry.colbert.encode(t, k))
if LONG:
for target in (8192, 32768):
text = repeated_text(registry.qwen.tokenizer, target)
actual = len(registry.qwen.tokenizer(
text, add_special_tokens=True, truncation=False)["input_ids"])
measure(f"qwen-passage-{target}", lambda t=text: registry.qwen.encode(t, "passage"), actual)
model = registry.colbert.model
prefix = model.document_prefix
text = repeated_text(model.tokenizer, 8192, prefix)
count = len(model.tokenizer(
prefix + text, add_special_tokens=True, truncation=False)["input_ids"])
measure("colbert-passage-8192",
lambda: registry.colbert.encode(text, "passage"), count)
if __name__ == "__main__":
main()