voyage-4-nano-mlx-4bit

voyageai/voyage-4-nano converted for MLX, so it runs on Apple silicon. Smallest. Measurably degraded; see the note below.

voyage-4-nano is not served by the Voyage API — the open checkpoint is the only way to run it. Embeddings from this port land in the same space as the hosted voyage-4 models, so you can index locally and query with those: same-text cosine 0.969 against voyage-4-lite, 0.952 against voyage-4, 0.894 against voyage-4-large (12 texts, versus 0.32–0.36 for non-matching text).

Unofficial community port. Not affiliated with Voyage AI or MongoDB.

Use

pip install voyage-4-nano-mlx
from voyage_4_nano_mlx import load

emb = load("sanjay920/voyage-4-nano-mlx-4bit")

q = emb.encode_query("Which planet is known as the Red Planet?")
d = emb.encode_document([
    "Venus is often called Earth's twin because of its similar size.",
    "Mars, known for its reddish appearance, is called the Red Planet.",
])
emb.similarity(q, d)          # [[0.4052 0.6514]]

Queries and documents take different trained prefixes; encode_query and encode_document apply them. Use encode() for symmetric tasks.

Matryoshka dims (2048 / 1024 / 512 / 256) and compact outputs:

emb.encode_document(docs, dims=256)                        # 256-d, unit norm
emb.encode_document(docs, dims=512, output_dtype="int8")   # 512 int8 values
emb.encode_document(docs, output_dtype="ubinary")          # 2048 bits = 256 bytes

To shrink an index, prefer dims=256 + int8 over a smaller weight file — it cuts the index 32x and leaves the encoder faithful.

Variants

repo size min cosine vs fp64 reference
sanjay920/voyage-4-nano-mlx-bf16 672 MB 0.99997
sanjay920/voyage-4-nano-mlx-8bit 362 MB 0.99966
sanjay920/voyage-4-nano-mlx-6bit 280 MB 0.99691
sanjay920/voyage-4-nano-mlx-4bit 198 MB 0.97465

Weight quantization saves disk, not time: at typical sequence lengths this model is compute-bound, and 8-bit and 4-bit both run ~7% slower than bf16.

Verification

Against the reference PyTorch implementation, with a float64 run as ground truth:

this variant, pooled cosine vs fp64 reference 0.97465
bf16 on MTEB SciFact / NFCorpus (nDCG@10) 0.75262 / 0.39576
PyTorch reference, same tasks 0.75191 / 0.39568

On MLX's CPU backend the unquantized port matches float64 as closely as PyTorch's own float32 does (max error 1.03e-04 vs 1.07e-04). Tokenization is byte-identical to the reference across 14 texts covering CJK, emoji, URLs and whitespace edge cases. Method and full numbers: docs/PARITY.md.

Model

A Qwen3 backbone run bidirectionally, not an encoder-only model:

Qwen3, 12 layers, d=1024, 16 heads / 8 KV heads, head_dim=128
RMSNorm, SwiGLU, RoPE theta=1e6, per-head q/k RMSNorm, bidirectional attention
-> final norm
-> linear 1024 -> 2048   (per token, before pooling)
-> mean pool over the attention mask
-> L2 normalize

346M parameters, 32k context, multilingual.

Notes

  • Metal's float32 matmul accumulates at reduced precision, so GPU fp32 is slightly less exact than CPU fp32 (0.9999995 vs 1.0000000 cosine). It does not affect retrieval.
  • 32k-token inputs use dense attention and take ~6 s each.
  • This 4-bit build loses real quality (cosine 0.97465, versus 0.99966 at 8-bit) and is not faster. Use it only when 198 MB genuinely matters; otherwise take 8-bit, or 4-bit with dims=256 accepted as a quality trade.

Source, tests and benchmarks: https://github.com/sanjay920/voyage-4-nano-mlx

Downloads last month
-
Safetensors
Model size
54.4M params
Tensor type
BF16
·
U32
·
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for sanjay920/voyage-4-nano-mlx-4bit

Quantized
(9)
this model