NohTow commited on
Commit
1eb28fb
·
1 Parent(s): 8b9ba80

Integrate with Sentence Transformers (#1)

Browse files

- Integrate with Sentence Transformers (5c47529aef3b03352ddc67513398c00822539220)

1_LogitScore/config.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "true_token_id": 9175,
3
+ "false_token_id": 2665,
4
+ "module_input_name": "causal_logits"
5
+ }
README.md CHANGED
@@ -15,6 +15,7 @@ tags:
15
  - document-reranking
16
  - vidore
17
  - beir
 
18
  datasets:
19
  - vidore/colpali_train_set
20
  - lightonai/embeddings-fine-tuning
@@ -125,12 +126,60 @@ Under the same protocol, jina-reranker-m0 scores 51.97 decontaminated mean and Q
125
  - **Fine-tuning:** joint text+vision LoRA (r=32, α=32, rsLoRA, merged into the released weights), mixed-modality batches (2 text + 2 vision groups per micro-batch), vision loss weight 1.3, lr 1e-4, 1 epoch (465 steps)
126
  - **Data:** same 213k groups as the listwise models — 107k text groups (NQ, TriviaQA, MS MARCO; each a `[pos, neg_0, neg_1, neg_2]` 4-list with hard negatives mined via the NV-Retriever approach with GTE-ModernBERT) + 106k vision groups (ColPali train set with negatives mined by Nomic). For pointwise training each group is flattened into (query, document, Yes/No) triples.
127
  - **Languages:** English (training), French (zero-shot transfer)
128
- - **Requirements:** `transformers >= 5.4.0` (`qwen3_5` architecture)
129
 
130
  ## Usage: pointwise reranking
131
 
132
  Each candidate is scored independently as `logit("Yes") − logit("No")` at the first generated position; sort candidates by descending score. The model was trained with a fixed system prompt and user template: use them verbatim for best results.
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  ```python
135
  import torch
136
  from transformers import AutoModelForImageTextToText, AutoProcessor
@@ -177,6 +226,7 @@ inputs = processor(
177
  with torch.inference_mode():
178
  logits = model(**inputs).logits[:, -1]
179
  scores = (logits[:, YES_TOKEN_ID] - logits[:, NO_TOKEN_ID]).tolist()
 
180
 
181
  ranked = sorted(zip(scores, documents), reverse=True)
182
  ```
 
15
  - document-reranking
16
  - vidore
17
  - beir
18
+ - sentence-transformers
19
  datasets:
20
  - vidore/colpali_train_set
21
  - lightonai/embeddings-fine-tuning
 
126
  - **Fine-tuning:** joint text+vision LoRA (r=32, α=32, rsLoRA, merged into the released weights), mixed-modality batches (2 text + 2 vision groups per micro-batch), vision loss weight 1.3, lr 1e-4, 1 epoch (465 steps)
127
  - **Data:** same 213k groups as the listwise models — 107k text groups (NQ, TriviaQA, MS MARCO; each a `[pos, neg_0, neg_1, neg_2]` 4-list with hard negatives mined via the NV-Retriever approach with GTE-ModernBERT) + 106k vision groups (ColPali train set with negatives mined by Nomic). For pointwise training each group is flattened into (query, document, Yes/No) triples.
128
  - **Languages:** English (training), French (zero-shot transfer)
129
+ - **Requirements:** `transformers >= 5.4.0` (`qwen3_5` architecture); `sentence-transformers >= 5.4.0` for the CrossEncoder usage
130
 
131
  ## Usage: pointwise reranking
132
 
133
  Each candidate is scored independently as `logit("Yes") − logit("No")` at the first generated position; sort candidates by descending score. The model was trained with a fixed system prompt and user template: use them verbatim for best results.
134
 
135
+ ### Using Sentence Transformers
136
+
137
+ Install Sentence Transformers (`>= 5.4.0`):
138
+
139
+ ```bash
140
+ pip install "sentence-transformers[image]"
141
+ ```
142
+
143
+ The trained system prompt and user templates are baked into the bundled `reranker` chat template, so query-document pairs are formatted correctly out of the box:
144
+
145
+ ```python
146
+ from sentence_transformers import CrossEncoder
147
+
148
+ model = CrossEncoder("lightonai/LightOn-rerank-PW-2B")
149
+
150
+ query = "What is late interaction in neural information retrieval?"
151
+ documents = [
152
+ "ColBERT computes token-level query-document interactions at search time...",
153
+ "The Eiffel Tower is located on the Champ de Mars in Paris.",
154
+ ]
155
+
156
+ pairs = [(query, doc) for doc in documents]
157
+ scores = model.predict(pairs)
158
+ print(scores)
159
+ # [-4.875 -6.75 ]
160
+
161
+ rankings = model.rank(query, documents)
162
+ print(rankings)
163
+ # [{'corpus_id': 0, 'score': -4.875}, {'corpus_id': 1, 'score': -6.75}]
164
+ ```
165
+
166
+ To rerank **page images**, pass a `PIL.Image` (or an image URL or file path string) as the document. Text and image candidates can be mixed in the same call:
167
+
168
+ ```python
169
+ from PIL import Image
170
+
171
+ pairs = [
172
+ (query, Image.open("page_1.png")),
173
+ (query, "https://example.com/page_2.png"),
174
+ (query, "A text passage candidate for the same query."),
175
+ ]
176
+ scores = model.predict(pairs)
177
+ ```
178
+
179
+ Scores are raw `logit("Yes") − logit("No")` differences. You can map them to 0...1 probabilities with `model.predict(pairs, activation_fn=torch.nn.Sigmoid())`.
180
+
181
+ ### Using Transformers
182
+
183
  ```python
184
  import torch
185
  from transformers import AutoModelForImageTextToText, AutoProcessor
 
226
  with torch.inference_mode():
227
  logits = model(**inputs).logits[:, -1]
228
  scores = (logits[:, YES_TOKEN_ID] - logits[:, NO_TOKEN_ID]).tolist()
229
+ # [-4.875, -6.75]
230
 
231
  ranked = sorted(zip(scores, documents), reverse=True)
232
  ```
additional_chat_templates/reranker.jinja ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set default_system = "Judge whether the document is relevant to the query. Answer Yes or No." -%}
2
+ {%- set ns = namespace(system_text="", doc_visual=false, doc_text=false) -%}
3
+ {%- for message in messages if message.role == "system" -%}
4
+ {%- if message.content is string -%}
5
+ {%- set ns.system_text = ns.system_text + message.content -%}
6
+ {%- else -%}
7
+ {%- for content in message.content -%}
8
+ {%- if 'text' in content -%}
9
+ {%- set ns.system_text = ns.system_text + content.text -%}
10
+ {%- endif -%}
11
+ {%- endfor -%}
12
+ {%- endif -%}
13
+ {%- endfor -%}
14
+ {%- if not ns.system_text -%}
15
+ {%- set ns.system_text = default_system -%}
16
+ {%- endif -%}
17
+ {%- for message in messages if message.role == "document" -%}
18
+ {%- if message.content is string -%}
19
+ {%- if message.content -%}
20
+ {%- set ns.doc_text = true -%}
21
+ {%- endif -%}
22
+ {%- else -%}
23
+ {%- for content in message.content -%}
24
+ {%- if content.type == 'image' or 'image' in content or 'image_url' in content or content.type == 'video' or 'video' in content -%}
25
+ {%- set ns.doc_visual = true -%}
26
+ {%- elif 'text' in content and content.text -%}
27
+ {%- set ns.doc_text = true -%}
28
+ {%- endif -%}
29
+ {%- endfor -%}
30
+ {%- endif -%}
31
+ {%- endfor -%}
32
+ {%- macro render_content(message) -%}
33
+ {%- if message.content is string -%}
34
+ {{- message.content -}}
35
+ {%- else -%}
36
+ {%- for content in message.content -%}
37
+ {%- if content.type == 'image' or 'image' in content or 'image_url' in content -%}
38
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
39
+ {%- elif content.type == 'video' or 'video' in content -%}
40
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
41
+ {%- elif 'text' in content -%}
42
+ {{- content.text -}}
43
+ {%- endif -%}
44
+ {%- endfor -%}
45
+ {%- endif -%}
46
+ {%- endmacro -%}
47
+ {%- macro render_visuals(message) -%}
48
+ {%- if message.content is not string -%}
49
+ {%- for content in message.content -%}
50
+ {%- if content.type == 'image' or 'image' in content or 'image_url' in content -%}
51
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
52
+ {%- elif content.type == 'video' or 'video' in content -%}
53
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
54
+ {%- endif -%}
55
+ {%- endfor -%}
56
+ {%- endif -%}
57
+ {%- endmacro -%}
58
+ {%- macro render_texts(message) -%}
59
+ {%- if message.content is string -%}
60
+ {{- message.content -}}
61
+ {%- else -%}
62
+ {%- for content in message.content -%}
63
+ {%- if content.type != 'image' and 'image' not in content and 'image_url' not in content and content.type != 'video' and 'video' not in content and 'text' in content -%}
64
+ {{- content.text -}}
65
+ {%- endif -%}
66
+ {%- endfor -%}
67
+ {%- endif -%}
68
+ {%- endmacro -%}
69
+ {%- set user_body -%}
70
+ {%- if ns.doc_visual -%}
71
+ {%- for message in messages if message.role == "document" -%}
72
+ {{- render_visuals(message) -}}
73
+ {%- endfor -%}
74
+ {{- 'Given a query, determine if the document image is relevant. The query is: ' -}}
75
+ {%- for message in messages if message.role == "query" -%}
76
+ {{- render_content(message) -}}
77
+ {%- endfor -%}
78
+ {%- if ns.doc_text -%}
79
+ {{- '\n\nDocument: ' -}}
80
+ {%- for message in messages if message.role == "document" -%}
81
+ {{- render_texts(message) -}}
82
+ {%- endfor -%}
83
+ {%- endif -%}
84
+ {%- else -%}
85
+ {{- 'Given a query, determine if the document is relevant. The query is: ' -}}
86
+ {%- for message in messages if message.role == "query" -%}
87
+ {{- render_content(message) -}}
88
+ {%- endfor -%}
89
+ {{- '\n\nDocument: ' -}}
90
+ {%- for message in messages if message.role == "document" -%}
91
+ {{- render_content(message) -}}
92
+ {%- endfor -%}
93
+ {%- endif -%}
94
+ {%- endset -%}
95
+ {{- '<|im_start|>system\n' + ns.system_text|trim + '<|im_end|>\n' -}}
96
+ {{- '<|im_start|>user\n' + user_body|trim + '<|im_end|>\n' -}}
97
+ {%- if add_generation_prompt -%}
98
+ {{- '<|im_start|>assistant\n<think>\n\n</think>\n\n' -}}
99
+ {%- endif -%}
config_sentence_transformers.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "__version__": {
3
+ "pytorch": "2.10.0+cu128",
4
+ "sentence_transformers": "5.6.0",
5
+ "transformers": "5.13.1"
6
+ },
7
+ "activation_fn": "torch.nn.modules.linear.Identity",
8
+ "default_prompt_name": null,
9
+ "model_type": "CrossEncoder",
10
+ "prompts": {}
11
+ }
modules.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_LogitScore",
12
+ "type": "sentence_transformers.cross_encoder.modules.logit_score.LogitScore"
13
+ }
14
+ ]
sentence_bert_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transformer_task": "any-to-any",
3
+ "modality_config": {
4
+ "text": {
5
+ "method": "forward",
6
+ "method_output_name": "logits"
7
+ },
8
+ "image": {
9
+ "method": "forward",
10
+ "method_output_name": "logits"
11
+ },
12
+ "video": {
13
+ "method": "forward",
14
+ "method_output_name": "logits"
15
+ },
16
+ "message": {
17
+ "method": "forward",
18
+ "method_output_name": "logits",
19
+ "format": "structured"
20
+ }
21
+ },
22
+ "module_output_name": "causal_logits",
23
+ "processing_kwargs": {
24
+ "chat_template": {
25
+ "chat_template": "reranker",
26
+ "add_generation_prompt": true
27
+ }
28
+ }
29
+ }