Model
Single-vector (dense) retrievers and multi-vector (late-interaction, ColBERT) retrievers are usually presented as a choice. They are strong in different places. A dense vector captures what a text is about, which is what counts for arguments, duplicate questions and scientific topics. Token-level matching finds the exact entity or phrase, which is what counts for multi-hop and factoid questions. We measured this on two fine-tunes of our own backbone: the dense one was ahead on ArguAna, SCIDOCS, FiQA and Touché, the ColBERT one on HotpotQA, NQ, MS MARCO and DBPedia, and simply adding their two scores beat the better of the two by more than two points on NanoBEIR.
The usual way to get both is to run two models, keep two indexes and fuse two result lists. UIBK-Embedding does it inside one model:
Nothing changes for the index or the scorer: the model loads as a PyLate ColBERT model and works with PLAID as it is.
The name spells the recipe, Unifying late Interaction and Bi-encoders with Knowledge distillation, and UIBK is the University of Innsbruck, where the model was built.
UIBK-Embedding-v1 is an English retrieval model built on ModernBERT-base (179M parameters) that holds a late-interaction (ColBERT) retriever and a dense retriever in one encoder and one index. It was trained by the Data Science group of the University of Innsbruck using PyLate.
Notably it:
Two things should be said for a fair reading of the comparison with LateOn. The model has 30M more parameters (its second 4-layer top), and it reads documents up to 2048 tokens, where LateOn's reported numbers use 300; with 2048-token documents LateOn scores 57.69 in our evaluation. And unlike LateOn it was trained with knowledge distillation and with prompts, the two steps LightOn name as the next ones for their own models.
All numbers of UIBK-Embedding-v1 below come from MTEB 2.21.6 with its stock PyLate wrapper and a PLAID index with default settings.
| Model | Average | Size (M) | Embed dim | ArguAna | CQADupstackRetrieval | ClimateFEVER | DBPedia | FEVER | FiQA2018 | HotpotQA | MSMARCO | NFCorpus | NQ | QuoraRetrieval | SCIDOCS | SciFact | TRECCOVID | Touche2020 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ColBERTv2 | 48.63 | 110 | 128 | 46.50 | 38.30 | 17.60 | 45.20 | 78.50 | 35.40 | 67.50 | 46.00 | 33.70 | 52.40 | 85.50 | 15.40 | 68.90 | 72.60 | 26.00 |
| Jina-ColBERT-v2 | 51.85 | 600 | 128 | 36.60 | 40.80 | 23.90 | 47.10 | 80.50 | 40.80 | 76.60 | 46.90 | 34.60 | 64.00 | 88.70 | 18.60 | 67.80 | 83.40 | 27.40 |
| ColBERT-small | 53.79 | 33 | 96 | 50.09 | 38.75 | 33.07 | 45.58 | 90.96 | 41.15 | 76.11 | 43.50 | 37.30 | 59.10 | 87.72 | 18.42 | 74.77 | 84.59 | 25.69 |
| GTE-ModernColBERT-v1 | 54.75 | 149 | 128 | 47.52 | 41.08 | 31.33 | 47.56 | 87.67 | 45.25 | 77.48 | 45.60 | 37.83 | 61.62 | 86.71 | 19.22 | 76.33 | 84.84 | 31.25 |
| ColBERT-Zero |
The numbers of the other models are those of the LateOn model card. The row "LateOn, 2048-token documents" is our own evaluation of LateOn with the document length raised from 300 to 2048.
UIBK-Embedding-v1 reaches 59.79 average NDCG@10, 2.57 points above LateOn, and is ahead of it on all 15 datasets. The largest gains are on Touche2020 (+9.26), ArguAna (+6.04), ClimateFEVER (+5.22), NQ (+3.89) and DBPedia (+3.23). These are datasets of both kinds: ArguAna and Touché are the long-query, argument-style datasets where dense models lead, NQ and DBPedia are entity-centred datasets where late interaction leads. The model does not trade one family's datasets for the other's, which is what the two heads are for.
Long documents explain part of the gap, not most of it: LateOn gains 0.47 on average from 2048-token documents (almost all of it on Touché), which leaves about two points to the model and its training.
Like LateOn and the other models trained on this collection, the model is not zero-shot on the BEIR datasets whose training splits are in the fine-tuning data (MS MARCO, NQ, HotpotQA, FEVER, FiQA). Training queries that equal a BEIR test or development query were removed from every training set (see Training Details).
| Model | Average | NanoArguAna | NanoClimateFever | NanoDBPedia | NanoFEVER | NanoFiQA2018 | NanoHotpotQA | NanoMSMARCO | NanoNFCorpus | NanoNQ | NanoQuora | NanoSCIDOCS | NanoSciFact | NanoTouche2020 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| UIBK-Embedding-v1 | 70.55 | 59.84 | 51.65 | 69.20 | 96.57 | 64.34 | 92.72 | 68.99 | 39.22 | 82.14 | 96.99 | 45.40 | 83.93 | 66.14 |
LateOn's card has no NanoBEIR numbers. In our development evaluation of NanoBEIR (exact MaxSim search, no index) LateOn scores 67.50 and UIBK-Embedding-v1 70.43.
One trunk, two tops. The ModernBERT embeddings and the first 18 layers are shared. Each head then has its own copy of the last 4 layers, so the token head and the dense head are free to make different errors. A single encoder with all 22 layers shared reached a plateau that three different training signals could not move; the two tops moved it.
Token head. The usual ColBERT projection (768 → 128), one vector per token. For long queries only the first 64 tokens take part in token matching.
Dense head. Latent-attention pooling (as in NV-Embed) over the dense top's states gives one 768-dimensional sentence vector, which reads the whole query or document. A 768-dimensional vector does not fit into a 128-dimensional slot, so it is rotated and cut into 3 slices of 127 dimensions: the 3 slot vectors. They take the first three positions of the output list.
One MaxSim. Every output vector has 128 dimensions and unit norm. Dimension 0 is a tag, positive for token vectors and negative for slot vectors:
token vector [ +sqrt(0.1) ; sqrt(0.9) * u_t ] u_t: the projected token state
slot vector s [ -sqrt(0.775) ; sqrt(0.225) * g_s ] g_s: slice s of the rotated dense vector, normalised
token x token = 0.1 + 0.9 * cos(u, u')
slot s x slot s = 0.775 + 0.225 * cos(g_s, g'_s)
token x slot = -0.28 + (about 0) never the maximum
Because of the tag, a query token only ever picks a document token and a slot only a slot. Plain MaxSim is therefore, up to a constant per query,
MaxSim(q, d) = 0.9 * [ token MaxSim + 0.75 * mean slice cosine ]
the fixed-weight sum of a ColBERT score and a dense score, computed by any ColBERT index and scorer with no change.
Every number in this section is a full BEIR run (15 datasets) of that model in our development evaluation: MTEB with a multi-GPU wrapper. The released model scores 59.74 there and 59.79 with MTEB's stock wrapper.
What did not help. Each of these was a full training run with a full BEIR evaluation:
Two seeds of one recipe differ by about 0.2, so differences below that are noise. Development decisions were taken on BEIR itself, which makes the last tenths optimistic; see Limitations.
query: and document: (required)branched_transformer.py (the two-top encoder) and hybrid_tokens.py (the output layer) in this repositoryColBERT(
(0): Transformer({'max_seq_length': 299, 'do_lower_case': False, 'shared_layers': 18, 'architecture': 'ModernBertModel'})
(1): HybridTokens({'dim': 768, 'token_dim': 128, 'slots': 3, 'local_weight': 0.9, 'slot_weight': 0.225, 'num_latents': 512, 'num_heads': 8, 'ff_mult': 4, 'query_marker_id': 50368, 'query_local_cap': 64, 'query_local_ref': 0, 'query_local_power': 1.0, 'query_slot_weight': -1.0, 'doc_slot_weight': -1.0, 'slot_copies': 1}, params=9.9M)
)
The input lengths are set by PyLate at encoding time from the model's configuration: 256 tokens for queries and 2048 for documents.
The model is used with PyLate. Two things differ from an ordinary ColBERT model:
trust_remote_code=True, because two small modules in this repository define the two-top encoder and the output layer;prompt_name="query" for queries and prompt_name="document" for documents.First install the PyLate library:
pip install -U pylate "transformers>=5.3" "sentence-transformers>=5.3"
from pylate import models, rank
model = models.ColBERT("DataScience-UIBK/uibk-embedding-v1", trust_remote_code=True)
query = "Which planet is known as the Red Planet?"
documents = [
"Venus is often called Earth's twin because of its similar size and proximity.",
"Mars, known for its reddish appearance, is often referred to as the Red Planet.",
"Jupiter, the largest planet in our solar system, has a prominent red spot.",
"Saturn, famous for its rings, is sometimes mistaken for the Red Planet.",
]
queries_embeddings = model.encode([query], is_query=True, prompt_name="query")
documents_embeddings = model.encode(documents, is_query=False, prompt_name="document")
print(queries_embeddings[0].shape, documents_embeddings[0].shape)
# (14, 128) (18, 128)
# MaxSim late-interaction scoring (higher is more relevant)
ranking = rank.rerank(
documents_ids=[[1, 2, 3, 4]],
queries_embeddings=queries_embeddings,
documents_embeddings=[documents_embeddings],
)
print([(d["id"], round(d["score"], 2)) for d in ranking[0]])
# [(2, 13.45), (4, 13.36), (3, 13.26), (1, 13.05)]
Use this model with PyLate to index and retrieve documents. The index uses FastPLAID for efficient similarity search.
Load the ColBERT model and initialize the PLAID index, then encode and index your documents:
from pylate import indexes, models, retrieve
# Step 1: Load the ColBERT model
model = models.ColBERT(
model_name_or_path="DataScience-UIBK/uibk-embedding-v1",
trust_remote_code=True,
)
# Step 2: Initialize the PLAID index
index = indexes.PLAID(
index_folder="pylate-index",
index_name="index",
override=True, # This overwrites the existing index if any
)
# Step 3: Encode the documents
documents_ids = ["1", "2", "3"]
documents = ["document 1 text", "document 2 text", "document 3 text"]
documents_embeddings = model.encode(
documents,
batch_size=32,
is_query=False, # Ensure that it is set to False to indicate that these are documents, not queries
prompt_name="document", # The document prompt is required
show_progress_bar=True,
)
# Step 4: Add document embeddings to the index by providing embeddings and corresponding ids
index.add_documents(
documents_ids=documents_ids,
documents_embeddings=documents_embeddings,
)
Note that you do not have to recreate the index and encode the documents every time. Once you have created an index and added the documents, you can re-use the index later by loading it:
# To load an index, simply instantiate it with the correct folder/name and without overriding it
index = indexes.PLAID(
index_folder="pylate-index",
index_name="index",
)
Once the documents are indexed, you can retrieve the top-k most relevant documents for a given set of queries. To do so, initialize the ColBERT retriever with the index you want to search in, encode the queries and then retrieve the top-k documents to get the top matches ids and relevance scores:
# Step 1: Initialize the ColBERT retriever
retriever = retrieve.ColBERT(index=index)
# Step 2: Encode the queries
queries_embeddings = model.encode(
["query for document 3", "query for document 1"],
batch_size=32,
is_query=True, # Ensure that it is set to True to indicate that these are queries
prompt_name="query", # The query prompt is required
show_progress_bar=True,
)
# Step 3: Retrieve top-k documents
scores = retriever.retrieve(
queries_embeddings=queries_embeddings,
k=10, # Retrieve the top 10 matches for each query
)
For large collections of long documents, pass batch_size="auto" to indexes.PLAID(...): with 2048-token documents the fixed default scoring batch can run out of GPU memory.
If you only want to use the model to perform reranking on top of your first-stage retrieval pipeline without building an index, you can simply use rank function and pass the queries and documents to rerank:
from pylate import rank, models
queries = [
"query A",
"query B",
]
documents = [
["document A", "document B"],
["document 1", "document C", "document B"],
]
documents_ids = [
[1, 2],
[1, 3, 2],
]
model = models.ColBERT(
model_name_or_path="DataScience-UIBK/uibk-embedding-v1",
trust_remote_code=True,
)
queries_embeddings = model.encode(
queries,
is_query=True,
prompt_name="query",
)
documents_embeddings = model.encode(
documents,
is_query=False,
prompt_name="document",
)
reranked_documents = rank.rerank(
documents_ids=documents_ids,
queries_embeddings=queries_embeddings,
documents_embeddings=documents_embeddings,
)
query: and document: .trust_remote_code=True, transformers >= 5.3 and the two prompts. The MultiVectorEncoder class of Sentence Transformers 6 has not been tested with this model.@misc{uibk2026embedding,
title={UIBK-Embedding-v1: Unifying late Interaction and Bi-encoders with Knowledge distillation},
author={{Data Science Group, University of Innsbruck}},
year={2026},
howpublished={\url{https://huggingface.co/DataScience-UIBK/uibk-embedding-v1}},
}
@misc{sourty2026denseonlateon,
title={DenseOn with the LateOn: Open State-of-the-Art Single and Multi-Vector Models},
author={Sourty, Raphael and Chaffin, Antoine and Weller, Orion and Demoura, Paulo and Chatelain, Amelie},
year={2026},
howpublished={\url{https://huggingface.co/blog/lightonai/denseon-lateon}},
}
@inproceedings{DBLP:conf/cikm/ChaffinS25,
author = {Antoine Chaffin and
Rapha{\"{e}}l Sourty},
editor = {Meeyoung Cha and
Chanyoung Park and
Noseong Park and
Carl Yang and
Senjuti Basu Roy and
Jessie Li and
Jaap Kamps and
Kijung Shin and
Bryan Hooi and
Lifang He},
title = {PyLate: Flexible Training and Retrieval for Late Interaction Models},
booktitle = {Proceedings of the 34th {ACM} International Conference on Information
and Knowledge Management, {CIKM} 2025, Seoul, Republic of Korea, November
10-14, 2025},
pages = {6334--6339},
publisher = {{ACM}},
year = {2025},
url = {https://github.com/lightonai/pylate},
doi = {10.1145/3746252.3761608},
}
@inproceedings{reimers-2019-sentence-bert,
title = "Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks",
author = "Reimers, Nils and Gurevych, Iryna",
booktitle = "Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing",
month = "11",
year = "2019",
publisher = "Association for Computational Linguistics",
url = "https://arxiv.org/abs/1908.10084"
}
This model stands on open work. We thank LightOn for PyLate, FastPLAID, the public pre-training and fine-tuning collections and LateOn, the reference that set the bar; Answer.AI and LightOn for ModernBERT; Mixedbread for the cross-encoder used as the teacher; the authors of NV-Embed for latent-attention pooling; and the teams behind Sentence Transformers, MTEB and BEIR.
Innsbruck means "bridge over the Inn". The logo is that bridge. The white arch stones are the token vectors of the late-interaction head, and the three golden keystones that hold the arch together are the three slot vectors of the dense head, gold like the roof of the Goldenes Dachl. Through the arch you see the Nordkette, below it runs the Inn. One arch, two kinds of stones: one model, two retrievers.
| 55.39 |
| 149 |
| 128 |
| 52.82 |
| 41.41 |
| 35.90 |
| 47.43 |
| 90.52 |
| 42.50 |
| 79.45 |
| 45.95 |
| 37.21 |
| 61.82 |
| 85.19 |
| 19.84 |
| 76.33 |
| 78.27 |
| 36.24 |
| LateOn-unsupervised | 50.11 | 149 | 128 | 43.12 | 47.71 | 18.76 | 43.36 | 65.74 | 51.94 | 68.17 | 37.51 | 37.15 | 58.41 | 89.48 | 21.13 | 76.89 | 69.81 | 22.53 |
| LateOn | 57.22 | 149 | 128 | 50.52 | 47.36 | 39.67 | 45.99 | 92.02 | 53.12 | 79.98 | 45.67 | 37.79 | 63.91 | 89.67 | 21.90 | 76.61 | 83.60 | 30.52 |
| LateOn, 2048-token documents | 57.69 | 149 | 128 | 50.19 | 47.17 | 40.13 | 46.22 | 93.07 | 54.16 | 79.97 | 45.83 | 37.93 | 63.28 | 89.66 | 21.86 | 77.20 | 83.62 | 35.09 |
| UIBK-Embedding-v1 | 59.79 | 179 | 128 | 56.56 | 48.55 | 44.89 | 49.22 | 93.28 | 53.73 | 81.50 | 46.82 | 38.79 | 67.80 | 89.92 | 23.60 | 78.57 | 83.82 | 39.78 |