fastembed-gpu 0.3.6__tar.gz → 0.4.0__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 (45) hide show
  1. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/PKG-INFO +3 -3
  2. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/README.md +1 -1
  3. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/model_management.py +53 -28
  4. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/onnx_model.py +16 -4
  5. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/utils.py +14 -2
  6. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/image_embedding.py +8 -5
  7. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/image_embedding_base.py +1 -1
  8. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/onnx_embedding.py +59 -10
  9. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/onnx_image_model.py +33 -15
  10. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/late_interaction/colbert.py +64 -11
  11. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/late_interaction/late_interaction_text_embedding.py +23 -17
  12. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/parallel_processor.py +46 -8
  13. fastembed_gpu-0.4.0/fastembed/rerank/cross_encoder/__init__.py +3 -0
  14. fastembed_gpu-0.4.0/fastembed/rerank/cross_encoder/onnx_text_cross_encoder.py +147 -0
  15. fastembed_gpu-0.4.0/fastembed/rerank/cross_encoder/onnx_text_model.py +70 -0
  16. fastembed_gpu-0.4.0/fastembed/rerank/cross_encoder/text_cross_encoder.py +87 -0
  17. fastembed_gpu-0.4.0/fastembed/rerank/cross_encoder/text_cross_encoder_base.py +37 -0
  18. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/bm25.py +28 -13
  19. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/bm42.py +64 -17
  20. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/sparse_text_embedding.py +7 -0
  21. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/splade_pp.py +59 -12
  22. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/utils/tokenizer.py +9 -2
  23. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/clip_embedding.py +10 -5
  24. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/e5_onnx_embedding.py +12 -4
  25. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/onnx_embedding.py +79 -25
  26. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/onnx_text_model.py +30 -10
  27. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/pooled_embedding.py +20 -15
  28. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/pooled_normalized_embedding.py +22 -10
  29. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/text_embedding.py +10 -7
  30. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/pyproject.toml +2 -2
  31. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/LICENSE +0 -0
  32. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/__init__.py +0 -0
  33. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/__init__.py +0 -0
  34. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/preprocessor_utils.py +0 -0
  35. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/common/types.py +0 -0
  36. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/embedding.py +0 -0
  37. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/__init__.py +0 -0
  38. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/transform/functional.py +0 -0
  39. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/image/transform/operators.py +0 -0
  40. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/late_interaction/__init__.py +0 -0
  41. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/late_interaction/late_interaction_embedding_base.py +0 -0
  42. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/__init__.py +0 -0
  43. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/sparse/sparse_embedding_base.py +0 -0
  44. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/__init__.py +0 -0
  45. {fastembed_gpu-0.3.6 → fastembed_gpu-0.4.0}/fastembed/text/text_embedding_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastembed-gpu
3
- Version: 0.3.6
3
+ Version: 0.4.0
4
4
  Summary: Fast, light, accurate library built for retrieval embedding generation
5
5
  Home-page: https://github.com/qdrant/fastembed
6
6
  License: Apache License
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Requires-Dist: PyStemmer (>=2.2.0,<3.0.0)
19
19
  Requires-Dist: huggingface-hub (>=0.20,<1.0)
20
20
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
21
- Requires-Dist: mmh3 (>=4.0,<5.0)
21
+ Requires-Dist: mmh3 (>=4.1.0,<5.0.0)
22
22
  Requires-Dist: numpy (>=1.21,<2) ; python_version < "3.12"
23
23
  Requires-Dist: numpy (>=1.26,<2) ; python_version >= "3.12"
24
24
  Requires-Dist: onnxruntime-gpu (>=1.17.0,<2.0.0)
@@ -179,7 +179,7 @@ It requires installation of the `fastembed-gpu` package.
179
179
  pip install fastembed-gpu
180
180
  ```
181
181
 
182
- Check our [example](https://qdrant.github.io/fastembed/examples/FastEmbed_GPU/) for detailed instructions and CUDA 12.x support.
182
+ Check our [example](https://qdrant.github.io/fastembed/examples/FastEmbed_GPU/) for detailed instructions, CUDA 12.x support and troubleshooting of the common issues.
183
183
 
184
184
  ```python
185
185
  from fastembed import TextEmbedding
@@ -147,7 +147,7 @@ It requires installation of the `fastembed-gpu` package.
147
147
  pip install fastembed-gpu
148
148
  ```
149
149
 
150
- Check our [example](https://qdrant.github.io/fastembed/examples/FastEmbed_GPU/) for detailed instructions and CUDA 12.x support.
150
+ Check our [example](https://qdrant.github.io/fastembed/examples/FastEmbed_GPU/) for detailed instructions, CUDA 12.x support and troubleshooting of the common issues.
151
151
 
152
152
  ```python
153
153
  from fastembed import TextEmbedding
@@ -74,7 +74,7 @@ class ModelManagement:
74
74
  if total_size_in_bytes == 0:
75
75
  print(f"Warning: Content-length header is missing or zero in the response from {url}.")
76
76
 
77
- show_progress = total_size_in_bytes and show_progress
77
+ show_progress = bool(total_size_in_bytes and show_progress)
78
78
 
79
79
  with tqdm(
80
80
  total=total_size_in_bytes,
@@ -95,6 +95,7 @@ class ModelManagement:
95
95
  hf_source_repo: str,
96
96
  cache_dir: Optional[str] = None,
97
97
  extra_patterns: Optional[List[str]] = None,
98
+ local_files_only: bool = False,
98
99
  **kwargs,
99
100
  ) -> str:
100
101
  """
@@ -104,6 +105,7 @@ class ModelManagement:
104
105
  cache_dir (Optional[str]): The path to the cache directory.
105
106
  extra_patterns (Optional[List[str]]): extra patterns to allow in the snapshot download, typically
106
107
  includes the required model files.
108
+ local_files_only (bool, optional): Whether to only use local files. Defaults to False.
107
109
  Returns:
108
110
  Path: The path to the model directory.
109
111
  """
@@ -121,7 +123,8 @@ class ModelManagement:
121
123
  repo_id=hf_source_repo,
122
124
  allow_patterns=allow_patterns,
123
125
  cache_dir=cache_dir,
124
- local_files_only=kwargs.get("local_files_only", False),
126
+ local_files_only=local_files_only,
127
+ **kwargs,
125
128
  )
126
129
 
127
130
  @classmethod
@@ -162,9 +165,10 @@ class ModelManagement:
162
165
  return cache_dir
163
166
 
164
167
  @classmethod
165
- def retrieve_model_gcs(cls, model_name: str, source_url: str, cache_dir: str) -> Path:
168
+ def retrieve_model_gcs(
169
+ cls, model_name: str, source_url: str, cache_dir: str, local_files_only: bool = False
170
+ ) -> Path:
166
171
  fast_model_name = f"fast-{model_name.split('/')[-1]}"
167
-
168
172
  cache_tmp_dir = Path(cache_dir) / "tmp"
169
173
  model_tmp_dir = cache_tmp_dir / fast_model_name
170
174
  model_dir = Path(cache_dir) / fast_model_name
@@ -183,22 +187,32 @@ class ModelManagement:
183
187
  if model_tar_gz.exists():
184
188
  model_tar_gz.unlink()
185
189
 
186
- cls.download_file_from_gcs(
187
- source_url,
188
- output_path=str(model_tar_gz),
189
- )
190
+ if not local_files_only:
191
+ cls.download_file_from_gcs(
192
+ source_url,
193
+ output_path=str(model_tar_gz),
194
+ )
190
195
 
191
- cls.decompress_to_cache(targz_path=str(model_tar_gz), cache_dir=str(cache_tmp_dir))
192
- assert model_tmp_dir.exists(), f"Could not find {model_tmp_dir} in {cache_tmp_dir}"
196
+ cls.decompress_to_cache(targz_path=str(model_tar_gz), cache_dir=str(cache_tmp_dir))
197
+ assert model_tmp_dir.exists(), f"Could not find {model_tmp_dir} in {cache_tmp_dir}"
193
198
 
194
- model_tar_gz.unlink()
195
- # Rename from tmp to final name is atomic
196
- model_tmp_dir.rename(model_dir)
199
+ model_tar_gz.unlink()
200
+ # Rename from tmp to final name is atomic
201
+ model_tmp_dir.rename(model_dir)
202
+ else:
203
+ logger.error(
204
+ f"Could not find the model tar.gz file at {model_dir} and local_files_only=True."
205
+ )
206
+ raise ValueError(
207
+ f"Could not find the model tar.gz file at {model_dir} and local_files_only=True."
208
+ )
197
209
 
198
210
  return model_dir
199
211
 
200
212
  @classmethod
201
- def download_model(cls, model: Dict[str, Any], cache_dir: Path, retries=3, **kwargs) -> Path:
213
+ def download_model(
214
+ cls, model: Dict[str, Any], cache_dir: Path, retries: int = 3, **kwargs
215
+ ) -> Path:
202
216
  """
203
217
  Downloads a model from HuggingFace Hub or Google Cloud Storage.
204
218
 
@@ -223,7 +237,8 @@ class ModelManagement:
223
237
  Returns:
224
238
  Path: The path to the downloaded model directory.
225
239
  """
226
-
240
+ local_files_only = kwargs.get("local_files_only", False)
241
+ retries = 1 if local_files_only else retries
227
242
  hf_source = model.get("sources", {}).get("hf")
228
243
  url_source = model.get("sources", {}).get("url")
229
244
 
@@ -241,24 +256,34 @@ class ModelManagement:
241
256
  hf_source,
242
257
  cache_dir=str(cache_dir),
243
258
  extra_patterns=extra_patterns,
244
- local_files_only=kwargs.get("local_files_only", False),
259
+ **kwargs,
245
260
  )
246
261
  )
247
262
  except (EnvironmentError, RepositoryNotFoundError, ValueError) as e:
248
- logger.error(
249
- f"Could not download model from HuggingFace: {e} "
250
- "Falling back to other sources."
251
- )
252
- if url_source:
263
+ if not local_files_only:
264
+ logger.error(
265
+ f"Could not download model from HuggingFace: {e} "
266
+ "Falling back to other sources."
267
+ )
268
+ if url_source or local_files_only:
253
269
  try:
254
- return cls.retrieve_model_gcs(model["model"], url_source, str(cache_dir))
270
+ return cls.retrieve_model_gcs(
271
+ model["model"],
272
+ url_source,
273
+ str(cache_dir),
274
+ local_files_only=local_files_only,
275
+ )
255
276
  except Exception:
256
- logger.error(f"Could not download model from url: {url_source}")
257
-
258
- logger.error(
259
- f"Could not download model from either source, sleeping for {sleep} seconds, {retries} retries left."
260
- )
277
+ if not local_files_only:
278
+ logger.error(f"Could not download model from url: {url_source}")
279
+
280
+ if local_files_only:
281
+ logger.error("Could not find model in cache_dir")
282
+ else:
283
+ logger.error(
284
+ f"Could not download model from either source, sleeping for {sleep} seconds, {retries} retries left."
285
+ )
261
286
  time.sleep(sleep)
262
287
  sleep *= 3
263
288
 
264
- raise ValueError(f"Could not download model {model['model']} from any source.")
289
+ raise ValueError(f"Could not load model {model['model']} from any source.")
@@ -50,19 +50,28 @@ class OnnxModel(Generic[T]):
50
50
  """
51
51
  return onnx_input
52
52
 
53
- def load_onnx_model(
53
+ def _load_onnx_model(
54
54
  self,
55
55
  model_dir: Path,
56
56
  model_file: str,
57
57
  threads: Optional[int],
58
58
  providers: Optional[Sequence[OnnxProvider]] = None,
59
+ cuda: bool = False,
60
+ device_id: Optional[int] = None,
59
61
  ) -> None:
60
62
  model_path = model_dir / model_file
61
63
  # List of Execution Providers: https://onnxruntime.ai/docs/execution-providers
62
64
 
63
- onnx_providers = (
64
- ["CPUExecutionProvider"] if providers is None else list(providers)
65
- )
65
+ if providers is not None:
66
+ onnx_providers = list(providers)
67
+ elif cuda:
68
+ if device_id is None:
69
+ onnx_providers = ["CUDAExecutionProvider"]
70
+ else:
71
+ onnx_providers = [("CUDAExecutionProvider", {"device_id": device_id})]
72
+ else:
73
+ onnx_providers = ["CPUExecutionProvider"]
74
+
66
75
  available_providers = ort.get_available_providers()
67
76
  requested_provider_names = []
68
77
  for provider in onnx_providers:
@@ -94,6 +103,9 @@ class OnnxModel(Generic[T]):
94
103
  RuntimeWarning,
95
104
  )
96
105
 
106
+ def load_onnx_model(self) -> None:
107
+ raise NotImplementedError("Subclasses must implement this method")
108
+
97
109
  def onnx_embed(self, *args, **kwargs) -> OnnxOutputContext:
98
110
  raise NotImplementedError("Subclasses must implement this method")
99
111
 
@@ -3,8 +3,11 @@ import tempfile
3
3
  from itertools import islice
4
4
  from pathlib import Path
5
5
  from typing import Generator, Iterable, Optional, Union
6
-
6
+ import unicodedata
7
+ import sys
7
8
  import numpy as np
9
+ import re
10
+ from typing import Set
8
11
 
9
12
 
10
13
  def normalize(input_array, p=2, dim=1, eps=1e-12) -> np.ndarray:
@@ -37,7 +40,16 @@ def define_cache_dir(cache_dir: Optional[str] = None) -> Path:
37
40
  cache_path = Path(os.getenv("FASTEMBED_CACHE_PATH", default_cache_dir))
38
41
  else:
39
42
  cache_path = Path(cache_dir)
40
-
41
43
  cache_path.mkdir(parents=True, exist_ok=True)
42
44
 
43
45
  return cache_path
46
+
47
+
48
+ def get_all_punctuation() -> Set[str]:
49
+ return set(
50
+ chr(i) for i in range(sys.maxunicode) if unicodedata.category(chr(i)).startswith("P")
51
+ )
52
+
53
+
54
+ def remove_non_alphanumeric(text: str) -> str:
55
+ return re.sub(r"[^\w\s]", " ", text, flags=re.UNICODE)
@@ -25,6 +25,7 @@ class ImageEmbedding(ImageEmbeddingBase):
25
25
  "model": "Qdrant/clip-ViT-B-32-vision",
26
26
  "dim": 512,
27
27
  "description": "CLIP vision encoder based on ViT-B/32",
28
+ "license": "mit",
28
29
  "size_in_GB": 0.33,
29
30
  "sources": {
30
31
  "hf": "Qdrant/clip-ViT-B-32-vision",
@@ -45,21 +46,23 @@ class ImageEmbedding(ImageEmbeddingBase):
45
46
  cache_dir: Optional[str] = None,
46
47
  threads: Optional[int] = None,
47
48
  providers: Optional[Sequence[OnnxProvider]] = None,
49
+ cuda: bool = False,
50
+ device_ids: Optional[List[int]] = None,
51
+ lazy_load: bool = False,
48
52
  **kwargs,
49
53
  ):
50
54
  super().__init__(model_name, cache_dir, threads, **kwargs)
51
-
52
55
  for EMBEDDING_MODEL_TYPE in self.EMBEDDINGS_REGISTRY:
53
56
  supported_models = EMBEDDING_MODEL_TYPE.list_supported_models()
54
- if any(
55
- model_name.lower() == model["model"].lower()
56
- for model in supported_models
57
- ):
57
+ if any(model_name.lower() == model["model"].lower() for model in supported_models):
58
58
  self.model = EMBEDDING_MODEL_TYPE(
59
59
  model_name,
60
60
  cache_dir,
61
61
  threads=threads,
62
62
  providers=providers,
63
+ cuda=cuda,
64
+ device_ids=device_ids,
65
+ lazy_load=lazy_load,
63
66
  **kwargs,
64
67
  )
65
68
  return
@@ -30,7 +30,7 @@ class ImageEmbeddingBase(ModelManagement):
30
30
  Embeds a list of images into a list of embeddings.
31
31
 
32
32
  Args:
33
- images - The list of image paths to preprocess and embed.
33
+ images: The list of image paths to preprocess and embed.
34
34
  batch_size: Batch size for encoding
35
35
  parallel:
36
36
  If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
@@ -13,6 +13,7 @@ supported_onnx_models = [
13
13
  "model": "Qdrant/clip-ViT-B-32-vision",
14
14
  "dim": 512,
15
15
  "description": "Image embeddings, Multimodal (text&image), 2021 year",
16
+ "license": "mit",
16
17
  "size_in_GB": 0.34,
17
18
  "sources": {
18
19
  "hf": "Qdrant/clip-ViT-B-32-vision",
@@ -23,6 +24,7 @@ supported_onnx_models = [
23
24
  "model": "Qdrant/resnet50-onnx",
24
25
  "dim": 2048,
25
26
  "description": "Image embeddings, Unimodal (image), 2016 year",
27
+ "license": "apache-2.0",
26
28
  "size_in_GB": 0.1,
27
29
  "sources": {
28
30
  "hf": "Qdrant/resnet50-onnx",
@@ -33,6 +35,7 @@ supported_onnx_models = [
33
35
  "model": "Qdrant/Unicom-ViT-B-16",
34
36
  "dim": 768,
35
37
  "description": "Image embeddings (more detailed than Unicom-ViT-B-32), Multimodal (text&image), 2023 year",
38
+ "license": "apache-2.0",
36
39
  "size_in_GB": 0.82,
37
40
  "sources": {
38
41
  "hf": "Qdrant/Unicom-ViT-B-16",
@@ -43,6 +46,7 @@ supported_onnx_models = [
43
46
  "model": "Qdrant/Unicom-ViT-B-32",
44
47
  "dim": 512,
45
48
  "description": "Image embeddings, Multimodal (text&image), 2023 year",
49
+ "license": "apache-2.0",
46
50
  "size_in_GB": 0.48,
47
51
  "sources": {
48
52
  "hf": "Qdrant/Unicom-ViT-B-32",
@@ -59,6 +63,10 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[np.ndarray]):
59
63
  cache_dir: Optional[str] = None,
60
64
  threads: Optional[int] = None,
61
65
  providers: Optional[Sequence[OnnxProvider]] = None,
66
+ cuda: bool = False,
67
+ device_ids: Optional[List[int]] = None,
68
+ lazy_load: bool = False,
69
+ device_id: Optional[int] = None,
62
70
  **kwargs,
63
71
  ):
64
72
  """
@@ -68,24 +76,56 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[np.ndarray]):
68
76
  Can be set using the `FASTEMBED_CACHE_PATH` env variable.
69
77
  Defaults to `fastembed_cache` in the system's temp directory.
70
78
  threads (int, optional): The number of threads single onnxruntime session can use. Defaults to None.
79
+ providers (Optional[Sequence[OnnxProvider]], optional): The list of onnxruntime providers to use.
80
+ Mutually exclusive with the `cuda` and `device_ids` arguments. Defaults to None.
81
+ cuda (bool, optional): Whether to use cuda for inference. Mutually exclusive with `providers`
82
+ Defaults to False.
83
+ device_ids (Optional[List[int]], optional): The list of device ids to use for data parallel processing in
84
+ workers. Should be used with `cuda=True`, mutually exclusive with `providers`. Defaults to None.
85
+ lazy_load (bool, optional): Whether to load the model during class initialization or on demand.
86
+ Should be set to True when using multiple-gpu and parallel encoding. Defaults to False.
87
+ device_id (Optional[int], optional): The device id to use for loading the model in the worker process.
71
88
 
72
89
  Raises:
73
90
  ValueError: If the model_name is not in the format <org>/<model> e.g. BAAI/bge-base-en.
74
91
  """
75
92
 
76
93
  super().__init__(model_name, cache_dir, threads, **kwargs)
77
-
78
- model_description = self._get_model_description(model_name)
94
+ self.providers = providers
95
+ self.lazy_load = lazy_load
96
+
97
+ # List of device ids, that can be used for data parallel processing in workers
98
+ self.device_ids = device_ids
99
+ self.cuda = cuda
100
+
101
+ # This device_id will be used if we need to load model in current process
102
+ if device_id is not None:
103
+ self.device_id = device_id
104
+ elif self.device_ids is not None:
105
+ self.device_id = self.device_ids[0]
106
+ else:
107
+ self.device_id = None
108
+
109
+ self.model_description = self._get_model_description(model_name)
79
110
  self.cache_dir = define_cache_dir(cache_dir)
80
- model_dir = self.download_model(
81
- model_description, self.cache_dir, local_files_only=self._local_files_only
111
+ self._model_dir = self.download_model(
112
+ self.model_description, self.cache_dir, local_files_only=self._local_files_only
82
113
  )
83
114
 
84
- self.load_onnx_model(
85
- model_dir=model_dir,
86
- model_file=model_description["model_file"],
87
- threads=threads,
88
- providers=providers,
115
+ if not self.lazy_load:
116
+ self.load_onnx_model()
117
+
118
+ def load_onnx_model(self) -> None:
119
+ """
120
+ Load the onnx model.
121
+ """
122
+ self._load_onnx_model(
123
+ model_dir=self._model_dir,
124
+ model_file=self.model_description["model_file"],
125
+ threads=self.threads,
126
+ providers=self.providers,
127
+ cuda=self.cuda,
128
+ device_id=self.device_id,
89
129
  )
90
130
 
91
131
  @classmethod
@@ -120,12 +160,16 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[np.ndarray]):
120
160
  Returns:
121
161
  List of embeddings, one per document
122
162
  """
163
+
123
164
  yield from self._embed_images(
124
165
  model_name=self.model_name,
125
166
  cache_dir=str(self.cache_dir),
126
167
  images=images,
127
168
  batch_size=batch_size,
128
169
  parallel=parallel,
170
+ providers=self.providers,
171
+ cuda=self.cuda,
172
+ device_ids=self.device_ids,
129
173
  **kwargs,
130
174
  )
131
175
 
@@ -148,4 +192,9 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[np.ndarray]):
148
192
 
149
193
  class OnnxImageEmbeddingWorker(ImageEmbeddingWorker):
150
194
  def init_embedding(self, model_name: str, cache_dir: str, **kwargs) -> OnnxImageEmbedding:
151
- return OnnxImageEmbedding(model_name=model_name, cache_dir=cache_dir, threads=1, **kwargs)
195
+ return OnnxImageEmbedding(
196
+ model_name=model_name,
197
+ cache_dir=cache_dir,
198
+ threads=1,
199
+ **kwargs,
200
+ )
@@ -36,21 +36,28 @@ class OnnxImageModel(OnnxModel[T]):
36
36
  """
37
37
  return onnx_input
38
38
 
39
- def load_onnx_model(
39
+ def _load_onnx_model(
40
40
  self,
41
41
  model_dir: Path,
42
42
  model_file: str,
43
43
  threads: Optional[int],
44
44
  providers: Optional[Sequence[OnnxProvider]] = None,
45
+ cuda: bool = False,
46
+ device_id: Optional[int] = None,
45
47
  ) -> None:
46
- super().load_onnx_model(
48
+ super()._load_onnx_model(
47
49
  model_dir=model_dir,
48
50
  model_file=model_file,
49
51
  threads=threads,
50
52
  providers=providers,
53
+ cuda=cuda,
54
+ device_id=device_id,
51
55
  )
52
56
  self.processor = load_preprocessor(model_dir=model_dir)
53
57
 
58
+ def load_onnx_model(self) -> None:
59
+ raise NotImplementedError("Subclasses must implement this method")
60
+
54
61
  def _build_onnx_input(self, encoded: np.ndarray) -> Dict[str, np.ndarray]:
55
62
  return {node.name: encoded for node in self.model.get_inputs()}
56
63
 
@@ -74,33 +81,44 @@ class OnnxImageModel(OnnxModel[T]):
74
81
  images: ImageInput,
75
82
  batch_size: int = 256,
76
83
  parallel: Optional[int] = None,
84
+ providers: Optional[Sequence[OnnxProvider]] = None,
85
+ cuda: bool = False,
86
+ device_ids: Optional[List[int]] = None,
77
87
  **kwargs,
78
88
  ) -> Iterable[T]:
79
89
  is_small = False
80
90
 
81
- if (
82
- isinstance(images, str)
83
- or isinstance(images, Path)
84
- or (isinstance(images, Image.Image))
85
- ):
91
+ if isinstance(images, (str, Path, Image.Image)):
86
92
  images = [images]
87
93
  is_small = True
88
94
 
89
- if isinstance(images, list):
90
- if len(images) < batch_size:
91
- is_small = True
92
-
93
- if parallel == 0:
94
- parallel = os.cpu_count()
95
+ if isinstance(images, list) and len(images) < batch_size:
96
+ is_small = True
95
97
 
96
98
  if parallel is None or is_small:
99
+ if not hasattr(self, "model") or self.model is None:
100
+ self.load_onnx_model()
101
+
97
102
  for batch in iter_batch(images, batch_size):
98
103
  yield from self._post_process_onnx_output(self.onnx_embed(batch))
99
104
  else:
105
+ if parallel == 0:
106
+ parallel = os.cpu_count()
107
+
100
108
  start_method = "forkserver" if "forkserver" in get_all_start_methods() else "spawn"
101
- params = {"model_name": model_name, "cache_dir": cache_dir, **kwargs}
109
+ params = {
110
+ "model_name": model_name,
111
+ "cache_dir": cache_dir,
112
+ "providers": providers,
113
+ **kwargs,
114
+ }
115
+
102
116
  pool = ParallelWorkerPool(
103
- parallel, self._get_worker_class(), start_method=start_method
117
+ num_workers=parallel or 1,
118
+ worker=self._get_worker_class(),
119
+ cuda=cuda,
120
+ device_ids=device_ids,
121
+ start_method=start_method,
104
122
  )
105
123
  for batch in pool.ordered_map(iter_batch(images, batch_size), **params):
106
124
  yield from self._post_process_onnx_output(batch)
@@ -17,6 +17,7 @@ supported_colbert_models = [
17
17
  "model": "colbert-ir/colbertv2.0",
18
18
  "dim": 128,
19
19
  "description": "Late interaction model",
20
+ "license": "mit",
20
21
  "size_in_GB": 0.44,
21
22
  "sources": {
22
23
  "hf": "colbert-ir/colbertv2.0",
@@ -27,6 +28,7 @@ supported_colbert_models = [
27
28
  "model": "answerdotai/answerai-colbert-small-v1",
28
29
  "dim": 96,
29
30
  "description": "Text embeddings, Unimodal (text), Multilingual (~100 languages), 512 input tokens truncation, 2024 year",
31
+ "license": "apache-2.0",
30
32
  "size_in_GB": 0.13,
31
33
  "sources": {
32
34
  "hf": "answerdotai/answerai-colbert-small-v1",
@@ -48,6 +50,11 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[np.ndarray]):
48
50
  if not is_doc:
49
51
  return output.model_output.astype(np.float32)
50
52
 
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
+ )
57
+
51
58
  for i, token_sequence in enumerate(output.input_ids):
52
59
  for j, token_id in enumerate(token_sequence):
53
60
  if token_id in self.skip_list or token_id == self.pad_token_id:
@@ -117,6 +124,10 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[np.ndarray]):
117
124
  cache_dir: Optional[str] = None,
118
125
  threads: Optional[int] = None,
119
126
  providers: Optional[Sequence[OnnxProvider]] = None,
127
+ cuda: bool = False,
128
+ device_ids: Optional[List[int]] = None,
129
+ lazy_load: bool = False,
130
+ device_id: Optional[int] = None,
120
131
  **kwargs,
121
132
  ):
122
133
  """
@@ -126,29 +137,60 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[np.ndarray]):
126
137
  Can be set using the `FASTEMBED_CACHE_PATH` env variable.
127
138
  Defaults to `fastembed_cache` in the system's temp directory.
128
139
  threads (int, optional): The number of threads single onnxruntime session can use. Defaults to None.
140
+ providers (Optional[Sequence[OnnxProvider]], optional): The list of onnxruntime providers to use.
141
+ Mutually exclusive with the `cuda` and `device_ids` arguments. Defaults to None.
142
+ cuda (bool, optional): Whether to use cuda for inference. Mutually exclusive with `providers`
143
+ Defaults to False.
144
+ device_ids (Optional[List[int]], optional): The list of device ids to use for data parallel processing in
145
+ workers. Should be used with `cuda=True`, mutually exclusive with `providers`. Defaults to None.
146
+ lazy_load (bool, optional): Whether to load the model during class initialization or on demand.
147
+ Should be set to True when using multiple-gpu and parallel encoding. Defaults to False.
148
+ device_id (Optional[int], optional): The device id to use for loading the model in the worker process.
129
149
 
130
150
  Raises:
131
151
  ValueError: If the model_name is not in the format <org>/<model> e.g. BAAI/bge-base-en.
132
152
  """
133
153
 
134
154
  super().__init__(model_name, cache_dir, threads, **kwargs)
155
+ self.providers = providers
156
+ self.lazy_load = lazy_load
157
+
158
+ # List of device ids, that can be used for data parallel processing in workers
159
+ self.device_ids = device_ids
160
+ self.cuda = cuda
161
+
162
+ # This device_id will be used if we need to load model in current process
163
+ if device_id is not None:
164
+ self.device_id = device_id
165
+ elif self.device_ids is not None:
166
+ self.device_id = self.device_ids[0]
167
+ else:
168
+ self.device_id = None
135
169
 
136
- model_description = self._get_model_description(model_name)
170
+ self.model_description = self._get_model_description(model_name)
137
171
  self.cache_dir = define_cache_dir(cache_dir)
138
172
 
139
- model_dir = self.download_model(
140
- model_description, self.cache_dir, local_files_only=self._local_files_only
173
+ self._model_dir = self.download_model(
174
+ self.model_description, self.cache_dir, local_files_only=self._local_files_only
141
175
  )
176
+ self.mask_token_id = None
177
+ self.pad_token_id = None
178
+ self.skip_list = set()
142
179
 
143
- self.load_onnx_model(
144
- model_dir=model_dir,
145
- model_file=model_description["model_file"],
146
- threads=threads,
147
- providers=providers,
180
+ if not self.lazy_load:
181
+ self.load_onnx_model()
182
+
183
+ def load_onnx_model(self) -> None:
184
+ self._load_onnx_model(
185
+ model_dir=self._model_dir,
186
+ model_file=self.model_description["model_file"],
187
+ threads=self.threads,
188
+ providers=self.providers,
189
+ cuda=self.cuda,
190
+ device_id=self.device_id,
148
191
  )
149
192
  self.mask_token_id = self.special_token_to_id["[MASK]"]
150
193
  self.pad_token_id = self.tokenizer.padding["pad_id"]
151
-
152
194
  self.skip_list = {
153
195
  self.tokenizer.encode(symbol, add_special_tokens=False).ids[0]
154
196
  for symbol in string.punctuation
@@ -182,13 +224,19 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[np.ndarray]):
182
224
  documents=documents,
183
225
  batch_size=batch_size,
184
226
  parallel=parallel,
227
+ providers=self.providers,
228
+ cuda=self.cuda,
229
+ device_ids=self.device_ids,
185
230
  **kwargs,
186
231
  )
187
232
 
188
- def query_embed(self, query: Union[str, List[str]], **kwargs) -> np.ndarray:
233
+ def query_embed(self, query: Union[str, List[str]], **kwargs) -> Iterable[np.ndarray]:
189
234
  if isinstance(query, str):
190
235
  query = [query]
191
236
 
237
+ if not hasattr(self, "model") or self.model is None:
238
+ self.load_onnx_model()
239
+
192
240
  for text in query:
193
241
  yield from self._post_process_onnx_output(
194
242
  self.onnx_embed([text], is_doc=False), is_doc=False
@@ -201,4 +249,9 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[np.ndarray]):
201
249
 
202
250
  class ColbertEmbeddingWorker(TextEmbeddingWorker):
203
251
  def init_embedding(self, model_name: str, cache_dir: str, **kwargs) -> Colbert:
204
- return Colbert(model_name=model_name, cache_dir=cache_dir, threads=1, **kwargs)
252
+ return Colbert(
253
+ model_name=model_name,
254
+ cache_dir=cache_dir,
255
+ threads=1,
256
+ **kwargs,
257
+ )