perf: batch BM25 sparse encoding in Qdrant insert (#5592)
This commit is contained in:
@@ -191,6 +191,43 @@ class Qdrant(VectorStoreBase):
|
||||
ids (list, optional): List of IDs corresponding to vectors. Defaults to None.
|
||||
"""
|
||||
logger.info(f"Inserting {len(vectors)} vectors into collection {self.collection_name}")
|
||||
|
||||
# Pre-compute BM25 sparse vectors in a single batch call. fastembed's
|
||||
# embed() accepts a list of texts, so batching avoids per-row encoder
|
||||
# overhead (model dispatch, tokenizer setup, etc.).
|
||||
bm25_sparse_vectors: list[Optional[SparseVector]] = [None] * len(vectors)
|
||||
if self._has_bm25_slot and payloads:
|
||||
texts_for_bm25: list[str] = []
|
||||
indices_for_bm25: list[int] = []
|
||||
for idx, payload in enumerate(payloads):
|
||||
text = payload.get("text_lemmatized") or payload.get("data", "")
|
||||
if text:
|
||||
texts_for_bm25.append(text)
|
||||
indices_for_bm25.append(idx)
|
||||
|
||||
if texts_for_bm25:
|
||||
encoder = self._get_bm25_encoder()
|
||||
if encoder is not None:
|
||||
try:
|
||||
sparse_results = list(encoder.embed(texts_for_bm25))
|
||||
if len(sparse_results) != len(texts_for_bm25):
|
||||
logger.warning(
|
||||
f"BM25 batch returned {len(sparse_results)} results for "
|
||||
f"{len(texts_for_bm25)} texts; falling back to per-row encoding"
|
||||
)
|
||||
raise ValueError("count mismatch")
|
||||
for i, sparse in enumerate(sparse_results):
|
||||
bm25_sparse_vectors[indices_for_bm25[i]] = SparseVector(
|
||||
indices=sparse.indices.tolist(),
|
||||
values=sparse.values.tolist(),
|
||||
)
|
||||
except Exception as e:
|
||||
# Fall back to per-row encoding so a single bad input
|
||||
# doesn't drop BM25 for the whole batch.
|
||||
logger.debug(f"Batch BM25 encoding failed, falling back to per-row: {e}")
|
||||
for i, text in enumerate(texts_for_bm25):
|
||||
bm25_sparse_vectors[indices_for_bm25[i]] = self._encode_bm25(text)
|
||||
|
||||
points = []
|
||||
for idx, vector in enumerate(vectors):
|
||||
payload = payloads[idx] if payloads else {}
|
||||
@@ -198,12 +235,8 @@ class Qdrant(VectorStoreBase):
|
||||
|
||||
# Build named vectors: dense + optional BM25 sparse (only if collection has the slot).
|
||||
named_vectors = {"": vector}
|
||||
if self._has_bm25_slot:
|
||||
text_for_bm25 = payload.get("text_lemmatized") or payload.get("data", "")
|
||||
if text_for_bm25:
|
||||
sparse = self._encode_bm25(text_for_bm25)
|
||||
if sparse is not None:
|
||||
named_vectors["bm25"] = sparse
|
||||
if self._has_bm25_slot and bm25_sparse_vectors[idx] is not None:
|
||||
named_vectors["bm25"] = bm25_sparse_vectors[idx]
|
||||
|
||||
points.append(PointStruct(id=point_id, vector=named_vectors, payload=payload))
|
||||
|
||||
@@ -476,6 +509,7 @@ class Qdrant(VectorStoreBase):
|
||||
if self._has_bm25_slot:
|
||||
text_for_bm25 = payload.get("text_lemmatized") or payload.get("data", "")
|
||||
if text_for_bm25:
|
||||
# Single-item update: per-row encoding is correct here; see insert() for the batch path.
|
||||
sparse = self._encode_bm25(text_for_bm25)
|
||||
if sparse is not None:
|
||||
named_vectors["bm25"] = sparse
|
||||
|
||||
Reference in New Issue
Block a user