ragsolver / scripts /ann_recall.py
CamiloS1111's picture
Upload folder using huggingface_hub
81f30c1 verified
Raw History Blame Contribute Delete
3.85 kB
"""Índice ANN de `mathlib_full`: construye IVF_SQ (si hace falta) y mide cuánto se aparta de la
búsqueda exacta (recall@k del ANN respecto del top-k exacto), latencia p50/p95 de ambas y RSS.
Consultas sintéticas sin cargar el embedder: normalize(e_i + e_j) para pares al azar de
declaraciones (caen entre dos documentos, no encima de uno). Uso:
python scripts/ann_recall.py [--build] [--n 200] [--out data/eval/results/ann_mathlib_full.json]
"""
from __future__ import annotations
import argparse
import json
import resource
import statistics
import time
import lancedb
import numpy as np
from ragsolver.rag.index import ANN_NPROBES, ANN_REFINE, build_ann_index, sql_quote
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--db-path", default="data/index/lancedb")
ap.add_argument("--table", default="mathlib_full")
ap.add_argument("--build", action="store_true")
ap.add_argument("--n", type=int, default=200)
ap.add_argument("--k", type=int, default=50)
ap.add_argument("--out", default="data/eval/results/ann_mathlib_full.json")
args = ap.parse_args()
t = lancedb.connect(args.db_path).open_table(args.table)
n_rows = t.count_rows()
if args.build:
t0 = time.time()
build_ann_index(t)
print(f"índice construido en {time.time() - t0:.0f} s", flush=True)
has_index = any("IVF" in str(i.index_type).upper() for i in t.list_indices())
rng = np.random.default_rng(13)
all_ids = t.search().select(["chunk_id"]).limit(n_rows).to_arrow().column("chunk_id").to_pylist()
pick = [all_ids[i] for i in rng.choice(n_rows, size=2 * args.n, replace=False)]
rows = t.search().where("chunk_id IN (" + ", ".join(sql_quote(c) for c in pick) + ")").select(["chunk_id", "embedding"]).limit(len(pick)).to_list()
by_id = {r["chunk_id"]: r["embedding"] for r in rows}
emb = np.array([by_id[c] for c in pick], dtype="float32")
queries = emb[0::2] + emb[1::2]
queries /= np.linalg.norm(queries, axis=1, keepdims=True)
rec10, rec50, lat_exact, lat_ann = [], [], [], []
for i, q in enumerate(queries):
t0 = time.time()
exact = [r["chunk_id"] for r in t.search(q, vector_column_name="embedding").bypass_vector_index().limit(args.k).select(["chunk_id"]).to_list()]
lat_exact.append(time.time() - t0)
t0 = time.time()
ann = [r["chunk_id"] for r in t.search(q, vector_column_name="embedding").nprobes(ANN_NPROBES).refine_factor(ANN_REFINE).limit(args.k).select(["chunk_id"]).to_list()]
lat_ann.append(time.time() - t0)
rec10.append(len(set(exact[:10]) & set(ann[:10])) / 10)
rec50.append(len(set(exact) & set(ann)) / len(exact))
if (i + 1) % 50 == 0:
print(f"{i + 1}/{len(queries)} recall@10 {statistics.mean(rec10):.4f} recall@50 {statistics.mean(rec50):.4f}", flush=True)
p = lambda xs, q: round(sorted(xs)[int(q * (len(xs) - 1))], 3)
out = {
"table": args.table, "rows": n_rows, "ann_index": has_index, "index_type": "IVF_SQ", "num_partitions": 256,
"nprobes": ANN_NPROBES, "refine_factor": ANN_REFINE, "n_queries": len(queries), "k": args.k,
"ann_recall@10_vs_exact": round(statistics.mean(rec10), 4), "ann_recall@50_vs_exact": round(statistics.mean(rec50), 4),
"latency_exact_s": {"p50": p(lat_exact, 0.5), "p95": p(lat_exact, 0.95)}, "latency_ann_s": {"p50": p(lat_ann, 0.5), "p95": p(lat_ann, 0.95)},
"maxrss_mb": resource.getrusage(resource.RUSAGE_SELF).ru_maxrss // 1024,
"note": "consultas sintéticas normalize(e_i + e_j); latencias medidas con la corrida eval_v1 en paralelo (CPU compartida)",
}
open(args.out, "w", encoding="utf-8").write(json.dumps(out, indent=2, ensure_ascii=False))
print(json.dumps(out, ensure_ascii=False))
if __name__ == "__main__":
main()