perf: batch BM25 sparse encoding in Qdrant insert (#5592)

This commit is contained in:
Jiangtian Feng
2026-06-23 16:29:16 +05:30
committed by GitHub
parent 87bd2d91e0
commit 7fa996261d
2 changed files with 133 additions and 6 deletions
+40 -6
View File
@@ -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