fastembed-gpu 0.2.7__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.
- fastembed/__init__.py +12 -0
- fastembed/common/__init__.py +3 -0
- fastembed/common/model_management.py +244 -0
- fastembed/common/models.py +54 -0
- fastembed/common/onnx_model.py +165 -0
- fastembed/common/utils.py +33 -0
- fastembed/embedding.py +24 -0
- fastembed/image/__init__.py +0 -0
- fastembed/parallel_processor.py +209 -0
- fastembed/sparse/__init__.py +4 -0
- fastembed/sparse/sparse_embedding_base.py +44 -0
- fastembed/sparse/sparse_text_embedding.py +86 -0
- fastembed/sparse/splade_pp.py +138 -0
- fastembed/text/__init__.py +3 -0
- fastembed/text/e5_onnx_embedding.py +62 -0
- fastembed/text/jina_onnx_embedding.py +67 -0
- fastembed/text/onnx_embedding.py +298 -0
- fastembed/text/text_embedding.py +93 -0
- fastembed/text/text_embedding_base.py +60 -0
- fastembed_gpu-0.2.7.dist-info/LICENSE +201 -0
- fastembed_gpu-0.2.7.dist-info/METADATA +145 -0
- fastembed_gpu-0.2.7.dist-info/RECORD +23 -0
- fastembed_gpu-0.2.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from multiprocessing import Queue, get_context
|
|
6
|
+
from multiprocessing.context import BaseContext
|
|
7
|
+
from multiprocessing.process import BaseProcess
|
|
8
|
+
from multiprocessing.sharedctypes import Synchronized as BaseValue
|
|
9
|
+
from queue import Empty
|
|
10
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type
|
|
11
|
+
|
|
12
|
+
# Single item should be processed in less than:
|
|
13
|
+
processing_timeout = 10 * 60 # seconds
|
|
14
|
+
|
|
15
|
+
max_internal_batch_size = 200
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QueueSignals(str, Enum):
|
|
19
|
+
stop = "stop"
|
|
20
|
+
confirm = "confirm"
|
|
21
|
+
error = "error"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Worker:
|
|
25
|
+
@classmethod
|
|
26
|
+
def start(cls, **kwargs: Any) -> "Worker":
|
|
27
|
+
raise NotImplementedError()
|
|
28
|
+
|
|
29
|
+
def process(self, items: Iterable[Tuple[int, Any]]) -> Iterable[Tuple[int, Any]]:
|
|
30
|
+
raise NotImplementedError()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _worker(
|
|
34
|
+
worker_class: Type[Worker],
|
|
35
|
+
input_queue: Queue,
|
|
36
|
+
output_queue: Queue,
|
|
37
|
+
num_active_workers: BaseValue,
|
|
38
|
+
worker_id: int,
|
|
39
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""
|
|
42
|
+
A worker that pulls data pints off the input queue, and places the execution result on the output queue.
|
|
43
|
+
When there are no data pints left on the input queue, it decrements
|
|
44
|
+
num_active_workers to signal completion.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
if kwargs is None:
|
|
48
|
+
kwargs = {}
|
|
49
|
+
|
|
50
|
+
logging.info(f"Reader worker: {worker_id} PID: {os.getpid()}")
|
|
51
|
+
try:
|
|
52
|
+
worker = worker_class.start(**kwargs)
|
|
53
|
+
|
|
54
|
+
# Keep going until you get an item that's None.
|
|
55
|
+
def input_queue_iterable() -> Iterable[Any]:
|
|
56
|
+
while True:
|
|
57
|
+
item = input_queue.get()
|
|
58
|
+
if item == QueueSignals.stop:
|
|
59
|
+
break
|
|
60
|
+
yield item
|
|
61
|
+
|
|
62
|
+
for processed_item in worker.process(input_queue_iterable()):
|
|
63
|
+
output_queue.put(processed_item)
|
|
64
|
+
except Exception as e: # pylint: disable=broad-except
|
|
65
|
+
logging.exception(e)
|
|
66
|
+
output_queue.put(QueueSignals.error)
|
|
67
|
+
finally:
|
|
68
|
+
# It's important that we close and join the queue here before
|
|
69
|
+
# decrementing num_active_workers. Otherwise our parent may join us
|
|
70
|
+
# before the queue's feeder thread has passed all buffered items to
|
|
71
|
+
# the underlying pipe resulting in a deadlock.
|
|
72
|
+
#
|
|
73
|
+
# See:
|
|
74
|
+
# https://docs.python.org/3.6/library/multiprocessing.html?highlight=process#pipes-and-queues
|
|
75
|
+
# https://docs.python.org/3.6/library/multiprocessing.html?highlight=process#programming-guidelines
|
|
76
|
+
output_queue.close()
|
|
77
|
+
output_queue.join_thread()
|
|
78
|
+
|
|
79
|
+
with num_active_workers.get_lock():
|
|
80
|
+
num_active_workers.value -= 1
|
|
81
|
+
|
|
82
|
+
logging.info(f"Reader worker {worker_id} finished")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ParallelWorkerPool:
|
|
86
|
+
def __init__(self, num_workers: int, worker: Type[Worker], start_method: Optional[str] = None):
|
|
87
|
+
self.worker_class = worker
|
|
88
|
+
self.num_workers = num_workers
|
|
89
|
+
self.input_queue: Optional[Queue] = None
|
|
90
|
+
self.output_queue: Optional[Queue] = None
|
|
91
|
+
self.ctx: BaseContext = get_context(start_method)
|
|
92
|
+
self.processes: List[BaseProcess] = []
|
|
93
|
+
self.queue_size = self.num_workers * max_internal_batch_size
|
|
94
|
+
|
|
95
|
+
self.num_active_workers: Optional[BaseValue] = None
|
|
96
|
+
|
|
97
|
+
def start(self, **kwargs: Any) -> None:
|
|
98
|
+
self.input_queue = self.ctx.Queue(self.queue_size)
|
|
99
|
+
self.output_queue = self.ctx.Queue(self.queue_size)
|
|
100
|
+
|
|
101
|
+
ctx_value = self.ctx.Value("i", self.num_workers)
|
|
102
|
+
assert isinstance(ctx_value, BaseValue)
|
|
103
|
+
self.num_active_workers = ctx_value
|
|
104
|
+
|
|
105
|
+
for worker_id in range(0, self.num_workers):
|
|
106
|
+
assert hasattr(self.ctx, "Process")
|
|
107
|
+
process = self.ctx.Process(
|
|
108
|
+
target=_worker,
|
|
109
|
+
args=(
|
|
110
|
+
self.worker_class,
|
|
111
|
+
self.input_queue,
|
|
112
|
+
self.output_queue,
|
|
113
|
+
self.num_active_workers,
|
|
114
|
+
worker_id,
|
|
115
|
+
kwargs.copy(),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
process.start()
|
|
119
|
+
self.processes.append(process)
|
|
120
|
+
|
|
121
|
+
def ordered_map(self, stream: Iterable[Any], *args: Any, **kwargs: Any) -> Iterable[Any]:
|
|
122
|
+
buffer = defaultdict(Any)
|
|
123
|
+
next_expected = 0
|
|
124
|
+
|
|
125
|
+
for idx, item in self.semi_ordered_map(stream, *args, **kwargs):
|
|
126
|
+
buffer[idx] = item
|
|
127
|
+
while next_expected in buffer:
|
|
128
|
+
yield buffer.pop(next_expected)
|
|
129
|
+
next_expected += 1
|
|
130
|
+
|
|
131
|
+
def semi_ordered_map(
|
|
132
|
+
self, stream: Iterable[Any], *args: Any, **kwargs: Any
|
|
133
|
+
) -> Iterable[Tuple[int, Any]]:
|
|
134
|
+
try:
|
|
135
|
+
self.start(**kwargs)
|
|
136
|
+
|
|
137
|
+
assert self.input_queue is not None, "Input queue was not initialized"
|
|
138
|
+
assert self.output_queue is not None, "Output queue was not initialized"
|
|
139
|
+
|
|
140
|
+
pushed = 0
|
|
141
|
+
read = 0
|
|
142
|
+
for idx, item in enumerate(stream):
|
|
143
|
+
if pushed - read < self.queue_size:
|
|
144
|
+
try:
|
|
145
|
+
out_item = self.output_queue.get_nowait()
|
|
146
|
+
except Empty:
|
|
147
|
+
out_item = None
|
|
148
|
+
else:
|
|
149
|
+
try:
|
|
150
|
+
out_item = self.output_queue.get(timeout=processing_timeout)
|
|
151
|
+
except Empty as e:
|
|
152
|
+
self.join_or_terminate()
|
|
153
|
+
raise e
|
|
154
|
+
|
|
155
|
+
if out_item is not None:
|
|
156
|
+
if out_item == QueueSignals.error:
|
|
157
|
+
self.join_or_terminate()
|
|
158
|
+
raise RuntimeError("Thread unexpectedly terminated")
|
|
159
|
+
yield out_item
|
|
160
|
+
read += 1
|
|
161
|
+
|
|
162
|
+
self.input_queue.put((idx, item))
|
|
163
|
+
pushed += 1
|
|
164
|
+
|
|
165
|
+
for _ in range(self.num_workers):
|
|
166
|
+
self.input_queue.put(QueueSignals.stop)
|
|
167
|
+
|
|
168
|
+
while read < pushed:
|
|
169
|
+
out_item = self.output_queue.get(timeout=processing_timeout)
|
|
170
|
+
if out_item == QueueSignals.error:
|
|
171
|
+
self.join_or_terminate()
|
|
172
|
+
raise RuntimeError("Thread unexpectedly terminated")
|
|
173
|
+
yield out_item
|
|
174
|
+
read += 1
|
|
175
|
+
finally:
|
|
176
|
+
assert self.input_queue is not None, "Input queue is None"
|
|
177
|
+
assert self.output_queue is not None, "Output queue is None"
|
|
178
|
+
self.input_queue.close()
|
|
179
|
+
self.output_queue.close()
|
|
180
|
+
|
|
181
|
+
def join_or_terminate(self, timeout: Optional[int] = 1) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Emergency shutdown
|
|
184
|
+
@param timeout:
|
|
185
|
+
@return:
|
|
186
|
+
"""
|
|
187
|
+
for process in self.processes:
|
|
188
|
+
process.join(timeout=timeout)
|
|
189
|
+
if process.is_alive():
|
|
190
|
+
process.terminate()
|
|
191
|
+
self.processes.clear()
|
|
192
|
+
|
|
193
|
+
def join(self) -> None:
|
|
194
|
+
for process in self.processes:
|
|
195
|
+
process.join()
|
|
196
|
+
self.processes.clear()
|
|
197
|
+
|
|
198
|
+
def __del__(self) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Terminate processes if the user hasn't joined. This is necessary as
|
|
201
|
+
leaving stray processes running can corrupt shared state. In brief,
|
|
202
|
+
we've observed shared memory counters being reused (when the memory was
|
|
203
|
+
free from the perspective of the parent process) while the stray
|
|
204
|
+
workers still held a reference to them.
|
|
205
|
+
For a discussion of using destructors in Python in this manner, see
|
|
206
|
+
https://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python/.
|
|
207
|
+
"""
|
|
208
|
+
for process in self.processes:
|
|
209
|
+
process.terminate()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Iterable, Optional, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from fastembed.common.model_management import ModelManagement
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class SparseEmbedding:
|
|
11
|
+
values: np.ndarray
|
|
12
|
+
indices: np.ndarray
|
|
13
|
+
|
|
14
|
+
def as_object(self) -> Dict[str, np.ndarray]:
|
|
15
|
+
return {
|
|
16
|
+
"values": self.values,
|
|
17
|
+
"indices": self.indices,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def as_dict(self) -> Dict[int, float]:
|
|
21
|
+
return {i: v for i, v in zip(self.indices, self.values)}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SparseTextEmbeddingBase(ModelManagement):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
model_name: str,
|
|
28
|
+
cache_dir: Optional[str] = None,
|
|
29
|
+
threads: Optional[int] = None,
|
|
30
|
+
**kwargs,
|
|
31
|
+
):
|
|
32
|
+
self.model_name = model_name
|
|
33
|
+
self.cache_dir = cache_dir
|
|
34
|
+
self.threads = threads
|
|
35
|
+
self._local_files_only = kwargs.pop("local_files_only", False)
|
|
36
|
+
|
|
37
|
+
def embed(
|
|
38
|
+
self,
|
|
39
|
+
documents: Union[str, Iterable[str]],
|
|
40
|
+
batch_size: int = 256,
|
|
41
|
+
parallel: Optional[int] = None,
|
|
42
|
+
**kwargs,
|
|
43
|
+
) -> Iterable[SparseEmbedding]:
|
|
44
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import List, Type, Dict, Any, Union, Iterable, Optional, Sequence
|
|
2
|
+
|
|
3
|
+
from fastembed.common import OnnxProvider
|
|
4
|
+
from fastembed.sparse.sparse_embedding_base import SparseTextEmbeddingBase, SparseEmbedding
|
|
5
|
+
from fastembed.sparse.splade_pp import SpladePP
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SparseTextEmbedding(SparseTextEmbeddingBase):
|
|
9
|
+
EMBEDDINGS_REGISTRY: List[Type[SparseTextEmbeddingBase]] = [
|
|
10
|
+
SpladePP,
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def list_supported_models(cls) -> List[Dict[str, Any]]:
|
|
15
|
+
"""
|
|
16
|
+
Lists the supported models.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
List[Dict[str, Any]]: A list of dictionaries containing the model information.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
```
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
"model": "prithvida/SPLADE_PP_en_v1",
|
|
26
|
+
"vocab_size": 30522,
|
|
27
|
+
"description": "Independent Implementation of SPLADE++ Model for English",
|
|
28
|
+
"size_in_GB": 0.532,
|
|
29
|
+
"sources": {
|
|
30
|
+
"hf": "qdrant/SPLADE_PP_en_v1",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
result = []
|
|
37
|
+
for embedding in cls.EMBEDDINGS_REGISTRY:
|
|
38
|
+
result.extend(embedding.list_supported_models())
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
model_name: str,
|
|
44
|
+
cache_dir: Optional[str] = None,
|
|
45
|
+
threads: Optional[int] = None,
|
|
46
|
+
providers: Optional[Sequence[OnnxProvider]] = None,
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
super().__init__(model_name, cache_dir, threads, **kwargs)
|
|
50
|
+
|
|
51
|
+
for EMBEDDING_MODEL_TYPE in self.EMBEDDINGS_REGISTRY:
|
|
52
|
+
supported_models = EMBEDDING_MODEL_TYPE.list_supported_models()
|
|
53
|
+
if any(model_name.lower() == model["model"].lower() for model in supported_models):
|
|
54
|
+
self.model = EMBEDDING_MODEL_TYPE(
|
|
55
|
+
model_name, cache_dir, threads, providers=providers, **kwargs
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Model {model_name} is not supported in SparseTextEmbedding."
|
|
61
|
+
"Please check the supported models using `SparseTextEmbedding.list_supported_models()`"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def embed(
|
|
65
|
+
self,
|
|
66
|
+
documents: Union[str, Iterable[str]],
|
|
67
|
+
batch_size: int = 256,
|
|
68
|
+
parallel: Optional[int] = None,
|
|
69
|
+
**kwargs,
|
|
70
|
+
) -> Iterable[SparseEmbedding]:
|
|
71
|
+
"""
|
|
72
|
+
Encode a list of documents into list of embeddings.
|
|
73
|
+
We use mean pooling with attention so that the model can handle variable-length inputs.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
documents: Iterator of documents or single document to embed
|
|
77
|
+
batch_size: Batch size for encoding -- higher values will use more memory, but be faster
|
|
78
|
+
parallel:
|
|
79
|
+
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
|
|
80
|
+
If 0, use all available cores.
|
|
81
|
+
If None, don't use data-parallel processing, use default onnxruntime threading instead.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of embeddings, one per document
|
|
85
|
+
"""
|
|
86
|
+
yield from self.model.embed(documents, batch_size, parallel, **kwargs)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, Type, Sequence
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from fastembed.common.onnx_model import EmbeddingWorker, OnnxModel, OnnxProvider
|
|
6
|
+
from fastembed.common.utils import define_cache_dir
|
|
7
|
+
from fastembed.sparse.sparse_embedding_base import SparseEmbedding, SparseTextEmbeddingBase
|
|
8
|
+
|
|
9
|
+
supported_splade_models = [
|
|
10
|
+
{
|
|
11
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
12
|
+
"vocab_size": 30522,
|
|
13
|
+
"description": "Misspelled version of the model. Retained for backward compatibility. Independent Implementation of SPLADE++ Model for English",
|
|
14
|
+
"size_in_GB": 0.532,
|
|
15
|
+
"sources": {
|
|
16
|
+
"hf": "Qdrant/SPLADE_PP_en_v1",
|
|
17
|
+
},
|
|
18
|
+
"model_file": "model.onnx",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"model": "prithivida/Splade_PP_en_v1",
|
|
22
|
+
"vocab_size": 30522,
|
|
23
|
+
"description": "Independent Implementation of SPLADE++ Model for English",
|
|
24
|
+
"size_in_GB": 0.532,
|
|
25
|
+
"sources": {
|
|
26
|
+
"hf": "Qdrant/SPLADE_PP_en_v1",
|
|
27
|
+
},
|
|
28
|
+
"model_file": "model.onnx",
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SpladePP(SparseTextEmbeddingBase, OnnxModel[SparseEmbedding]):
|
|
34
|
+
@classmethod
|
|
35
|
+
def _post_process_onnx_output(
|
|
36
|
+
cls, output: Tuple[np.ndarray, np.ndarray]
|
|
37
|
+
) -> Iterable[SparseEmbedding]:
|
|
38
|
+
logits, attention_mask = output
|
|
39
|
+
relu_log = np.log(1 + np.maximum(logits, 0))
|
|
40
|
+
|
|
41
|
+
weighted_log = relu_log * np.expand_dims(attention_mask, axis=-1)
|
|
42
|
+
|
|
43
|
+
scores = np.max(weighted_log, axis=1)
|
|
44
|
+
|
|
45
|
+
# Score matrix of shape (batch_size, vocab_size)
|
|
46
|
+
# Most of the values are 0, only a few are non-zero
|
|
47
|
+
for row_scores in scores:
|
|
48
|
+
indices = row_scores.nonzero()[0]
|
|
49
|
+
scores = row_scores[indices]
|
|
50
|
+
yield SparseEmbedding(values=scores, indices=indices)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def list_supported_models(cls) -> List[Dict[str, Any]]:
|
|
54
|
+
"""Lists the supported models.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List[Dict[str, Any]]: A list of dictionaries containing the model information.
|
|
58
|
+
"""
|
|
59
|
+
return supported_splade_models
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
model_name: str,
|
|
64
|
+
cache_dir: Optional[str] = None,
|
|
65
|
+
threads: Optional[int] = None,
|
|
66
|
+
providers: Optional[Sequence[OnnxProvider]] = None,
|
|
67
|
+
**kwargs,
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Args:
|
|
71
|
+
model_name (str): The name of the model to use.
|
|
72
|
+
cache_dir (str, optional): The path to the cache directory.
|
|
73
|
+
Can be set using the `FASTEMBED_CACHE_PATH` env variable.
|
|
74
|
+
Defaults to `fastembed_cache` in the system's temp directory.
|
|
75
|
+
threads (int, optional): The number of threads single onnxruntime session can use. Defaults to None.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If the model_name is not in the format <org>/<model> e.g. BAAI/bge-base-en.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
super().__init__(model_name, cache_dir, threads, **kwargs)
|
|
82
|
+
|
|
83
|
+
model_description = self._get_model_description(model_name)
|
|
84
|
+
cache_dir = define_cache_dir(cache_dir)
|
|
85
|
+
|
|
86
|
+
model_dir = self.download_model(
|
|
87
|
+
model_description, cache_dir, local_files_only=self._local_files_only
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.load_onnx_model(
|
|
91
|
+
model_dir=model_dir,
|
|
92
|
+
model_file=model_description["model_file"],
|
|
93
|
+
threads=threads,
|
|
94
|
+
providers=providers,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def embed(
|
|
98
|
+
self,
|
|
99
|
+
documents: Union[str, Iterable[str]],
|
|
100
|
+
batch_size: int = 256,
|
|
101
|
+
parallel: Optional[int] = None,
|
|
102
|
+
**kwargs,
|
|
103
|
+
) -> Iterable[SparseEmbedding]:
|
|
104
|
+
"""
|
|
105
|
+
Encode a list of documents into list of embeddings.
|
|
106
|
+
We use mean pooling with attention so that the model can handle variable-length inputs.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
documents: Iterator of documents or single document to embed
|
|
110
|
+
batch_size: Batch size for encoding -- higher values will use more memory, but be faster
|
|
111
|
+
parallel:
|
|
112
|
+
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
|
|
113
|
+
If 0, use all available cores.
|
|
114
|
+
If None, don't use data-parallel processing, use default onnxruntime threading instead.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of embeddings, one per document
|
|
118
|
+
"""
|
|
119
|
+
yield from self._embed_documents(
|
|
120
|
+
model_name=self.model_name,
|
|
121
|
+
cache_dir=str(self.cache_dir),
|
|
122
|
+
documents=documents,
|
|
123
|
+
batch_size=batch_size,
|
|
124
|
+
parallel=parallel,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def _get_worker_class(cls) -> Type[EmbeddingWorker]:
|
|
129
|
+
return SpladePPEmbeddingWorker
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SpladePPEmbeddingWorker(EmbeddingWorker):
|
|
133
|
+
def init_embedding(
|
|
134
|
+
self,
|
|
135
|
+
model_name: str,
|
|
136
|
+
cache_dir: str,
|
|
137
|
+
) -> SpladePP:
|
|
138
|
+
return SpladePP(model_name=model_name, cache_dir=cache_dir, threads=1)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Type, List, Dict, Any
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from fastembed.common.onnx_model import EmbeddingWorker
|
|
6
|
+
from fastembed.text.onnx_embedding import OnnxTextEmbedding, OnnxTextEmbeddingWorker
|
|
7
|
+
|
|
8
|
+
supported_multilingual_e5_models = [
|
|
9
|
+
{
|
|
10
|
+
"model": "intfloat/multilingual-e5-large",
|
|
11
|
+
"dim": 1024,
|
|
12
|
+
"description": "Multilingual model, e5-large. Recommend using this model for non-English languages",
|
|
13
|
+
"size_in_GB": 2.24,
|
|
14
|
+
"sources": {
|
|
15
|
+
"url": "https://storage.googleapis.com/qdrant-fastembed/fast-multilingual-e5-large.tar.gz",
|
|
16
|
+
"hf": "qdrant/multilingual-e5-large-onnx",
|
|
17
|
+
},
|
|
18
|
+
"model_file": "model.onnx",
|
|
19
|
+
"additional_files": ["model.onnx_data"],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"model": "sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
|
|
23
|
+
"dim": 768,
|
|
24
|
+
"description": "Sentence-transformers model for tasks like clustering or semantic search",
|
|
25
|
+
"size_in_GB": 1.00,
|
|
26
|
+
"sources": {
|
|
27
|
+
"hf": "xenova/paraphrase-multilingual-mpnet-base-v2",
|
|
28
|
+
},
|
|
29
|
+
"model_file": "onnx/model.onnx",
|
|
30
|
+
},
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class E5OnnxEmbedding(OnnxTextEmbedding):
|
|
35
|
+
@classmethod
|
|
36
|
+
def _get_worker_class(cls) -> Type["EmbeddingWorker"]:
|
|
37
|
+
return E5OnnxEmbeddingWorker
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def list_supported_models(cls) -> List[Dict[str, Any]]:
|
|
41
|
+
"""Lists the supported models.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List[Dict[str, Any]]: A list of dictionaries containing the model information.
|
|
45
|
+
"""
|
|
46
|
+
return supported_multilingual_e5_models
|
|
47
|
+
|
|
48
|
+
def _preprocess_onnx_input(self, onnx_input: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
|
|
49
|
+
"""
|
|
50
|
+
Preprocess the onnx input.
|
|
51
|
+
"""
|
|
52
|
+
onnx_input.pop("token_type_ids", None)
|
|
53
|
+
return onnx_input
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class E5OnnxEmbeddingWorker(OnnxTextEmbeddingWorker):
|
|
57
|
+
def init_embedding(
|
|
58
|
+
self,
|
|
59
|
+
model_name: str,
|
|
60
|
+
cache_dir: str,
|
|
61
|
+
) -> E5OnnxEmbedding:
|
|
62
|
+
return E5OnnxEmbedding(model_name=model_name, cache_dir=cache_dir, threads=1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Type, List, Dict, Any, Tuple, Iterable
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from fastembed.common.models import normalize
|
|
6
|
+
from fastembed.common.onnx_model import EmbeddingWorker
|
|
7
|
+
from fastembed.text.onnx_embedding import OnnxTextEmbedding, OnnxTextEmbeddingWorker
|
|
8
|
+
|
|
9
|
+
supported_jina_models = [
|
|
10
|
+
{
|
|
11
|
+
"model": "jinaai/jina-embeddings-v2-base-en",
|
|
12
|
+
"dim": 768,
|
|
13
|
+
"description": "English embedding model supporting 8192 sequence length",
|
|
14
|
+
"size_in_GB": 0.52,
|
|
15
|
+
"sources": {"hf": "xenova/jina-embeddings-v2-base-en"},
|
|
16
|
+
"model_file": "onnx/model.onnx",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"model": "jinaai/jina-embeddings-v2-small-en",
|
|
20
|
+
"dim": 512,
|
|
21
|
+
"description": "English embedding model supporting 8192 sequence length",
|
|
22
|
+
"size_in_GB": 0.12,
|
|
23
|
+
"sources": {"hf": "xenova/jina-embeddings-v2-small-en"},
|
|
24
|
+
"model_file": "onnx/model.onnx",
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JinaOnnxEmbedding(OnnxTextEmbedding):
|
|
30
|
+
@classmethod
|
|
31
|
+
def _get_worker_class(cls) -> Type[EmbeddingWorker]:
|
|
32
|
+
return JinaEmbeddingWorker
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def mean_pooling(cls, model_output, attention_mask) -> np.ndarray:
|
|
36
|
+
token_embeddings = model_output
|
|
37
|
+
input_mask_expanded = (np.expand_dims(attention_mask, axis=-1)).astype(float)
|
|
38
|
+
|
|
39
|
+
sum_embeddings = np.sum(token_embeddings * input_mask_expanded, axis=1)
|
|
40
|
+
mask_sum = np.clip(np.sum(input_mask_expanded, axis=1), a_min=1e-9, a_max=None)
|
|
41
|
+
|
|
42
|
+
return sum_embeddings / mask_sum
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def list_supported_models(cls) -> List[Dict[str, Any]]:
|
|
46
|
+
"""Lists the supported models.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List[Dict[str, Any]]: A list of dictionaries containing the model information.
|
|
50
|
+
"""
|
|
51
|
+
return supported_jina_models
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _post_process_onnx_output(
|
|
55
|
+
cls, output: Tuple[np.ndarray, np.ndarray]
|
|
56
|
+
) -> Iterable[np.ndarray]:
|
|
57
|
+
embeddings, attn_mask = output
|
|
58
|
+
return normalize(cls.mean_pooling(embeddings, attn_mask)).astype(np.float32)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class JinaEmbeddingWorker(OnnxTextEmbeddingWorker):
|
|
62
|
+
def init_embedding(
|
|
63
|
+
self,
|
|
64
|
+
model_name: str,
|
|
65
|
+
cache_dir: str,
|
|
66
|
+
) -> OnnxTextEmbedding:
|
|
67
|
+
return JinaOnnxEmbedding(model_name=model_name, cache_dir=cache_dir, threads=1)
|