This model powers DocuFind: find anything on your Mac by describing it.
Documents, photos and recordings, searched by meaning, entirely on your Mac's Neural Engine.
macOS 15 or later · Running on Apple silicon · Free and open source
DocuFind
You remember what it was, not where you put it. Type "that sunset at the beach", "the invoice from the plumber" or "the song with the saxophone", and DocuFind finds it. It runs this model on your Mac: no cloud, no account, no peeking.
- One search for everything: documents, PDFs (even scanned ones), photos and recordings
- Drop it or record it: search with a file, or with a few seconds of sound from the microphone
- Pops up anywhere: press ⌥ Space in any app for a floating search bar
- Straight to the good part: recordings play from the matching moment, PDFs open at the right page
- Exact words too: invoice numbers, names, and text inside screenshots
- Quick to index: on an M4, about 50 photos a minute and an hour of recordings in under a minute
Download DocuFind, drag it into Applications, and its welcome guide downloads this model once and gets your Mac ready.
The model
A Core ML conversion of BidirLM-Omni-2.5B-Embedding
(revision 447a6e31be61b84443144afda21374339ce408e6) that embeds text, photos and audio entirely on the
Apple Neural Engine, with no PyTorch. It produces the same 2,048-dimensional vectors as the original model.
Speed
On an Apple M4, compared with the original model in PyTorch (bf16) on the Apple GPU:
| Apple GPU | Neural Engine | ||
|---|---|---|---|
| Text | 0.12 s | 0.04 s | 2.7× faster |
| Photo | 1.13 s | 0.59 s | 1.9× faster |
| 30 s of audio | 0.56 s | 0.33 s | 1.7× faster |
About twice as fast on average, and the GPU stays free. The first load compiles the packages for the Mac, which takes a few minutes once.
Quality
Checked end to end against the original model in bf16 on the Apple GPU, on real files:
| Inputs | Agreement (mean / lowest) | Same best search match | |
|---|---|---|---|
| Text | 19 passages, up to 895 tokens | 0.9989 / 0.9983 | |
| Photos | 23 photos | 0.9981 / 0.9970 | 8 of 8 queries |
| Audio | 8 recordings, 30 s each | 0.9986 / 0.9975 | 8 of 8 queries |
Files
| File | What it is | Size |
|---|---|---|
text_first.mlpackage |
Text layers 1–14, with the photo features injected after layers 1 and 2. Functions tokens512, tokens640, tokens1024 share one set of 8-bit weights |
0.68 GB |
text_last.mlpackage |
Text layers 15–28, final norm and mean pooling (up to 8 inputs per run). Same functions, 8-bit weights | 0.68 GB |
vision.mlpackage |
The vision encoder, functions patches1728 and patches2304, float16 |
0.73 GB |
audio.mlpackage |
The audio encoder for one 30-second window, float16 | 0.61 GB |
embeddings.npy |
Token embedding table, float16 | 0.62 GB |
tokenizer.json, vision_positions.npy, mel_filters.npy |
Tokenizer, vision position table, Whisper mel filters | |
omni.json, files.json |
Settings, and every file's size and SHA-256 | |
check/ |
Sample inputs with the original model's vectors, to check a Mac before use | |
omni_coreml.py |
A NumPy runtime: tokens, photo patches, audio spectrum, positions, and the Core ML calls |
All parts use Apple's Neural Engine layout: (batch, channels, 1, tokens) tensors, 1×1 convolutions, attention per head. Every size-dependent input (positions, masks, photo grids) is computed outside, so the models have fixed shapes and run entirely on the Neural Engine. Requires macOS 15 or later on Apple silicon.
Usage
from pathlib import Path
from PIL import Image
from omni_coreml import OmniModel
model = OmniModel(Path("BidirLM-Omni-2.5B-Embedding-CoreML")) # a local copy of this repository
print(model.self_check()) # agreement with the original model's samples
text = model.encode_texts(["a beach at sunset"])[0]
photo = model.encode_image(Image.open("photo.jpg"))
sound = model.encode_audio(samples, sampling_rate) # mono float32 samples
Vectors are unnormalized mean-pooled embeddings: normalize them before comparing with cosine similarity.
It needs numpy, coremltools, tokenizers, Pillow, and librosa for audio that isn't at 16 kHz.
Limitations
- Photos are read at most 768 pixels on their longest side (at most 2,304 patches).
- Text is read whole up to 1,024 tokens; longer text is embedded in parts and averaged.
- Audio is read in 30-second windows; longer audio is averaged over windows.
- Text weights are 8-bit; all computation is float16, the Neural Engine's format.
Citation
@misc{boizard2026bidirlmtextomnimodalbidirectional,
title={BidirLM: From Text to Omnimodal Bidirectional Encoders by Adapting and Composing Causal LLMs},
author={Nicolas Boizard and Théo Deschamps-Berger and Hippolyte Gisserot-Boukhlef and Céline Hudelot and Pierre Colombo},
year={2026},
eprint={2604.02045},
archivePrefix={arXiv},
primaryClass={cs.CL},
url={https://arxiv.org/abs/2604.02045},
}
Licensed under Apache 2.0, like the original model. DocuFind is MIT-licensed.