Sentence Similarity
Safetensors
sentence-transformers
Italian
pylate
modernbert
colbert
late-interaction
italian
retrieval
information-retrieval
rag
multi-vector
text-embeddings-inference
Instructions to use enricollen/ItColBERT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use enricollen/ItColBERT with sentence-transformers:
from sentence_transformers import MultiVectorEncoder model = MultiVectorEncoder("enricollen/ItColBERT") queries = ["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.", ] query_embeddings = model.encode_query(queries) document_embeddings = model.encode_document(documents) similarities = model.similarity(query_embeddings, document_embeddings) print(similarities) - Notebooks
- Google Colab
- Kaggle
Add new ColBERT model
Browse files- .gitattributes +1 -0
- 1_Dense/config.json +7 -0
- 1_Dense/model.safetensors +3 -0
- README.md +390 -0
- banner.jpg +3 -0
- config.json +80 -0
- config_sentence_transformers.json +53 -0
- model.safetensors +3 -0
- modules.json +14 -0
- sentence_bert_config.json +4 -0
- tokenizer.json +0 -0
- tokenizer_config.json +30 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
banner.jpg filter=lfs diff=lfs merge=lfs -text
|
1_Dense/config.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"in_features": 768,
|
| 3 |
+
"out_features": 128,
|
| 4 |
+
"bias": false,
|
| 5 |
+
"activation_function": "torch.nn.modules.linear.Identity",
|
| 6 |
+
"use_residual": false
|
| 7 |
+
}
|
1_Dense/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:71027f8d3555a15591f7067c157616966ace757b17f8641c74044fa3e5ead669
|
| 3 |
+
size 393304
|
README.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- it
|
| 4 |
+
library_name: pylate
|
| 5 |
+
tags:
|
| 6 |
+
- colbert
|
| 7 |
+
- late-interaction
|
| 8 |
+
- sentence-transformers
|
| 9 |
+
- italian
|
| 10 |
+
- retrieval
|
| 11 |
+
- information-retrieval
|
| 12 |
+
- rag
|
| 13 |
+
- multi-vector
|
| 14 |
+
pipeline_tag: sentence-similarity
|
| 15 |
+
license: apache-2.0
|
| 16 |
+
base_model: nickprock/Italian-ModernBERT-base-embed-mmarco-mnrl
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
# ItColBERT
|
| 20 |
+
|
| 21 |
+

|
| 22 |
+
|
| 23 |
+
**A monolingual Italian late-interaction retriever.** Built with
|
| 24 |
+
[PyLate](https://github.com/lightonai/pylate) on top of an Italian ModernBERT
|
| 25 |
+
backbone, for semantic search and RAG over Italian text.
|
| 26 |
+
|
| 27 |
+
*Built and maintained by Enrico Nello. Training code, evaluation harness, and
|
| 28 |
+
full development history: [github.com/enricollen/it-colbert](https://github.com/enricollen/it-colbert).*
|
| 29 |
+
|
| 30 |
+
## TL;DR
|
| 31 |
+
|
| 32 |
+
- **What:** ColBERT-style multi-vector retriever, specialized on Italian.
|
| 33 |
+
- **Why it exists:** as far as I could find, there was no *Italian-only*
|
| 34 |
+
late-interaction retriever — see "Why I built this" below.
|
| 35 |
+
- **Size:** ModernBERT-base backbone (~150M parameters), 128-dim token vectors.
|
| 36 |
+
- **Best at:** short-to-medium Italian passages (search, RAG chunks, FAQ
|
| 37 |
+
retrieval). Weakest at long documents unless you use the chunking recipe
|
| 38 |
+
below.
|
| 39 |
+
- **Not:** multilingual, and not the strongest late-interaction model overall
|
| 40 |
+
— see [How it compares](#how-it-compares).
|
| 41 |
+
|
| 42 |
+
## Why I built this
|
| 43 |
+
|
| 44 |
+
Before starting, I looked for an Italian late-interaction model and mostly
|
| 45 |
+
found two things: multilingual late-interaction models that *include* Italian
|
| 46 |
+
among many languages ([`jina-colbert-v2`](https://huggingface.co/jinaai/jina-colbert-v2),
|
| 47 |
+
[`mLateOn`](https://huggingface.co/lightonai/mLateOn), [`ColBERT-XM`](https://huggingface.co/antoinelouis/colbertxm),
|
| 48 |
+
[`SauerkrautLM-Multi-ModernColBERT`](https://huggingface.co/VAGOsolutions/SauerkrautLM-Multi-ModernColBERT)),
|
| 49 |
+
and strong Italian dense embedding models that give up ColBERT's token-level
|
| 50 |
+
matching for a single vector per passage. Nothing I found combined the two:
|
| 51 |
+
an Italian-specialized model that keeps late interaction. That gap is what
|
| 52 |
+
made me want to try building one — not "the first Italian ColBERT" (it
|
| 53 |
+
isn't, the models above already cover Italian), but the first one that's
|
| 54 |
+
*specialized* on it rather than one language among many.
|
| 55 |
+
|
| 56 |
+
## What is late interaction, in plain terms?
|
| 57 |
+
|
| 58 |
+
Most retrieval embedding models compress a whole passage into a single
|
| 59 |
+
vector, so a search is one comparison per document. ColBERT-style models
|
| 60 |
+
instead keep one small vector **per token**, and score a document by finding
|
| 61 |
+
the best-matching document token for every query token (MaxSim), summing the
|
| 62 |
+
result. That keeps fine-grained lexical detail — rare names, specific
|
| 63 |
+
numbers, exact phrasing — that gets blurred away when everything is squeezed
|
| 64 |
+
into one vector. It costs more storage (many vectors instead of one) in
|
| 65 |
+
exchange for that precision.
|
| 66 |
+
|
| 67 |
+
## What this is for
|
| 68 |
+
|
| 69 |
+
- Building a RAG retrieval stage over Italian documents.
|
| 70 |
+
- Semantic search over Italian text where exact wording/entities matter, not
|
| 71 |
+
just topic similarity.
|
| 72 |
+
- Reranking a first-stage retriever's candidates.
|
| 73 |
+
|
| 74 |
+
**Not** intended for: cross-lingual retrieval (query in one language,
|
| 75 |
+
documents in another — this model wasn't trained for it), or as a drop-in
|
| 76 |
+
replacement for large multilingual dense embedders when Italian isn't the
|
| 77 |
+
only language in your corpus.
|
| 78 |
+
|
| 79 |
+
## Quickstart
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
pip install -U pylate
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Reranking a short candidate list
|
| 86 |
+
|
| 87 |
+
The simplest usage — no index needed, good for reranking a first-stage
|
| 88 |
+
retriever's top results:
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
from pylate import rank, models
|
| 92 |
+
|
| 93 |
+
model = models.ColBERT(model_name_or_path="enricollen/ItColBERT")
|
| 94 |
+
|
| 95 |
+
queries = ["Qual è la capitale d'Italia?"]
|
| 96 |
+
documents = [[
|
| 97 |
+
"Roma è la capitale d'Italia.",
|
| 98 |
+
"Milano è la capitale economica del Paese.",
|
| 99 |
+
"Napoli è una città del sud Italia.",
|
| 100 |
+
]]
|
| 101 |
+
documents_ids = [[1, 2, 3]]
|
| 102 |
+
|
| 103 |
+
queries_embeddings = model.encode(queries, is_query=True)
|
| 104 |
+
documents_embeddings = model.encode(documents, is_query=False)
|
| 105 |
+
|
| 106 |
+
reranked = rank.rerank(
|
| 107 |
+
documents_ids=documents_ids,
|
| 108 |
+
queries_embeddings=queries_embeddings,
|
| 109 |
+
documents_embeddings=documents_embeddings,
|
| 110 |
+
)
|
| 111 |
+
print(reranked)
|
| 112 |
+
# [[{'id': 1, 'score': 31.682}, {'id': 2, 'score': 31.552}, {'id': 3, 'score': 31.454}]]
|
| 113 |
+
# one list per query, sorted highest score first — "Roma" wins, as expected.
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### Indexing a larger corpus
|
| 117 |
+
|
| 118 |
+
For anything beyond a handful of documents per query, build a persistent
|
| 119 |
+
index instead of reranking in memory every time:
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
from pylate import indexes, models, retrieve
|
| 123 |
+
|
| 124 |
+
model = models.ColBERT(model_name_or_path="enricollen/ItColBERT")
|
| 125 |
+
|
| 126 |
+
index = indexes.PLAID(
|
| 127 |
+
index_folder="pylate-index",
|
| 128 |
+
index_name="index",
|
| 129 |
+
override=True,
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
documents_ids = ["1", "2", "3"]
|
| 133 |
+
documents = ["document 1 text", "document 2 text", "document 3 text"]
|
| 134 |
+
|
| 135 |
+
documents_embeddings = model.encode(
|
| 136 |
+
documents, batch_size=32, is_query=False, show_progress_bar=True,
|
| 137 |
+
)
|
| 138 |
+
index.add_documents(documents_ids=documents_ids, documents_embeddings=documents_embeddings)
|
| 139 |
+
|
| 140 |
+
retriever = retrieve.ColBERT(index=index)
|
| 141 |
+
queries_embeddings = model.encode(
|
| 142 |
+
["a query"], batch_size=32, is_query=True, show_progress_bar=True,
|
| 143 |
+
)
|
| 144 |
+
results = retriever.retrieve(queries_embeddings=queries_embeddings, k=10)
|
| 145 |
+
print(results)
|
| 146 |
+
# [[{'id': '1', 'score': 30.81}, {'id': '2', 'score': 30.80}, {'id': '3', 'score': 30.78}]]
|
| 147 |
+
# same shape as reranking above — list per query, sorted by score — but drawn
|
| 148 |
+
# from a persistent index instead of the in-memory documents you pass in.
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
Reload an existing index later without re-encoding anything:
|
| 152 |
+
|
| 153 |
+
```python
|
| 154 |
+
index = indexes.PLAID(index_folder="pylate-index", index_name="index")
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
**Documents longer than 512 tokens are truncated at index time.** If your
|
| 158 |
+
corpus has long documents (articles, reports, legal text), split them into
|
| 159 |
+
~2,000-character overlapping chunks, index each chunk separately, and take
|
| 160 |
+
the max score per source document — see [Evaluation](#evaluation) for why
|
| 161 |
+
this matters and how much it recovers.
|
| 162 |
+
|
| 163 |
+
## Model details
|
| 164 |
+
|
| 165 |
+
| | |
|
| 166 |
+
|---|---|
|
| 167 |
+
| Base model | [`nickprock/Italian-ModernBERT-base-embed-mmarco-mnrl`](https://huggingface.co/nickprock/Italian-ModernBERT-base-embed-mmarco-mnrl) |
|
| 168 |
+
| Architecture | ModernBERT backbone (~150M params) → dense projection → 128-dim token vectors |
|
| 169 |
+
| Similarity | MaxSim (late interaction) |
|
| 170 |
+
| Query length | 32 tokens |
|
| 171 |
+
| Document length | 512 tokens (see the chunking note above for longer documents) |
|
| 172 |
+
| Language | Italian only |
|
| 173 |
+
| License | Apache 2.0 |
|
| 174 |
+
| Trained on | One RTX 3090 (24GB), Intel Core i7-14700K, 32GB RAM (27GB usable under WSL2) — no cluster |
|
| 175 |
+
|
| 176 |
+
```
|
| 177 |
+
ColBERT(
|
| 178 |
+
(0): Transformer({'max_seq_length': 31, 'architecture': 'ModernBertModel'})
|
| 179 |
+
(1): Dense({'in_features': 768, 'out_features': 128, 'bias': False})
|
| 180 |
+
)
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
## Training recipe
|
| 184 |
+
|
| 185 |
+
Two stages, following the [ColBERT-Zero](https://huggingface.co/blog/lightonai/colbert-zero)
|
| 186 |
+
result that starting from a retrieval-capable checkpoint and running
|
| 187 |
+
supervised contrastive + distillation reaches ~99% of full multi-vector
|
| 188 |
+
pretraining at roughly a tenth of the cost — which is why this doesn't start
|
| 189 |
+
from a raw language model.
|
| 190 |
+
|
| 191 |
+
**1. Supervised contrastive** (`CachedContrastive`, temperature 0.02), on:
|
| 192 |
+
|
| 193 |
+
- [`unicamp-dl/mmarco`](https://huggingface.co/datasets/unicamp-dl/mmarco) — Italian triples
|
| 194 |
+
- [`hotchpotch/mmarco-hard-negatives-reranker-filtered`](https://huggingface.co/datasets/hotchpotch/mmarco-hard-negatives-reranker-filtered) — reranker-mined hard negatives
|
| 195 |
+
- [`nickprock/it-wiki-retrieval-synthetic-hn`](https://huggingface.co/datasets/nickprock/it-wiki-retrieval-synthetic-hn)
|
| 196 |
+
- [`yuri-no/miracl-ita-argos`](https://huggingface.co/datasets/yuri-no/miracl-ita-argos) and [`yuri-no/squad-ita`](https://huggingface.co/datasets/yuri-no/squad-ita) — community machine translations, added to widen the mix past machine-translated mMARCO
|
| 197 |
+
|
| 198 |
+
**2. Knowledge distillation** (`Distillation`, KL) from a single cross-encoder
|
| 199 |
+
teacher — [`lightonai/embeddings-fine-tuning-filtered-it`](https://huggingface.co/datasets/lightonai/embeddings-fine-tuning-filtered-it)
|
| 200 |
+
(`mxbai-rerank-large-v2` scores) — sample budget spread proportionally across
|
| 201 |
+
all 8 splits.
|
| 202 |
+
|
| 203 |
+
Checkpoints were selected on pooled MLDR-it nDCG@10 and mMARCO-it MRR@10, not
|
| 204 |
+
on hold-out KD KL divergence — that metric measures how closely the student
|
| 205 |
+
copies the teacher's opinion on the teacher's own data, not whether retrieval
|
| 206 |
+
actually improved, and in an earlier run it kept climbing while real
|
| 207 |
+
retrieval quality fell.
|
| 208 |
+
|
| 209 |
+
Everything here — training, benchmarking, significance testing — ran on a
|
| 210 |
+
single consumer GPU, no cluster. That budget shaped some choices directly:
|
| 211 |
+
batch/mini-batch sizes, and the fact that chunked long-document evaluation
|
| 212 |
+
(~26GB host RAM) runs right at this machine's ceiling.
|
| 213 |
+
|
| 214 |
+
## How I got here
|
| 215 |
+
|
| 216 |
+
The short version of the road to this checkpoint, including the parts that
|
| 217 |
+
didn't work — because a model card that only shows the winning run isn't
|
| 218 |
+
telling the whole story:
|
| 219 |
+
|
| 220 |
+
1. **Start from a model that already retrieves**, rather than a raw language
|
| 221 |
+
model — the ColBERT-Zero efficiency result above.
|
| 222 |
+
2. **Broaden the training data, then distil.** The starting checkpoint only
|
| 223 |
+
knew machine-translated mMARCO. I added more varied Italian data and
|
| 224 |
+
applied a second distillation stage. This produced the model in this
|
| 225 |
+
repository — the strongest Italian-specialized late-interaction model I
|
| 226 |
+
could benchmark it against, except on long documents, which was its
|
| 227 |
+
weakest result.
|
| 228 |
+
3. **The long-document weakness turned out to be mostly mechanical.** My
|
| 229 |
+
hardest benchmark's documents run a few thousand words each, and were
|
| 230 |
+
being cut off at 512 tokens during indexing — discarding roughly 80% of
|
| 231 |
+
the average document before the model ever saw it. Splitting documents
|
| 232 |
+
into overlapping chunks at query time, with **no retraining at all**,
|
| 233 |
+
recovered most of that gap (see the two MLDR-it rows below).
|
| 234 |
+
4. **Two follow-up training attempts, both tested, neither beat that free
|
| 235 |
+
fix.** I mined harder training examples from the model's own predictions
|
| 236 |
+
— no improvement, and a small real drop in generalization. I then trained
|
| 237 |
+
a second version to read twice as much text per document instead of
|
| 238 |
+
relying on chunking — statistically no better than chunking a normally
|
| 239 |
+
-trained model, once compared fairly on held-out data.
|
| 240 |
+
5. **What shipped.** Since neither training attempt beat "train normally,
|
| 241 |
+
then chunk long documents at query time," that's what's in this repo:
|
| 242 |
+
this model, plus the chunking recipe for anything longer than 512 tokens.
|
| 243 |
+
|
| 244 |
+
## Evaluation
|
| 245 |
+
|
| 246 |
+
Four Italian retrieval benchmarks. 95% confidence intervals in brackets —
|
| 247 |
+
treat any gap smaller than the interval width as noise, not a real
|
| 248 |
+
difference.
|
| 249 |
+
|
| 250 |
+
| Benchmark | Metric | Score (95% CI) | In-domain? |
|
| 251 |
+
|---|---|---|---|
|
| 252 |
+
| MLDR-it (test, ~10k docs) | nDCG@10 | 0.4008 [0.3404, 0.4589] | No — the clean out-of-domain test |
|
| 253 |
+
| MLDR-it, **chunked at query time** | nDCG@10 | 0.4610 [0.4002, 0.5212] | No |
|
| 254 |
+
| mMARCO-it (dev, pooled 100k) | MRR@10 (rank only) | 0.7196 [0.7104, 0.7291] | Partially — mMARCO is in the training mix |
|
| 255 |
+
| MIRACL-ita (dev, pooled) | nDCG@10 | 0.7194 [0.6984, 0.7375] | Partially — different split of a training source |
|
| 256 |
+
| SQuAD-ita (test, pooled) | nDCG@10 | 0.9026 [0.8974, 0.9075] | Partially — different split of a training source |
|
| 257 |
+
|
| 258 |
+
**MLDR-it is the only clean out-of-domain benchmark, so weigh it most.** Its
|
| 259 |
+
documents run a median ~2,700 tokens against this model's 512-token index —
|
| 260 |
+
almost all of it is truncated by default. Splitting each document into
|
| 261 |
+
2,000-character overlapping chunks and max-pooling scores at query time (no
|
| 262 |
+
retraining, ~7× the indexing cost) recovers most of the gap. Use the
|
| 263 |
+
truncated number if index size/latency is the constraint, the chunked number
|
| 264 |
+
if document coverage matters more.
|
| 265 |
+
|
| 266 |
+
### How it compares
|
| 267 |
+
|
| 268 |
+
Same protocol, real numbers, paired-bootstrap significance tested against
|
| 269 |
+
this model. A **†** marks a score that is *not* statistically distinguishable
|
| 270 |
+
from ItColBERT (p > .05) — read those as ties, not losses or wins, regardless
|
| 271 |
+
of which number is higher.
|
| 272 |
+
|
| 273 |
+
| Model | MLDR-it (nDCG@10) | mMARCO-it (MRR@10) | MIRACL-ita (nDCG@10) | SQuAD-ita (nDCG@10) |
|
| 274 |
+
|---|---|---|---|---|
|
| 275 |
+
| **ItColBERT (this model)** | **0.4008** (0.4610 chunked) | **0.7196** | **0.7194** | **0.9026** |
|
| 276 |
+
| mLateOn | 0.4623 | 0.8207 | 0.7880 | 0.9480 |
|
| 277 |
+
| jina-colbert-v2 | 0.3858 † | 0.8389 | 0.7755 | 0.8849 |
|
| 278 |
+
| bge-m3 (dense) | 0.4531 | 0.7812 | 0.7566 | 0.8247 |
|
| 279 |
+
| multilingual-e5-large (dense) | 0.4310 † | 0.8239 | 0.7653 | 0.8513 |
|
| 280 |
+
| SauerkrautLM-Multi-ModernColBERT | 0.3122 | 0.5342 | 0.5996 | 0.8338 |
|
| 281 |
+
| ColBERT-XM | 0.2734 | 0.6654 | 0.6260 | 0.8558 |
|
| 282 |
+
| BM25 | 0.4850 (vs. ItColBERT's 0.4610 chunked: † ) | 0.5715 | 0.5516 | 0.8262 |
|
| 283 |
+
|
| 284 |
+
Reading the MLDR-it column: against BM25's 0.4850, ItColBERT's plain
|
| 285 |
+
512-token number (0.4008) loses significantly — but that's comparing unequal
|
| 286 |
+
document access, since BM25 reads the whole document and ItColBERT reads the
|
| 287 |
+
first 512 tokens of it. Once ItColBERT is allowed to read the same amount of
|
| 288 |
+
each document (the 0.4610 chunked number), the two are a statistical tie.
|
| 289 |
+
|
| 290 |
+
In plain terms: this is the strongest **Italian-specialized**
|
| 291 |
+
late-interaction model I could find and benchmark against, and it beats most
|
| 292 |
+
general-purpose late-interaction alternatives outright. It doesn't beat the
|
| 293 |
+
single strongest multilingual late-interaction model I tested (mLateOn), and
|
| 294 |
+
it doesn't beat large multilingual dense embedders on most benchmarks —
|
| 295 |
+
matching those was never the goal; they're a different, much larger model
|
| 296 |
+
class.
|
| 297 |
+
|
| 298 |
+
**Protocol notes that matter for these numbers:**
|
| 299 |
+
|
| 300 |
+
- All late-interaction models above are indexed at the same document length
|
| 301 |
+
unless a row is marked "chunked".
|
| 302 |
+
- Pooled corpora inflate absolute scores; use them for relative ranking, not
|
| 303 |
+
as numbers comparable to published full-corpus results.
|
| 304 |
+
- MLDR-it has 200 queries — differences under ~0.03 nDCG@10 are inside the
|
| 305 |
+
noise. Don't read a ranking claim as established without a significance
|
| 306 |
+
test behind it.
|
| 307 |
+
- MIRACL-ita and SQuAD-ita are **community machine translations**, not
|
| 308 |
+
official resources, and both overlap this model's training data source
|
| 309 |
+
(different splits, checked for direct query leakage — none found).
|
| 310 |
+
|
| 311 |
+
## Limitations
|
| 312 |
+
|
| 313 |
+
- Italian only. Cross-lingual retrieval isn't the goal here.
|
| 314 |
+
- Much of the training data derives from machine-translated mMARCO, and it
|
| 315 |
+
shows: short-passage retrieval is the strongest result, long-document
|
| 316 |
+
retrieval the weakest, even after the chunking fix.
|
| 317 |
+
- Documents longer than 512 tokens truncate at index time unless you chunk
|
| 318 |
+
and max-pool (see Quickstart and Evaluation above).
|
| 319 |
+
- Multi-vector indexes are larger than single-vector dense indexes, and
|
| 320 |
+
chunking multiplies that further — budget accordingly for large corpora.
|
| 321 |
+
- Behind the strongest multilingual late-interaction model I tested
|
| 322 |
+
(mLateOn) on every benchmark, and behind large multilingual dense
|
| 323 |
+
embedders on most — see "How it compares".
|
| 324 |
+
- Not evaluated for bias or harmful content. Usual caveats apply for a model
|
| 325 |
+
trained substantially on machine-translated web and QA data.
|
| 326 |
+
|
| 327 |
+
## What's next
|
| 328 |
+
|
| 329 |
+
Not done yet, in rough priority order:
|
| 330 |
+
|
| 331 |
+
- Fusing this model's rankings with BM25 (rank fusion) — cheap, and the
|
| 332 |
+
chunked MLDR-it numbers above suggest it should help, since the two
|
| 333 |
+
disagree on individual queries while scoring about the same overall.
|
| 334 |
+
- Re-running the comparison table above with every model chunked, not just
|
| 335 |
+
this one, for a fully apples-to-apples long-document comparison.
|
| 336 |
+
- A smaller 64-dimension variant, for when index size matters more than the
|
| 337 |
+
last points of quality — planned, not trained yet.
|
| 338 |
+
- An MTEB-Italian-style community benchmark check, if/when suitable
|
| 339 |
+
multi-vector support exists in that harness.
|
| 340 |
+
|
| 341 |
+
## Citation
|
| 342 |
+
|
| 343 |
+
If you use this model, please cite it, plus ColBERT/ColBERTv2, PyLate,
|
| 344 |
+
mMARCO, and MLDR below.
|
| 345 |
+
|
| 346 |
+
```bibtex
|
| 347 |
+
@misc{nello2026itcolbert,
|
| 348 |
+
author = {Nello, Enrico},
|
| 349 |
+
title = {ItColBERT: A Monolingual Italian Late-Interaction Retriever},
|
| 350 |
+
year = {2026},
|
| 351 |
+
publisher = {Hugging Face},
|
| 352 |
+
howpublished = {\url{https://huggingface.co/enricollen/ItColBERT}},
|
| 353 |
+
}
|
| 354 |
+
```
|
| 355 |
+
|
| 356 |
+
```bibtex
|
| 357 |
+
@inproceedings{DBLP:conf/cikm/ChaffinS25,
|
| 358 |
+
author = {Antoine Chaffin and Rapha{\"{e}}l Sourty},
|
| 359 |
+
title = {PyLate: Flexible Training and Retrieval for Late Interaction Models},
|
| 360 |
+
booktitle = {Proceedings of the 34th {ACM} International Conference on Information
|
| 361 |
+
and Knowledge Management, {CIKM} 2025, Seoul, Republic of Korea, November
|
| 362 |
+
10-14, 2025},
|
| 363 |
+
pages = {6334--6339},
|
| 364 |
+
publisher = {{ACM}},
|
| 365 |
+
year = {2025},
|
| 366 |
+
url = {https://github.com/lightonai/pylate},
|
| 367 |
+
doi = {10.1145/3746252.3761608},
|
| 368 |
+
}
|
| 369 |
+
```
|
| 370 |
+
|
| 371 |
+
Further reading: [ColBERTv2](https://arxiv.org/abs/2112.01488),
|
| 372 |
+
[mMARCO](https://arxiv.org/abs/2108.13897),
|
| 373 |
+
[MLDR / BGE-M3](https://arxiv.org/abs/2402.03216).
|
| 374 |
+
|
| 375 |
+
## Acknowledgments
|
| 376 |
+
|
| 377 |
+
Built on [`nickprock/Italian-ModernBERT-base-embed-mmarco-mnrl`](https://huggingface.co/nickprock/Italian-ModernBERT-base-embed-mmarco-mnrl)
|
| 378 |
+
and [PyLate](https://github.com/lightonai/pylate) (LightOn). Benchmarked
|
| 379 |
+
against [`SauerkrautLM-Multi-ModernColBERT`](https://huggingface.co/VAGOsolutions/SauerkrautLM-Multi-ModernColBERT),
|
| 380 |
+
[`jina-colbert-v2`](https://huggingface.co/jinaai/jina-colbert-v2),
|
| 381 |
+
[`mLateOn`](https://huggingface.co/lightonai/mLateOn), and
|
| 382 |
+
[`ColBERT-XM`](https://huggingface.co/antoinelouis/colbertxm) — thank you to
|
| 383 |
+
everyone building and sharing these, Italian NLP is a small enough space that
|
| 384 |
+
every open checkpoint helps.
|
| 385 |
+
|
| 386 |
+
### Framework versions
|
| 387 |
+
|
| 388 |
+
Python 3.11.15 · Sentence Transformers 5.3.0 · PyLate 1.5.0 · Transformers
|
| 389 |
+
5.3.0 · PyTorch 2.6.0+cu124 · Accelerate 1.14.0 · Datasets 5.0.1 · Tokenizers
|
| 390 |
+
0.22.2
|
banner.jpg
ADDED
|
Git LFS Details
|
config.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"ModernBertModel"
|
| 4 |
+
],
|
| 5 |
+
"attention_bias": false,
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"bos_token_id": 2,
|
| 8 |
+
"classifier_activation": "gelu",
|
| 9 |
+
"classifier_bias": false,
|
| 10 |
+
"classifier_dropout": 0.0,
|
| 11 |
+
"classifier_pooling": "mean",
|
| 12 |
+
"cls_token_id": 2,
|
| 13 |
+
"decoder_bias": true,
|
| 14 |
+
"deterministic_flash_attn": false,
|
| 15 |
+
"dtype": "float32",
|
| 16 |
+
"embedding_dropout": 0.0,
|
| 17 |
+
"eos_token_id": 3,
|
| 18 |
+
"global_attn_every_n_layers": 3,
|
| 19 |
+
"gradient_checkpointing": false,
|
| 20 |
+
"hidden_activation": "gelu",
|
| 21 |
+
"hidden_size": 768,
|
| 22 |
+
"initializer_cutoff_factor": 2.0,
|
| 23 |
+
"initializer_range": 0.02,
|
| 24 |
+
"intermediate_size": 1152,
|
| 25 |
+
"layer_norm_eps": 1e-05,
|
| 26 |
+
"layer_types": [
|
| 27 |
+
"full_attention",
|
| 28 |
+
"sliding_attention",
|
| 29 |
+
"sliding_attention",
|
| 30 |
+
"full_attention",
|
| 31 |
+
"sliding_attention",
|
| 32 |
+
"sliding_attention",
|
| 33 |
+
"full_attention",
|
| 34 |
+
"sliding_attention",
|
| 35 |
+
"sliding_attention",
|
| 36 |
+
"full_attention",
|
| 37 |
+
"sliding_attention",
|
| 38 |
+
"sliding_attention",
|
| 39 |
+
"full_attention",
|
| 40 |
+
"sliding_attention",
|
| 41 |
+
"sliding_attention",
|
| 42 |
+
"full_attention",
|
| 43 |
+
"sliding_attention",
|
| 44 |
+
"sliding_attention",
|
| 45 |
+
"full_attention",
|
| 46 |
+
"sliding_attention",
|
| 47 |
+
"sliding_attention",
|
| 48 |
+
"full_attention"
|
| 49 |
+
],
|
| 50 |
+
"local_attention": 128,
|
| 51 |
+
"mask_token_id": 4,
|
| 52 |
+
"max_position_embeddings": 8192,
|
| 53 |
+
"mlp_bias": false,
|
| 54 |
+
"mlp_dropout": 0.0,
|
| 55 |
+
"model_type": "modernbert",
|
| 56 |
+
"norm_bias": false,
|
| 57 |
+
"norm_eps": 1e-05,
|
| 58 |
+
"num_attention_heads": 12,
|
| 59 |
+
"num_hidden_layers": 22,
|
| 60 |
+
"pad_token_id": 1,
|
| 61 |
+
"position_embedding_type": "absolute",
|
| 62 |
+
"repad_logits_with_grad": false,
|
| 63 |
+
"rope_parameters": {
|
| 64 |
+
"full_attention": {
|
| 65 |
+
"rope_theta": 160000.0,
|
| 66 |
+
"rope_type": "default"
|
| 67 |
+
},
|
| 68 |
+
"sliding_attention": {
|
| 69 |
+
"rope_theta": 10000.0,
|
| 70 |
+
"rope_type": "default"
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"sep_token_id": 3,
|
| 74 |
+
"sparse_pred_ignore_index": -100,
|
| 75 |
+
"sparse_prediction": false,
|
| 76 |
+
"tie_word_embeddings": true,
|
| 77 |
+
"transformers_version": "5.3.0",
|
| 78 |
+
"unk_token_id": 0,
|
| 79 |
+
"vocab_size": 32002
|
| 80 |
+
}
|
config_sentence_transformers.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"__version__": {
|
| 3 |
+
"sentence_transformers": "5.3.0",
|
| 4 |
+
"transformers": "5.3.0",
|
| 5 |
+
"pytorch": "2.6.0+cu124"
|
| 6 |
+
},
|
| 7 |
+
"prompts": {
|
| 8 |
+
"query": "",
|
| 9 |
+
"document": ""
|
| 10 |
+
},
|
| 11 |
+
"default_prompt_name": null,
|
| 12 |
+
"similarity_fn_name": "MaxSim",
|
| 13 |
+
"query_prefix": "[Q] ",
|
| 14 |
+
"document_prefix": "[D] ",
|
| 15 |
+
"query_length": 32,
|
| 16 |
+
"document_length": 512,
|
| 17 |
+
"attend_to_expansion_tokens": false,
|
| 18 |
+
"skiplist_words": [
|
| 19 |
+
"!",
|
| 20 |
+
"\"",
|
| 21 |
+
"#",
|
| 22 |
+
"$",
|
| 23 |
+
"%",
|
| 24 |
+
"&",
|
| 25 |
+
"'",
|
| 26 |
+
"(",
|
| 27 |
+
")",
|
| 28 |
+
"*",
|
| 29 |
+
"+",
|
| 30 |
+
",",
|
| 31 |
+
"-",
|
| 32 |
+
".",
|
| 33 |
+
"/",
|
| 34 |
+
":",
|
| 35 |
+
";",
|
| 36 |
+
"<",
|
| 37 |
+
"=",
|
| 38 |
+
">",
|
| 39 |
+
"?",
|
| 40 |
+
"@",
|
| 41 |
+
"[",
|
| 42 |
+
"\\",
|
| 43 |
+
"]",
|
| 44 |
+
"^",
|
| 45 |
+
"_",
|
| 46 |
+
"`",
|
| 47 |
+
"{",
|
| 48 |
+
"|",
|
| 49 |
+
"}",
|
| 50 |
+
"~"
|
| 51 |
+
],
|
| 52 |
+
"do_query_expansion": true
|
| 53 |
+
}
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:27d4cd409f50488bbb572ca7d31a9a5043c18b26719938f983e5b22696105059
|
| 3 |
+
size 539649784
|
modules.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"idx": 0,
|
| 4 |
+
"name": "0",
|
| 5 |
+
"path": "",
|
| 6 |
+
"type": "sentence_transformers.models.Transformer"
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"idx": 1,
|
| 10 |
+
"name": "1",
|
| 11 |
+
"path": "1_Dense",
|
| 12 |
+
"type": "pylate.models.Dense.Dense"
|
| 13 |
+
}
|
| 14 |
+
]
|
sentence_bert_config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"max_seq_length": 31,
|
| 3 |
+
"do_lower_case": false
|
| 4 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"clean_up_tokenization_spaces": true,
|
| 4 |
+
"cls_token": "[CLS]",
|
| 5 |
+
"is_local": true,
|
| 6 |
+
"mask_token": "[MASK]",
|
| 7 |
+
"max_length": 8192,
|
| 8 |
+
"model_input_names": [
|
| 9 |
+
"input_ids",
|
| 10 |
+
"attention_mask"
|
| 11 |
+
],
|
| 12 |
+
"model_max_length": 511,
|
| 13 |
+
"pad_to_multiple_of": null,
|
| 14 |
+
"pad_token": "[MASK]",
|
| 15 |
+
"pad_token_type_id": 0,
|
| 16 |
+
"padding_side": "right",
|
| 17 |
+
"sep_token": "[SEP]",
|
| 18 |
+
"special_tokens": {
|
| 19 |
+
"cls_token": "[CLS]",
|
| 20 |
+
"mask_token": "[MASK]",
|
| 21 |
+
"pad_token": "[PAD]",
|
| 22 |
+
"sep_token": "[SEP]",
|
| 23 |
+
"unk_token": "[UNK]"
|
| 24 |
+
},
|
| 25 |
+
"stride": 0,
|
| 26 |
+
"tokenizer_class": "TokenizersBackend",
|
| 27 |
+
"truncation_side": "right",
|
| 28 |
+
"truncation_strategy": "longest_first",
|
| 29 |
+
"unk_token": "[UNK]"
|
| 30 |
+
}
|