Compare commits

...
Author SHA1 Message Date
hh-space-invader 9e21de21d6 fix: Pass providers and cuda to multimodal models 2025-03-13 08:24:04 +02:00
hh-space-invader 4bc5fbedf9 fix: Fix passing cuda and providers in single gpu settings 2025-03-13 07:15:13 +02:00
hh-space-invader 3d90072e8a new: Added experiment to benchmark fastembed on gpu 2025-03-07 06:23:01 +02:00
hh-space-invader 5ea3bbcc84 docs: Add description for changes 2025-03-05 03:08:47 +02:00
hh-space-invader 1c016a2a3f fix: Fix multi gpu settings 2025-03-05 02:45:37 +02:00
hh-space-invader 4037e14f3b chore: Remove print statement 2025-03-04 12:29:59 +02:00
hh-space-invader 758d33984d new: Shrink empty arena for multi gpu settings 2025-03-04 11:00:14 +02:00
hh-space-invader 5c46b17a24 specify shrinkage as run options not session options 2025-03-04 09:01:36 +02:00
hh-space-invader f63333d620 specify shrinkage as run options not session options 2025-03-04 08:55:52 +02:00
hh-space-invader 13b7d6d7ef specify shrinkage as run options not session options 2025-03-04 08:49:16 +02:00
hh-space-invader c212c1fe41 specify shrinkage as run options not session options 2025-03-04 08:42:49 +02:00
hh-space-invader b82e4d05f9 a 2025-03-04 08:27:29 +02:00
hh-space-invader a761dcf657 change initial chunk size 2025-03-04 07:53:01 +02:00
hh-space-invader b8d30b1cb6 new: Add arena extend strategy 2025-03-04 07:31:42 +02:00
hh-space-invader a18f735983 nit 2025-03-04 04:26:24 +02:00
hh-space-invader 4c5001595c fix: Minimize gpu memory fragmentation 2025-02-28 11:00:03 +02:00
9 changed files with 645 additions and 31 deletions
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -68,7 +68,15 @@ class OnnxModel(Generic[T]):
if device_id is None:
onnx_providers = ["CUDAExecutionProvider"]
else:
onnx_providers = [("CUDAExecutionProvider", {"device_id": device_id})]
# kSameAsRequested: Allocates only the requested memory, avoiding over-allocation.
# more precise than 'kNextPowerOfTwo', which grows memory aggressively.
# source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
onnx_providers = [
(
"CUDAExecutionProvider",
{"device_id": device_id, "arena_extend_strategy": "kSameAsRequested"},
)
]
else:
onnx_providers = ["CPUExecutionProvider"]
@@ -132,5 +140,7 @@ class EmbeddingWorker(Worker, Generic[T]):
def start(cls, model_name: str, cache_dir: str, **kwargs: Any) -> "EmbeddingWorker[T]":
return cls(model_name=model_name, cache_dir=cache_dir, **kwargs)
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
raise NotImplementedError("Subclasses must implement this method")
+17 -2
View File
@@ -5,12 +5,12 @@ import tempfile
import unicodedata
from pathlib import Path
from itertools import islice
from typing import Iterable, Optional, TypeVar
from typing import Iterable, Optional, TypeVar, Sequence
import numpy as np
from numpy.typing import NDArray
from fastembed.common.types import NumpyArray
from fastembed.common.types import NumpyArray, OnnxProvider
T = TypeVar("T")
@@ -67,3 +67,18 @@ def get_all_punctuation() -> set[str]:
def remove_non_alphanumeric(text: str) -> str:
return re.sub(r"[^\w\s]", " ", text, flags=re.UNICODE)
def is_cuda_enabled(cuda: bool, providers: Optional[Sequence[OnnxProvider]]) -> bool:
"""
Check if CUDA is enabled based on the `cuda` and `providers` parameters
"""
if cuda:
return True
if not providers:
return False
if isinstance(providers, str):
return "CUDAExecutionProvider" in providers
return isinstance(providers, (list, tuple)) and any(
isinstance(p, str) and "CUDAExecutionProvider" in p for p in providers
)
+24 -5
View File
@@ -6,13 +6,14 @@ from typing import Any, Iterable, Optional, Sequence, Type, Union
import numpy as np
from PIL import Image
import onnxruntime as ort
from fastembed.image.transform.operators import Compose
from fastembed.common.types import NumpyArray
from fastembed.common import ImageInput, OnnxProvider
from fastembed.common.onnx_model import EmbeddingWorker, OnnxModel, OnnxOutputContext, T
from fastembed.common.preprocessor_utils import load_preprocessor
from fastembed.common.utils import iter_batch
from fastembed.common.utils import iter_batch, is_cuda_enabled
from fastembed.parallel_processor import ParallelWorkerPool
# Holds type of the embedding result
@@ -74,7 +75,21 @@ class OnnxImageModel(OnnxModel[T]):
encoded = np.array(self.processor(image_files))
onnx_input = self._build_onnx_input(encoded)
onnx_input = self._preprocess_onnx_input(onnx_input)
model_output = self.model.run(None, onnx_input) # type: ignore[union-attr]
run_options = ort.RunOptions()
providers = kwargs.get("providers", None)
cuda = kwargs.get("cuda", False)
if is_cuda_enabled(cuda, providers):
device_id = kwargs.get("device_id", None)
device_id = str(device_id if isinstance(device_id, int) else 0)
# enables memory arena shrinkage, freeing unused memory after each Run() cycle.
# helps prevent excessive memory retention, especially for dynamic workloads.
# source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
run_options.add_run_config_entry(
"memory.enable_memory_arena_shrinkage", f"gpu:{device_id}"
)
model_output = self.model.run(None, onnx_input, run_options) # type: ignore[union-attr]
embeddings = model_output[0].reshape(len(images), -1)
return OnnxOutputContext(model_output=embeddings)
@@ -104,7 +119,9 @@ class OnnxImageModel(OnnxModel[T]):
self.load_onnx_model()
for batch in iter_batch(images, batch_size):
yield from self._post_process_onnx_output(self.onnx_embed(batch))
yield from self._post_process_onnx_output(
self.onnx_embed(batch, cuda=cuda, providers=providers)
)
else:
if parallel == 0:
parallel = os.cpu_count()
@@ -129,7 +146,9 @@ class OnnxImageModel(OnnxModel[T]):
class ImageEmbeddingWorker(EmbeddingWorker[T]):
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
for idx, batch in items:
embeddings = self.model.onnx_embed(batch)
embeddings = self.model.onnx_embed(batch, **kwargs)
yield idx, embeddings
@@ -6,13 +6,14 @@ from typing import Any, Iterable, Optional, Sequence, Type, Union
import numpy as np
from PIL import Image
import onnxruntime as ort
from tokenizers import Encoding, Tokenizer
from fastembed.common import OnnxProvider, ImageInput
from fastembed.common.onnx_model import EmbeddingWorker, OnnxModel, OnnxOutputContext, T
from fastembed.common.preprocessor_utils import load_tokenizer, load_preprocessor
from fastembed.common.types import NumpyArray
from fastembed.common.utils import iter_batch
from fastembed.common.utils import iter_batch, is_cuda_enabled
from fastembed.image.transform.operators import Compose
from fastembed.parallel_processor import ParallelWorkerPool
@@ -103,7 +104,21 @@ class OnnxMultimodalModel(OnnxModel[T]):
)
onnx_input = self._preprocess_onnx_text_input(onnx_input, **kwargs)
model_output = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input) # type: ignore[union-attr]
run_options = ort.RunOptions()
providers = kwargs.get("providers", None)
cuda = kwargs.get("cuda", False)
if is_cuda_enabled(cuda, providers):
device_id = kwargs.get("device_id", None)
device_id = str(device_id if isinstance(device_id, int) else 0)
# enables memory arena shrinkage, freeing unused memory after each Run() cycle.
# helps prevent excessive memory retention, especially for dynamic workloads.
# source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
run_options.add_run_config_entry(
"memory.enable_memory_arena_shrinkage", f"gpu:{device_id}"
)
model_output = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input, run_options) # type: ignore[union-attr]
return OnnxOutputContext(
model_output=model_output[0],
attention_mask=onnx_input.get("attention_mask", attention_mask),
@@ -136,7 +151,9 @@ class OnnxMultimodalModel(OnnxModel[T]):
if not hasattr(self, "model") or self.model is None:
self.load_onnx_model()
for batch in iter_batch(documents, batch_size):
yield from self._post_process_onnx_text_output(self.onnx_embed_text(batch))
yield from self._post_process_onnx_text_output(
self.onnx_embed_text(batch, cuda=cuda, providers=providers)
)
else:
if parallel == 0:
parallel = os.cpu_count()
@@ -169,7 +186,21 @@ class OnnxMultimodalModel(OnnxModel[T]):
encoded = np.array(self.processor(image_files))
onnx_input = {"pixel_values": encoded}
onnx_input = self._preprocess_onnx_image_input(onnx_input, **kwargs)
model_output = self.model.run(None, onnx_input) # type: ignore[union-attr]
run_options = ort.RunOptions()
providers = kwargs.get("providers", None)
cuda = kwargs.get("cuda", False)
if is_cuda_enabled(cuda, providers):
device_id = kwargs.get("device_id", None)
device_id = str(device_id if isinstance(device_id, int) else 0)
# enables memory arena shrinkage, freeing unused memory after each Run() cycle.
# helps prevent excessive memory retention, especially for dynamic workloads.
# source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
run_options.add_run_config_entry(
"memory.enable_memory_arena_shrinkage", f"gpu:{device_id}"
)
model_output = self.model.run(None, onnx_input, run_options) # type: ignore[union-attr]
embeddings = model_output[0].reshape(len(images), -1)
return OnnxOutputContext(model_output=embeddings)
@@ -199,7 +230,9 @@ class OnnxMultimodalModel(OnnxModel[T]):
self.load_onnx_model()
for batch in iter_batch(images, batch_size):
yield from self._post_process_onnx_image_output(self.onnx_embed_image(batch))
yield from self._post_process_onnx_image_output(
self.onnx_embed_image(batch, cuda=cuda, providers=providers)
)
else:
if parallel == 0:
parallel = os.cpu_count()
@@ -241,9 +274,11 @@ class TextEmbeddingWorker(EmbeddingWorker[T]):
) -> OnnxMultimodalModel:
raise NotImplementedError()
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
for idx, batch in items:
onnx_output = self.model.onnx_embed_text(batch)
onnx_output = self.model.onnx_embed_text(batch, **kwargs)
yield idx, onnx_output
@@ -265,7 +300,9 @@ class ImageEmbeddingWorker(EmbeddingWorker[T]):
) -> OnnxMultimodalModel:
raise NotImplementedError()
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
for idx, batch in items:
embeddings = self.model.onnx_embed_image(batch)
embeddings = self.model.onnx_embed_image(batch, **kwargs)
yield idx, embeddings
+4 -2
View File
@@ -28,7 +28,9 @@ class Worker:
def start(cls, *args: Any, **kwargs: Any) -> "Worker":
raise NotImplementedError()
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
raise NotImplementedError()
@@ -63,7 +65,7 @@ def _worker(
break
yield item
for processed_item in worker.process(input_queue_iterable()):
for processed_item in worker.process(input_queue_iterable(), **kwargs):
output_queue.put(processed_item)
except Exception as e: # pylint: disable=broad-except
logging.exception(e)
@@ -4,6 +4,7 @@ from pathlib import Path
from typing import Any, Iterable, Optional, Sequence, Type
import numpy as np
import onnxruntime as ort
from tokenizers import Encoding
from fastembed.common.onnx_model import (
@@ -14,7 +15,7 @@ from fastembed.common.onnx_model import (
)
from fastembed.common.types import NumpyArray
from fastembed.common.preprocessor_utils import load_tokenizer
from fastembed.common.utils import iter_batch
from fastembed.common.utils import iter_batch, is_cuda_enabled
from fastembed.parallel_processor import ParallelWorkerPool
@@ -71,7 +72,21 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
tokenized_input = self.tokenize(pairs, **kwargs)
inputs = self._build_onnx_input(tokenized_input)
onnx_input = self._preprocess_onnx_input(inputs, **kwargs)
outputs = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input) # type: ignore[union-attr]
run_options = ort.RunOptions()
providers = kwargs.get("providers", None)
cuda = kwargs.get("cuda", False)
if is_cuda_enabled(cuda, providers):
device_id = kwargs.get("device_id", None)
device_id = str(device_id if isinstance(device_id, int) else 0)
# Enables memory arena shrinkage, freeing unused memory after each Run() cycle.
# Helps prevent excessive memory retention, especially for dynamic workloads.
# Source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
run_options.add_run_config_entry(
"memory.enable_memory_arena_shrinkage", f"gpu:{device_id}"
)
outputs = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input, run_options) # type: ignore[union-attr]
relevant_output = outputs[0]
scores: NumpyArray = relevant_output[:, 0]
return OnnxOutputContext(model_output=scores)
@@ -110,7 +125,9 @@ class OnnxCrossEncoderModel(OnnxModel[float]):
if not hasattr(self, "model") or self.model is None:
self.load_onnx_model()
for batch in iter_batch(pairs, batch_size):
yield from self._post_process_onnx_output(self.onnx_embed_pairs(batch, **kwargs))
yield from self._post_process_onnx_output(
self.onnx_embed_pairs(batch, cuda=cuda, providers=providers, **kwargs)
)
else:
if parallel == 0:
parallel = os.cpu_count()
@@ -163,7 +180,9 @@ class TextRerankerWorker(EmbeddingWorker[float]):
) -> OnnxCrossEncoderModel:
raise NotImplementedError()
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, Any]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, Any]]:
for idx, batch in items:
onnx_output = self.model.onnx_embed_pairs(batch)
onnx_output = self.model.onnx_embed_pairs(batch, **kwargs)
yield idx, onnx_output
+1 -1
View File
@@ -344,7 +344,7 @@ class Bm25Worker(Worker):
return cls(model_name=model_name, cache_dir=cache_dir, **kwargs)
def process(
self, items: Iterable[tuple[int, Any]]
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, list[SparseEmbedding]]]:
for idx, batch in items:
onnx_output = self.model.raw_embed(batch)
+24 -5
View File
@@ -4,13 +4,14 @@ from pathlib import Path
from typing import Any, Iterable, Optional, Sequence, Type, Union
import numpy as np
import onnxruntime as ort
from numpy.typing import NDArray
from tokenizers import Encoding, Tokenizer
from fastembed.common.types import NumpyArray, OnnxProvider
from fastembed.common.onnx_model import EmbeddingWorker, OnnxModel, OnnxOutputContext, T
from fastembed.common.preprocessor_utils import load_tokenizer
from fastembed.common.utils import iter_batch
from fastembed.common.utils import iter_batch, is_cuda_enabled
from fastembed.parallel_processor import ParallelWorkerPool
@@ -82,7 +83,21 @@ class OnnxTextModel(OnnxModel[T]):
)
onnx_input = self._preprocess_onnx_input(onnx_input, **kwargs)
model_output = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input) # type: ignore[union-attr]
run_options = ort.RunOptions()
providers = kwargs.get("providers", None)
cuda = kwargs.get("cuda", False)
if is_cuda_enabled(cuda, providers):
device_id = kwargs.get("device_id", None)
device_id = str(device_id if isinstance(device_id, int) else 0)
# enables memory arena shrinkage, freeing unused memory after each Run() cycle.
# helps prevent excessive memory retention, especially for dynamic workloads.
# source: https://onnxruntime.ai/docs/get-started/with-c.html#features:~:text=Memory%20arena%20shrinkage:
run_options.add_run_config_entry(
"memory.enable_memory_arena_shrinkage", f"gpu:{device_id}"
)
model_output = self.model.run(self.ONNX_OUTPUT_NAMES, onnx_input, run_options) # type: ignore[union-attr]
return OnnxOutputContext(
model_output=model_output[0],
attention_mask=onnx_input.get("attention_mask", attention_mask),
@@ -115,7 +130,9 @@ class OnnxTextModel(OnnxModel[T]):
if not hasattr(self, "model") or self.model is None:
self.load_onnx_model()
for batch in iter_batch(documents, batch_size):
yield from self._post_process_onnx_output(self.onnx_embed(batch))
yield from self._post_process_onnx_output(
self.onnx_embed(batch, cuda=cuda, providers=providers)
)
else:
if parallel == 0:
parallel = os.cpu_count()
@@ -140,7 +157,9 @@ class OnnxTextModel(OnnxModel[T]):
class TextEmbeddingWorker(EmbeddingWorker[T]):
def process(self, items: Iterable[tuple[int, Any]]) -> Iterable[tuple[int, OnnxOutputContext]]:
def process(
self, items: Iterable[tuple[int, Any]], **kwargs: Any
) -> Iterable[tuple[int, OnnxOutputContext]]:
for idx, batch in items:
onnx_output = self.model.onnx_embed(batch)
onnx_output = self.model.onnx_embed(batch, **kwargs)
yield idx, onnx_output