quantumbit commited on
Commit
353f5fa
·
verified ·
1 Parent(s): 48fd09f

Upload folder using huggingface_hub

Browse files
rag_system/config.py CHANGED
@@ -9,10 +9,12 @@ class Settings(BaseSettings):
9
  llm_temperature: float = 0.1
10
  llm_max_tokens: int = 1024
11
 
12
- #bge embeddings
13
  embedding_model: str = "BAAI/bge-large-en-v1.5"
14
  embedding_dimensions: int = 1024
15
- embedding_device: str = "cuda"
 
 
16
  embedding_batch_size: int = 32
17
  embedding_normalize: bool = True
18
 
@@ -75,4 +77,9 @@ def get_settings() -> Settings:
75
  return Settings()
76
 
77
  settings = get_settings()
78
- print(f"[Config] Loaded. Model: {settings.chat_model}, Embedding Model: {settings.embedding_model},EmbedDim: {settings.embedding_dimensions}")
 
 
 
 
 
 
9
  llm_temperature: float = 0.1
10
  llm_max_tokens: int = 1024
11
 
12
+ # Embeddings (GPU vs CPU auto selection)
13
  embedding_model: str = "BAAI/bge-large-en-v1.5"
14
  embedding_dimensions: int = 1024
15
+ embedding_model_cpu: str = "BAAI/bge-small-en-v1.5"
16
+ embedding_dimensions_cpu: int = 384
17
+ embedding_device: str = "auto"
18
  embedding_batch_size: int = 32
19
  embedding_normalize: bool = True
20
 
 
77
  return Settings()
78
 
79
  settings = get_settings()
80
+ print(
81
+ "[Config] Loaded. Model: "
82
+ f"{settings.chat_model}, Embedding GPU: {settings.embedding_model} ({settings.embedding_dimensions}), "
83
+ f"Embedding CPU: {settings.embedding_model_cpu} ({settings.embedding_dimensions_cpu}), "
84
+ f"Device: {settings.embedding_device}"
85
+ )
rag_system/embeddings.py CHANGED
@@ -1,10 +1,11 @@
1
  """
2
- Local embedding model: BAAI/bge-large-en-v1.5
3
- - 1024-dim output, consistently top-ranked on MTEB leaderboard
 
4
  - Runs fully local via sentence-transformers — zero API calls, zero cost
5
  - BGE requires a special query prefix: 'Represent this sentence for searching'
6
- (documents are embedded as-is; only queries get the prefix)
7
- - LangChain's HuggingFaceBgeEmbeddings handles the prefix automatically
8
  """
9
 
10
  import asyncio
@@ -30,45 +31,80 @@ warnings.filterwarnings("ignore", category=UnsupportedFieldAttributeWarning)
30
 
31
  _executor = ThreadPoolExecutor(max_workers=2)
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  @lru_cache(maxsize=1)
34
  def get_embeddings() -> HuggingFaceEmbeddings:
35
  """
36
- Singleton BGE embedding model
37
 
38
  encode_kwargs:
39
- normalize_embeddings=True -> required for BGE Cosine similarity to work correctly
40
 
41
  query_encode_kwargs:
42
  BGE was finetuned with an instruction-like query prefix.
43
  We pass that prefix for query encoding only; documents remain unchanged.
44
  """
45
- device = settings.embedding_device
46
- if device and device != "cpu":
47
- try:
48
- import torch
49
- if device == "cuda" and not torch.cuda.is_available():
50
- logger.warning("CUDA requested but not available; falling back to CPU")
51
- device = "cpu"
52
- except Exception:
53
- logger.warning("Torch unavailable or CUDA check failed; falling back to CPU")
54
- device = "cpu"
55
 
56
- device = device or "cpu"
57
- logger.info(f"Loading BGE model: {settings.embedding_model} on {device}")
58
  model = HuggingFaceEmbeddings(
59
- model_name = settings.embedding_model,
60
  model_kwargs={
61
  "device": device,
62
  },
63
  encode_kwargs={
64
  "normalize_embeddings": settings.embedding_normalize,
65
- "batch_size": settings.embedding_batch_size
66
  },
67
  query_encode_kwargs={
68
  "prompt": "Represent this sentence for searching relevant passages: ",
69
  },
70
  )
71
- logger.info(f"BGE model loaded. Output dim={settings.embedding_dimensions}")
72
  return model
73
 
74
  #Async wrappers
@@ -114,5 +150,5 @@ def cosine_similarity(a:list[float],b:list[float]) -> float:
114
  return 0.0
115
  return float(np.dot(a_np,b_np)/denom)
116
 
117
- print("[embeddings] BGE module ready. Model will load on first embed call")
118
  #the model can be preloaded using a warmup call at start
 
1
  """
2
+ Local embedding model with GPU/CPU auto selection.
3
+ - GPU: BAAI/bge-large-en-v1.5 (1024-dim output)
4
+ - CPU fallback: BAAI/bge-small-en-v1.5 (384-dim output)
5
  - Runs fully local via sentence-transformers — zero API calls, zero cost
6
  - BGE requires a special query prefix: 'Represent this sentence for searching'
7
+ (documents are embedded as-is; only queries get the prefix)
8
+ - LangChain's HuggingFaceEmbeddings handles the prefix automatically
9
  """
10
 
11
  import asyncio
 
31
 
32
  _executor = ThreadPoolExecutor(max_workers=2)
33
 
34
+ def _resolve_device() -> str:
35
+ device = (settings.embedding_device or "auto").lower()
36
+ if device == "auto":
37
+ try:
38
+ import torch
39
+ if torch.cuda.is_available():
40
+ return "cuda"
41
+ except Exception:
42
+ logger.warning("Torch unavailable or CUDA check failed; falling back to CPU")
43
+ return "cpu"
44
+
45
+ if device != "cpu":
46
+ try:
47
+ import torch
48
+ if device == "cuda" and not torch.cuda.is_available():
49
+ logger.warning("CUDA requested but not available; falling back to CPU")
50
+ return "cpu"
51
+ except Exception:
52
+ logger.warning("Torch unavailable or CUDA check failed; falling back to CPU")
53
+ return "cpu"
54
+
55
+ return device
56
+
57
+
58
+ def _select_embedding_config() -> tuple[str, int, str]:
59
+ device = _resolve_device()
60
+ if device == "cpu":
61
+ model_name = settings.embedding_model_cpu or settings.embedding_model
62
+ dimensions = settings.embedding_dimensions_cpu or settings.embedding_dimensions
63
+ else:
64
+ model_name = settings.embedding_model
65
+ dimensions = settings.embedding_dimensions
66
+
67
+ return model_name, dimensions, device
68
+
69
+
70
+ def get_embedding_info() -> dict[str, str | int]:
71
+ model_name, dimensions, device = _select_embedding_config()
72
+ return {
73
+ "model_name": model_name,
74
+ "dimensions": dimensions,
75
+ "device": device,
76
+ }
77
+
78
+
79
  @lru_cache(maxsize=1)
80
  def get_embeddings() -> HuggingFaceEmbeddings:
81
  """
82
+ Singleton embedding model
83
 
84
  encode_kwargs:
85
+ normalize_embeddings=True -> required for cosine similarity to work correctly
86
 
87
  query_encode_kwargs:
88
  BGE was finetuned with an instruction-like query prefix.
89
  We pass that prefix for query encoding only; documents remain unchanged.
90
  """
91
+ model_name, dimensions, device = _select_embedding_config()
 
 
 
 
 
 
 
 
 
92
 
93
+ logger.info(f"Loading embedding model: {model_name} on {device}")
 
94
  model = HuggingFaceEmbeddings(
95
+ model_name=model_name,
96
  model_kwargs={
97
  "device": device,
98
  },
99
  encode_kwargs={
100
  "normalize_embeddings": settings.embedding_normalize,
101
+ "batch_size": settings.embedding_batch_size,
102
  },
103
  query_encode_kwargs={
104
  "prompt": "Represent this sentence for searching relevant passages: ",
105
  },
106
  )
107
+ logger.info(f"Embedding model loaded. Output dim={dimensions}")
108
  return model
109
 
110
  #Async wrappers
 
150
  return 0.0
151
  return float(np.dot(a_np,b_np)/denom)
152
 
153
+ print("[embeddings] Module ready. Model will load on first embed call")
154
  #the model can be preloaded using a warmup call at start
rag_system/vector_store.py CHANGED
@@ -10,7 +10,7 @@ from langchain_core.documents import Document
10
  from langchain_community.vectorstores import FAISS
11
 
12
  from .config import get_settings
13
- from .embeddings import get_embeddings
14
 
15
  logger = logging.getLogger(__name__)
16
  settings = get_settings()
@@ -42,7 +42,18 @@ def load_or_create_store(collection: str = "default") -> FAISS:
42
  embeddings,
43
  allow_dangerous_deserialization=True,
44
  )
45
- _stores[collection] = store
 
 
 
 
 
 
 
 
 
 
 
46
  else:
47
  logger.warning(f"No index at {path}. Will create on first Ingest.")
48
  _stores[collection] = None
 
10
  from langchain_community.vectorstores import FAISS
11
 
12
  from .config import get_settings
13
+ from .embeddings import get_embeddings, get_embedding_info
14
 
15
  logger = logging.getLogger(__name__)
16
  settings = get_settings()
 
42
  embeddings,
43
  allow_dangerous_deserialization=True,
44
  )
45
+ expected_dim = int(get_embedding_info()["dimensions"])
46
+ if store.index.d != expected_dim:
47
+ logger.error(
48
+ "Embedding dim mismatch for collection '%s': index dim=%s, expected=%s. "
49
+ "Re-ingest with force_reindex or delete the collection.",
50
+ collection,
51
+ store.index.d,
52
+ expected_dim,
53
+ )
54
+ _stores[collection] = None
55
+ else:
56
+ _stores[collection] = store
57
  else:
58
  logger.warning(f"No index at {path}. Will create on first Ingest.")
59
  _stores[collection] = None