Instructions to use MBZUAI/Omni-Embed-Mini-0.9B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use MBZUAI/Omni-Embed-Mini-0.9B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="MBZUAI/Omni-Embed-Mini-0.9B", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("MBZUAI/Omni-Embed-Mini-0.9B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Omni-Embed-Mini-0.9B
935M parameters. Text, speech, general audio, image, video, and visually-rich documents in a single shared cosine space, from a backbone that is never updated.
Project page | Code | Sibling model: Omni-Embed-Mini-2.3B
What this is
Omni-Embed-Mini recasts cross-modal alignment as self-distillation through one shared frozen causal backbone. Every media sample is paired with a dense cascaded caption; the teacher target is the EOS-pooled embedding of that caption produced by the identical frozen backbone that processes the student input. Teacher and student therefore inhabit byte-identical geometry, and because no text-side parameter is ever updated, adding five modalities cannot degrade the text representation.
Only the projectors and small phased LoRA adapters on the modality encoders are trained. A Matryoshka SigLIP contrastive objective and an online hybrid hard-negative miner supply the contrastive signal.
At 935M parameters this is 2.7x to 9.5x smaller than competing open omni embedders.
Results
Six modalities, evaluated with the pipeline in the code repository.
| Modality | Benchmark | Metric | 0.9B v1 | 0.9B v2 | 2.3B v1 | 2.3B v2 |
|---|---|---|---|---|---|---|
| Text | MTEB-v2 BEIR-8 | nDCG@10 | 49.57 | 47.94 | ||
| Speech | MAEB (12 tasks) | mean | 43.28 | 48.86 | ||
| Audio | MAEB (10 tasks) | mean | 33.43 | 33.44 | ||
| Image | MMEB-V2 (10 tasks) | hit@1 | 26.29 | 64.80 | ||
| Video | MMEB-V2 (6 tasks) | hit@1 | 18.48 | 55.18 | ||
| Vis-Doc | ViDoRe-v3 (7 tasks) | nDCG@5 | 46.92 | 58.10 |
v1 and v2 are tagged revisions of this same repository, so revision="v1.0" pins the
numbers in the v1 column.
The text score is the headline result: the 0.9B preserves its frozen backbone's MTEB-v2 score while extending it to five further modalities.
Quick start
pip install "transformers>=5.0" torch pillow numpy soundfile huggingface_hub
Qwen/Qwen3.5-0.8B (about 1.7 GB) is fetched on first load to build the visual tower.
import torch
from transformers import AutoModel, AutoProcessor
REPO = "MBZUAI/Omni-Embed-Mini-0.9B"
model = AutoModel.from_pretrained(
REPO, revision="v1.0", trust_remote_code=True, dtype=torch.bfloat16,
).cuda().eval()
processor = AutoProcessor.from_pretrained(REPO, revision="v1.0", trust_remote_code=True)
# OpenAI-style multimodal messages: text, audio, image, video, doc, or any composition.
messages = [{"role": "user", "content": [
{"type": "audio", "audio": "path/to/clip.wav"},
{"type": "text", "text": "rain on a tin roof at night"},
]}]
inputs = processor.apply_chat_template(
messages, role="passage", tokenize=True, return_tensors="pt",
).to("cuda")
with torch.no_grad():
doc = model(**inputs).pooler_output # (1, 1024), already L2-normalised
# Query side. role="query" applies the retrieval instruction template.
# text_recipe="chat" puts a text query in the same subspace as media documents;
# omit it only for pure text-to-text retrieval, which uses the native recipe.
query = processor.apply_chat_template(
[{"role": "user", "content": "rain on a tin roof at night"}],
role="query", tokenize=True, return_tensors="pt", text_recipe="chat",
).to("cuda")
with torch.no_grad():
q = model(**query).pooler_output
print("cosine:", float(q @ doc.T)) # normalised, so dot == cosine
Matryoshka (truncatable) embeddings
model(**inputs, truncate_dim=N).pooler_output returns a prefix-truncated, renormalised
vector. Slicing pooler_output yourself is not equivalent unless you renormalise after.
Supported N: 128, 256, 512, 1024. Use a smaller N to cut index size at a modest recall cost.
Architecture
| Component | This model |
|---|---|
| Backbone (frozen) | Qwen/Qwen3-Embedding-0.6B |
| Embedding dim | 1024 |
| Vision | custom Qwen/Qwen3.5-0.8B tower (vision_encoder.pt) |
| Speech encoder | openai/whisper-small |
| Audio encoder | mispeech/dasheng-base |
| Matryoshka dims | 128, 256, 512, 1024 |
| Video | encoded as sampled frames (video_as_images: true) |
Trained parameters: audio and vision projectors plus phase-2 LoRA adapters on the modality encoders (48 Whisper, 24 Dasheng, and 24 vision adapted tensors, merged into the released weights). The backbone is frozen at every stage and ships unmodified apart from a vocabulary resize that adds the media placeholder tokens.
Files
config.json modeling_omni_embed.py processing_omni_embed.py
configuration_omni_embed.py chat_template.jinja processor_config.json
tokenizer.json tokenizer_config.json
backbone/ # frozen backbone (vocab-resized), safetensors
whisper_encoder.pt dasheng_encoder.pt vision_encoder.pt
projector_weights.pt # projectors; LoRA already merged into the encoders
The .pt files are PyTorch pickles, and the custom modeling code requires
trust_remote_code=True. Load only from a source you trust.
Citation
@inproceedings{omniembedmini2026,
title = {Omni-Embed-Mini: Binding Modalities Without Forgetting via Dense Distillation},
author = {TBD},
booktitle = {TBD},
year = {2026},
note = {Camera-ready in preparation}
}
- Downloads last month
- 25