How to use from the
Use from the
llama-cpp-python library
# !pip install llama-cpp-python

from llama_cpp import Llama

llm = Llama.from_pretrained(
	repo_id="Uncino/bge-m3-multivector-gguf",
	filename="",
)
output = llm(
	"Once upon a time,",
	max_tokens=512,
	echo=True
)
print(output)

BGE-M3 GGUF β€” Dense + Sparse + ColBERT

This repository provides pre-converted GGUF files for the BAAI BGE-M3 embedding model, enhanced to output all three embedding types from a single inference call:

Head Output Shape Use Case
Dense CLS pooling β†’ L2 normalization float[1024] Semantic similarity (cosine)
Sparse linear β†’ ReLU β†’ log(1+x) per token [token_id, weight] pairs Lexical / hybrid search
ColBERT linear β†’ L2 norm per token n_tokens Γ— float[1024] Token-level late interaction

A single llama_decode() computes all three. No separate models, no external BM25.

Unlike the standard BGE-M3 GGUF (which only preserves the dense head), these files contain the full cls.sparse and cls.colbert weights extracted from the original sentence-transformers checkpoint.


Available Files

File Quantization Size Heads
bge-m3-multivector-q8.gguf Q8_0 ~600 MB Dense + Sparse + ColBERT
bge-m3-multivector-f16.gguf F16 ~1.1 GB Dense + Sparse + ColBERT

Q8_0 is recommended for production β€” identical quality, half the size.


Usage

With llama.cpp-mv (recommended)

Use the companion llama.cpp-mv fork, which supports all three heads natively. Start the server:

llama-server --model bge-m3-multivector-q8.gguf \
    --pooling cls --no-mmap --embedding \
    --host 0.0.0.0 --port 8080 \
    -c 8192 -b 8192 -ub 8192 -np 1 --fit off \
    -ngl 99

Companion fork with inference code: [llama.cpp-mv](https://github.com/iz0eyj/llama.cpp-mv)

Default call returns dense + sparse:

curl -X POST http://127.0.0.1:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"input": "Samarcanda Γ¨ una cittΓ  meravigliosa"}'

Add ?colbert=true (or "colbert": true in the JSON body) for all three:

curl -X POST "http://127.0.0.1:8080/v1/embeddings?colbert=true" \
  -H "Content-Type: application/json" \
  -d '{"input": "Samarcanda Γ¨ una cittΓ  meravigliosa"}'

JSON Response Format

{
  "data": [{
    "embedding": [0.001, -0.023, ...],           // dense: 1024 floats
    "sparse_embedding": [                          // sparse: [token_id, weight] pairs
      [0,       0.116],
      [121283,  0.154],
      [2,       0.095]
    ],
    "colbert_embedding": [                         // colbert: n_tokens Γ— 1024
      [-0.768, -0.048, ...],
      [ 0.312,  1.205, ...],
      [-0.015, -0.823, ...]
    ],
    "index": 0,
    "object": "embedding"
  }],
  "model": "bge-m3-multivector-q8.gguf",
  "usage": {"prompt_tokens": 5, "total_tokens": 5}
}
  • colbert_embedding is only present when ?colbert=true is set
  • Sparse weights > 0 indicate lexically active tokens
  • ColBERT vectors are L2-normalized per token (norm β‰ˆ 32, use dot product for similarity)

Per-Token ColBERT Similarity (Late Interaction)

import numpy as np

def colbert_score(vecs_a, vecs_b):
    """MaxSim per token, then average."""
    sim = np.dot(vecs_a, vecs_b.T)  # [n_a, n_b]
    return np.mean(np.max(sim, axis=1))

# Example: "Samarcanda Γ¨ bella" vs "Samarcanda Γ¨ meravigliosa"
# score β‰ˆ 1010 (similar) vs β‰ˆ 330 (different topic)

How These GGUF Files Were Created

The standard llama.cpp converter strips tensors it doesn't recognize. BGE-M3's sparse and colbert heads (sparse_linear.pt, colbert_linear.pt) are stored separately from the main model checkpoint and were silently skipped.

Our converter patches (in llama.cpp-mv):

  1. gguf-py/gguf/constants.py β€” Added CLS_SPARSE and CLS_COLBERT tensor types
  2. gguf-py/gguf/tensor_mapping.py β€” Mapped sparse_linear β†’ cls.sparse, colbert_linear β†’ cls.colbert
  3. conversion/bert.py β€” Load .pt files from model directory via generate_extra_tensors()

Command:

python convert_hf_to_gguf.py BAAI/bge-m3 --outtype q8_0 --outfile bge-m3-multivector-q8.gguf

Inference Architecture (llama.cpp-mv)

The companion C++ fork adds three separate output paths from the same hidden states:

Transformer (24 layers XLM-RoBERTa)
    β”‚
    β–Ό hidden_states [1024, n_tokens]
    β”‚
    β”œβ”€β”€ [CLS] ──► L2 normalize ──► dense [1024]
    β”œβ”€β”€ sparse_linear β†’ ReLU β†’ log1p ──► sparse [n_tokens] scalars
    └── colbert_linear β†’ L2 norm ──► colbert [n_tokens Γ— 1024]

Key design decisions:

  • log1p for sparse is applied on CPU after GPU transfer (avoids new GGML op)
  • ColBERT uses ggml_norm for per-token L2 normalization
  • Output buffers are sized for max batch size, zero overhead for small requests

Limitations

  • ColBERT scales with token count β€” 1000 tokens = 4 MB per document. Best used on short text (search queries, titles, paragraphs). For long documents, pre-filter with dense search, then apply ColBERT to top-K candidates.
  • CPU mode is unstable on some upstream llama.cpp versions due to F16/F32 mixed-type operations. Use Vulkan (`-ngl 99

Companion fork with inference code: llama.cpp-mv `) or CUDA.

  • The ColBERT head is 1024Γ—1024 (4 MB) β€” negligible compared to the 600 MB model body.

License

MIT β€” matches both BAAI/bge-m3 and llama.cpp.

Credits

Downloads last month
481
GGUF
Model size
0.6B params
Architecture
bert
Hardware compatibility
Log In to add your hardware

16-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support