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 SentenceTransformer model = SentenceTransformer("enricollen/ItColBERT") sentences = [ "Questa è una persona felice", "Questo è un cane felice", "Questa è una persona molto felice", "Oggi è una giornata di sole" ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [4, 4] - 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 |
+
}
|