Spaces:
Running
Running
| """M5 sub-experiment B — early directional read from a PARTIAL golds-first index. | |
| The full UniXcoder index runs golds-first (index_corpus.py GOLDS_FIRST=1), so the | |
| first ~22k embedded docs are every eval query's gold target; docs after that are | |
| train-split distractors whose pool grows as indexing proceeds. This script does a | |
| fair, exact brute-force cosine A/B over the SAME candidate pool for both models, | |
| reading vectors straight from the .cache/*.npy files — no Qdrant, no HNSW recall | |
| loss, no CPU contention with the running indexer. | |
| Pool at run time = the first P = unixcoder embed-progress docs (golds-first order). | |
| UniXcoder doc vectors come from its cache[0:P]; MiniLM doc vectors are pulled from | |
| the full MiniLM cache for the SAME doc set (mapped by doc_id). Queries evaluated = | |
| those whose gold is within the pool. Metrics: MRR@10 / nDCG@10 / Recall@100. | |
| Usage: | |
| .venv/bin/python scripts/sample_eval_partial.py # eval at current P | |
| .venv/bin/python scripts/sample_eval_partial.py --min-docs 44000 # require P first | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import sys | |
| import numpy as np | |
| sys.path.insert(0, "src") | |
| from codesearch.data import load_codesearch # noqa: E402 | |
| from codesearch.eval.metrics import ( # noqa: E402 | |
| mean_ndcg, | |
| mean_recall, | |
| mean_reciprocal_rank, | |
| ) | |
| from codesearch.embedding import load_encoder # noqa: E402 | |
| CACHE = ".cache" | |
| UNIX_MODEL = "microsoft/unixcoder-base" | |
| MINILM_MODEL = "all-MiniLM-L6-v2" | |
| UNIX_NPY = f"{CACHE}/corpus_vectors_microsoft_unixcoder-base.npy" | |
| UNIX_PROG = f"{CACHE}/corpus_vectors_microsoft_unixcoder-base.progress" | |
| MINILM_NPY = f"{CACHE}/corpus_vectors_all-MiniLM-L6-v2.npy" | |
| UNIX_DIM, MINILM_DIM = 768, 384 | |
| def _read_progress(path: str) -> int: | |
| with open(path) as f: | |
| return int(f.read().strip() or "0") | |
| def _golds_first_corpus(corpus, queries): | |
| """Reproduce index_corpus.py's GOLDS_FIRST reorder exactly.""" | |
| gold_ids = {q["relevant_id"] for q in queries} | |
| golds = [d for d in corpus if d["id"] in gold_ids] | |
| rest = [d for d in corpus if d["id"] not in gold_ids] | |
| return golds + rest | |
| def _topk_ids(query_vecs, doc_vecs, doc_ids, k=100, chunk=512): | |
| """Brute-force cosine (vectors are pre-normalized): top-k doc_ids per query.""" | |
| out = [] | |
| for i in range(0, len(query_vecs), chunk): | |
| sims = query_vecs[i : i + chunk] @ doc_vecs.T # (b, P) | |
| kk = min(k, sims.shape[1]) | |
| part = np.argpartition(-sims, kk - 1, axis=1)[:, :kk] | |
| for r in range(sims.shape[0]): | |
| order = part[r][np.argsort(-sims[r, part[r]])] | |
| out.append([doc_ids[j] for j in order]) | |
| return out | |
| def _eval_model(name, npy_path, dim, doc_rows, query_texts, relevant_ids, encoder): | |
| """doc_rows: np.ndarray of cache-row indices for the pool docs (in pool order).""" | |
| mm = np.memmap(npy_path, dtype=np.float32, mode="r").reshape(-1, dim) | |
| doc_vecs = np.ascontiguousarray(mm[doc_rows]) # (P, dim) | |
| print(f"[{name}] encoding {len(query_texts):,} queries...", flush=True) | |
| qv = encoder.encode(query_texts, normalize_embeddings=True, batch_size=128) | |
| qv = np.asarray(qv, dtype=np.float32) | |
| retrieved = _topk_ids(qv, doc_vecs, POOL_IDS, k=100) | |
| qr = list(zip(retrieved, relevant_ids)) | |
| return ( | |
| mean_reciprocal_rank(qr, k=10), | |
| mean_ndcg(qr, k=10), | |
| mean_recall(qr, k=100), | |
| ) | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--min-docs", type=int, default=0, | |
| help="Require at least this many embedded docs before evaluating.") | |
| args = ap.parse_args() | |
| P = _read_progress(UNIX_PROG) | |
| print(f"UniXcoder embed-progress P = {P:,} docs") | |
| if P < args.min_docs: | |
| print(f"Not enough yet (need {args.min_docs:,}); exiting.") | |
| return 0 | |
| corpus, queries = load_codesearch(n=-1) | |
| orig_row = {d["id"]: i for i, d in enumerate(corpus)} # doc_id -> MiniLM cache row | |
| gf = _golds_first_corpus(corpus, queries) # golds-first (== unixcoder cache order) | |
| pool = gf[:P] | |
| global POOL_IDS | |
| POOL_IDS = [d["id"] for d in pool] | |
| pool_id_set = set(POOL_IDS) | |
| # rows into each cache for the pool docs, in pool order | |
| unix_rows = np.arange(P) # unixcoder cache is already golds-first | |
| minilm_rows = np.array([orig_row[i] for i in POOL_IDS]) # map to original-order MiniLM cache | |
| subset = [q for q in queries if q["relevant_id"] in pool_id_set] | |
| texts = [q["query"] for q in subset] | |
| rel = [q["relevant_id"] for q in subset] | |
| print(f"Pool: {P:,} docs | eval queries (gold in pool): {len(subset):,}") | |
| if not subset: | |
| print("No queries have their gold in the pool yet — wait for more golds.") | |
| return 0 | |
| unix_enc = load_encoder(UNIX_MODEL) | |
| minilm_enc = load_encoder(MINILM_MODEL) | |
| u_mrr, u_ndcg, u_rec = _eval_model("unixcoder", UNIX_NPY, UNIX_DIM, unix_rows, texts, rel, unix_enc) | |
| m_mrr, m_ndcg, m_rec = _eval_model("minilm", MINILM_NPY, MINILM_DIM, minilm_rows, texts, rel, minilm_enc) | |
| print("\n" + "=" * 62) | |
| print(f"PARTIAL-INDEX A/B (pool={P:,} docs, n={len(subset):,} queries, brute-force cosine)") | |
| print("NOTE: reduced pool inflates absolute scores; the MODEL DELTA is the signal.") | |
| print("=" * 62) | |
| print(f"{'Model':<16}{'MRR@10':>10}{'nDCG@10':>10}{'Recall@100':>12}") | |
| print("-" * 62) | |
| print(f"{'MiniLM (base)':<16}{m_mrr:>10.4f}{m_ndcg:>10.4f}{m_rec:>12.4f}") | |
| print(f"{'UniXcoder':<16}{u_mrr:>10.4f}{u_ndcg:>10.4f}{u_rec:>12.4f}") | |
| print(f"{'Δ (Unix-Mini)':<16}{u_mrr-m_mrr:>+10.4f}{u_ndcg-m_ndcg:>+10.4f}{u_rec-m_rec:>+12.4f}") | |
| print("=" * 62) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |