optimize_index_v2.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. #!/usr/bin/env python3
  2. """
  3. Optimize RAG index v2: aggressive pruning + numpy arrays
  4. """
  5. import json
  6. import pickle
  7. import time
  8. import struct
  9. from pathlib import Path
  10. BASE = Path("/data/knowledge/maritime")
  11. INDEX_FILE = BASE / "rag_index.json"
  12. KEYWORD_FILE = BASE / "rag_index.keywords.json"
  13. OPT_INDEX = BASE / "rag_index.pkl"
  14. OPT_KEYWORDS = BASE / "rag_keywords.pkl"
  15. print("=== RAG Index Optimizer v2 ===")
  16. # Load
  17. print("Loading...")
  18. t0 = time.time()
  19. with open(INDEX_FILE) as f:
  20. data = json.load(f)
  21. chunks = data["chunks"]
  22. print(f" Chunks: {len(chunks)}, {time.time()-t0:.1f}s")
  23. t0 = time.time()
  24. with open(KEYWORD_FILE) as f:
  25. keywords = json.load(f)
  26. print(f" Keywords: {len(keywords)}, {time.time()-t0:.1f}s")
  27. # Build chunk_id -> int index
  28. chunk_ids = [c["id"] for c in chunks]
  29. id_to_idx = {cid: i for i, cid in enumerate(chunk_ids)}
  30. # More aggressive pruning
  31. MIN_FREQ = 3 # Must appear in >=3 chunks
  32. MAX_FREQ_PCT = 0.15 # Must appear in <15% of chunks
  33. MIN_KW_LEN = 2 # Keyword must be >=2 chars
  34. max_freq = int(len(chunks) * MAX_FREQ_PCT)
  35. pruned = {}
  36. stats = {"rare": 0, "common": 0, "short": 0, "kept": 0}
  37. for kw, posting_list in keywords.items():
  38. n = len(posting_list)
  39. if len(kw) < MIN_KW_LEN:
  40. stats["short"] += 1
  41. continue
  42. if n < MIN_FREQ:
  43. stats["rare"] += 1
  44. continue
  45. if n > max_freq:
  46. stats["common"] += 1
  47. continue
  48. # Convert to int indices, deduplicate, sort
  49. int_postings = sorted(set(id_to_idx[cid] for cid in posting_list if cid in id_to_idx))
  50. if int_postings:
  51. # Store as bytes for compact storage (2 bytes per int for <65536 chunks)
  52. if len(chunks) < 65536:
  53. pruned[kw] = bytes(struct.pack(f"<{len(int_postings)}H", *int_postings))
  54. else:
  55. pruned[kw] = int_postings
  56. stats["kept"] += 1
  57. print(f"\n--- Pruning stats ---")
  58. for k, v in stats.items():
  59. print(f" {k}: {v}")
  60. # Optimized chunks (only essential fields)
  61. opt_chunks = []
  62. for c in chunks:
  63. opt_chunks.append((c["source"], c["chunk_id"], c["text"]))
  64. # Save
  65. print("\nSaving...")
  66. t0 = time.time()
  67. opt_data = {
  68. "total": len(opt_chunks),
  69. "files": data.get("total_files", 0),
  70. "chunks": opt_chunks, # list of (source, chunk_id, text) tuples
  71. "use_bytes": len(chunks) < 65536,
  72. }
  73. with open(OPT_INDEX, "wb") as f:
  74. pickle.dump(opt_data, f, protocol=pickle.HIGHEST_PROTOCOL)
  75. with open(OPT_KEYWORDS, "wb") as f:
  76. pickle.dump(pruned, f, protocol=pickle.HIGHEST_PROTOCOL)
  77. t1 = time.time()
  78. print(f" Saved in {t1-t0:.1f}s")
  79. idx_size = OPT_INDEX.stat().st_size / 1024 / 1024
  80. kw_size = OPT_KEYWORDS.stat().st_size / 1024 / 1024
  81. print(f"\n--- File sizes ---")
  82. print(f" Chunks: {idx_size:.1f} MB")
  83. print(f" Keywords: {kw_size:.1f} MB (was 2439.7 MB JSON)")
  84. # Verify
  85. print("\n--- Load test ---")
  86. t0 = time.time()
  87. with open(OPT_INDEX, "rb") as f:
  88. d1 = pickle.load(f)
  89. t1 = time.time()
  90. with open(OPT_KEYWORDS, "rb") as f:
  91. d2 = pickle.load(f)
  92. t2 = time.time()
  93. print(f" Chunks: {(t1-t0)*1000:.0f}ms")
  94. print(f" Keywords: {(t2-t1)*1000:.0f}ms")
  95. print(f" Total: {(t2-t0)*1000:.0f}ms (was 50s JSON)")