fastembed-gpu 0.7.2__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/PKG-INFO +13 -6
  2. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/model_management.py +26 -11
  3. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/onnx_model.py +38 -0
  4. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/onnx_embedding.py +3 -0
  5. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/onnx_image_model.py +6 -0
  6. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/colbert.py +65 -34
  7. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/late_interaction_embedding_base.py +9 -0
  8. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/late_interaction_text_embedding.py +27 -0
  9. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction_multimodal/colpali.py +22 -1
  10. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction_multimodal/late_interaction_multimodal_embedding.py +21 -0
  11. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction_multimodal/late_interaction_multimodal_embedding_base.py +8 -0
  12. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction_multimodal/onnx_multimodal_model.py +10 -0
  13. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/onnx_text_cross_encoder.py +17 -0
  14. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/onnx_text_model.py +20 -0
  15. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/text_cross_encoder.py +14 -0
  16. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/text_cross_encoder_base.py +4 -0
  17. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/bm25.py +9 -0
  18. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/bm42.py +20 -2
  19. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/minicoil.py +18 -2
  20. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/sparse_embedding_base.py +4 -0
  21. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/sparse_text_embedding.py +14 -0
  22. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/splade_pp.py +8 -0
  23. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/onnx_embedding.py +8 -1
  24. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/onnx_text_model.py +21 -0
  25. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/text_embedding.py +14 -0
  26. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/text_embedding_base.py +4 -0
  27. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/pyproject.toml +17 -8
  28. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/LICENSE +0 -0
  29. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/NOTICE +0 -0
  30. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/README.md +0 -0
  31. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/__init__.py +0 -0
  32. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/__init__.py +0 -0
  33. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/model_description.py +0 -0
  34. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/preprocessor_utils.py +0 -0
  35. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/types.py +0 -0
  36. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/common/utils.py +0 -0
  37. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/embedding.py +0 -0
  38. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/__init__.py +0 -0
  39. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/image_embedding.py +0 -0
  40. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/image_embedding_base.py +0 -0
  41. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/transform/functional.py +0 -0
  42. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/image/transform/operators.py +0 -0
  43. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/__init__.py +0 -0
  44. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/jina_colbert.py +0 -0
  45. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction/token_embeddings.py +0 -0
  46. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/late_interaction_multimodal/__init__.py +0 -0
  47. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/parallel_processor.py +0 -0
  48. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/postprocess/__init__.py +0 -0
  49. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/postprocess/muvera.py +0 -0
  50. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/py.typed +0 -0
  51. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/__init__.py +0 -0
  52. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/rerank/cross_encoder/custom_text_cross_encoder.py +0 -0
  53. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/__init__.py +0 -0
  54. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/utils/minicoil_encoder.py +0 -0
  55. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/utils/sparse_vectors_converter.py +0 -0
  56. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/utils/tokenizer.py +0 -0
  57. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/sparse/utils/vocab_resolver.py +0 -0
  58. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/__init__.py +0 -0
  59. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/clip_embedding.py +0 -0
  60. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/custom_text_embedding.py +0 -0
  61. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/multitask_embedding.py +0 -0
  62. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/pooled_embedding.py +0 -0
  63. {fastembed_gpu-0.7.2 → fastembed_gpu-0.7.4}/fastembed/text/pooled_normalized_embedding.py +0 -0
@@ -1,8 +1,10 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: fastembed-gpu
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Fast, light, accurate library built for retrieval embedding generation
5
5
  License: Apache License
6
+ License-File: LICENSE
7
+ License-File: NOTICE
6
8
  Keywords: vector,embedding,neural,search,qdrant,sentence-transformers
7
9
  Author: Qdrant Team
8
10
  Author-email: info@qdrant.tech
@@ -14,17 +16,22 @@ Classifier: Programming Language :: Python :: 3.10
14
16
  Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: Programming Language :: Python :: 3.12
16
18
  Classifier: Programming Language :: Python :: 3.13
17
- Requires-Dist: huggingface-hub (>=0.20,<1.0)
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Dist: huggingface-hub (>=0.20,<2.0)
18
21
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
19
22
  Requires-Dist: mmh3 (>=4.1.0,<6.0.0)
20
- Requires-Dist: numpy (>=1.21) ; python_version >= "3.10" and python_version < "3.12"
23
+ Requires-Dist: numpy (>=1.21) ; python_version == "3.11"
21
24
  Requires-Dist: numpy (>=1.21,<2.1.0) ; python_version < "3.10"
25
+ Requires-Dist: numpy (>=1.21,<2.3.0) ; python_version == "3.10"
22
26
  Requires-Dist: numpy (>=1.26) ; python_version == "3.12"
23
- Requires-Dist: numpy (>=2.1.0) ; python_version >= "3.13"
27
+ Requires-Dist: numpy (>=2.1.0) ; python_version == "3.13"
28
+ Requires-Dist: numpy (>=2.3.0) ; python_version >= "3.14"
24
29
  Requires-Dist: onnxruntime-gpu (>1.20.0) ; python_version >= "3.13"
25
30
  Requires-Dist: onnxruntime-gpu (>=1.17.0,!=1.20.0) ; python_version >= "3.10" and python_version < "3.13"
26
31
  Requires-Dist: onnxruntime-gpu (>=1.17.0,<1.20.0) ; python_version < "3.10"
27
- Requires-Dist: pillow (>=10.3.0,<12.0.0)
32
+ Requires-Dist: pillow (>=10.3.0,<11.0) ; python_version < "3.10"
33
+ Requires-Dist: pillow (>=10.3.0,<12.0) ; python_version >= "3.10" and python_version < "3.13"
34
+ Requires-Dist: pillow (>=11.0.0,<12.0) ; python_version >= "3.13"
28
35
  Requires-Dist: py-rust-stemmers (>=0.1.0,<0.2.0)
29
36
  Requires-Dist: requests (>=2.31,<3.0)
30
37
  Requires-Dist: tokenizers (>=0.15,<1.0)
@@ -3,6 +3,7 @@ import time
3
3
  import json
4
4
  import shutil
5
5
  import tarfile
6
+ from copy import deepcopy
6
7
  from pathlib import Path
7
8
  from typing import Any, Optional, Union, TypeVar, Generic
8
9
 
@@ -224,11 +225,6 @@ class ModelManagement(Generic[T]):
224
225
  logger.warning(
225
226
  "Local file sizes do not match the metadata."
226
227
  ) # do not raise, still make an attempt to load the model
227
- else:
228
- logger.warning(
229
- "Metadata file not found. Proceeding without checking local files."
230
- ) # if users have downloaded models from hf manually, or they're updating from previous versions of
231
- # fastembed
232
228
  result = snapshot_download(
233
229
  repo_id=hf_source_repo,
234
230
  allow_patterns=allow_patterns,
@@ -408,14 +404,32 @@ class ModelManagement(Generic[T]):
408
404
  hf_source = model.sources.hf
409
405
  url_source = model.sources.url
410
406
 
407
+ extra_patterns = [model.model_file]
408
+ extra_patterns.extend(model.additional_files)
409
+
410
+ if hf_source:
411
+ try:
412
+ cache_kwargs = deepcopy(kwargs)
413
+ cache_kwargs["local_files_only"] = True
414
+ return Path(
415
+ cls.download_files_from_huggingface(
416
+ hf_source,
417
+ cache_dir=cache_dir,
418
+ extra_patterns=extra_patterns,
419
+ **cache_kwargs,
420
+ )
421
+ )
422
+ except Exception:
423
+ pass
424
+ finally:
425
+ enable_progress_bars()
426
+
411
427
  sleep = 3.0
412
428
  while retries > 0:
413
429
  retries -= 1
414
430
 
415
- if hf_source:
416
- extra_patterns = [model.model_file]
417
- extra_patterns.extend(model.additional_files)
418
-
431
+ if hf_source and not local_files_only:
432
+ # we have already tried loading with `local_files_only=True` via hf and we failed
419
433
  try:
420
434
  return Path(
421
435
  cls.download_files_from_huggingface(
@@ -448,11 +462,12 @@ class ModelManagement(Generic[T]):
448
462
 
449
463
  if local_files_only:
450
464
  logger.error("Could not find model in cache_dir")
465
+ break
451
466
  else:
452
467
  logger.error(
453
468
  f"Could not download model from either source, sleeping for {sleep} seconds, {retries} retries left."
454
469
  )
455
- time.sleep(sleep)
456
- sleep *= 3
470
+ time.sleep(sleep)
471
+ sleep *= 3
457
472
 
458
473
  raise ValueError(f"Could not load model {model.model} from any source.")
@@ -24,6 +24,8 @@ class OnnxOutputContext:
24
24
 
25
25
 
26
26
  class OnnxModel(Generic[T]):
27
+ EXPOSED_SESSION_OPTIONS = ("enable_cpu_mem_arena",)
28
+
27
29
  @classmethod
28
30
  def _get_worker_class(cls) -> Type["EmbeddingWorker[T]"]:
29
31
  raise NotImplementedError("Subclasses must implement this method")
@@ -60,6 +62,7 @@ class OnnxModel(Generic[T]):
60
62
  providers: Optional[Sequence[OnnxProvider]] = None,
61
63
  cuda: bool = False,
62
64
  device_id: Optional[int] = None,
65
+ extra_session_options: Optional[dict[str, Any]] = None,
63
66
  ) -> None:
64
67
  model_path = model_dir / model_file
65
68
  # List of Execution Providers: https://onnxruntime.ai/docs/execution-providers
@@ -99,6 +102,9 @@ class OnnxModel(Generic[T]):
99
102
  so.intra_op_num_threads = threads
100
103
  so.inter_op_num_threads = threads
101
104
 
105
+ if extra_session_options is not None:
106
+ self.add_extra_session_options(so, extra_session_options)
107
+
102
108
  self.model = ort.InferenceSession(
103
109
  str(model_path), providers=onnx_providers, sess_options=so
104
110
  )
@@ -113,6 +119,38 @@ class OnnxModel(Generic[T]):
113
119
  RuntimeWarning,
114
120
  )
115
121
 
122
+ @classmethod
123
+ def _select_exposed_session_options(cls, model_kwargs: dict[str, Any]) -> dict[str, Any]:
124
+ """A convenience method to select the exposed session options in models
125
+
126
+ Args:
127
+ model_kwargs (dict[str, Any]): The model kwargs.
128
+
129
+ Returns:
130
+ dict[str, Any]: a dict with filtered exposed session options.
131
+ """
132
+ return {k: v for k, v in model_kwargs.items() if k in cls.EXPOSED_SESSION_OPTIONS}
133
+
134
+ @classmethod
135
+ def add_extra_session_options(
136
+ cls, session_options: ort.SessionOptions, extra_options: dict[str, Any]
137
+ ) -> None:
138
+ """Add extra session options to the existing options object in-place
139
+
140
+ Args:
141
+ session_options (ort.SessionOptions): The existing session options object.
142
+ extra_options (dict[str, Any]): The extra session options available in cls.EXPOSED_SESSION_OPTIONS.
143
+
144
+ Returns:
145
+ None
146
+ """
147
+ for option in extra_options:
148
+ assert (
149
+ option in cls.EXPOSED_SESSION_OPTIONS
150
+ ), f"{option} is unknown or not exposed (exposed options: {cls.EXPOSED_SESSION_OPTIONS})"
151
+ if "enable_cpu_mem_arena" in extra_options:
152
+ session_options.enable_cpu_mem_arena = extra_options["enable_cpu_mem_arena"]
153
+
116
154
  def load_onnx_model(self) -> None:
117
155
  raise NotImplementedError("Subclasses must implement this method")
118
156
 
@@ -98,6 +98,7 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[NumpyArray]):
98
98
  super().__init__(model_name, cache_dir, threads, **kwargs)
99
99
  self.providers = providers
100
100
  self.lazy_load = lazy_load
101
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
101
102
 
102
103
  # List of device ids, that can be used for data parallel processing in workers
103
104
  self.device_ids = device_ids
@@ -134,6 +135,7 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[NumpyArray]):
134
135
  providers=self.providers,
135
136
  cuda=self.cuda,
136
137
  device_id=self.device_id,
138
+ extra_session_options=self._extra_session_options,
137
139
  )
138
140
 
139
141
  @classmethod
@@ -180,6 +182,7 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[NumpyArray]):
180
182
  device_ids=self.device_ids,
181
183
  local_files_only=self._local_files_only,
182
184
  specific_model_path=self._specific_model_path,
185
+ extra_session_options=self._extra_session_options,
183
186
  **kwargs,
184
187
  )
185
188
 
@@ -55,6 +55,7 @@ class OnnxImageModel(OnnxModel[T]):
55
55
  providers: Optional[Sequence[OnnxProvider]] = None,
56
56
  cuda: bool = False,
57
57
  device_id: Optional[int] = None,
58
+ extra_session_options: Optional[dict[str, Any]] = None,
58
59
  ) -> None:
59
60
  super()._load_onnx_model(
60
61
  model_dir=model_dir,
@@ -63,6 +64,7 @@ class OnnxImageModel(OnnxModel[T]):
63
64
  providers=providers,
64
65
  cuda=cuda,
65
66
  device_id=device_id,
67
+ extra_session_options=extra_session_options,
66
68
  )
67
69
  self.processor = load_preprocessor(model_dir=model_dir)
68
70
 
@@ -99,6 +101,7 @@ class OnnxImageModel(OnnxModel[T]):
99
101
  device_ids: Optional[list[int]] = None,
100
102
  local_files_only: bool = False,
101
103
  specific_model_path: Optional[str] = None,
104
+ extra_session_options: Optional[dict[str, Any]] = None,
102
105
  **kwargs: Any,
103
106
  ) -> Iterable[T]:
104
107
  is_small = False
@@ -130,6 +133,9 @@ class OnnxImageModel(OnnxModel[T]):
130
133
  **kwargs,
131
134
  }
132
135
 
136
+ if extra_session_options is not None:
137
+ params.update(extra_session_options)
138
+
133
139
  pool = ParallelWorkerPool(
134
140
  num_workers=parallel or 1,
135
141
  worker=self._get_worker_class(),
@@ -2,12 +2,13 @@ import string
2
2
  from typing import Any, Iterable, Optional, Sequence, Type, Union
3
3
 
4
4
  import numpy as np
5
- from tokenizers import Encoding
5
+ from tokenizers import Encoding, Tokenizer
6
6
 
7
+ from fastembed.common.preprocessor_utils import load_tokenizer
7
8
  from fastembed.common.types import NumpyArray
8
9
  from fastembed.common import OnnxProvider
9
10
  from fastembed.common.onnx_model import OnnxOutputContext
10
- from fastembed.common.utils import define_cache_dir
11
+ from fastembed.common.utils import define_cache_dir, iter_batch
11
12
  from fastembed.late_interaction.late_interaction_embedding_base import (
12
13
  LateInteractionTextEmbeddingBase,
13
14
  )
@@ -48,24 +49,24 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
48
49
  if not is_doc:
49
50
  for embedding in output.model_output:
50
51
  yield embedding
52
+ else:
53
+ if output.input_ids is None or output.attention_mask is None:
54
+ raise ValueError(
55
+ "input_ids and attention_mask must be provided for document post-processing"
56
+ )
51
57
 
52
- if output.input_ids is None or output.attention_mask is None:
53
- raise ValueError(
54
- "input_ids and attention_mask must be provided for document post-processing"
55
- )
56
-
57
- for i, token_sequence in enumerate(output.input_ids):
58
- for j, token_id in enumerate(token_sequence): # type: ignore
59
- if token_id in self.skip_list or token_id == self.pad_token_id:
60
- output.attention_mask[i, j] = 0
58
+ for i, token_sequence in enumerate(output.input_ids):
59
+ for j, token_id in enumerate(token_sequence): # type: ignore
60
+ if token_id in self.skip_list or token_id == self.pad_token_id:
61
+ output.attention_mask[i, j] = 0
61
62
 
62
- output.model_output *= np.expand_dims(output.attention_mask, 2)
63
- norm = np.linalg.norm(output.model_output, ord=2, axis=2, keepdims=True)
64
- norm_clamped = np.maximum(norm, 1e-12)
65
- output.model_output /= norm_clamped
63
+ output.model_output *= np.expand_dims(output.attention_mask, 2)
64
+ norm = np.linalg.norm(output.model_output, ord=2, axis=2, keepdims=True)
65
+ norm_clamped = np.maximum(norm, 1e-12)
66
+ output.model_output /= norm_clamped
66
67
 
67
- for embedding, attention_mask in zip(output.model_output, output.attention_mask):
68
- yield embedding[attention_mask == 1]
68
+ for embedding, attention_mask in zip(output.model_output, output.attention_mask):
69
+ yield embedding[attention_mask == 1]
69
70
 
70
71
  def _preprocess_onnx_input(
71
72
  self, onnx_input: dict[str, NumpyArray], is_doc: bool = True, **kwargs: Any
@@ -87,29 +88,46 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
87
88
  )
88
89
 
89
90
  def _tokenize_query(self, query: str) -> list[Encoding]:
90
- assert self.tokenizer is not None
91
- encoded = self.tokenizer.encode_batch([query])
92
- # colbert authors recommend to pad queries with [MASK] tokens for query augmentation to improve performance
93
- if len(encoded[0].ids) < self.MIN_QUERY_LENGTH:
94
- prev_padding = None
95
- if self.tokenizer.padding:
96
- prev_padding = self.tokenizer.padding
97
- self.tokenizer.enable_padding(
98
- pad_token=self.MASK_TOKEN,
99
- pad_id=self.mask_token_id,
100
- length=self.MIN_QUERY_LENGTH,
101
- )
102
- encoded = self.tokenizer.encode_batch([query])
103
- if prev_padding is None:
104
- self.tokenizer.no_padding()
105
- else:
106
- self.tokenizer.enable_padding(**prev_padding)
91
+ assert self.query_tokenizer is not None
92
+ encoded = self.query_tokenizer.encode_batch([query])
107
93
  return encoded
108
94
 
109
95
  def _tokenize_documents(self, documents: list[str]) -> list[Encoding]:
110
96
  encoded = self.tokenizer.encode_batch(documents) # type: ignore[union-attr]
111
97
  return encoded
112
98
 
99
+ def token_count(
100
+ self,
101
+ texts: Union[str, Iterable[str]],
102
+ batch_size: int = 1024,
103
+ is_doc: bool = True,
104
+ include_extension: bool = False,
105
+ **kwargs: Any,
106
+ ) -> int:
107
+ if not hasattr(self, "model") or self.model is None:
108
+ self.load_onnx_model() # loads the tokenizer as well
109
+ token_num = 0
110
+ texts = [texts] if isinstance(texts, str) else texts
111
+ tokenizer = self.tokenizer if is_doc else self.query_tokenizer
112
+ assert tokenizer is not None
113
+ for batch in iter_batch(texts, batch_size):
114
+ for tokens in tokenizer.encode_batch(batch):
115
+ if is_doc:
116
+ token_num += sum(tokens.attention_mask)
117
+ else:
118
+ attend_count = sum(tokens.attention_mask)
119
+ if include_extension:
120
+ token_num += max(attend_count, self.MIN_QUERY_LENGTH)
121
+
122
+ else:
123
+ token_num += attend_count
124
+ if include_extension:
125
+ token_num += len(
126
+ batch
127
+ ) # add 1 for each cls.DOC_MARKER_TOKEN_ID or cls.QUERY_MARKER_TOKEN_ID
128
+
129
+ return token_num
130
+
113
131
  @classmethod
114
132
  def _list_supported_models(cls) -> list[DenseModelDescription]:
115
133
  """Lists the supported models.
@@ -157,6 +175,7 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
157
175
  super().__init__(model_name, cache_dir, threads, **kwargs)
158
176
  self.providers = providers
159
177
  self.lazy_load = lazy_load
178
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
160
179
 
161
180
  # List of device ids, that can be used for data parallel processing in workers
162
181
  self.device_ids = device_ids
@@ -183,6 +202,8 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
183
202
  self.pad_token_id: Optional[int] = None
184
203
  self.skip_list: set[int] = set()
185
204
 
205
+ self.query_tokenizer: Optional[Tokenizer] = None
206
+
186
207
  if not self.lazy_load:
187
208
  self.load_onnx_model()
188
209
 
@@ -194,7 +215,10 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
194
215
  providers=self.providers,
195
216
  cuda=self.cuda,
196
217
  device_id=self.device_id,
218
+ extra_session_options=self._extra_session_options,
197
219
  )
220
+ self.query_tokenizer, _ = load_tokenizer(model_dir=self._model_dir)
221
+
198
222
  assert self.tokenizer is not None
199
223
  self.mask_token_id = self.special_token_to_id[self.MASK_TOKEN]
200
224
  self.pad_token_id = self.tokenizer.padding["pad_id"]
@@ -205,6 +229,12 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
205
229
  current_max_length = self.tokenizer.truncation["max_length"]
206
230
  # ensure not to overflow after adding document-marker
207
231
  self.tokenizer.enable_truncation(max_length=current_max_length - 1)
232
+ self.query_tokenizer.enable_truncation(max_length=current_max_length - 1)
233
+ self.query_tokenizer.enable_padding(
234
+ pad_token=self.MASK_TOKEN,
235
+ pad_id=self.mask_token_id,
236
+ length=self.MIN_QUERY_LENGTH,
237
+ )
208
238
 
209
239
  def embed(
210
240
  self,
@@ -239,6 +269,7 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
239
269
  device_ids=self.device_ids,
240
270
  local_files_only=self._local_files_only,
241
271
  specific_model_path=self._specific_model_path,
272
+ extra_session_options=self._extra_session_options,
242
273
  **kwargs,
243
274
  )
244
275
 
@@ -69,3 +69,12 @@ class LateInteractionTextEmbeddingBase(ModelManagement[DenseModelDescription]):
69
69
  def embedding_size(self) -> int:
70
70
  """Returns embedding size for the current model"""
71
71
  raise NotImplementedError("Subclasses must implement this method")
72
+
73
+ def token_count(
74
+ self,
75
+ texts: Union[str, Iterable[str]],
76
+ batch_size: int = 1024,
77
+ **kwargs: Any,
78
+ ) -> int:
79
+ """Returns the number of tokens in the texts."""
80
+ raise NotImplementedError("Subclasses must implement this method")
@@ -151,3 +151,30 @@ class LateInteractionTextEmbedding(LateInteractionTextEmbeddingBase):
151
151
 
152
152
  # This is model-specific, so that different models can have specialized implementations
153
153
  yield from self.model.query_embed(query, **kwargs)
154
+
155
+ def token_count(
156
+ self,
157
+ texts: Union[str, Iterable[str]],
158
+ batch_size: int = 1024,
159
+ is_doc: bool = True,
160
+ include_extension: bool = False,
161
+ **kwargs: Any,
162
+ ) -> int:
163
+ """Returns the number of tokens in the texts.
164
+
165
+ Args:
166
+ texts (str | Iterable[str]): The list of texts to embed.
167
+ batch_size (int): Batch size for encoding
168
+ is_doc (bool): Whether the texts are documents (disable embedding a query with include_mask=True).
169
+ include_extension (bool): Turn on to count DOC / QUERY marker tokens, and [MASK] token in query mode.
170
+
171
+ Returns:
172
+ int: Sum of number of tokens in the texts.
173
+ """
174
+ return self.model.token_count(
175
+ texts,
176
+ batch_size=batch_size,
177
+ is_doc=is_doc,
178
+ include_extension=include_extension,
179
+ **kwargs,
180
+ )
@@ -6,7 +6,7 @@ from tokenizers import Encoding
6
6
  from fastembed.common import OnnxProvider, ImageInput
7
7
  from fastembed.common.onnx_model import OnnxOutputContext
8
8
  from fastembed.common.types import NumpyArray
9
- from fastembed.common.utils import define_cache_dir
9
+ from fastembed.common.utils import define_cache_dir, iter_batch
10
10
  from fastembed.late_interaction_multimodal.late_interaction_multimodal_embedding_base import (
11
11
  LateInteractionMultimodalEmbeddingBase,
12
12
  )
@@ -80,6 +80,7 @@ class ColPali(LateInteractionMultimodalEmbeddingBase, OnnxMultimodalModel[NumpyA
80
80
  super().__init__(model_name, cache_dir, threads, **kwargs)
81
81
  self.providers = providers
82
82
  self.lazy_load = lazy_load
83
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
83
84
 
84
85
  # List of device ids, that can be used for data parallel processing in workers
85
86
  self.device_ids = device_ids
@@ -125,6 +126,7 @@ class ColPali(LateInteractionMultimodalEmbeddingBase, OnnxMultimodalModel[NumpyA
125
126
  providers=self.providers,
126
127
  cuda=self.cuda,
127
128
  device_id=self.device_id,
129
+ extra_session_options=self._extra_session_options,
128
130
  )
129
131
 
130
132
  def _post_process_onnx_image_output(
@@ -170,6 +172,23 @@ class ColPali(LateInteractionMultimodalEmbeddingBase, OnnxMultimodalModel[NumpyA
170
172
  encoded = self.tokenizer.encode_batch(texts_query) # type: ignore[union-attr]
171
173
  return encoded
172
174
 
175
+ def token_count(
176
+ self,
177
+ texts: Union[str, Iterable[str]],
178
+ batch_size: int = 1024,
179
+ include_extension: bool = False,
180
+ **kwargs: Any,
181
+ ) -> int:
182
+ if not hasattr(self, "model") or self.model is None:
183
+ self.load_onnx_model() # loads the tokenizer as well
184
+ token_num = 0
185
+ texts = [texts] if isinstance(texts, str) else texts
186
+ assert self.tokenizer is not None
187
+ tokenize_func = self.tokenize if include_extension else self.tokenizer.encode_batch
188
+ for batch in iter_batch(texts, batch_size):
189
+ token_num += sum([sum(encoding.attention_mask) for encoding in tokenize_func(batch)])
190
+ return token_num
191
+
173
192
  def _preprocess_onnx_text_input(
174
193
  self, onnx_input: dict[str, NumpyArray], **kwargs: Any
175
194
  ) -> dict[str, NumpyArray]:
@@ -238,6 +257,7 @@ class ColPali(LateInteractionMultimodalEmbeddingBase, OnnxMultimodalModel[NumpyA
238
257
  device_ids=self.device_ids,
239
258
  local_files_only=self._local_files_only,
240
259
  specific_model_path=self._specific_model_path,
260
+ extra_session_options=self._extra_session_options,
241
261
  **kwargs,
242
262
  )
243
263
 
@@ -273,6 +293,7 @@ class ColPali(LateInteractionMultimodalEmbeddingBase, OnnxMultimodalModel[NumpyA
273
293
  device_ids=self.device_ids,
274
294
  local_files_only=self._local_files_only,
275
295
  specific_model_path=self._specific_model_path,
296
+ extra_session_options=self._extra_session_options,
276
297
  **kwargs,
277
298
  )
278
299
 
@@ -162,3 +162,24 @@ class LateInteractionMultimodalEmbedding(LateInteractionMultimodalEmbeddingBase)
162
162
  List of embeddings, one per image
163
163
  """
164
164
  yield from self.model.embed_image(images, batch_size, parallel, **kwargs)
165
+
166
+ def token_count(
167
+ self,
168
+ texts: Union[str, Iterable[str]],
169
+ batch_size: int = 1024,
170
+ include_extension: bool = False,
171
+ **kwargs: Any,
172
+ ) -> int:
173
+ """Returns the number of tokens in the texts.
174
+
175
+ Args:
176
+ texts (str | Iterable[str]): The list of texts to embed.
177
+ batch_size (int): Batch size for encoding
178
+ include_extension (bool): Whether to include tokens added by preprocessing
179
+
180
+ Returns:
181
+ int: Sum of number of tokens in the texts.
182
+ """
183
+ return self.model.token_count(
184
+ texts, batch_size=batch_size, include_extension=include_extension, **kwargs
185
+ )
@@ -76,3 +76,11 @@ class LateInteractionMultimodalEmbeddingBase(ModelManagement[DenseModelDescripti
76
76
  def embedding_size(self) -> int:
77
77
  """Returns embedding size for the current model"""
78
78
  raise NotImplementedError("Subclasses must implement this method")
79
+
80
+ def token_count(
81
+ self,
82
+ texts: Union[str, Iterable[str]],
83
+ **kwargs: Any,
84
+ ) -> int:
85
+ """Returns the number of tokens in the texts."""
86
+ raise NotImplementedError("Subclasses must implement this method")
@@ -64,6 +64,7 @@ class OnnxMultimodalModel(OnnxModel[T]):
64
64
  providers: Optional[Sequence[OnnxProvider]] = None,
65
65
  cuda: bool = False,
66
66
  device_id: Optional[int] = None,
67
+ extra_session_options: Optional[dict[str, Any]] = None,
67
68
  ) -> None:
68
69
  super()._load_onnx_model(
69
70
  model_dir=model_dir,
@@ -72,6 +73,7 @@ class OnnxMultimodalModel(OnnxModel[T]):
72
73
  providers=providers,
73
74
  cuda=cuda,
74
75
  device_id=device_id,
76
+ extra_session_options=extra_session_options,
75
77
  )
76
78
  self.tokenizer, self.special_token_to_id = load_tokenizer(model_dir=model_dir)
77
79
  assert self.tokenizer is not None
@@ -122,6 +124,7 @@ class OnnxMultimodalModel(OnnxModel[T]):
122
124
  device_ids: Optional[list[int]] = None,
123
125
  local_files_only: bool = False,
124
126
  specific_model_path: Optional[str] = None,
127
+ extra_session_options: Optional[dict[str, Any]] = None,
125
128
  **kwargs: Any,
126
129
  ) -> Iterable[T]:
127
130
  is_small = False
@@ -153,6 +156,9 @@ class OnnxMultimodalModel(OnnxModel[T]):
153
156
  **kwargs,
154
157
  }
155
158
 
159
+ if extra_session_options is not None:
160
+ params.update(extra_session_options)
161
+
156
162
  pool = ParallelWorkerPool(
157
163
  num_workers=parallel or 1,
158
164
  worker=self._get_text_worker_class(),
@@ -189,6 +195,7 @@ class OnnxMultimodalModel(OnnxModel[T]):
189
195
  device_ids: Optional[list[int]] = None,
190
196
  local_files_only: bool = False,
191
197
  specific_model_path: Optional[str] = None,
198
+ extra_session_options: Optional[dict[str, Any]] = None,
192
199
  **kwargs: Any,
193
200
  ) -> Iterable[T]:
194
201
  is_small = False
@@ -220,6 +227,9 @@ class OnnxMultimodalModel(OnnxModel[T]):
220
227
  **kwargs,
221
228
  }
222
229
 
230
+ if extra_session_options is not None:
231
+ params.update(extra_session_options)
232
+
223
233
  pool = ParallelWorkerPool(
224
234
  num_workers=parallel or 1,
225
235
  worker=self._get_image_worker_class(),
@@ -111,6 +111,7 @@ class OnnxTextCrossEncoder(TextCrossEncoderBase, OnnxCrossEncoderModel):
111
111
  super().__init__(model_name, cache_dir, threads, **kwargs)
112
112
  self.providers = providers
113
113
  self.lazy_load = lazy_load
114
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
114
115
 
115
116
  # List of device ids, that can be used for data parallel processing in workers
116
117
  self.device_ids = device_ids
@@ -150,6 +151,7 @@ class OnnxTextCrossEncoder(TextCrossEncoderBase, OnnxCrossEncoderModel):
150
151
  providers=self.providers,
151
152
  cuda=self.cuda,
152
153
  device_id=self.device_id,
154
+ extra_session_options=self._extra_session_options,
153
155
  )
154
156
 
155
157
  def rerank(
@@ -192,6 +194,7 @@ class OnnxTextCrossEncoder(TextCrossEncoderBase, OnnxCrossEncoderModel):
192
194
  device_ids=self.device_ids,
193
195
  local_files_only=self._local_files_only,
194
196
  specific_model_path=self._specific_model_path,
197
+ extra_session_options=self._extra_session_options,
195
198
  **kwargs,
196
199
  )
197
200
 
@@ -204,6 +207,20 @@ class OnnxTextCrossEncoder(TextCrossEncoderBase, OnnxCrossEncoderModel):
204
207
  ) -> Iterable[float]:
205
208
  return (float(elem) for elem in output.model_output)
206
209
 
210
+ def token_count(
211
+ self, pairs: Iterable[tuple[str, str]], batch_size: int = 1024, **kwargs: Any
212
+ ) -> int:
213
+ """Returns the number of tokens in the pairs.
214
+
215
+ Args:
216
+ pairs: Iterable of tuples, where each tuple contains a query and a document to be tokenized
217
+ batch_size: Batch size for tokenizing
218
+
219
+ Returns:
220
+ token count: overall number of tokens in the pairs
221
+ """
222
+ return self._token_count(pairs, batch_size=batch_size, **kwargs)
223
+
207
224
 
208
225
  class TextCrossEncoderWorker(TextRerankerWorker):
209
226
  def init_embedding(
@@ -33,6 +33,7 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
33
33
  providers: Optional[Sequence[OnnxProvider]] = None,
34
34
  cuda: bool = False,
35
35
  device_id: Optional[int] = None,
36
+ extra_session_options: Optional[dict[str, Any]] = None,
36
37
  ) -> None:
37
38
  super()._load_onnx_model(
38
39
  model_dir=model_dir,
@@ -41,6 +42,7 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
41
42
  providers=providers,
42
43
  cuda=cuda,
43
44
  device_id=device_id,
45
+ extra_session_options=extra_session_options,
44
46
  )
45
47
  self.tokenizer, _ = load_tokenizer(model_dir=model_dir)
46
48
  assert self.tokenizer is not None
@@ -96,6 +98,7 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
96
98
  device_ids: Optional[list[int]] = None,
97
99
  local_files_only: bool = False,
98
100
  specific_model_path: Optional[str] = None,
101
+ extra_session_options: Optional[dict[str, Any]] = None,
99
102
  **kwargs: Any,
100
103
  ) -> Iterable[float]:
101
104
  is_small = False
@@ -127,6 +130,9 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
127
130
  **kwargs,
128
131
  }
129
132
 
133
+ if extra_session_options is not None:
134
+ params.update(extra_session_options)
135
+
130
136
  pool = ParallelWorkerPool(
131
137
  num_workers=parallel or 1,
132
138
  worker=self._get_worker_class(),
@@ -159,6 +165,20 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
159
165
  """
160
166
  return onnx_input
161
167
 
168
+ def _token_count(
169
+ self, pairs: Iterable[tuple[str, str]], batch_size: int = 1024, **_: Any
170
+ ) -> int:
171
+ if not hasattr(self, "model") or self.model is None:
172
+ self.load_onnx_model() # loads the tokenizer as well
173
+
174
+ token_num = 0
175
+ assert self.tokenizer is not None
176
+ for batch in iter_batch(pairs, batch_size):
177
+ for tokens in self.tokenizer.encode_batch(batch):
178
+ token_num += sum(tokens.attention_mask)
179
+
180
+ return token_num
181
+
162
182
 
163
183
  class TextRerankerWorker(EmbeddingWorker[float]):
164
184
  def __init__(
@@ -161,3 +161,17 @@ class TextCrossEncoder(TextCrossEncoderBase):
161
161
  additional_files=additional_files or [],
162
162
  )
163
163
  )
164
+
165
+ def token_count(
166
+ self, pairs: Iterable[tuple[str, str]], batch_size: int = 1024, **kwargs: Any
167
+ ) -> int:
168
+ """Returns the number of tokens in the pairs.
169
+
170
+ Args:
171
+ pairs: Iterable of tuples, where each tuple contains a query and a document to be tokenized
172
+ batch_size: Batch size for tokenizing
173
+
174
+ Returns:
175
+ token count: overall number of tokens in the pairs
176
+ """
177
+ return self.model.token_count(pairs, batch_size=batch_size, **kwargs)
@@ -57,3 +57,7 @@ class TextCrossEncoderBase(ModelManagement[BaseModelDescription]):
57
57
  Iterable[float]: Scores for each individual pair
58
58
  """
59
59
  raise NotImplementedError("This method should be overridden by subclasses")
60
+
61
+ def token_count(self, pairs: Iterable[tuple[str, str]], **kwargs: Any) -> int:
62
+ """Returns the number of tokens in the pairs."""
63
+ raise NotImplementedError("This method should be overridden by subclasses")
@@ -268,6 +268,15 @@ class Bm25(SparseTextEmbeddingBase):
268
268
  embeddings.append(SparseEmbedding.from_dict(token_id2value))
269
269
  return embeddings
270
270
 
271
+ def token_count(self, texts: Union[str, Iterable[str]], **kwargs: Any) -> int:
272
+ token_num = 0
273
+ texts = [texts] if isinstance(texts, str) else texts
274
+ for text in texts:
275
+ document = remove_non_alphanumeric(text)
276
+ tokens = self.tokenizer.tokenize(document)
277
+ token_num += len(tokens)
278
+ return token_num
279
+
271
280
  def _term_frequency(self, tokens: list[str]) -> dict[int, float]:
272
281
  """Calculate the term frequency part of the BM25 formula.
273
282
 
@@ -31,9 +31,17 @@ supported_bm42_models: list[SparseModelDescription] = [
31
31
  ),
32
32
  ]
33
33
 
34
- MODEL_TO_LANGUAGE = {
34
+
35
+ _MODEL_TO_LANGUAGE = {
35
36
  "Qdrant/bm42-all-minilm-l6-v2-attentions": "english",
36
37
  }
38
+ MODEL_TO_LANGUAGE = {
39
+ model_name.lower(): language for model_name, language in _MODEL_TO_LANGUAGE.items()
40
+ }
41
+
42
+
43
+ def get_language_by_model_name(model_name: str) -> str:
44
+ return MODEL_TO_LANGUAGE[model_name.lower()]
37
45
 
38
46
 
39
47
  class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
@@ -95,6 +103,7 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
95
103
  super().__init__(model_name, cache_dir, threads, **kwargs)
96
104
  self.providers = providers
97
105
  self.lazy_load = lazy_load
106
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
98
107
 
99
108
  # List of device ids, that can be used for data parallel processing in workers
100
109
  self.device_ids = device_ids
@@ -124,7 +133,7 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
124
133
  self.special_tokens_ids: set[int] = set()
125
134
  self.punctuation = set(string.punctuation)
126
135
  self.stopwords = set(self._load_stopwords(self._model_dir))
127
- self.stemmer = SnowballStemmer(MODEL_TO_LANGUAGE[model_name])
136
+ self.stemmer = SnowballStemmer(get_language_by_model_name(self.model_name))
128
137
  self.alpha = alpha
129
138
 
130
139
  if not self.lazy_load:
@@ -138,6 +147,7 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
138
147
  providers=self.providers,
139
148
  cuda=self.cuda,
140
149
  device_id=self.device_id,
150
+ extra_session_options=self._extra_session_options,
141
151
  )
142
152
 
143
153
  for token, idx in self.tokenizer.get_vocab().items(): # type: ignore[union-attr]
@@ -304,6 +314,7 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
304
314
  alpha=self.alpha,
305
315
  local_files_only=self._local_files_only,
306
316
  specific_model_path=self._specific_model_path,
317
+ extra_session_options=self._extra_session_options,
307
318
  )
308
319
 
309
320
  @classmethod
@@ -341,6 +352,13 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
341
352
  def _get_worker_class(cls) -> Type[TextEmbeddingWorker[SparseEmbedding]]:
342
353
  return Bm42TextEmbeddingWorker
343
354
 
355
+ def token_count(
356
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
357
+ ) -> int:
358
+ if not hasattr(self, "model") or self.model is None:
359
+ self.load_onnx_model() # loads the tokenizer as well
360
+ return self._token_count(texts, batch_size=batch_size, **kwargs)
361
+
344
362
 
345
363
  class Bm42TextEmbeddingWorker(TextEmbeddingWorker[SparseEmbedding]):
346
364
  def init_embedding(self, model_name: str, cache_dir: str, **kwargs: Any) -> Bm42:
@@ -46,9 +46,16 @@ supported_minicoil_models: list[SparseModelDescription] = [
46
46
  ),
47
47
  ]
48
48
 
49
- MODEL_TO_LANGUAGE = {
49
+ _MODEL_TO_LANGUAGE = {
50
50
  "Qdrant/minicoil-v1": "english",
51
51
  }
52
+ MODEL_TO_LANGUAGE = {
53
+ model_name.lower(): language for model_name, language in _MODEL_TO_LANGUAGE.items()
54
+ }
55
+
56
+
57
+ def get_language_by_model_name(model_name: str) -> str:
58
+ return MODEL_TO_LANGUAGE[model_name.lower()]
52
59
 
53
60
 
54
61
  class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
@@ -110,6 +117,8 @@ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
110
117
  self.device_ids = device_ids
111
118
  self.cuda = cuda
112
119
  self.device_id = device_id
120
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
121
+
113
122
  self.k = k
114
123
  self.b = b
115
124
  self.avg_len = avg_len
@@ -146,6 +155,7 @@ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
146
155
  providers=self.providers,
147
156
  cuda=self.cuda,
148
157
  device_id=self.device_id,
158
+ extra_session_options=self._extra_session_options,
149
159
  )
150
160
 
151
161
  assert self.tokenizer is not None
@@ -156,7 +166,7 @@ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
156
166
  self.special_tokens_ids = set(self.special_token_to_id.values())
157
167
  self.stopwords = set(self._load_stopwords(self._model_dir))
158
168
 
159
- stemmer = SnowballStemmer(MODEL_TO_LANGUAGE[self.model_name])
169
+ stemmer = SnowballStemmer(get_language_by_model_name(self.model_name))
160
170
 
161
171
  self.vocab_resolver = VocabResolver(
162
172
  tokenizer=VocabTokenizer(self.tokenizer),
@@ -177,6 +187,11 @@ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
177
187
  avg_len=self.avg_len,
178
188
  )
179
189
 
190
+ def token_count(
191
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
192
+ ) -> int:
193
+ return self._token_count(texts, batch_size=batch_size, **kwargs)
194
+
180
195
  def embed(
181
196
  self,
182
197
  documents: Union[str, Iterable[str]],
@@ -214,6 +229,7 @@ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
214
229
  is_query=False,
215
230
  local_files_only=self._local_files_only,
216
231
  specific_model_path=self._specific_model_path,
232
+ extra_session_options=self._extra_session_options,
217
233
  **kwargs,
218
234
  )
219
235
 
@@ -86,3 +86,7 @@ class SparseTextEmbeddingBase(ModelManagement[SparseModelDescription]):
86
86
  yield from self.embed([query], **kwargs)
87
87
  else:
88
88
  yield from self.embed(query, **kwargs)
89
+
90
+ def token_count(self, texts: Union[str, Iterable[str]], **kwargs: Any) -> int:
91
+ """Returns the number of tokens in the texts."""
92
+ raise NotImplementedError("Subclasses must implement this method")
@@ -128,3 +128,17 @@ class SparseTextEmbedding(SparseTextEmbeddingBase):
128
128
  Iterable[SparseEmbedding]: The sparse embeddings.
129
129
  """
130
130
  yield from self.model.query_embed(query, **kwargs)
131
+
132
+ def token_count(
133
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
134
+ ) -> int:
135
+ """Returns the number of tokens in the texts.
136
+
137
+ Args:
138
+ texts (str | Iterable[str]): The list of texts to embed.
139
+ batch_size (int): Batch size for encoding
140
+
141
+ Returns:
142
+ int: Sum of number of tokens in the texts.
143
+ """
144
+ return self.model.token_count(texts, batch_size=batch_size, **kwargs)
@@ -53,6 +53,11 @@ class SpladePP(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
53
53
  scores = row_scores[indices]
54
54
  yield SparseEmbedding(values=scores, indices=indices)
55
55
 
56
+ def token_count(
57
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
58
+ ) -> int:
59
+ return self._token_count(texts, batch_size=batch_size, **kwargs)
60
+
56
61
  @classmethod
57
62
  def _list_supported_models(cls) -> list[SparseModelDescription]:
58
63
  """Lists the supported models.
@@ -99,6 +104,7 @@ class SpladePP(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
99
104
  super().__init__(model_name, cache_dir, threads, **kwargs)
100
105
  self.providers = providers
101
106
  self.lazy_load = lazy_load
107
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
102
108
 
103
109
  # List of device ids, that can be used for data parallel processing in workers
104
110
  self.device_ids = device_ids
@@ -133,6 +139,7 @@ class SpladePP(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
133
139
  providers=self.providers,
134
140
  cuda=self.cuda,
135
141
  device_id=self.device_id,
142
+ extra_session_options=self._extra_session_options,
136
143
  )
137
144
 
138
145
  def embed(
@@ -168,6 +175,7 @@ class SpladePP(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
168
175
  device_ids=self.device_ids,
169
176
  local_files_only=self._local_files_only,
170
177
  specific_model_path=self._specific_model_path,
178
+ extra_session_options=self._extra_session_options,
171
179
  **kwargs,
172
180
  )
173
181
 
@@ -233,7 +233,7 @@ class OnnxTextEmbedding(TextEmbeddingBase, OnnxTextModel[NumpyArray]):
233
233
  super().__init__(model_name, cache_dir, threads, **kwargs)
234
234
  self.providers = providers
235
235
  self.lazy_load = lazy_load
236
-
236
+ self._extra_session_options = self._select_exposed_session_options(kwargs)
237
237
  # List of device ids, that can be used for data parallel processing in workers
238
238
  self.device_ids = device_ids
239
239
  self.cuda = cuda
@@ -291,6 +291,7 @@ class OnnxTextEmbedding(TextEmbeddingBase, OnnxTextModel[NumpyArray]):
291
291
  device_ids=self.device_ids,
292
292
  local_files_only=self._local_files_only,
293
293
  specific_model_path=self._specific_model_path,
294
+ extra_session_options=self._extra_session_options,
294
295
  **kwargs,
295
296
  )
296
297
 
@@ -327,8 +328,14 @@ class OnnxTextEmbedding(TextEmbeddingBase, OnnxTextModel[NumpyArray]):
327
328
  providers=self.providers,
328
329
  cuda=self.cuda,
329
330
  device_id=self.device_id,
331
+ extra_session_options=self._extra_session_options,
330
332
  )
331
333
 
334
+ def token_count(
335
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
336
+ ) -> int:
337
+ return self._token_count(texts, batch_size=batch_size, **kwargs)
338
+
332
339
 
333
340
  class OnnxTextEmbeddingWorker(TextEmbeddingWorker[NumpyArray]):
334
341
  def init_embedding(
@@ -54,6 +54,7 @@ class OnnxTextModel(OnnxModel[T]):
54
54
  providers: Optional[Sequence[OnnxProvider]] = None,
55
55
  cuda: bool = False,
56
56
  device_id: Optional[int] = None,
57
+ extra_session_options: Optional[dict[str, Any]] = None,
57
58
  ) -> None:
58
59
  super()._load_onnx_model(
59
60
  model_dir=model_dir,
@@ -62,6 +63,7 @@ class OnnxTextModel(OnnxModel[T]):
62
63
  providers=providers,
63
64
  cuda=cuda,
64
65
  device_id=device_id,
66
+ extra_session_options=extra_session_options,
65
67
  )
66
68
  self.tokenizer, self.special_token_to_id = load_tokenizer(model_dir=model_dir)
67
69
 
@@ -110,6 +112,7 @@ class OnnxTextModel(OnnxModel[T]):
110
112
  device_ids: Optional[list[int]] = None,
111
113
  local_files_only: bool = False,
112
114
  specific_model_path: Optional[str] = None,
115
+ extra_session_options: Optional[dict[str, Any]] = None,
113
116
  **kwargs: Any,
114
117
  ) -> Iterable[T]:
115
118
  is_small = False
@@ -143,6 +146,9 @@ class OnnxTextModel(OnnxModel[T]):
143
146
  **kwargs,
144
147
  }
145
148
 
149
+ if extra_session_options is not None:
150
+ params.update(extra_session_options)
151
+
146
152
  pool = ParallelWorkerPool(
147
153
  num_workers=parallel or 1,
148
154
  worker=self._get_worker_class(),
@@ -153,6 +159,21 @@ class OnnxTextModel(OnnxModel[T]):
153
159
  for batch in pool.ordered_map(iter_batch(documents, batch_size), **params):
154
160
  yield from self._post_process_onnx_output(batch, **kwargs) # type: ignore
155
161
 
162
+ def _token_count(
163
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **_: Any
164
+ ) -> int:
165
+ if not hasattr(self, "model") or self.model is None:
166
+ self.load_onnx_model() # loads the tokenizer as well
167
+
168
+ token_num = 0
169
+ assert self.tokenizer is not None
170
+ texts = [texts] if isinstance(texts, str) else texts
171
+ for batch in iter_batch(texts, batch_size):
172
+ for tokens in self.tokenizer.encode_batch(batch):
173
+ token_num += sum(tokens.attention_mask)
174
+
175
+ return token_num
176
+
156
177
 
157
178
  class TextEmbeddingWorker(EmbeddingWorker[T]):
158
179
  def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, OnnxOutputContext]]:
@@ -212,3 +212,17 @@ class TextEmbedding(TextEmbeddingBase):
212
212
  """
213
213
  # This is model-specific, so that different models can have specialized implementations
214
214
  yield from self.model.passage_embed(texts, **kwargs)
215
+
216
+ def token_count(
217
+ self, texts: Union[str, Iterable[str]], batch_size: int = 1024, **kwargs: Any
218
+ ) -> int:
219
+ """Returns the number of tokens in the texts.
220
+
221
+ Args:
222
+ texts (str | Iterable[str]): The list of texts to embed.
223
+ batch_size (int): Batch size for encoding
224
+
225
+ Returns:
226
+ int: Sum of number of tokens in the texts.
227
+ """
228
+ return self.model.token_count(texts, batch_size=batch_size, **kwargs)
@@ -69,3 +69,7 @@ class TextEmbeddingBase(ModelManagement[DenseModelDescription]):
69
69
  def embedding_size(self) -> int:
70
70
  """Returns embedding size for the current model"""
71
71
  raise NotImplementedError("Subclasses must implement this method")
72
+
73
+ def token_count(self, texts: Union[str, Iterable[str]], **kwargs: Any) -> int:
74
+ """Returns the number of tokens in the texts."""
75
+ raise NotImplementedError("Subclasses must implement this method")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fastembed-gpu"
3
- version = "0.7.2"
3
+ version = "0.7.4"
4
4
  description = "Fast, light, accurate library built for retrieval embedding generation"
5
5
  authors = ["Qdrant Team <info@qdrant.tech>", "NirantK <nirant.bits@gmail.com>"]
6
6
  license = "Apache License"
@@ -13,10 +13,12 @@ keywords = ["vector", "embedding", "neural", "search", "qdrant", "sentence-trans
13
13
  [tool.poetry.dependencies]
14
14
  python = ">=3.9.0"
15
15
  numpy = [
16
- { version = ">=1.21", python = ">=3.10,<3.12" },
17
- { version = ">=1.26", python = ">=3.12,<3.13" },
18
- { version = ">=2.1.0", python = ">=3.13" },
19
16
  { version = ">=1.21,<2.1.0", python = "<3.10" },
17
+ { version = ">=1.21,<2.3.0", python = ">=3.10,<3.11" },
18
+ { version = ">=1.21", python = ">=3.11,<3.12" },
19
+ { version = ">=1.26", python = ">=3.12,<3.13" },
20
+ { version = ">=2.1.0", python = ">=3.13,<3.14" },
21
+ { version = ">=2.3.0", python = ">=3.14" },
20
22
  ]
21
23
  onnxruntime-gpu = [
22
24
  { version = ">=1.17.0,<1.20.0", python = "<3.10" },
@@ -26,9 +28,13 @@ onnxruntime-gpu = [
26
28
  tqdm = "^4.66"
27
29
  requests = "^2.31"
28
30
  tokenizers = ">=0.15,<1.0"
29
- huggingface-hub = ">=0.20,<1.0"
31
+ huggingface-hub = ">=0.20,<2.0"
30
32
  loguru = "^0.7.2"
31
- pillow = ">=10.3.0,<12.0.0"
33
+ pillow = [
34
+ { version = ">=10.3.0,<11.0", python = "<3.10" },
35
+ { version = ">=10.3.0,<12.0", python = ">=3.10,<3.13" },
36
+ { version = ">=11.0.0,<12.0", python = ">=3.13" },
37
+ ]
32
38
  mmh3 = ">=4.1.0,<6.0.0"
33
39
  py-rust-stemmers = "^0.1.0"
34
40
 
@@ -39,12 +45,15 @@ ruff = ">=0.3.1,<1.0"
39
45
  [tool.poetry.group.dev.dependencies]
40
46
  notebook = ">=7.0.2"
41
47
  pre-commit = "^3.6.2"
42
- onnx = ">=1.15.0"
48
+ onnx = [
49
+ { version = ">=1.15.0", python = "<3.13" },
50
+ { version = ">=1.18.0", python = ">=3.13" },
51
+ ]
43
52
 
44
53
  [tool.poetry.group.docs.dependencies]
45
54
  mkdocs-material = "^9.5.10"
46
55
  mkdocstrings = "^0.24.0"
47
- pillow = ">=10.3.0,<12.0.0"
56
+ pillow = ">=10.3.0,<13.0.0"
48
57
  cairosvg = "^2.7.1"
49
58
  mknotebooks = "^0.8.0"
50
59
 
File without changes
File without changes
File without changes