Feature Extraction
sentence-transformers
Safetensors
English
slideon
sparse
sentence-similarity
custom-code
sparse-retrieval
matryoshka
custom_code
🇪🇺 Region: EU
Instructions to use lightonai/SlideOn with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use lightonai/SlideOn with sentence-transformers:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("lightonai/SlideOn", trust_remote_code=True) sentences = [ "The weather is lovely today.", "It's so sunny outside!", "He drove to the stadium." ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [3, 3] - Notebooks
- Google Colab
- Kaggle
Paulo de Moura commited on
Commit ·
450a2c8
1
Parent(s): dbee038
Use max_active_dims for pooled latent pruning
Browse files- README.md +8 -6
- config.json +2 -2
- modeling_slideon.py +10 -7
- slideon.py +5 -3
README.md
CHANGED
|
@@ -207,8 +207,8 @@ documents = [
|
|
| 207 |
"Wind turbines convert the kinetic energy of wind into electricity.",
|
| 208 |
]
|
| 209 |
|
| 210 |
-
query_embeddings = model.encode_query(queries,
|
| 211 |
-
document_embeddings = model.encode_document(documents,
|
| 212 |
|
| 213 |
print(query_embeddings.shape, document_embeddings.shape)
|
| 214 |
# torch.Size([1, 32768]) torch.Size([2, 32768])
|
|
@@ -221,16 +221,18 @@ print(scores)
|
|
| 221 |
|
| 222 |
### Choosing Query and Document Budgets
|
| 223 |
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
|
| 226 |
To reproduce the unpruned setting:
|
| 227 |
|
| 228 |
```python
|
| 229 |
-
query_embeddings = model.encode_query(queries,
|
| 230 |
-
document_embeddings = model.encode_document(documents,
|
| 231 |
```
|
| 232 |
|
| 233 |
-
Omitting `
|
| 234 |
|
| 235 |
Dimensions represent **learned latents**, so they do not map directly to tokenizer vocabulary entries. Inspect the sparse indices and weights; `SparseEncoder.decode()` cannot label them as vocabulary tokens. Retrieval uses raw dot products, so leave final vector normalization disabled.
|
| 236 |
|
|
|
|
| 207 |
"Wind turbines convert the kinetic energy of wind into electricity.",
|
| 208 |
]
|
| 209 |
|
| 210 |
+
query_embeddings = model.encode_query(queries, max_active_dims=64)
|
| 211 |
+
document_embeddings = model.encode_document(documents, max_active_dims=512)
|
| 212 |
|
| 213 |
print(query_embeddings.shape, document_embeddings.shape)
|
| 214 |
# torch.Size([1, 32768]) torch.Size([2, 32768])
|
|
|
|
| 221 |
|
| 222 |
### Choosing Query and Document Budgets
|
| 223 |
|
| 224 |
+
Set `max_active_dims=N` to keep the **top-N latents with the highest weights** in each pooled vector and set the rest to zero. You can choose a different N for queries and documents. This is separate from the per-token top-32 operation and does not change the vector dimensionality.
|
| 225 |
+
|
| 226 |
+
Use a positive integer for a cap, or `None` for the saved unpruned defaults; `0` is invalid.
|
| 227 |
|
| 228 |
To reproduce the unpruned setting:
|
| 229 |
|
| 230 |
```python
|
| 231 |
+
query_embeddings = model.encode_query(queries, max_active_dims=None)
|
| 232 |
+
document_embeddings = model.encode_document(documents, max_active_dims=None)
|
| 233 |
```
|
| 234 |
|
| 235 |
+
Omitting `max_active_dims` also uses the saved unpruned defaults. To use the **q-full / d-512** setting, keep the query call at `max_active_dims=None` and set the document call to `max_active_dims=512`.
|
| 236 |
|
| 237 |
Dimensions represent **learned latents**, so they do not map directly to tokenizer vocabulary entries. Inspect the sparse indices and weights; `SparseEncoder.decode()` cannot label them as vocabulary tokens. Retrieval uses raw dot products, so leave final vector normalization disabled.
|
| 238 |
|
config.json
CHANGED
|
@@ -94,7 +94,7 @@
|
|
| 94 |
},
|
| 95 |
"inference": {
|
| 96 |
"document_length": 8192,
|
| 97 |
-
"
|
| 98 |
"head_chunk_size": 2048,
|
| 99 |
"pooling": "max_relu",
|
| 100 |
"prompts": {
|
|
@@ -102,7 +102,7 @@
|
|
| 102 |
"query": "query: "
|
| 103 |
},
|
| 104 |
"query_length": 8192,
|
| 105 |
-
"
|
| 106 |
"score": "dot",
|
| 107 |
"token_k": 32
|
| 108 |
},
|
|
|
|
| 94 |
},
|
| 95 |
"inference": {
|
| 96 |
"document_length": 8192,
|
| 97 |
+
"document_max_active_dims": null,
|
| 98 |
"head_chunk_size": 2048,
|
| 99 |
"pooling": "max_relu",
|
| 100 |
"prompts": {
|
|
|
|
| 102 |
"query": "query: "
|
| 103 |
},
|
| 104 |
"query_length": 8192,
|
| 105 |
+
"query_max_active_dims": null,
|
| 106 |
"score": "dot",
|
| 107 |
"token_k": 32
|
| 108 |
},
|
modeling_slideon.py
CHANGED
|
@@ -22,7 +22,7 @@ class SlideOnConfig(PretrainedConfig):
|
|
| 22 |
self.projection_configs = projection_configs or []
|
| 23 |
self.head_config = head_config or dict(d_model=768, n_latents=32768, d_lift=0)
|
| 24 |
self.inference = inference or dict(
|
| 25 |
-
token_k=32,
|
| 26 |
query_length=8192, document_length=8192, head_chunk_size=2048,
|
| 27 |
prompts={"query": "", "document": ""}, pooling="max_relu", score="dot")
|
| 28 |
self.tokenizer_settings = tokenizer_settings or {}
|
|
@@ -134,7 +134,9 @@ class SlideOnModel(PreTrainedModel):
|
|
| 134 |
|
| 135 |
|
| 136 |
def forward(self, input_ids, attention_mask, token_type_ids=None, task=None,
|
| 137 |
-
slideon_task=None,
|
|
|
|
|
|
|
| 138 |
features = {"input_ids": input_ids, "attention_mask": attention_mask}
|
| 139 |
if slideon_task is not None or task is not None:
|
| 140 |
features["slideon_task"] = slideon_task or task
|
|
@@ -144,10 +146,10 @@ class SlideOnModel(PreTrainedModel):
|
|
| 144 |
if features.get("slideon_task", task) != task:
|
| 145 |
raise ValueError("Preprocessing and forward tasks must match")
|
| 146 |
n_latents = self.get_embedding_dimension()
|
| 147 |
-
cap = self.settings
|
| 148 |
k = self.settings["token_k"] if token_k is None else token_k
|
| 149 |
-
if not
|
| 150 |
-
raise ValueError(
|
| 151 |
if not isinstance(k, int) or not 1 <= k <= n_latents:
|
| 152 |
raise ValueError(f"token_k must be an integer between 1 and {n_latents}")
|
| 153 |
model_inputs = {
|
|
@@ -199,8 +201,9 @@ class SlideOnModel(PreTrainedModel):
|
|
| 199 |
pooled.view(mask.shape[0], n_latents)
|
| 200 |
* self.head.gate_relu_phi.float().relu()
|
| 201 |
)
|
| 202 |
-
if cap:
|
| 203 |
-
|
|
|
|
| 204 |
pooled = torch.zeros_like(pooled).scatter(1, indices, values)
|
| 205 |
# The reference stores retrieval weights in FP16; retain that rounding.
|
| 206 |
features["sentence_embedding"] = pooled if self.training else pooled.half().float()
|
|
|
|
| 22 |
self.projection_configs = projection_configs or []
|
| 23 |
self.head_config = head_config or dict(d_model=768, n_latents=32768, d_lift=0)
|
| 24 |
self.inference = inference or dict(
|
| 25 |
+
token_k=32, query_max_active_dims=None, document_max_active_dims=None,
|
| 26 |
query_length=8192, document_length=8192, head_chunk_size=2048,
|
| 27 |
prompts={"query": "", "document": ""}, pooling="max_relu", score="dot")
|
| 28 |
self.tokenizer_settings = tokenizer_settings or {}
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
def forward(self, input_ids, attention_mask, token_type_ids=None, task=None,
|
| 137 |
+
slideon_task=None, max_active_dims=None, token_k=None, **kwargs):
|
| 138 |
+
if "top_k" in kwargs:
|
| 139 |
+
raise TypeError("top_k has been removed; use max_active_dims (None for unpruned)")
|
| 140 |
features = {"input_ids": input_ids, "attention_mask": attention_mask}
|
| 141 |
if slideon_task is not None or task is not None:
|
| 142 |
features["slideon_task"] = slideon_task or task
|
|
|
|
| 146 |
if features.get("slideon_task", task) != task:
|
| 147 |
raise ValueError("Preprocessing and forward tasks must match")
|
| 148 |
n_latents = self.get_embedding_dimension()
|
| 149 |
+
cap = self.settings.get(task + "_max_active_dims") if max_active_dims is None else max_active_dims
|
| 150 |
k = self.settings["token_k"] if token_k is None else token_k
|
| 151 |
+
if cap is not None and (type(cap) is not int or cap <= 0):
|
| 152 |
+
raise ValueError("max_active_dims must be a positive integer or None")
|
| 153 |
if not isinstance(k, int) or not 1 <= k <= n_latents:
|
| 154 |
raise ValueError(f"token_k must be an integer between 1 and {n_latents}")
|
| 155 |
model_inputs = {
|
|
|
|
| 201 |
pooled.view(mask.shape[0], n_latents)
|
| 202 |
* self.head.gate_relu_phi.float().relu()
|
| 203 |
)
|
| 204 |
+
if cap is not None:
|
| 205 |
+
# Prune before FP16 rounding
|
| 206 |
+
values, indices = pooled.topk(min(cap, n_latents), dim=-1)
|
| 207 |
pooled = torch.zeros_like(pooled).scatter(1, indices, values)
|
| 208 |
# The reference stores retrieval weights in FP16; retain that rounding.
|
| 209 |
features["sentence_embedding"] = pooled if self.training else pooled.half().float()
|
slideon.py
CHANGED
|
@@ -8,7 +8,7 @@ from transformers import AutoConfig, AutoModel, AutoTokenizer
|
|
| 8 |
|
| 9 |
class SlideOn(InputModule):
|
| 10 |
config_file_name = "config.json"
|
| 11 |
-
forward_kwargs = {"task", "
|
| 12 |
save_in_root = True
|
| 13 |
|
| 14 |
def __init__(self, checkpoint, model_kwargs=None, processor_kwargs=None,
|
|
@@ -75,10 +75,12 @@ class SlideOn(InputModule):
|
|
| 75 |
features["slideon_task"] = task
|
| 76 |
return features
|
| 77 |
|
| 78 |
-
def forward(self, features, task=None,
|
|
|
|
|
|
|
| 79 |
inputs = {key: features[key] for key in
|
| 80 |
("input_ids", "attention_mask", "token_type_ids", "slideon_task") if key in features}
|
| 81 |
-
features.update(self.model(**inputs, task=task,
|
| 82 |
return features
|
| 83 |
|
| 84 |
def save(self, output_path, safe_serialization=True, **kwargs):
|
|
|
|
| 8 |
|
| 9 |
class SlideOn(InputModule):
|
| 10 |
config_file_name = "config.json"
|
| 11 |
+
forward_kwargs = {"task", "max_active_dims", "token_k"}
|
| 12 |
save_in_root = True
|
| 13 |
|
| 14 |
def __init__(self, checkpoint, model_kwargs=None, processor_kwargs=None,
|
|
|
|
| 75 |
features["slideon_task"] = task
|
| 76 |
return features
|
| 77 |
|
| 78 |
+
def forward(self, features, task=None, max_active_dims=None, token_k=None, **kwargs):
|
| 79 |
+
if "top_k" in kwargs:
|
| 80 |
+
raise TypeError("top_k has been removed; use max_active_dims (None for unpruned)")
|
| 81 |
inputs = {key: features[key] for key in
|
| 82 |
("input_ids", "attention_mask", "token_type_ids", "slideon_task") if key in features}
|
| 83 |
+
features.update(self.model(**inputs, task=task, max_active_dims=max_active_dims, token_k=token_k))
|
| 84 |
return features
|
| 85 |
|
| 86 |
def save(self, output_path, safe_serialization=True, **kwargs):
|