fastembed-gpu 0.5.1__py3-none-any.whl

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 (47) hide show
  1. fastembed/__init__.py +20 -0
  2. fastembed/common/__init__.py +3 -0
  3. fastembed/common/model_management.py +301 -0
  4. fastembed/common/onnx_model.py +132 -0
  5. fastembed/common/preprocessor_utils.py +82 -0
  6. fastembed/common/types.py +16 -0
  7. fastembed/common/utils.py +55 -0
  8. fastembed/embedding.py +24 -0
  9. fastembed/image/__init__.py +3 -0
  10. fastembed/image/image_embedding.py +97 -0
  11. fastembed/image/image_embedding_base.py +44 -0
  12. fastembed/image/onnx_embedding.py +211 -0
  13. fastembed/image/onnx_image_model.py +131 -0
  14. fastembed/image/transform/functional.py +150 -0
  15. fastembed/image/transform/operators.py +268 -0
  16. fastembed/late_interaction/__init__.py +5 -0
  17. fastembed/late_interaction/colbert.py +256 -0
  18. fastembed/late_interaction/jina_colbert.py +62 -0
  19. fastembed/late_interaction/late_interaction_embedding_base.py +62 -0
  20. fastembed/late_interaction/late_interaction_text_embedding.py +114 -0
  21. fastembed/parallel_processor.py +252 -0
  22. fastembed/rerank/cross_encoder/__init__.py +3 -0
  23. fastembed/rerank/cross_encoder/onnx_text_cross_encoder.py +224 -0
  24. fastembed/rerank/cross_encoder/onnx_text_model.py +150 -0
  25. fastembed/rerank/cross_encoder/text_cross_encoder.py +120 -0
  26. fastembed/rerank/cross_encoder/text_cross_encoder_base.py +58 -0
  27. fastembed/sparse/__init__.py +4 -0
  28. fastembed/sparse/bm25.py +347 -0
  29. fastembed/sparse/bm42.py +340 -0
  30. fastembed/sparse/sparse_embedding_base.py +83 -0
  31. fastembed/sparse/sparse_text_embedding.py +121 -0
  32. fastembed/sparse/splade_pp.py +180 -0
  33. fastembed/sparse/utils/tokenizer.py +120 -0
  34. fastembed/text/__init__.py +3 -0
  35. fastembed/text/clip_embedding.py +54 -0
  36. fastembed/text/e5_onnx_embedding.py +72 -0
  37. fastembed/text/onnx_embedding.py +333 -0
  38. fastembed/text/onnx_text_model.py +145 -0
  39. fastembed/text/pooled_embedding.py +92 -0
  40. fastembed/text/pooled_normalized_embedding.py +125 -0
  41. fastembed/text/text_embedding.py +107 -0
  42. fastembed/text/text_embedding_base.py +62 -0
  43. fastembed_gpu-0.5.1.dist-info/LICENSE +201 -0
  44. fastembed_gpu-0.5.1.dist-info/METADATA +262 -0
  45. fastembed_gpu-0.5.1.dist-info/NOTICE +14 -0
  46. fastembed_gpu-0.5.1.dist-info/RECORD +47 -0
  47. fastembed_gpu-0.5.1.dist-info/WHEEL +4 -0
fastembed/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ import importlib.metadata
2
+
3
+ from fastembed.image import ImageEmbedding
4
+ from fastembed.late_interaction import LateInteractionTextEmbedding
5
+ from fastembed.sparse import SparseEmbedding, SparseTextEmbedding
6
+ from fastembed.text import TextEmbedding
7
+
8
+ try:
9
+ version = importlib.metadata.version("fastembed")
10
+ except importlib.metadata.PackageNotFoundError as _:
11
+ version = importlib.metadata.version("fastembed-gpu")
12
+
13
+ __version__ = version
14
+ __all__ = [
15
+ "TextEmbedding",
16
+ "SparseTextEmbedding",
17
+ "SparseEmbedding",
18
+ "ImageEmbedding",
19
+ "LateInteractionTextEmbedding",
20
+ ]
@@ -0,0 +1,3 @@
1
+ from fastembed.common.types import ImageInput, OnnxProvider, PathInput, PilInput
2
+
3
+ __all__ = ["OnnxProvider", "ImageInput", "PathInput", "PilInput"]
@@ -0,0 +1,301 @@
1
+ import os
2
+ import time
3
+ import shutil
4
+ import tarfile
5
+ from pathlib import Path
6
+ from typing import Any, Optional
7
+
8
+ import requests
9
+ from huggingface_hub import snapshot_download
10
+ from huggingface_hub.utils import (
11
+ RepositoryNotFoundError,
12
+ disable_progress_bars,
13
+ enable_progress_bars,
14
+ )
15
+ from loguru import logger
16
+ from tqdm import tqdm
17
+
18
+
19
+ class ModelManagement:
20
+ @classmethod
21
+ def list_supported_models(cls) -> list[dict[str, Any]]:
22
+ """Lists the supported models.
23
+
24
+ Returns:
25
+ list[dict[str, Any]]: A list of dictionaries containing the model information.
26
+ """
27
+ raise NotImplementedError()
28
+
29
+ @classmethod
30
+ def _get_model_description(cls, model_name: str) -> dict[str, Any]:
31
+ """
32
+ Gets the model description from the model_name.
33
+
34
+ Args:
35
+ model_name (str): The name of the model.
36
+
37
+ raises:
38
+ ValueError: If the model_name is not supported.
39
+
40
+ Returns:
41
+ dict[str, Any]: The model description.
42
+ """
43
+ for model in cls.list_supported_models():
44
+ if model_name.lower() == model["model"].lower():
45
+ return model
46
+
47
+ raise ValueError(f"Model {model_name} is not supported in {cls.__name__}.")
48
+
49
+ @classmethod
50
+ def download_file_from_gcs(cls, url: str, output_path: str, show_progress: bool = True) -> str:
51
+ """
52
+ Downloads a file from Google Cloud Storage.
53
+
54
+ Args:
55
+ url (str): The URL to download the file from.
56
+ output_path (str): The path to save the downloaded file to.
57
+ show_progress (bool, optional): Whether to show a progress bar. Defaults to True.
58
+
59
+ Returns:
60
+ str: The path to the downloaded file.
61
+ """
62
+
63
+ if os.path.exists(output_path):
64
+ return output_path
65
+ response = requests.get(url, stream=True)
66
+
67
+ # Handle HTTP errors
68
+ if response.status_code == 403:
69
+ raise PermissionError(
70
+ "Authentication Error: You do not have permission to access this resource. "
71
+ "Please check your credentials."
72
+ )
73
+
74
+ # Get the total size of the file
75
+ total_size_in_bytes = int(response.headers.get("content-length", 0))
76
+
77
+ # Warn if the total size is zero
78
+ if total_size_in_bytes == 0:
79
+ print(f"Warning: Content-length header is missing or zero in the response from {url}.")
80
+
81
+ show_progress = bool(total_size_in_bytes and show_progress)
82
+
83
+ with tqdm(
84
+ total=total_size_in_bytes,
85
+ unit="iB",
86
+ unit_scale=True,
87
+ disable=not show_progress,
88
+ ) as progress_bar:
89
+ with open(output_path, "wb") as file:
90
+ for chunk in response.iter_content(chunk_size=1024):
91
+ if chunk: # Filter out keep-alive new chunks
92
+ progress_bar.update(len(chunk))
93
+ file.write(chunk)
94
+ return output_path
95
+
96
+ @classmethod
97
+ def download_files_from_huggingface(
98
+ cls,
99
+ hf_source_repo: str,
100
+ cache_dir: str,
101
+ extra_patterns: Optional[list[str]] = None,
102
+ local_files_only: bool = False,
103
+ **kwargs,
104
+ ) -> str:
105
+ """
106
+ Downloads a model from HuggingFace Hub.
107
+ Args:
108
+ hf_source_repo (str): Name of the model on HuggingFace Hub, e.g. "qdrant/all-MiniLM-L6-v2-onnx".
109
+ cache_dir (Optional[str]): The path to the cache directory.
110
+ extra_patterns (Optional[list[str]]): extra patterns to allow in the snapshot download, typically
111
+ includes the required model files.
112
+ local_files_only (bool, optional): Whether to only use local files. Defaults to False.
113
+ Returns:
114
+ Path: The path to the model directory.
115
+ """
116
+ allow_patterns = [
117
+ "config.json",
118
+ "tokenizer.json",
119
+ "tokenizer_config.json",
120
+ "special_tokens_map.json",
121
+ "preprocessor_config.json",
122
+ ]
123
+ if extra_patterns is not None:
124
+ allow_patterns.extend(extra_patterns)
125
+
126
+ snapshot_dir = Path(cache_dir) / f"models--{hf_source_repo.replace('/', '--')}"
127
+ is_cached = snapshot_dir.exists()
128
+
129
+ if is_cached:
130
+ disable_progress_bars()
131
+
132
+ return snapshot_download(
133
+ repo_id=hf_source_repo,
134
+ allow_patterns=allow_patterns,
135
+ cache_dir=cache_dir,
136
+ local_files_only=local_files_only,
137
+ **kwargs,
138
+ )
139
+
140
+ @classmethod
141
+ def decompress_to_cache(cls, targz_path: str, cache_dir: str):
142
+ """
143
+ Decompresses a .tar.gz file to a cache directory.
144
+
145
+ Args:
146
+ targz_path (str): Path to the .tar.gz file.
147
+ cache_dir (str): Path to the cache directory.
148
+
149
+ Returns:
150
+ cache_dir (str): Path to the cache directory.
151
+ """
152
+ # Check if targz_path exists and is a file
153
+ if not os.path.isfile(targz_path):
154
+ raise ValueError(f"{targz_path} does not exist or is not a file.")
155
+
156
+ # Check if targz_path is a .tar.gz file
157
+ if not targz_path.endswith(".tar.gz"):
158
+ raise ValueError(f"{targz_path} is not a .tar.gz file.")
159
+
160
+ try:
161
+ # Open the tar.gz file
162
+ with tarfile.open(targz_path, "r:gz") as tar:
163
+ # Extract all files into the cache directory
164
+ tar.extractall(
165
+ path=cache_dir,
166
+ )
167
+ except tarfile.TarError as e:
168
+ # If any error occurs while opening or extracting the tar.gz file,
169
+ # delete the cache directory (if it was created in this function)
170
+ # and raise the error again
171
+ if "tmp" in cache_dir:
172
+ shutil.rmtree(cache_dir)
173
+ raise ValueError(f"An error occurred while decompressing {targz_path}: {e}")
174
+
175
+ return cache_dir
176
+
177
+ @classmethod
178
+ def retrieve_model_gcs(
179
+ cls, model_name: str, source_url: str, cache_dir: str, local_files_only: bool = False
180
+ ) -> Path:
181
+ fast_model_name = f"fast-{model_name.split('/')[-1]}"
182
+ cache_tmp_dir = Path(cache_dir) / "tmp"
183
+ model_tmp_dir = cache_tmp_dir / fast_model_name
184
+ model_dir = Path(cache_dir) / fast_model_name
185
+
186
+ # check if the model_dir and the model files are both present for macOS
187
+ if model_dir.exists() and len(list(model_dir.glob("*"))) > 0:
188
+ return model_dir
189
+
190
+ if model_tmp_dir.exists():
191
+ shutil.rmtree(model_tmp_dir)
192
+
193
+ cache_tmp_dir.mkdir(parents=True, exist_ok=True)
194
+
195
+ model_tar_gz = Path(cache_dir) / f"{fast_model_name}.tar.gz"
196
+
197
+ if model_tar_gz.exists():
198
+ model_tar_gz.unlink()
199
+
200
+ if not local_files_only:
201
+ cls.download_file_from_gcs(
202
+ source_url,
203
+ output_path=str(model_tar_gz),
204
+ )
205
+
206
+ cls.decompress_to_cache(targz_path=str(model_tar_gz), cache_dir=str(cache_tmp_dir))
207
+ assert model_tmp_dir.exists(), f"Could not find {model_tmp_dir} in {cache_tmp_dir}"
208
+
209
+ model_tar_gz.unlink()
210
+ # Rename from tmp to final name is atomic
211
+ model_tmp_dir.rename(model_dir)
212
+ else:
213
+ logger.error(
214
+ f"Could not find the model tar.gz file at {model_dir} and local_files_only=True."
215
+ )
216
+ raise ValueError(
217
+ f"Could not find the model tar.gz file at {model_dir} and local_files_only=True."
218
+ )
219
+
220
+ return model_dir
221
+
222
+ @classmethod
223
+ def download_model(
224
+ cls, model: dict[str, Any], cache_dir: Path, retries: int = 3, **kwargs
225
+ ) -> Path:
226
+ """
227
+ Downloads a model from HuggingFace Hub or Google Cloud Storage.
228
+
229
+ Args:
230
+ model (dict[str, Any]): The model description.
231
+ Example:
232
+ ```
233
+ {
234
+ "model": "BAAI/bge-base-en-v1.5",
235
+ "dim": 768,
236
+ "description": "Base English model, v1.5",
237
+ "size_in_GB": 0.44,
238
+ "sources": {
239
+ "url": "https://storage.googleapis.com/qdrant-fastembed/fast-bge-base-en-v1.5.tar.gz",
240
+ "hf": "qdrant/bge-base-en-v1.5-onnx-q",
241
+ }
242
+ }
243
+ ```
244
+ cache_dir (str): The path to the cache directory.
245
+ retries: (int): The number of times to retry (including the first attempt)
246
+
247
+ Returns:
248
+ Path: The path to the downloaded model directory.
249
+ """
250
+ local_files_only = kwargs.get("local_files_only", False)
251
+ retries = 1 if local_files_only else retries
252
+ hf_source = model.get("sources", {}).get("hf")
253
+ url_source = model.get("sources", {}).get("url")
254
+
255
+ sleep = 3.0
256
+ while retries > 0:
257
+ retries -= 1
258
+
259
+ if hf_source:
260
+ extra_patterns = [model["model_file"]]
261
+ extra_patterns.extend(model.get("additional_files", []))
262
+
263
+ try:
264
+ return Path(
265
+ cls.download_files_from_huggingface(
266
+ hf_source,
267
+ cache_dir=str(cache_dir),
268
+ extra_patterns=extra_patterns,
269
+ **kwargs,
270
+ )
271
+ )
272
+ except (EnvironmentError, RepositoryNotFoundError, ValueError) as e:
273
+ if not local_files_only:
274
+ logger.error(
275
+ f"Could not download model from HuggingFace: {e} "
276
+ "Falling back to other sources."
277
+ )
278
+ finally:
279
+ enable_progress_bars()
280
+ if url_source or local_files_only:
281
+ try:
282
+ return cls.retrieve_model_gcs(
283
+ model["model"],
284
+ url_source,
285
+ str(cache_dir),
286
+ local_files_only=local_files_only,
287
+ )
288
+ except Exception:
289
+ if not local_files_only:
290
+ logger.error(f"Could not download model from url: {url_source}")
291
+
292
+ if local_files_only:
293
+ logger.error("Could not find model in cache_dir")
294
+ else:
295
+ logger.error(
296
+ f"Could not download model from either source, sleeping for {sleep} seconds, {retries} retries left."
297
+ )
298
+ time.sleep(sleep)
299
+ sleep *= 3
300
+
301
+ raise ValueError(f"Could not load model {model['model']} from any source.")
@@ -0,0 +1,132 @@
1
+ import warnings
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import Any, Generic, Iterable, Optional, Sequence, Type, TypeVar
5
+
6
+ import numpy as np
7
+ import onnxruntime as ort
8
+
9
+ from fastembed.common.types import OnnxProvider
10
+ from fastembed.parallel_processor import Worker
11
+
12
+ # Holds type of the embedding result
13
+ T = TypeVar("T")
14
+
15
+
16
+ @dataclass
17
+ class OnnxOutputContext:
18
+ model_output: np.ndarray
19
+ attention_mask: Optional[np.ndarray] = None
20
+ input_ids: Optional[np.ndarray] = None
21
+
22
+
23
+ class OnnxModel(Generic[T]):
24
+ @classmethod
25
+ def _get_worker_class(cls) -> Type["EmbeddingWorker"]:
26
+ raise NotImplementedError("Subclasses must implement this method")
27
+
28
+ def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[T]:
29
+ raise NotImplementedError("Subclasses must implement this method")
30
+
31
+ def __init__(self) -> None:
32
+ self.model = None
33
+ self.tokenizer = None
34
+
35
+ def _preprocess_onnx_input(
36
+ self, onnx_input: dict[str, np.ndarray], **kwargs
37
+ ) -> dict[str, np.ndarray]:
38
+ """
39
+ Preprocess the onnx input.
40
+ """
41
+ return onnx_input
42
+
43
+ def _load_onnx_model(
44
+ self,
45
+ model_dir: Path,
46
+ model_file: str,
47
+ threads: Optional[int],
48
+ providers: Optional[Sequence[OnnxProvider]] = None,
49
+ cuda: bool = False,
50
+ device_id: Optional[int] = None,
51
+ ) -> None:
52
+ model_path = model_dir / model_file
53
+ # List of Execution Providers: https://onnxruntime.ai/docs/execution-providers
54
+
55
+ if cuda and providers is not None:
56
+ warnings.warn(
57
+ f"`cuda` and `providers` are mutually exclusive parameters, cuda: {cuda}, providers: {providers}",
58
+ category=UserWarning,
59
+ stacklevel=6,
60
+ )
61
+
62
+ if providers is not None:
63
+ onnx_providers = list(providers)
64
+ elif cuda:
65
+ if device_id is None:
66
+ onnx_providers = ["CUDAExecutionProvider"]
67
+ else:
68
+ onnx_providers = [("CUDAExecutionProvider", {"device_id": device_id})]
69
+ else:
70
+ onnx_providers = ["CPUExecutionProvider"]
71
+
72
+ available_providers = ort.get_available_providers()
73
+ requested_provider_names = []
74
+ for provider in onnx_providers:
75
+ # check providers available
76
+ provider_name = provider if isinstance(provider, str) else provider[0]
77
+ requested_provider_names.append(provider_name)
78
+ if provider_name not in available_providers:
79
+ raise ValueError(
80
+ f"Provider {provider_name} is not available. Available providers: {available_providers}"
81
+ )
82
+
83
+ so = ort.SessionOptions()
84
+ so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
85
+
86
+ if threads is not None:
87
+ so.intra_op_num_threads = threads
88
+ so.inter_op_num_threads = threads
89
+
90
+ self.model = ort.InferenceSession(
91
+ str(model_path), providers=onnx_providers, sess_options=so
92
+ )
93
+ if "CUDAExecutionProvider" in requested_provider_names:
94
+ current_providers = self.model.get_providers()
95
+ if "CUDAExecutionProvider" not in current_providers:
96
+ warnings.warn(
97
+ f"Attempt to set CUDAExecutionProvider failed. Current providers: {current_providers}."
98
+ "If you are using CUDA 12.x, install onnxruntime-gpu via "
99
+ "`pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/`",
100
+ RuntimeWarning,
101
+ )
102
+
103
+ def load_onnx_model(self) -> None:
104
+ raise NotImplementedError("Subclasses must implement this method")
105
+
106
+ def onnx_embed(self, *args, **kwargs) -> OnnxOutputContext:
107
+ raise NotImplementedError("Subclasses must implement this method")
108
+
109
+
110
+ class EmbeddingWorker(Worker):
111
+ def init_embedding(
112
+ self,
113
+ model_name: str,
114
+ cache_dir: str,
115
+ **kwargs,
116
+ ) -> OnnxModel:
117
+ raise NotImplementedError()
118
+
119
+ def __init__(
120
+ self,
121
+ model_name: str,
122
+ cache_dir: str,
123
+ **kwargs,
124
+ ):
125
+ self.model = self.init_embedding(model_name, cache_dir, **kwargs)
126
+
127
+ @classmethod
128
+ def start(cls, model_name: str, cache_dir: str, **kwargs: Any) -> "EmbeddingWorker":
129
+ return cls(model_name=model_name, cache_dir=cache_dir, **kwargs)
130
+
131
+ def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
132
+ raise NotImplementedError("Subclasses must implement this method")
@@ -0,0 +1,82 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from tokenizers import AddedToken, Tokenizer
5
+
6
+ from fastembed.image.transform.operators import Compose
7
+
8
+
9
+ def load_special_tokens(model_dir: Path) -> dict:
10
+ tokens_map_path = model_dir / "special_tokens_map.json"
11
+ if not tokens_map_path.exists():
12
+ raise ValueError(f"Could not find special_tokens_map.json in {model_dir}")
13
+
14
+ with open(str(tokens_map_path)) as tokens_map_file:
15
+ tokens_map = json.load(tokens_map_file)
16
+
17
+ return tokens_map
18
+
19
+
20
+ def load_tokenizer(model_dir: Path) -> tuple[Tokenizer, dict]:
21
+ config_path = model_dir / "config.json"
22
+ if not config_path.exists():
23
+ raise ValueError(f"Could not find config.json in {model_dir}")
24
+
25
+ tokenizer_path = model_dir / "tokenizer.json"
26
+ if not tokenizer_path.exists():
27
+ raise ValueError(f"Could not find tokenizer.json in {model_dir}")
28
+
29
+ tokenizer_config_path = model_dir / "tokenizer_config.json"
30
+ if not tokenizer_config_path.exists():
31
+ raise ValueError(f"Could not find tokenizer_config.json in {model_dir}")
32
+
33
+ with open(str(config_path)) as config_file:
34
+ config = json.load(config_file)
35
+
36
+ with open(str(tokenizer_config_path)) as tokenizer_config_file:
37
+ tokenizer_config = json.load(tokenizer_config_file)
38
+ assert (
39
+ "model_max_length" in tokenizer_config or "max_length" in tokenizer_config
40
+ ), "Models without model_max_length or max_length are not supported."
41
+ if "model_max_length" not in tokenizer_config:
42
+ max_context = tokenizer_config["max_length"]
43
+ elif "max_length" not in tokenizer_config:
44
+ max_context = tokenizer_config["model_max_length"]
45
+ else:
46
+ max_context = min(tokenizer_config["model_max_length"], tokenizer_config["max_length"])
47
+
48
+ tokens_map = load_special_tokens(model_dir)
49
+
50
+ tokenizer = Tokenizer.from_file(str(tokenizer_path))
51
+ tokenizer.enable_truncation(max_length=max_context)
52
+ tokenizer.enable_padding(
53
+ pad_id=config.get("pad_token_id", 0), pad_token=tokenizer_config["pad_token"]
54
+ )
55
+
56
+ for token in tokens_map.values():
57
+ if isinstance(token, str):
58
+ tokenizer.add_special_tokens([token])
59
+ elif isinstance(token, dict):
60
+ tokenizer.add_special_tokens([AddedToken(**token)])
61
+
62
+ special_token_to_id = {}
63
+
64
+ for token in tokens_map.values():
65
+ if isinstance(token, str):
66
+ special_token_to_id[token] = tokenizer.token_to_id(token)
67
+ elif isinstance(token, dict):
68
+ token_str = token.get("content", "")
69
+ special_token_to_id[token_str] = tokenizer.token_to_id(token_str)
70
+
71
+ return tokenizer, special_token_to_id
72
+
73
+
74
+ def load_preprocessor(model_dir: Path) -> Compose:
75
+ preprocessor_config_path = model_dir / "preprocessor_config.json"
76
+ if not preprocessor_config_path.exists():
77
+ raise ValueError(f"Could not find preprocessor_config.json in {model_dir}")
78
+
79
+ with open(str(preprocessor_config_path)) as preprocessor_config_file:
80
+ preprocessor_config = json.load(preprocessor_config_file)
81
+ transforms = Compose.from_config(preprocessor_config)
82
+ return transforms
@@ -0,0 +1,16 @@
1
+ import os
2
+ import sys
3
+ from PIL import Image
4
+ from typing import Any, Iterable, Union
5
+
6
+ if sys.version_info >= (3, 10):
7
+ from typing import TypeAlias
8
+ else:
9
+ from typing_extensions import TypeAlias
10
+
11
+
12
+ PathInput: TypeAlias = Union[str, os.PathLike]
13
+ PilInput: TypeAlias = Union[Image.Image, Iterable[Image.Image]]
14
+ ImageInput: TypeAlias = Union[PathInput, Iterable[PathInput], PilInput]
15
+
16
+ OnnxProvider: TypeAlias = Union[str, tuple[str, dict[Any, Any]]]
@@ -0,0 +1,55 @@
1
+ import os
2
+ import sys
3
+ import re
4
+ import tempfile
5
+ import unicodedata
6
+ from pathlib import Path
7
+ from itertools import islice
8
+ from typing import Generator, Iterable, Optional, Union
9
+
10
+ import numpy as np
11
+
12
+
13
+ def normalize(input_array, p=2, dim=1, eps=1e-12) -> np.ndarray:
14
+ # Calculate the Lp norm along the specified dimension
15
+ norm = np.linalg.norm(input_array, ord=p, axis=dim, keepdims=True)
16
+ norm = np.maximum(norm, eps) # Avoid division by zero
17
+ normalized_array = input_array / norm
18
+ return normalized_array
19
+
20
+
21
+ def iter_batch(iterable: Union[Iterable, Generator], size: int) -> Iterable:
22
+ """
23
+ >>> list(iter_batch([1,2,3,4,5], 3))
24
+ [[1, 2, 3], [4, 5]]
25
+ """
26
+ source_iter = iter(iterable)
27
+ while source_iter:
28
+ b = list(islice(source_iter, size))
29
+ if len(b) == 0:
30
+ break
31
+ yield b
32
+
33
+
34
+ def define_cache_dir(cache_dir: Optional[str] = None) -> Path:
35
+ """
36
+ Define the cache directory for fastembed
37
+ """
38
+ if cache_dir is None:
39
+ default_cache_dir = os.path.join(tempfile.gettempdir(), "fastembed_cache")
40
+ cache_path = Path(os.getenv("FASTEMBED_CACHE_PATH", default_cache_dir))
41
+ else:
42
+ cache_path = Path(cache_dir)
43
+ cache_path.mkdir(parents=True, exist_ok=True)
44
+
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)
fastembed/embedding.py ADDED
@@ -0,0 +1,24 @@
1
+ from typing import Optional
2
+
3
+ from loguru import logger
4
+
5
+ from fastembed import TextEmbedding
6
+
7
+ logger.warning(
8
+ "DefaultEmbedding, FlagEmbedding, JinaEmbedding are deprecated."
9
+ "Use from fastembed import TextEmbedding instead."
10
+ )
11
+
12
+ DefaultEmbedding = TextEmbedding
13
+ FlagEmbedding = TextEmbedding
14
+
15
+
16
+ class JinaEmbedding(TextEmbedding):
17
+ def __init__(
18
+ self,
19
+ model_name: str = "jinaai/jina-embeddings-v2-base-en",
20
+ cache_dir: Optional[str] = None,
21
+ threads: Optional[int] = None,
22
+ **kwargs,
23
+ ):
24
+ super().__init__(model_name, cache_dir, threads, **kwargs)
@@ -0,0 +1,3 @@
1
+ from fastembed.image.image_embedding import ImageEmbedding
2
+
3
+ __all__ = ["ImageEmbedding"]