Skip to content

Commit d240d9d

Browse files
fix: share ML model instances to reduce startup time (#412)
* fix: share ML model instances to reduce startup time The backend startup was slow because RetrieverTools.initialize() creates 6 retriever chains, and each one independently loaded its own copy of the embedding model (thenlper/gte-large) and reranker model (BAAI/bge-reranker-base). That meant 12 heavy model loads when only 2 are actually needed, since all chains use the same model config. This fix creates both models once at the top of initialize() and passes the shared instances down through HybridRetrieverChain, SimilarityRetrieverChain, and FAISSVectorDatabase. Both models are stateless (they only run encode/score inference) so sharing a single instance across all chains is safe. Each chain still builds its own independent FAISS index with its own documents. Startup model loading goes from ~34s to ~7s on a local machine (4.9x). Resolves #88 Signed-off-by: Harsh Kumar <harshkumar3446@gmail.com> * refactor(backend): build every embedding model in one factory RetrieverTools had its own copy of the embedding setup. That copy built plain Gemini embeddings without the retry on temporary 503 errors, and it passed the old model_name argument to VertexAIEmbeddings. The shared and per-database models now come from create_embedding_model, so the two paths cannot drift. Signed-off-by: Vitor Bandeira <vvbandeira@precisioninno.com> * fix(backend): share the HuggingFace reranker only when it is used With RERANKER_TYPE=VERTEX_AI, the chains build a Vertex AI reranker. Building the shared CrossEncoder anyway downloaded and loaded a model that no chain used. Signed-off-by: Vitor Bandeira <vvbandeira@precisioninno.com> --------- Signed-off-by: Harsh Kumar <harshkumar3446@gmail.com> Signed-off-by: Vitor Bandeira <vvbandeira@precisioninno.com> Co-authored-by: Harsh Kumar <harshkumar3446@gmail.com>
1 parent 92d452e commit d240d9d

7 files changed

Lines changed: 198 additions & 44 deletions

File tree

‎backend/src/agents/retriever_tools.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import os
2+
import logging
23
from typing import Tuple, Optional, Union
34
from dotenv import load_dotenv
45

56
from langchain_core.tools import tool
67
from langchain_classic.retrievers import EnsembleRetriever
78
from langchain_classic.retrievers import ContextualCompressionRetriever
9+
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
810

911
from ..chains.hybrid_retriever_chain import HybridRetrieverChain
12+
from ..vectorstores.faiss import create_embedding_model
1013
from ..tools.format_docs import format_docs
1114

1215
load_dotenv()
@@ -46,6 +49,19 @@ def initialize(
4649
use_cuda: bool = False,
4750
fast_mode: bool = False,
4851
) -> None:
52+
# Create shared model instances once
53+
embedding_model = create_embedding_model(
54+
embeddings_config["type"], embeddings_config["name"], use_cuda
55+
)
56+
logging.info("Shared embedding model created.")
57+
58+
# Only the HuggingFace reranker is a local model worth sharing. The
59+
# chains build the Vertex AI reranker as an API client.
60+
reranker_model: Optional[HuggingFaceCrossEncoder] = None
61+
if os.getenv("RERANKER_TYPE", "HF").upper() != "VERTEX_AI":
62+
reranker_model = HuggingFaceCrossEncoder(model_name=reranking_model_name)
63+
logging.info("Shared reranker model created.")
64+
4965
markdown_docs_map = {
5066
"general": [
5167
"./data/markdown/OR_docs",
@@ -100,6 +116,8 @@ def initialize(
100116
contextual_rerank=True,
101117
search_k=search_k,
102118
chunk_size=chunk_size,
119+
embedding_model=embedding_model,
120+
reranker_model=reranker_model,
103121
)
104122
general_retriever_chain.create_hybrid_retriever()
105123
RetrieverTools.general_retriever = general_retriever_chain.retriever
@@ -115,6 +133,8 @@ def initialize(
115133
contextual_rerank=True,
116134
search_k=search_k,
117135
chunk_size=chunk_size,
136+
embedding_model=embedding_model,
137+
reranker_model=reranker_model,
118138
)
119139
install_retriever_chain.create_hybrid_retriever()
120140
RetrieverTools.install_retriever = install_retriever_chain.retriever
@@ -131,6 +151,8 @@ def initialize(
131151
contextual_rerank=True,
132152
search_k=search_k,
133153
chunk_size=chunk_size,
154+
embedding_model=embedding_model,
155+
reranker_model=reranker_model,
134156
)
135157
commands_retriever_chain.create_hybrid_retriever()
136158
RetrieverTools.commands_retriever = commands_retriever_chain.retriever
@@ -146,6 +168,8 @@ def initialize(
146168
contextual_rerank=True,
147169
search_k=search_k,
148170
chunk_size=chunk_size,
171+
embedding_model=embedding_model,
172+
reranker_model=reranker_model,
149173
)
150174
yosys_rtdocs_retriever_chain.create_hybrid_retriever()
151175
RetrieverTools.yosys_rtdocs_retriever = yosys_rtdocs_retriever_chain.retriever
@@ -161,6 +185,8 @@ def initialize(
161185
contextual_rerank=True,
162186
search_k=search_k,
163187
chunk_size=chunk_size,
188+
embedding_model=embedding_model,
189+
reranker_model=reranker_model,
164190
)
165191
klayout_retriever_chain.create_hybrid_retriever()
166192
RetrieverTools.klayout_retriever = klayout_retriever_chain.retriever
@@ -176,6 +202,8 @@ def initialize(
176202
contextual_rerank=True,
177203
search_k=search_k,
178204
chunk_size=chunk_size,
205+
embedding_model=embedding_model,
206+
reranker_model=reranker_model,
179207
)
180208
errinfo_retriever_chain.create_hybrid_retriever()
181209
RetrieverTools.errinfo_retriever = errinfo_retriever_chain.retriever

‎backend/src/chains/hybrid_retriever_chain.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .similarity_retriever_chain import SimilarityRetrieverChain
1616
from .mmr_retriever_chain import MMRRetrieverChain
1717
from .bm25_retriever_chain import BM25RetrieverChain
18-
from ..vectorstores.faiss import FAISSVectorDatabase
18+
from ..vectorstores.faiss import EmbeddingModel, FAISSVectorDatabase
1919

2020

2121
class HybridRetrieverChain(BaseChain):
@@ -37,6 +37,8 @@ def __init__(
3737
weights: list[float] = [0.33, 0.33, 0.33],
3838
chunk_size: int = 500,
3939
contextual_rerank: bool = False,
40+
embedding_model: Optional[EmbeddingModel] = None,
41+
reranker_model: Optional[HuggingFaceCrossEncoder] = None,
4042
):
4143
super().__init__(
4244
llm_model=llm_model,
@@ -47,6 +49,8 @@ def __init__(
4749

4850
self.reranking_model_name: Optional[str] = reranking_model_name
4951
self.use_cuda: bool = use_cuda
52+
self.embedding_model: Optional[EmbeddingModel] = embedding_model
53+
self.reranker_model: Optional[HuggingFaceCrossEncoder] = reranker_model
5054

5155
self.search_k: int = search_k
5256
self.weights: list[float] = weights
@@ -73,6 +77,7 @@ def create_hybrid_retriever(self) -> None:
7377
html_docs_path=self.html_docs_path,
7478
chunk_size=self.chunk_size,
7579
use_cuda=self.use_cuda,
80+
embedding_model=self.embedding_model,
7681
)
7782
if self.vector_db is None:
7883
cur_path = os.path.abspath(__file__)
@@ -147,12 +152,10 @@ def create_hybrid_retriever(self) -> None:
147152
)
148153
logging.info("Using Vertex AI reranker")
149154
else:
150-
compressor = CrossEncoderReranker(
151-
model=HuggingFaceCrossEncoder(
152-
model_name=self.reranking_model_name
153-
),
154-
top_n=self.search_k,
155+
reranker = self.reranker_model or HuggingFaceCrossEncoder(
156+
model_name=self.reranking_model_name
155157
)
158+
compressor = CrossEncoderReranker(model=reranker, top_n=self.search_k)
156159
logging.info("Using HuggingFace CrossEncoder reranker")
157160

158161
self.retriever = ContextualCompressionRetriever(

‎backend/src/chains/similarity_retriever_chain.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
55
from langchain_core.documents import Document
6-
from langchain_google_vertexai import ChatVertexAI
76
from langchain_google_genai import ChatGoogleGenerativeAI
7+
from langchain_google_vertexai import ChatVertexAI
88
from langchain_ollama import ChatOllama
99

10-
from ..vectorstores.faiss import FAISSVectorDatabase
10+
from ..vectorstores.faiss import EmbeddingModel, FAISSVectorDatabase
1111
from .base_chain import BaseChain
1212

1313

@@ -28,6 +28,7 @@ def __init__(
2828
embeddings_config: Optional[dict[str, str]] = None,
2929
use_cuda: bool = False,
3030
chunk_size: int = 500,
31+
embedding_model: Optional[EmbeddingModel] = None,
3132
):
3233
super().__init__(
3334
llm_model=llm_model,
@@ -40,6 +41,7 @@ def __init__(
4041

4142
self.embeddings_config: Optional[dict[str, str]] = embeddings_config
4243
self.use_cuda: bool = use_cuda
44+
self.embedding_model: Optional[EmbeddingModel] = embedding_model
4345

4446
self.markdown_docs_path: Optional[list[str]] = markdown_docs_path
4547
self.other_docs_path: Optional[list[str]] = other_docs_path
@@ -125,6 +127,7 @@ def create_vector_db(self) -> None:
125127
embeddings_model_name=self.embeddings_config["name"],
126128
embeddings_type=self.embeddings_config["type"],
127129
use_cuda=self.use_cuda,
130+
embedding_model=self.embedding_model,
128131
)
129132
else:
130133
raise ValueError("Embeddings model config not provided correctly.")

‎backend/src/vectorstores/faiss.py‎

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,38 @@ def embed_query(self, text: str, **kwargs: Any) -> list[float]:
7272
return super().embed_query(text, **kwargs)
7373

7474

75+
EmbeddingModel = Union[
76+
HuggingFaceEmbeddings, GoogleGenerativeAIEmbeddings, VertexAIEmbeddings
77+
]
78+
79+
80+
def create_embedding_model(
81+
embeddings_type: str, model_name: str, use_cuda: bool = False
82+
) -> EmbeddingModel:
83+
"""Build the embedding model for an EMBEDDINGS_TYPE value.
84+
85+
Every caller uses this function, so a shared model has the same
86+
configuration as one built by FAISSVectorDatabase.
87+
"""
88+
if embeddings_type == "GOOGLE_GENAI":
89+
logging.info("Using Google GenerativeAI embeddings...")
90+
return _RetryingGoogleGenerativeAIEmbeddings(
91+
model=model_name, task_type="retrieval_document"
92+
)
93+
if embeddings_type == "GOOGLE_VERTEXAI":
94+
logging.info("Using Google VertexAI embeddings...")
95+
return VertexAIEmbeddings(model=model_name)
96+
if embeddings_type == "HF":
97+
logging.info("Using HuggingFace embeddings...")
98+
return HuggingFaceEmbeddings(
99+
model_name=model_name,
100+
multi_process=False,
101+
encode_kwargs={"normalize_embeddings": True},
102+
model_kwargs={"device": "cuda" if use_cuda else "cpu"},
103+
)
104+
raise ValueError("Invalid embeddings type specified.")
105+
106+
75107
class FAISSVectorDatabase:
76108
def __init__(
77109
self,
@@ -80,37 +112,17 @@ def __init__(
80112
distance_strategy: DistanceStrategy = DistanceStrategy.COSINE,
81113
debug: bool = False,
82114
use_cuda: bool = False,
115+
embedding_model: Optional[EmbeddingModel] = None,
83116
):
84117
self.embeddings_model_name = embeddings_model_name
85118

86-
model_kwargs = {"device": "cuda"} if use_cuda else {"device": "cpu"}
87-
88-
self.embedding_model: Union[
89-
HuggingFaceEmbeddings, GoogleGenerativeAIEmbeddings, VertexAIEmbeddings
90-
]
91-
92-
if embeddings_type == "GOOGLE_GENAI":
93-
self.embedding_model = _RetryingGoogleGenerativeAIEmbeddings(
94-
model=self.embeddings_model_name,
95-
task_type="retrieval_document",
96-
)
97-
logging.info("Using Google GenerativeAI embeddings...")
98-
99-
elif embeddings_type == "GOOGLE_VERTEXAI":
100-
self.embedding_model = VertexAIEmbeddings(model=self.embeddings_model_name)
101-
logging.info("Using Google VertexAI embeddings...")
102-
103-
elif embeddings_type == "HF":
104-
self.embedding_model = HuggingFaceEmbeddings(
105-
model_name=self.embeddings_model_name,
106-
multi_process=False,
107-
encode_kwargs={"normalize_embeddings": True},
108-
model_kwargs=model_kwargs,
119+
self.embedding_model: EmbeddingModel = (
120+
embedding_model
121+
if embedding_model is not None
122+
else create_embedding_model(
123+
embeddings_type, self.embeddings_model_name, use_cuda
109124
)
110-
logging.info("Using HuggingFace embeddings...")
111-
112-
else:
113-
raise ValueError("Invalid embdeddings type specified.")
125+
)
114126

115127
self.debug = debug
116128
self.distance_strategy = distance_strategy

‎backend/tests/test_faiss_vectorstore.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_init_with_google_vertexai_embeddings(self):
6161

6262
def test_init_with_invalid_embeddings_type(self):
6363
"""Test initialization with invalid embeddings type raises error."""
64-
with pytest.raises(ValueError, match="Invalid embdeddings type specified"):
64+
with pytest.raises(ValueError, match="Invalid embeddings type specified"):
6565
FAISSVectorDatabase(
6666
embeddings_type="INVALID", embeddings_model_name="test-model"
6767
)

0 commit comments

Comments
 (0)