Optimize large-scale search performance
mainFor large-scale search applications, native JavaScript may be too slow. To improve performance:
repository·main·Indexed 23 days ago
https://github.com/unum-cloud/uformA multimodal AI library for content understanding (embeddings) and generation (chat, captioning, VQA). UForm provides pocket-sized models designed for portability across servers and mobile devices using ONNX, CoreML, and PyTorch. It includes JavaScript and Python SDKs for generating text and image embeddings, and its generative models are compatible with the Hugging Face transformers library.
For large-scale search applications, native JavaScript may be too slow. To improve performance:
To reduce memory usage and increase speed, you can convert embeddings into smaller numeric representations.
Note: These approaches are natively supported by the USearch vector-search engine and SimSIMD numerics libraries.
import numpy as np
f32_embedding: np.ndarray = model.encode_text(text_data, return_features=False)
f16_embedding: np.ndarray = f32_embedding.astype(np.float16)
i8_embedding: np.ndarray = (f32_embedding * 127).astype(np.int8)
b1_embedding: np.ndarray = np.packbits((f32_embedding > 0).astype(np.uint8))When initializing encoders, you can specify computeUnits to control which Apple hardware is used. While .all is the default for maximum compatibility, explicitly targeting the Apple Neural Engine (ANE) can significantly improve performance, especially for quantized models.
Performance Comparison (Median Latency on M4 iPad):
| Model | GPU Text E. | ANE Text E. | GPU Image E. | ANE Image E. |
|---|---|---|---|---|
english-small | 2.53 ms | 0.53 ms | 6.57 ms | 1.23 ms |
english-base | 2.54 ms | 0.61 ms | 18.90 ms | 3.79 ms |
english-large | 2.30 ms | 0.61 ms | 79.68 ms | 20.94 ms |
multilingual-base | 2.34 ms | 0.50 ms | 18.98 ms | 3.77 ms |
Note: Quantized encoders use a mixture of i8, f16, and f32 and rely heavily on the ANE for performance.
Matryoshka embeddings allow you to slice large embeddings into smaller parts, enabling hierarchical search strategies. This is an alternative to standard quantization.
import numpy as np
large_embedding: np.ndarray = model.encode_text(text_data, return_features=False)
small_embedding: np.ndarray = large_embedding[:, :256]
tiny_embedding: np.ndarray = large_embedding[:, :64]UForm embedding models allow you to understand and search visual and textual content. You can load a model using get_model, which returns both processors and models for different modalities (TEXT and IMAGE). By default, it uses dtype='bfloat16' for approximately 2x speedup with minimal accuracy loss.
from uform import get_model, Modality
# Load the model and processors
processors, models = get_model('unum-cloud/uform3-image-text-english-small', device='cuda')
# Access specific encoders and processors
model_text = models[Modality.TEXT_ENCODER]
model_image = models[Modality.IMAGE_ENCODER]
processor_text = processors[Modality.TEXT_ENCODER]
processor_image = processors[Modality.IMAGE_ENCODER]To use UForm in your Python environment, install the package via pip.
pip install uformYou can install the UForm JavaScript SDK using any of the following package managers:
pnpm add uform
npm add uform
yarn add uformpnpm add uformTo increase throughput, wrap the model encoders in torch.nn.DataParallel.
Important Notes:
model.return_features = False on the original models before wrapping.DataParallel wrapper, you must use the .forward() method instead of .encode()..detach().cpu().numpy() to convert the resulting tensors back into NumPy arrays.from uform import get_model, Modality
import torch.nn as nn
# Load model
processors, models = get_model('unum-cloud/uform-vl-english-small', backend='torch')
model_text = models[Modality.TEXT_ENCODER]
model_image = models[Modality.IMAGE_ENCODER]
processor_text = processors[Modality.TEXT_ENCODER]
processor_image = processors[Modality.IMAGE_ENCODER]
# Prepare for parallel
model_text.return_features = False
model_image.return_features = False
model_text_parallel = nn.DataParallel(model_text)
model_image_parallel = nn.DataParallel(model_image)
# Usage pattern
def get_image_embedding(images):
preprocessed = processor_image(images)
embedding = model_image_parallel.forward(preprocessed)
return embedding.detach().cpu().numpy()To avoid the heavy PyTorch dependency (which can be ~5.2GB), you can install UForm with the ONNX runtime. This significantly reduces memory consumption and deployment latency, often bringing the total weight down to ~100MB for both model and runtime.
Supported ONNX execution providers include:
# Install with Torch dependency
$ conda create -n uform_torch python=3.10 -y
$ conda activate uform_torch && pip install -e ".[torch]" && conda deactivate
# Install with ONNX runtime (lighter weight)
$ conda create -n uform_onnx python=3.10 -y
$ conda activate uform_onnx && pip install -e ".[onnx]" && conda deactivateTo use UForm in your Swift project, add it via Swift Package Manager.
swift package init --type executableswift package add uformimport UFormswift package init --type executable
swift package add uformUForm generative models are designed for Chat, Image Captioning, and Visual Question Answering (VQA). They are natively compatible with the transformers library. You can load them using AutoModel and AutoProcessor with trust_remote_code=True.
from transformers import AutoModel, AutoProcessor
import torch
from PIL import Image
model = AutoModel.from_pretrained('unum-cloud/uform-gen2-dpo', trust_remote_code=True)
processor = AutoProcessor.from_pretrained('unum-cloud/uform-gen2-dpo', trust_remote_code=True)
prompt = 'Question or Instruction'
image = Image.open('image.jpg')
inputs = processor(text=[prompt], images=[image], return_tensors='pt')
with torch.inference_mode():
output = model.generate(
**inputs,
do_sample=False,
use_cache=True,
max_new_tokens=256,
eos_token_id=151645,
pad_token_id=processor.tokenizer.pad_token_id
)
prompt_len = inputs['input_ids'].shape[1]
decoded_text = processor.batch_decode(output[:, prompt_len:])[0]When using CUDA 12 or newer with ONNX backends, you must install the Nvidia toolkit and the specific onnxruntime-gpu package from the custom repository.
Follow these steps to set up the environment:
onnxruntime-gpu using the specific extra index URL.CUDA_PATH, PATH, and LD_LIBRARY_PATH to point to your CUDA installation.wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install cuda-toolkit-12
pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
export CUDA_PATH="/usr/local/cuda-12/bin"
export PATH="/usr/local/cuda-12/bin${PATH:+:${PATH}}"
export LD_LIBRARY_PATH="/usr/local/cuda-12/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"