Visual Document Retrieval
ColPali
Safetensors
English
vidore
tomaarsen HF Staff commited on
Commit
d56fc86
·
verified ·
1 Parent(s): b3ba576

Integrate with Sentence Transformers via MultiVectorEncoder

Browse files

Hello!

Heads up, this PR was AI-generated and human-reviewed. The `MultiVectorEncoder` class ships in the next Sentence Transformers release, planned for around the 18th, so for now the install below pulls from source. I would love to feature this model in that release's blog post and documentation, especially once it loads without the `revision` pin (that is, once this PR is merged).

Here's a summary of the changes as reported by my agent:

## Pull Request overview
* Integrate `vidore/colpali` with [Sentence Transformers](https://www.sbert.net/) as a multi-vector (ColBERT-style late interaction) retriever via `MultiVectorEncoder`.

## Details

This adds a Sentence Transformers loading path on top of the existing LoRA adapter, exposing the usual `model.encode_query(...)` / `model.encode_document(...)` / `model.similarity(...)` API with MaxSim scoring. The stock `Transformer` module loads the adapter directly onto the PaliGemma backbone through a small `key_mapping` that strips `colpali-engine`'s `model.` wrapper prefix, so no custom modeling code or `trust_remote_code` is needed, only `transformers>=5.15.0` (which ships huggingface/transformers#46766) and `peft`. The frozen `custom_text_proj` (2048 to 128) ships pre-merged as a roughly 1 MB `1_Dense` module. The trained weights are untouched and the existing `colpali-engine` usage keeps working unchanged.

On the query format: this checkpoint predates a tagged `colpali-engine` release and the revision it records is not in the `illuin-tech/colpali` history, so I gave it the August 2024 `Question: ` query format of its near-contemporaries. Current `colpali-engine` no longer sends that format: 0.3.4 changed the prefix from `Question: ` to `Query: ` (illuin-tech/colpali#125), 0.3.11 dropped the trailing newline (illuin-tech/colpali#280), and 0.3.13 dropped the prefix entirely (illuin-tech/colpali#339). This configuration reproduces the training-time format, so its embeddings differ slightly from current `colpali-engine` output, and the README flags this next to the `colpali-engine` snippet. On a ViDoRe v1 check, reproducing the training-time format improved nDCG@5 over the current `colpali-engine` format in 6 of 6 checkpoint x dataset cells measured.

```bash
pip install "sentence-transformers[image] @ git+https://github.com/huggingface/sentence-transformers.git"
```

```python
from sentence_transformers import MultiVectorEncoder

model = MultiVectorEncoder("vidore/colpali", revision="refs/pr/N")

queries = [
"What is the variable represented on the y-axis of the graph?",
"Total outlay is maximum in which year?",
]
documents = [
f"https://huggingface.co/datasets/sentence-transformers/example-documents/resolve/main/doc{i}.jpg"
for i in range(1, 5)
]

query_embeddings = model.encode_query(queries, convert_to_tensor=True)
document_embeddings = model.encode_document(documents, convert_to_tensor=True)
print(tuple(query_embeddings[0].shape), tuple(document_embeddings[0].shape))
# (23, 128) (1030, 128)

print(model.similarity(query_embeddings, document_embeddings))
# tensor([[17.3789, 17.1055, 15.4727, 15.4082],
# [ 8.3750, 12.3047, 8.5898, 9.0957]])
```

- Tom Aarsen

1_Dense/config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "in_features": 2048,
3
+ "out_features": 128,
4
+ "bias": true,
5
+ "activation_function": "torch.nn.modules.linear.Identity",
6
+ "module_input_name": "token_embeddings",
7
+ "module_output_name": "token_embeddings",
8
+ "use_residual": false
9
+ }
1_Dense/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9b0ae57a26f3f576a8652b1827896ee6e6385674a4309009f05c3f186d8a2d0
3
+ size 1049248
2_Normalize/config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "module_input_name": "token_embeddings",
3
+ "module_output_name": "token_embeddings"
4
+ }
3_MultiVectorMask/config.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "skiplist_words": []
3
+ }
README.md CHANGED
@@ -7,6 +7,8 @@ language:
7
  tags:
8
  - colpali
9
  - vidore
 
 
10
  new_version: vidore/colpali-v1.1
11
  datasets:
12
  - vidore/colpali_train_set
@@ -46,8 +48,58 @@ We train on an 8 GPU setup with data parallelism, a learning rate of 5e-5 with l
46
 
47
  ## Usage
48
 
49
- ### For best performance, newer models are available (vidore/colpali-v1.2)
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  ```bash
53
  # This model checkpoint is compatible with version 0.1.1, but not more recent versions of the inference lib
 
7
  tags:
8
  - colpali
9
  - vidore
10
+ - sentence-transformers
11
+ - multi-vector
12
  new_version: vidore/colpali-v1.1
13
  datasets:
14
  - vidore/colpali_train_set
 
48
 
49
  ## Usage
50
 
51
+ ### Using Sentence Transformers
52
 
53
+ ColPali can be used as a multi-vector (ColBERT-style late interaction) retriever directly with Sentence Transformers via the `MultiVectorEncoder`.
54
+
55
+ ```bash
56
+ pip install "sentence-transformers[image]>=6.0.0"
57
+ ```
58
+
59
+ ```python
60
+ from sentence_transformers import MultiVectorEncoder
61
+
62
+ model = MultiVectorEncoder("tomaarsen/colpali-st")
63
+
64
+ queries = [
65
+ "What is the variable represented on the y-axis of the graph?",
66
+ "Total outlay is maximum in which year?",
67
+ ]
68
+ images = [
69
+ "https://huggingface.co/datasets/sentence-transformers/example-documents/resolve/main/doc1.jpg",
70
+ "https://huggingface.co/datasets/sentence-transformers/example-documents/resolve/main/doc2.jpg",
71
+ "https://huggingface.co/datasets/sentence-transformers/example-documents/resolve/main/doc3.jpg",
72
+ "https://huggingface.co/datasets/sentence-transformers/example-documents/resolve/main/doc4.jpg",
73
+ ]
74
+
75
+ query_embeddings = model.encode_query(queries, convert_to_tensor=True)
76
+ document_embeddings = model.encode_document(images, convert_to_tensor=True)
77
+ print(f"Query 0 shape: {tuple(query_embeddings[0].shape)}")
78
+ print(f"Document 0 shape: {tuple(document_embeddings[0].shape)}")
79
+ # Query 0 shape: (23, 128)
80
+ # Document 0 shape: (1030, 128)
81
+
82
+ # MaxSim late-interaction scoring (rows = queries, columns = images)
83
+ scores = model.similarity(query_embeddings, document_embeddings)
84
+ print(scores)
85
+ # tensor([[17.3789, 17.1055, 15.4727, 15.4082],
86
+ # [ 8.3750, 12.3047, 8.5898, 9.0957]])
87
+ ```
88
+
89
+ ### Using ColPali Engine
90
+
91
+ > [!WARNING]
92
+ > Note: current `colpali-engine` no longer sends the query prefix and trailing newline that this
93
+ > checkpoint was trained with. The trailing newline went in 0.3.11 (illuin-tech/colpali#280) and the prefix in 0.3.13 (illuin-tech/colpali#339). The Sentence Transformers
94
+ > configuration in this repository reproduces the original training-time format, so its embeddings differ
95
+ > slightly from current `colpali-engine` output.
96
+ > Release 0.3.4 had already changed the prefix from `Question: ` to `Query: ` (illuin-tech/colpali#125),
97
+ > which this checkpoint predates.
98
+ > The Sentence Transformers configuration also sends `token_type_ids` to the model, which on
99
+ > `transformers` 5.x is what makes PaliGemma build an explicit attention mask at all. Without it no
100
+ > mask is materialized and the shorter queries in a batch attend to their own padding.
101
+
102
+ > For best performance, newer models are available (vidore/colpali-v1.2)
103
 
104
  ```bash
105
  # This model checkpoint is compatible with version 0.1.1, but not more recent versions of the inference lib
chat_template.jinja ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- for message in messages -%}
2
+ {%- set images = message['content'] | selectattr('type', 'equalto', 'image') | list -%}
3
+ {%- set texts = message['content'] | selectattr('type', 'equalto', 'text') | map(attribute='text') | list -%}
4
+ {%- if images -%}
5
+ {%- for _ in images -%}{{- '<image>' -}}{%- endfor -%}
6
+ {{- texts[0] if texts else 'Describe the image.' -}}
7
+ {%- else -%}
8
+ {{ bos_token }}Question: {{ texts[0] }}{{ '<unused0>' * 5 }}
9
+ {% endif %}
10
+ {% endfor %}
config_sentence_transformers.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "__version__": {
3
+ "sentence_transformers": "5.7.0"
4
+ },
5
+ "default_prompt_name": null,
6
+ "model_type": "MultiVectorEncoder",
7
+ "requirements": {
8
+ "transformers": {
9
+ "specifier": ">=5.15",
10
+ "reason": "Older versions ignore the key_mapping, which silently randomizes the adapter weights."
11
+ }
12
+ },
13
+ "prompts": {
14
+ "document": "",
15
+ "query": ""
16
+ },
17
+ "similarity_fn_name": null
18
+ }
modules.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "idx": 0,
4
+ "name": "0",
5
+ "path": "",
6
+ "type": "sentence_transformers.base.modules.transformer.Transformer"
7
+ },
8
+ {
9
+ "idx": 1,
10
+ "name": "1",
11
+ "path": "1_Dense",
12
+ "type": "sentence_transformers.base.modules.dense.Dense"
13
+ },
14
+ {
15
+ "idx": 2,
16
+ "name": "2",
17
+ "path": "2_Normalize",
18
+ "type": "sentence_transformers.sentence_transformer.modules.normalize.Normalize"
19
+ },
20
+ {
21
+ "idx": 3,
22
+ "name": "3",
23
+ "path": "3_MultiVectorMask",
24
+ "type": "sentence_transformers.multi_vector_encoder.modules.multi_vector_mask.MultiVectorMask"
25
+ }
26
+ ]
preprocessor_config.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "_valid_processor_keys": [
3
- "images",
4
- "do_resize",
5
- "size",
6
- "resample",
7
- "do_rescale",
8
- "rescale_factor",
9
- "do_normalize",
10
- "image_mean",
11
- "image_std",
12
- "return_tensors",
13
- "data_format",
14
- "input_data_format",
15
- "do_convert_rgb"
16
- ],
17
- "do_convert_rgb": null,
18
- "do_normalize": true,
19
- "do_rescale": true,
20
- "do_resize": true,
21
- "image_mean": [
22
- 0.5,
23
- 0.5,
24
- 0.5
25
- ],
26
- "image_processor_type": "SiglipImageProcessor",
27
- "image_seq_length": 1024,
28
- "image_std": [
29
- 0.5,
30
- 0.5,
31
- 0.5
32
- ],
33
- "processor_class": "PaliGemmaProcessor",
34
- "resample": 3,
35
- "rescale_factor": 0.00392156862745098,
36
- "size": {
37
- "height": 448,
38
- "width": 448
39
- }
40
- }
 
1
+ {
2
+ "_valid_processor_keys": [
3
+ "images",
4
+ "do_resize",
5
+ "size",
6
+ "resample",
7
+ "do_rescale",
8
+ "rescale_factor",
9
+ "do_normalize",
10
+ "image_mean",
11
+ "image_std",
12
+ "return_tensors",
13
+ "data_format",
14
+ "input_data_format",
15
+ "do_convert_rgb"
16
+ ],
17
+ "do_convert_rgb": true,
18
+ "do_normalize": true,
19
+ "do_rescale": true,
20
+ "do_resize": true,
21
+ "image_mean": [
22
+ 0.5,
23
+ 0.5,
24
+ 0.5
25
+ ],
26
+ "image_processor_type": "SiglipImageProcessor",
27
+ "image_seq_length": 1024,
28
+ "image_std": [
29
+ 0.5,
30
+ 0.5,
31
+ 0.5
32
+ ],
33
+ "processor_class": "PaliGemmaProcessor",
34
+ "resample": 3,
35
+ "rescale_factor": 0.00392156862745098,
36
+ "size": {
37
+ "height": 448,
38
+ "width": 448
39
+ }
40
+ }
sentence_bert_config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transformer_task": "feature-extraction",
3
+ "modality_config": {
4
+ "text": {
5
+ "method": "forward",
6
+ "method_output_name": "last_hidden_state"
7
+ },
8
+ "image": {
9
+ "method": "forward",
10
+ "method_output_name": "last_hidden_state"
11
+ },
12
+ "message": {
13
+ "method": "forward",
14
+ "method_output_name": "last_hidden_state",
15
+ "format": "structured"
16
+ }
17
+ },
18
+ "module_output_name": "token_embeddings",
19
+ "model_kwargs": {
20
+ "key_mapping": {
21
+ "^model\\.": ""
22
+ }
23
+ },
24
+ "processor_kwargs": {
25
+ "model_input_names": [
26
+ "input_ids",
27
+ "attention_mask",
28
+ "token_type_ids"
29
+ ]
30
+ }
31
+ }
tokenizer_config.json CHANGED
The diff for this file is too large to render. See raw diff