fastembed-gpu 0.6.1__tar.gz → 0.7.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 (61) hide show
  1. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/PKG-INFO +1 -1
  2. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/onnx_model.py +10 -1
  3. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/preprocessor_utils.py +3 -3
  4. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/types.py +1 -0
  5. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/onnx_embedding.py +3 -1
  6. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/onnx_image_model.py +12 -3
  7. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/transform/functional.py +10 -10
  8. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction/colbert.py +1 -1
  9. fastembed_gpu-0.7.0/fastembed/late_interaction/token_embeddings.py +83 -0
  10. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/onnx_text_cross_encoder.py +3 -1
  11. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/onnx_text_model.py +12 -1
  12. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/bm25.py +2 -12
  13. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/bm42.py +3 -1
  14. fastembed_gpu-0.7.0/fastembed/sparse/minicoil.py +349 -0
  15. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/sparse_text_embedding.py +2 -1
  16. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/splade_pp.py +3 -1
  17. fastembed_gpu-0.7.0/fastembed/sparse/utils/minicoil_encoder.py +146 -0
  18. fastembed_gpu-0.7.0/fastembed/sparse/utils/sparse_vectors_converter.py +247 -0
  19. fastembed_gpu-0.7.0/fastembed/sparse/utils/vocab_resolver.py +202 -0
  20. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/clip_embedding.py +3 -1
  21. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/custom_text_embedding.py +3 -1
  22. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/onnx_embedding.py +3 -1
  23. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/onnx_text_model.py +14 -3
  24. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/pooled_embedding.py +3 -1
  25. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/pooled_normalized_embedding.py +3 -1
  26. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/pyproject.toml +2 -2
  27. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/LICENSE +0 -0
  28. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/NOTICE +0 -0
  29. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/README.md +0 -0
  30. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/__init__.py +0 -0
  31. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/__init__.py +0 -0
  32. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/model_description.py +0 -0
  33. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/model_management.py +0 -0
  34. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/common/utils.py +0 -0
  35. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/embedding.py +0 -0
  36. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/__init__.py +0 -0
  37. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/image_embedding.py +0 -0
  38. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/image_embedding_base.py +0 -0
  39. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/image/transform/operators.py +0 -0
  40. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction/__init__.py +0 -0
  41. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction/jina_colbert.py +0 -0
  42. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction/late_interaction_embedding_base.py +0 -0
  43. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction/late_interaction_text_embedding.py +0 -0
  44. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction_multimodal/__init__.py +0 -0
  45. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction_multimodal/colpali.py +0 -0
  46. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction_multimodal/late_interaction_multimodal_embedding.py +0 -0
  47. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction_multimodal/late_interaction_multimodal_embedding_base.py +0 -0
  48. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/late_interaction_multimodal/onnx_multimodal_model.py +0 -0
  49. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/parallel_processor.py +0 -0
  50. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/py.typed +0 -0
  51. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/__init__.py +0 -0
  52. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/custom_text_cross_encoder.py +0 -0
  53. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/text_cross_encoder.py +0 -0
  54. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/rerank/cross_encoder/text_cross_encoder_base.py +0 -0
  55. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/__init__.py +0 -0
  56. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/sparse_embedding_base.py +0 -0
  57. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/sparse/utils/tokenizer.py +0 -0
  58. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/__init__.py +0 -0
  59. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/multitask_embedding.py +0 -0
  60. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/text_embedding.py +0 -0
  61. {fastembed_gpu-0.6.1 → fastembed_gpu-0.7.0}/fastembed/text/text_embedding_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fastembed-gpu
3
- Version: 0.6.1
3
+ Version: 0.7.0
4
4
  Summary: Fast, light, accurate library built for retrieval embedding generation
5
5
  License: Apache License
6
6
  Keywords: vector,embedding,neural,search,qdrant,sentence-transformers
@@ -28,7 +28,16 @@ class OnnxModel(Generic[T]):
28
28
  def _get_worker_class(cls) -> Type["EmbeddingWorker[T]"]:
29
29
  raise NotImplementedError("Subclasses must implement this method")
30
30
 
31
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[T]:
31
+ def _post_process_onnx_output(self, output: OnnxOutputContext, **kwargs: Any) -> Iterable[T]:
32
+ """Post-process the ONNX model output to convert it into a usable format.
33
+
34
+ Args:
35
+ output (OnnxOutputContext): The raw output from the ONNX model.
36
+ **kwargs: Additional keyword arguments that may be needed by specific implementations.
37
+
38
+ Returns:
39
+ Iterable[T]: Post-processed output as an iterable of type T.
40
+ """
32
41
  raise NotImplementedError("Subclasses must implement this method")
33
42
 
34
43
  def __init__(self) -> None:
@@ -36,9 +36,9 @@ def load_tokenizer(model_dir: Path) -> tuple[Tokenizer, dict[str, int]]:
36
36
 
37
37
  with open(str(tokenizer_config_path)) as tokenizer_config_file:
38
38
  tokenizer_config = json.load(tokenizer_config_file)
39
- assert (
40
- "model_max_length" in tokenizer_config or "max_length" in tokenizer_config
41
- ), "Models without model_max_length or max_length are not supported."
39
+ assert "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
+ )
42
42
  if "model_max_length" not in tokenizer_config:
43
43
  max_context = tokenizer_config["max_length"]
44
44
  elif "max_length" not in tokenizer_config:
@@ -16,6 +16,7 @@ ImageInput: TypeAlias = Union[PathInput, Image.Image]
16
16
 
17
17
  OnnxProvider: TypeAlias = Union[str, tuple[str, dict[Any, Any]]]
18
18
  NumpyArray = Union[
19
+ NDArray[np.float64],
19
20
  NDArray[np.float32],
20
21
  NDArray[np.float16],
21
22
  NDArray[np.int8],
@@ -193,7 +193,9 @@ class OnnxImageEmbedding(ImageEmbeddingBase, OnnxImageModel[NumpyArray]):
193
193
 
194
194
  return onnx_input
195
195
 
196
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[NumpyArray]:
196
+ def _post_process_onnx_output(
197
+ self, output: OnnxOutputContext, **kwargs: Any
198
+ ) -> Iterable[NumpyArray]:
197
199
  return normalize(output.model_output)
198
200
 
199
201
 
@@ -23,7 +23,16 @@ class OnnxImageModel(OnnxModel[T]):
23
23
  def _get_worker_class(cls) -> Type["ImageEmbeddingWorker[T]"]:
24
24
  raise NotImplementedError("Subclasses must implement this method")
25
25
 
26
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[T]:
26
+ def _post_process_onnx_output(self, output: OnnxOutputContext, **kwargs: Any) -> Iterable[T]:
27
+ """Post-process the ONNX model output to convert it into a usable format.
28
+
29
+ Args:
30
+ output (OnnxOutputContext): The raw output from the ONNX model.
31
+ **kwargs: Additional keyword arguments that may be needed by specific implementations.
32
+
33
+ Returns:
34
+ Iterable[T]: Post-processed output as an iterable of type T.
35
+ """
27
36
  raise NotImplementedError("Subclasses must implement this method")
28
37
 
29
38
  def __init__(self) -> None:
@@ -104,7 +113,7 @@ class OnnxImageModel(OnnxModel[T]):
104
113
  self.load_onnx_model()
105
114
 
106
115
  for batch in iter_batch(images, batch_size):
107
- yield from self._post_process_onnx_output(self.onnx_embed(batch))
116
+ yield from self._post_process_onnx_output(self.onnx_embed(batch), **kwargs)
108
117
  else:
109
118
  if parallel == 0:
110
119
  parallel = os.cpu_count()
@@ -125,7 +134,7 @@ class OnnxImageModel(OnnxModel[T]):
125
134
  start_method=start_method,
126
135
  )
127
136
  for batch in pool.ordered_map(iter_batch(images, batch_size), **params):
128
- yield from self._post_process_onnx_output(batch) # type: ignore
137
+ yield from self._post_process_onnx_output(batch, **kwargs) # type: ignore
129
138
 
130
139
 
131
140
  class ImageEmbeddingWorker(EmbeddingWorker[T]):
@@ -72,26 +72,26 @@ def normalize(
72
72
  if not np.issubdtype(image.dtype, np.floating):
73
73
  image = image.astype(np.float32)
74
74
 
75
- mean = mean if isinstance(mean, list) else [mean] * num_channels
75
+ mean_list = mean if isinstance(mean, list) else [mean] * num_channels
76
76
 
77
- if len(mean) != num_channels:
77
+ if len(mean_list) != num_channels:
78
78
  raise ValueError(
79
79
  f"mean must have the same number of channels as the image, image has {num_channels} channels, got "
80
- f"{len(mean)}"
80
+ f"{len(mean_list)}"
81
81
  )
82
82
 
83
- mean_arr = np.array(mean, dtype=np.float32)
83
+ mean_arr = np.array(mean_list, dtype=np.float32)
84
84
 
85
- std = std if isinstance(std, list) else [std] * num_channels
86
- if len(std) != num_channels:
85
+ std_list = std if isinstance(std, list) else [std] * num_channels
86
+ if len(std_list) != num_channels:
87
87
  raise ValueError(
88
- f"std must have the same number of channels as the image, image has {num_channels} channels, got {len(std)}"
88
+ f"std must have the same number of channels as the image, image has {num_channels} channels, got {len(std_list)}"
89
89
  )
90
90
 
91
- std_arr = np.array(std, dtype=np.float32)
91
+ std_arr = np.array(std_list, dtype=np.float32)
92
92
 
93
- image = ((image.T - mean_arr) / std_arr).T
94
- return image
93
+ image_upd = ((image.T - mean_arr) / std_arr).T
94
+ return image_upd
95
95
 
96
96
 
97
97
  def resize(
@@ -43,7 +43,7 @@ class Colbert(LateInteractionTextEmbeddingBase, OnnxTextModel[NumpyArray]):
43
43
  MASK_TOKEN = "[MASK]"
44
44
 
45
45
  def _post_process_onnx_output(
46
- self, output: OnnxOutputContext, is_doc: bool = True
46
+ self, output: OnnxOutputContext, is_doc: bool = True, **kwargs: Any
47
47
  ) -> Iterable[NumpyArray]:
48
48
  if not is_doc:
49
49
  return output.model_output
@@ -0,0 +1,83 @@
1
+ from dataclasses import asdict
2
+ from typing import Union, Iterable, Optional, Any, Type
3
+
4
+ from fastembed.common.model_description import DenseModelDescription, ModelSource
5
+ from fastembed.common.onnx_model import OnnxOutputContext
6
+ from fastembed.common.types import NumpyArray
7
+ from fastembed.late_interaction.late_interaction_embedding_base import (
8
+ LateInteractionTextEmbeddingBase,
9
+ )
10
+ from fastembed.text.onnx_embedding import OnnxTextEmbedding
11
+ from fastembed.text.onnx_text_model import TextEmbeddingWorker
12
+ import numpy as np
13
+
14
+ supported_token_embeddings_models = [
15
+ DenseModelDescription(
16
+ model="jinaai/jina-embeddings-v2-small-en-tokens",
17
+ dim=512,
18
+ description="Text embeddings, Unimodal (text), English, 8192 input tokens truncation,"
19
+ " Prefixes for queries/documents: not necessary, 2023 year.",
20
+ license="apache-2.0",
21
+ size_in_GB=0.12,
22
+ sources=ModelSource(hf="xenova/jina-embeddings-v2-small-en"),
23
+ model_file="onnx/model.onnx",
24
+ ),
25
+ ]
26
+
27
+
28
+ class TokenEmbeddingsModel(OnnxTextEmbedding, LateInteractionTextEmbeddingBase):
29
+ @classmethod
30
+ def _list_supported_models(cls) -> list[DenseModelDescription]:
31
+ """Lists the supported models.
32
+
33
+ Returns:
34
+ list[DenseModelDescription]: A list of DenseModelDescription objects containing the model information.
35
+ """
36
+ return supported_token_embeddings_models
37
+
38
+ @classmethod
39
+ def list_supported_models(cls) -> list[dict[str, Any]]:
40
+ """Lists the supported models.
41
+
42
+ Returns:
43
+ list[dict[str, Any]]: A list of dictionaries containing the model information.
44
+ """
45
+ return [asdict(model) for model in cls._list_supported_models()]
46
+
47
+ @classmethod
48
+ def _get_worker_class(cls) -> Type[TextEmbeddingWorker[NumpyArray]]:
49
+ return TokensEmbeddingWorker
50
+
51
+ def _post_process_onnx_output(
52
+ self, output: OnnxOutputContext, **kwargs: Any
53
+ ) -> Iterable[NumpyArray]:
54
+ # Size: (batch_size, sequence_length, hidden_size)
55
+ embeddings = output.model_output
56
+ # Size: (batch_size, sequence_length)
57
+ assert output.attention_mask is not None
58
+ masks = output.attention_mask
59
+
60
+ # For each document we only select those embeddings that are not masked out
61
+ for i in range(embeddings.shape[0]):
62
+ yield embeddings[i, masks[i] == 1]
63
+
64
+ def embed(
65
+ self,
66
+ documents: Union[str, Iterable[str]],
67
+ batch_size: int = 256,
68
+ parallel: Optional[int] = None,
69
+ **kwargs: Any,
70
+ ) -> Iterable[NumpyArray]:
71
+ yield from super().embed(documents, batch_size=batch_size, parallel=parallel, **kwargs)
72
+
73
+
74
+ class TokensEmbeddingWorker(TextEmbeddingWorker[NumpyArray]):
75
+ def init_embedding(
76
+ self, model_name: str, cache_dir: str, **kwargs: Any
77
+ ) -> TokenEmbeddingsModel:
78
+ return TokenEmbeddingsModel(
79
+ model_name=model_name,
80
+ cache_dir=cache_dir,
81
+ threads=1,
82
+ **kwargs,
83
+ )
@@ -196,7 +196,9 @@ class OnnxTextCrossEncoder(TextCrossEncoderBase, OnnxCrossEncoderModel):
196
196
  def _get_worker_class(cls) -> Type[TextRerankerWorker]:
197
197
  return TextCrossEncoderWorker
198
198
 
199
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[float]:
199
+ def _post_process_onnx_output(
200
+ self, output: OnnxOutputContext, **kwargs: Any
201
+ ) -> Iterable[float]:
200
202
  return (float(elem) for elem in output.model_output)
201
203
 
202
204
 
@@ -133,7 +133,18 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
133
133
  for batch in pool.ordered_map(iter_batch(pairs, batch_size), **params):
134
134
  yield from self._post_process_onnx_output(batch) # type: ignore
135
135
 
136
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[float]:
136
+ def _post_process_onnx_output(
137
+ self, output: OnnxOutputContext, **kwargs: Any
138
+ ) -> Iterable[float]:
139
+ """Post-process the ONNX model output to convert it into a usable format.
140
+
141
+ Args:
142
+ output (OnnxOutputContext): The raw output from the ONNX model.
143
+ **kwargs: Additional keyword arguments that may be needed by specific implementations.
144
+
145
+ Returns:
146
+ Iterable[float]: Post-processed output as an iterable of float values.
147
+ """
137
148
  raise NotImplementedError("Subclasses must implement this method")
138
149
 
139
150
  def _preprocess_onnx_input(
@@ -21,13 +21,9 @@ from fastembed.sparse.sparse_embedding_base import (
21
21
  from fastembed.sparse.utils.tokenizer import SimpleTokenizer
22
22
  from fastembed.common.model_description import SparseModelDescription, ModelSource
23
23
 
24
+
24
25
  supported_languages = [
25
26
  "arabic",
26
- "azerbaijani",
27
- "basque",
28
- "bengali",
29
- "catalan",
30
- "chinese",
31
27
  "danish",
32
28
  "dutch",
33
29
  "english",
@@ -35,21 +31,15 @@ supported_languages = [
35
31
  "french",
36
32
  "german",
37
33
  "greek",
38
- "hebrew",
39
- "hinglish",
40
34
  "hungarian",
41
- "indonesian",
42
35
  "italian",
43
- "kazakh",
44
- "nepali",
45
36
  "norwegian",
46
37
  "portuguese",
47
38
  "romanian",
48
39
  "russian",
49
- "slovene",
50
40
  "spanish",
51
41
  "swedish",
52
- "tajik",
42
+ "tamil",
53
43
  "turkish",
54
44
  ]
55
45
 
@@ -217,7 +217,9 @@ class Bm42(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
217
217
 
218
218
  return new_vector
219
219
 
220
- def _post_process_onnx_output(self, output: OnnxOutputContext) -> Iterable[SparseEmbedding]:
220
+ def _post_process_onnx_output(
221
+ self, output: OnnxOutputContext, **kwargs: Any
222
+ ) -> Iterable[SparseEmbedding]:
221
223
  if output.input_ids is None:
222
224
  raise ValueError("input_ids must be provided for document post-processing")
223
225
 
@@ -0,0 +1,349 @@
1
+ from pathlib import Path
2
+
3
+ from typing import Any, Optional, Sequence, Iterable, Union, Type
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from py_rust_stemmers import SnowballStemmer
8
+ from tokenizers import Tokenizer
9
+
10
+ from fastembed.common.model_description import SparseModelDescription, ModelSource
11
+ from fastembed.common.onnx_model import OnnxOutputContext
12
+ from fastembed.common import OnnxProvider
13
+ from fastembed.common.utils import define_cache_dir
14
+ from fastembed.sparse.sparse_embedding_base import (
15
+ SparseEmbedding,
16
+ SparseTextEmbeddingBase,
17
+ )
18
+ from fastembed.sparse.utils.minicoil_encoder import Encoder
19
+ from fastembed.sparse.utils.sparse_vectors_converter import SparseVectorConverter, WordEmbedding
20
+ from fastembed.sparse.utils.vocab_resolver import VocabResolver, VocabTokenizer
21
+ from fastembed.text.onnx_text_model import OnnxTextModel, TextEmbeddingWorker
22
+
23
+
24
+ MINICOIL_MODEL_FILE = "minicoil.triplet.model.npy"
25
+ MINICOIL_VOCAB_FILE = "minicoil.triplet.model.vocab"
26
+ STOPWORDS_FILE = "stopwords.txt"
27
+
28
+
29
+ supported_minicoil_models: list[SparseModelDescription] = [
30
+ SparseModelDescription(
31
+ model="Qdrant/minicoil-v1",
32
+ vocab_size=19125,
33
+ description="Sparse embedding model, that resolves semantic meaning of the words, "
34
+ "while keeping exact keyword match behavior. "
35
+ "Based on jinaai/jina-embeddings-v2-small-en-tokens",
36
+ license="apache-2.0",
37
+ size_in_GB=0.09,
38
+ sources=ModelSource(hf="Qdrant/minicoil-v1"),
39
+ model_file="onnx/model.onnx",
40
+ additional_files=[
41
+ STOPWORDS_FILE,
42
+ MINICOIL_MODEL_FILE,
43
+ MINICOIL_VOCAB_FILE,
44
+ ],
45
+ requires_idf=True,
46
+ ),
47
+ ]
48
+
49
+ MODEL_TO_LANGUAGE = {
50
+ "Qdrant/minicoil-v1": "english",
51
+ }
52
+
53
+
54
+ class MiniCOIL(SparseTextEmbeddingBase, OnnxTextModel[SparseEmbedding]):
55
+ """
56
+ MiniCOIL is a sparse embedding model, that resolves semantic meaning of the words,
57
+ while keeping exact keyword match behavior.
58
+
59
+ Each vocabulary token is converted into 4d component of a sparse vector, which is then weighted by the token frequency in the corpus.
60
+ If the token is not found in the corpus, it is treated exactly like in BM25.
61
+ `
62
+ The model is based on `jinaai/jina-embeddings-v2-small-en-tokens`
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ model_name: str,
68
+ cache_dir: Optional[str] = None,
69
+ threads: Optional[int] = None,
70
+ providers: Optional[Sequence[OnnxProvider]] = None,
71
+ k: float = 1.2,
72
+ b: float = 0.75,
73
+ avg_len: float = 150.0,
74
+ cuda: bool = False,
75
+ device_ids: Optional[list[int]] = None,
76
+ lazy_load: bool = False,
77
+ device_id: Optional[int] = None,
78
+ specific_model_path: Optional[str] = None,
79
+ **kwargs: Any,
80
+ ):
81
+ """
82
+ Args:
83
+ model_name (str): The name of the model to use.
84
+ cache_dir (str, optional): The path to the cache directory.
85
+ Can be set using the `FASTEMBED_CACHE_PATH` env variable.
86
+ Defaults to `fastembed_cache` in the system's temp directory.
87
+ threads (int, optional): The number of threads single onnxruntime session can use. Defaults to None.
88
+ providers (Optional[Sequence[OnnxProvider]], optional): The providers to use for onnxruntime.
89
+ k (float, optional): The k parameter in the BM25 formula. Defines the saturation of the term frequency.
90
+ I.e. defines how fast the moment when additional terms stop to increase the score. Defaults to 1.2.
91
+ b (float, optional): The b parameter in the BM25 formula. Defines the importance of the document length.
92
+ Defaults to 0.75.
93
+ avg_len (float, optional): The average length of the documents in the corpus. Defaults to 150.0.
94
+ cuda (bool, optional): Whether to use cuda for inference. Mutually exclusive with `providers`
95
+ Defaults to False.
96
+ device_ids (Optional[list[int]], optional): The list of device ids to use for data parallel processing in
97
+ workers. Should be used with `cuda=True`, mutually exclusive with `providers`. Defaults to None.
98
+ lazy_load (bool, optional): Whether to load the model during class initialization or on demand.
99
+ Should be set to True when using multiple-gpu and parallel encoding. Defaults to False.
100
+ device_id (Optional[int], optional): The device id to use for loading the model in the worker process.
101
+ specific_model_path (Optional[str], optional): The specific path to the onnx model dir if it should be imported from somewhere else
102
+
103
+ Raises:
104
+ ValueError: If the model_name is not in the format <org>/<model> e.g. BAAI/bge-base-en.
105
+ """
106
+
107
+ super().__init__(model_name, cache_dir, threads, **kwargs)
108
+ self.providers = providers
109
+ self.lazy_load = lazy_load
110
+ self.device_ids = device_ids
111
+ self.cuda = cuda
112
+ self.device_id = device_id
113
+ self.k = k
114
+ self.b = b
115
+ self.avg_len = avg_len
116
+
117
+ # Initialize class attributes
118
+ self.tokenizer: Optional[Tokenizer] = None
119
+ self.invert_vocab: dict[int, str] = {}
120
+ self.special_tokens: set[str] = set()
121
+ self.special_tokens_ids: set[int] = set()
122
+ self.stopwords: set[str] = set()
123
+ self.vocab_resolver: Optional[VocabResolver] = None
124
+ self.encoder: Optional[Encoder] = None
125
+ self.output_dim: Optional[int] = None
126
+ self.sparse_vector_converter: Optional[SparseVectorConverter] = None
127
+
128
+ self.model_description = self._get_model_description(model_name)
129
+ self.cache_dir = str(define_cache_dir(cache_dir))
130
+ self._model_dir = self.download_model(
131
+ self.model_description,
132
+ self.cache_dir,
133
+ local_files_only=self._local_files_only,
134
+ specific_model_path=specific_model_path,
135
+ )
136
+
137
+ if not self.lazy_load:
138
+ self.load_onnx_model()
139
+
140
+ def load_onnx_model(self) -> None:
141
+ self._load_onnx_model(
142
+ model_dir=self._model_dir,
143
+ model_file=self.model_description.model_file,
144
+ threads=self.threads,
145
+ providers=self.providers,
146
+ cuda=self.cuda,
147
+ device_id=self.device_id,
148
+ )
149
+
150
+ assert self.tokenizer is not None
151
+
152
+ for token, idx in self.tokenizer.get_vocab().items(): # type: ignore[union-attr]
153
+ self.invert_vocab[idx] = token
154
+ self.special_tokens = set(self.special_token_to_id.keys())
155
+ self.special_tokens_ids = set(self.special_token_to_id.values())
156
+ self.stopwords = set(self._load_stopwords(self._model_dir))
157
+
158
+ stemmer = SnowballStemmer(MODEL_TO_LANGUAGE[self.model_name])
159
+
160
+ self.vocab_resolver = VocabResolver(
161
+ tokenizer=VocabTokenizer(self.tokenizer),
162
+ stopwords=self.stopwords,
163
+ stemmer=stemmer,
164
+ )
165
+ self.vocab_resolver.load_json_vocab(str(self._model_dir / MINICOIL_VOCAB_FILE))
166
+
167
+ weights = np.load(str(self._model_dir / MINICOIL_MODEL_FILE), mmap_mode="r")
168
+ self.encoder = Encoder(weights)
169
+ self.output_dim = self.encoder.output_dim
170
+
171
+ self.sparse_vector_converter = SparseVectorConverter(
172
+ stopwords=self.stopwords,
173
+ stemmer=stemmer,
174
+ k=self.k,
175
+ b=self.b,
176
+ avg_len=self.avg_len,
177
+ )
178
+
179
+ def embed(
180
+ self,
181
+ documents: Union[str, Iterable[str]],
182
+ batch_size: int = 256,
183
+ parallel: Optional[int] = None,
184
+ **kwargs: Any,
185
+ ) -> Iterable[SparseEmbedding]:
186
+ """
187
+ Encode a list of documents into list of embeddings.
188
+ We use mean pooling with attention so that the model can handle variable-length inputs.
189
+
190
+ Args:
191
+ documents: Iterator of documents or single document to embed
192
+ batch_size: Batch size for encoding -- higher values will use more memory, but be faster
193
+ parallel:
194
+ If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
195
+ If 0, use all available cores.
196
+ If None, don't use data-parallel processing, use default onnxruntime threading instead.
197
+
198
+ Returns:
199
+ List of embeddings, one per document
200
+ """
201
+ yield from self._embed_documents(
202
+ model_name=self.model_name,
203
+ cache_dir=str(self.cache_dir),
204
+ documents=documents,
205
+ batch_size=batch_size,
206
+ parallel=parallel,
207
+ providers=self.providers,
208
+ cuda=self.cuda,
209
+ device_ids=self.device_ids,
210
+ k=self.k,
211
+ b=self.b,
212
+ avg_len=self.avg_len,
213
+ is_query=False,
214
+ )
215
+
216
+ def query_embed(
217
+ self, query: Union[str, Iterable[str]], **kwargs: Any
218
+ ) -> Iterable[SparseEmbedding]:
219
+ """
220
+ Encode a list of queries into list of embeddings.
221
+ """
222
+ yield from self._embed_documents(
223
+ model_name=self.model_name,
224
+ cache_dir=str(self.cache_dir),
225
+ documents=query,
226
+ providers=self.providers,
227
+ cuda=self.cuda,
228
+ device_ids=self.device_ids,
229
+ k=self.k,
230
+ b=self.b,
231
+ avg_len=self.avg_len,
232
+ is_query=True,
233
+ )
234
+
235
+ @classmethod
236
+ def _load_stopwords(cls, model_dir: Path) -> list[str]:
237
+ stopwords_path = model_dir / STOPWORDS_FILE
238
+ if not stopwords_path.exists():
239
+ return []
240
+
241
+ with open(stopwords_path, "r") as f:
242
+ return f.read().splitlines()
243
+
244
+ @classmethod
245
+ def _list_supported_models(cls) -> list[SparseModelDescription]:
246
+ """Lists the supported models.
247
+
248
+ Returns:
249
+ list[SparseModelDescription]: A list of SparseModelDescription objects containing the model information.
250
+ """
251
+ return supported_minicoil_models
252
+
253
+ def _post_process_onnx_output(
254
+ self, output: OnnxOutputContext, is_query: bool = False, **kwargs: Any
255
+ ) -> Iterable[SparseEmbedding]:
256
+ if output.input_ids is None:
257
+ raise ValueError("input_ids must be provided for document post-processing")
258
+
259
+ assert self.vocab_resolver is not None
260
+ assert self.encoder is not None
261
+ assert self.sparse_vector_converter is not None
262
+
263
+ # Size: (batch_size, sequence_length, hidden_size)
264
+ embeddings = output.model_output
265
+ # Size: (batch_size, sequence_length)
266
+ assert output.attention_mask is not None
267
+ masks = output.attention_mask
268
+
269
+ vocab_size = self.vocab_resolver.vocab_size()
270
+ embedding_size = self.encoder.output_dim
271
+
272
+ # For each document we only select those embeddings that are not masked out
273
+
274
+ for i in range(embeddings.shape[0]):
275
+ # Size: (sequence_length, hidden_size)
276
+ token_embeddings = embeddings[i, masks[i] == 1]
277
+
278
+ # Size: (sequence_length)
279
+ token_ids: NDArray[np.int64] = output.input_ids[i, masks[i] == 1]
280
+
281
+ word_ids_array, counts, oov, forms = self.vocab_resolver.resolve_tokens(token_ids)
282
+
283
+ # Size: (1, words)
284
+ word_ids_array_expanded: NDArray[np.int64] = np.expand_dims(word_ids_array, axis=0)
285
+
286
+ # Size: (1, words, embedding_size)
287
+ token_embeddings_array: NDArray[np.float32] = np.expand_dims(token_embeddings, axis=0)
288
+
289
+ assert word_ids_array_expanded.shape[1] == token_embeddings_array.shape[1]
290
+
291
+ # Size of word_ids_mapping: (unique_words, 2) - [vocab_id, batch_id]
292
+ # Size of embeddings: (unique_words, embedding_size)
293
+ ids_mapping, minicoil_embeddings = self.encoder.forward(
294
+ word_ids_array_expanded, token_embeddings_array
295
+ )
296
+
297
+ # Size of counts: (unique_words)
298
+ words_ids: list[int] = ids_mapping[:, 0].tolist() # type: ignore[assignment]
299
+
300
+ sentence_result: dict[str, WordEmbedding] = {}
301
+
302
+ words = [self.vocab_resolver.lookup_word(word_id) for word_id in words_ids]
303
+
304
+ for word, word_id, emb in zip(words, words_ids, minicoil_embeddings.tolist()): # type: ignore[arg-type]
305
+ if word_id == 0:
306
+ continue
307
+
308
+ sentence_result[word] = WordEmbedding(
309
+ word=word,
310
+ forms=forms[word],
311
+ count=int(counts[word_id]),
312
+ word_id=int(word_id),
313
+ embedding=emb, # type: ignore[arg-type]
314
+ )
315
+
316
+ for oov_word, count in oov.items():
317
+ # {
318
+ # "word": oov_word,
319
+ # "forms": [oov_word],
320
+ # "count": int(count),
321
+ # "word_id": -1,
322
+ # "embedding": [1]
323
+ # }
324
+ sentence_result[oov_word] = WordEmbedding(
325
+ word=oov_word, forms=[oov_word], count=int(count), word_id=-1, embedding=[1]
326
+ )
327
+
328
+ if not is_query:
329
+ yield self.sparse_vector_converter.embedding_to_vector(
330
+ sentence_result, vocab_size=vocab_size, embedding_size=embedding_size
331
+ )
332
+ else:
333
+ yield self.sparse_vector_converter.embedding_to_vector_query(
334
+ sentence_result, vocab_size=vocab_size, embedding_size=embedding_size
335
+ )
336
+
337
+ @classmethod
338
+ def _get_worker_class(cls) -> Type["MiniCoilTextEmbeddingWorker"]:
339
+ return MiniCoilTextEmbeddingWorker
340
+
341
+
342
+ class MiniCoilTextEmbeddingWorker(TextEmbeddingWorker[SparseEmbedding]):
343
+ def init_embedding(self, model_name: str, cache_dir: str, **kwargs: Any) -> MiniCOIL:
344
+ return MiniCOIL(
345
+ model_name=model_name,
346
+ cache_dir=cache_dir,
347
+ threads=1,
348
+ **kwargs,
349
+ )
@@ -4,6 +4,7 @@ from dataclasses import asdict
4
4
  from fastembed.common import OnnxProvider
5
5
  from fastembed.sparse.bm25 import Bm25
6
6
  from fastembed.sparse.bm42 import Bm42
7
+ from fastembed.sparse.minicoil import MiniCOIL
7
8
  from fastembed.sparse.sparse_embedding_base import (
8
9
  SparseEmbedding,
9
10
  SparseTextEmbeddingBase,
@@ -14,7 +15,7 @@ from fastembed.common.model_description import SparseModelDescription
14
15
 
15
16
 
16
17
  class SparseTextEmbedding(SparseTextEmbeddingBase):
17
- EMBEDDINGS_REGISTRY: list[Type[SparseTextEmbeddingBase]] = [SpladePP, Bm42, Bm25]
18
+ EMBEDDINGS_REGISTRY: list[Type[SparseTextEmbeddingBase]] = [SpladePP, Bm42, Bm25, MiniCOIL]
18
19
 
19
20
  @classmethod
20
21
  def list_supported_models(cls) -> list[dict[str, Any]]: