boccaccioAI / scripts /convert_index_to_flat.py
lollix91's picture
Upload folder using huggingface_hub
187a491 verified
Raw History Blame Contribute Delete
3.32 kB
"""Converte un indice FAISS IVF (IndexIVFFlat) in ricerca esatta (IndexFlatIP).
Motivazione: l'indice IVF con nprobe basso salta la maggior parte dei cluster e
manca i chunk rilevanti per le entita' meno comuni (recall crollata), causando
allucinazioni nel RAG. L'IVFFlat memorizza gia' tutti i vettori interi, quindi
la conversione in IndexFlatIP non costa RAM aggiuntiva e garantisce recall
perfetta con latenza comparabile (~27 ms/query su 3.2M vettori).
Uso:
python scripts/convert_index_to_flat.py \
--input rag/index/faiss/index.faiss \
--output rag/index/faiss/index_flat.faiss
Se --output coincide con --input, l'originale viene salvato in <input>.ivf.bak
prima della sovrascrittura.
"""
from __future__ import annotations
import argparse
import logging
import os
import shutil
import time
import faiss
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Convert FAISS IVF index to exact IndexFlatIP.")
p.add_argument("--input", default="rag/index/faiss/index.faiss",
help="Indice IVF di input.")
p.add_argument("--output", default="rag/index/faiss/index_flat.faiss",
help="Indice FlatIP di output.")
return p.parse_args()
def convert(input_path: str, output_path: str) -> None:
"""Ricostruisce i vettori da un IVF e li inserisce in un IndexFlatIP.
Parameters
----------
input_path : str
Percorso dell'indice IVF (IndexIVFFlat, METRIC_INNER_PRODUCT).
output_path : str
Percorso dove salvare l'indice FlatIP risultante.
Raises
------
FileNotFoundError
Se input_path non esiste.
ValueError
Se l'indice non espone reconstruct (vettori non recuperabili).
"""
if not os.path.exists(input_path):
raise FileNotFoundError(f"{input_path} non trovato.")
log.info("Caricamento indice IVF: %s", input_path)
t = time.time()
index = faiss.read_index(input_path)
log.info(" caricato in %.1fs type=%s ntotal=%s d=%s",
time.time() - t, type(index).__name__, f"{index.ntotal:,}", index.d)
log.info("Ricostruzione vettori (make_direct_map + reconstruct_n)...")
t = time.time()
index.make_direct_map()
vectors = index.reconstruct_n(0, index.ntotal)
log.info(" ricostruiti %s vettori in %.1fs shape=%s",
f"{index.ntotal:,}", time.time() - t, vectors.shape)
log.info("Costruzione IndexFlatIP (ricerca esatta, inner product)...")
t = time.time()
flat = faiss.IndexFlatIP(index.d)
flat.add(vectors)
log.info(" aggiunti %s vettori in %.1fs", f"{flat.ntotal:,}", time.time() - t)
if os.path.abspath(output_path) == os.path.abspath(input_path):
backup = input_path + ".ivf.bak"
log.info("Output == input: backup dell'IVF in %s", backup)
shutil.copy2(input_path, backup)
log.info("Scrittura indice FlatIP: %s", output_path)
faiss.write_index(flat, output_path)
size_gb = os.path.getsize(output_path) / 1e9
log.info("Fatto: %s (%.2f GB)", output_path, size_gb)
def main() -> None:
args = parse_args()
convert(args.input, args.output)
if __name__ == "__main__":
main()