File size: 8,956 Bytes
c773995
 
3acb305
 
 
 
 
 
 
 
 
 
 
c773995
 
4b65b64
c773995
3acb305
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
 
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
 
 
c773995
3acb305
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
c773995
470db40
c773995
3acb305
 
 
 
 
 
 
 
c773995
3acb305
 
c773995
3acb305
c773995
3acb305
 
 
 
c773995
3acb305
 
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
 
 
c773995
470db40
3acb305
 
c773995
3acb305
 
 
 
 
 
 
 
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
 
 
c773995
3acb305
 
 
 
 
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
c773995
3acb305
 
 
c773995
3acb305
 
 
 
 
 
 
 
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
 
 
c773995
3acb305
 
 
c773995
3acb305
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
c773995
3acb305
 
 
c773995
3acb305
 
 
 
c773995
3acb305
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
 
 
 
 
 
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
c773995
3acb305
c773995
3acb305
c773995
3acb305
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
---
library_name: transformers
license: cc-by-nc-sa-4.0
pipeline_tag: text-ranking
tags:
  - reranker
  - sequence-classification
  - qwen3
  - multilingual
  - bfloat16
  - 32k
base_model: ContextualAI/ctxl-rerank-v2-instruct-multilingual-1b
model_type: qwen3
---

# Contextual AI Reranker v2 1B — **SequenceClassification (single-logit) Converted Model**

This repository contains a **drop-in SequenceClassification** version of the original **ContextualAI/ctxl-rerank-v2-instruct-multilingual-1b**.
It exposes a **single logit per input** (one score) that is **numerically equivalent** to the original model’s last-token **`vocab_id=0`** logit (`next_logits[:, 0]`). That means you can use standard **text-classification/CrossEncoder** tooling for fast, simple reranking—without custom logits processors—while preserving the original scores and ranking order.

> **What changed?** We copy the LM head’s **row 0** vector into a 1-logit classification head (`score.weight ← lm_head.weight[0]`), set bias to 0 (or the matching bias row if present), and keep tokenizer/padding behavior aligned with the original. Result: `SequenceClassification` output ≡ original `next_logits[:, 0]`.

---

## Highlights

* **Parity with the original**: The score from this model equals the original **ID=0** logit at the very last token position (use the same prompt template and left-padding).
* **Frictionless integration**: Works out-of-the-box with **Sentence-Transformers CrossEncoder** and standard **Transformers** classification interfaces.
* **Fast & memory-light**: Computes a single logit (`hidden_size × 1`) instead of a full vocabulary projection.
* **Multilingual** and long-context (inherits capabilities from the base reranker).

---

## Model Overview

* **Type**: Text Reranking (single-logit SequenceClassification)
* **Base**: `ContextualAI/ctxl-rerank-v2-instruct-multilingual-1b` (Qwen3 CausalLM)
* **Languages**: 100+ (inherited)
* **Params**: \~1B (inherited)
* **Context Length**: up to 32K (inherited)
* **Scoring definition**: single logit ≡ original `next_logits[:, 0]`

---

## Input Formatting (keep this template)

```text
Check whether a given document contains information helpful to answer the query.
<Document> {document}
<Query> {query}{optional_instruction} ??
```

* Use **left padding** so the **last token** aligns across a batch.
* If the tokenizer has no `pad_token`, set `pad_token = eos_token`.

---

## Updated Usage

Below are **drop-in** examples for the converted model. These mirror the original card’s behavior but through **SequenceClassification**.

### Updated Sentence Transformers Usage (CrossEncoder)

```python
from sentence_transformers import CrossEncoder

MODEL_ID = "sigridjineth/ctxl-rerank-v2-1b-seq-cls"  # or local folder

def format_prompts(query: str, instruction: str, docs: list[str]) -> list[str]:
    inst = f" {instruction}" if instruction else ""
    return [
        "Check whether a given document contains information helpful to answer the query.\n"
        f"<Document> {d}\n"
        f"<Query> {query}{inst} ??"
        for d in docs
    ]

query = "Which is a domestic animal?"
docs = ["Cats are pets.", "The moon is made of cheese.", "Dogs are loyal companions."]

ce = CrossEncoder(MODEL_ID, max_length=8192)

# Ensure original padding behavior
if ce.tokenizer.pad_token is None:
    ce.tokenizer.pad_token = ce.tokenizer.eos_token
ce.tokenizer.padding_side = "left"

prompts = format_prompts(query, "", docs)
scores = ce.predict(prompts)  # one logit per doc (higher = more relevant)

ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)
for s, d in ranked:
    print(f"{s:.4f} | {d}")
```

### Updated Transformers Usage (SequenceClassification)

```python
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

MODEL_ID = "sigridjineth/ctxl-rerank-v2-1b-seq-cls"  # or local folder
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32

def format_prompts(query: str, instruction: str, docs: list[str]) -> list[str]:
    inst = f" {instruction}" if instruction else ""
    return [
        "Check whether a given document contains information helpful to answer the query.\n"
        f"<Document> {d}\n"
        f"<Query> {query}{inst} ??"
        for d in docs
    ]

tok = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
if tok.pad_token is None:
    tok.pad_token = tok.eos_token
tok.padding_side = "left"

model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID, torch_dtype=dtype).to(device).eval()

query = "Which is a domestic animal?"
docs = ["Cats are pets.", "The moon is made of cheese."]
prompts = format_prompts(query, "", docs)

enc = tok(prompts, return_tensors="pt", padding=True, truncation=True).to(device)
with torch.no_grad():
    logits = model(**enc).logits.squeeze(-1)     # [batch]
    # Optional: exact parity rounding with original BF16 readout
    scores = logits.to(torch.bfloat16).float().cpu().tolist()

ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)
for s, d in ranked:
    print(f"{s:.4f} | {d}")
```

> **Note on parity**: Casting the output logit to **bf16 then back to float** matches the original card’s BF16 rounding step.

---

## (Reference) Original Transformers Usage (CausalLM)

If you prefer to call the original model directly, compute `next_logits[:, -1, 0]` as specified in the base card.

```python
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

BASE_ID = "ContextualAI/ctxl-rerank-v2-instruct-multilingual-1b"
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32

def format_prompts(q: str, inst: str, docs: list[str]) -> list[str]:
    inst = f" {inst}" if inst else ""
    return [
        "Check whether a given document contains information helpful to answer the query.\n"
        f"<Document> {d}\n"
        f"<Query> {q}{inst} ??"
        for d in docs
    ]

tok = AutoTokenizer.from_pretrained(BASE_ID, use_fast=True)
if tok.pad_token is None:
    tok.pad_token = tok.eos_token
tok.padding_side = "left"

lm = AutoModelForCausalLM.from_pretrained(BASE_ID, torch_dtype=dtype).to(device).eval()

docs = ["Cats are pets.", "The moon is made of cheese."]
prompts = format_prompts("Which is a domestic animal?", "", docs)
enc = tok(prompts, return_tensors="pt", padding=True, truncation=True).to(device)

with torch.no_grad():
    out = lm(**enc).logits[:, -1, :]        # [batch, vocab]
    scores = out[:, 0].to(torch.bfloat16).float().cpu().tolist()

for s, d in sorted(zip(scores, docs), key=lambda x: x[0], reverse=True):
    print(f"{s:.4f} | {d}")
```

---

## Conversion Details

* **Architecture**: `Qwen3ForSequenceClassification(num_labels=1)`
* **Head initialization**:

  * `score.weight ← lm_head.weight[0]` (row for `vocab_id=0`)
  * `score.bias ← 0` (or the corresponding bias term if present in LM head)
* **Tokenizer/Config**:

  * Ensure `pad_token` exists (`pad_token = eos_token` if missing)
  * Set `padding_side="left"`
  * Propagate `pad/eos/bos` IDs into the model `config` for correct batching
* **Parity check**:

  * Verified that `SequenceClassification` logit ≡ original `next_logits[:, 0]`
  * Optional BF16 round-trip on the score for exact rounding parity

---

## Intended Use & Limitations

* **Use**: Document reranking for search/QA/multilingual scenarios; batch scoring of `(query, document)` prompts.
* **Not for**: Open-ended generation; the model emits a **single score** per input.
* **License constraints**: Non-commercial & Share-Alike. If you redistribute derivatives, include attribution and the same license.
* **Bias & safety**: Inherits all limitations and potential biases of the base model; evaluate before deployment.

---

## Requirements

* **Transformers** ≥ 4.51.0
* **PyTorch** with BF16 support recommended on GPU
* Long inputs: set `max_length` accordingly (up to the inherited context window)

---

## Citation

If you use this converted model, please cite the original work:

```bibtex
@misc{ctxl_rerank_v2_instruct_multilingual,
  title = {Contextual AI Reranker v2},
  author = {George Halal and Sheshansh Agrawal and Bo Han and Arnav Palkhiwala},
  year = {2025},
  url = {https://contextual.ai/blog/rerank-v2}
}
```

---

## License

This repository follows the original **Creative Commons Attribution Non Commercial Share Alike 4.0 (CC-BY-NC-SA-4.0)** license.
You **must** provide attribution, **may not** use it commercially, and **must** distribute derivatives under the same license.

---

## Acknowledgements

All modeling, training, and evaluation credit goes to **Contextual AI** for the original `ctxl-rerank-v2` family.
This repository provides a **compatibility conversion** to a single-logit `SequenceClassification` interface for easier integration and deployment.