Paulo de Moura commited on
Commit
450a2c8
·
1 Parent(s): dbee038

Use max_active_dims for pooled latent pruning

Browse files
Files changed (4) hide show
  1. README.md +8 -6
  2. config.json +2 -2
  3. modeling_slideon.py +10 -7
  4. 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, top_k=64)
211
- document_embeddings = model.encode_document(documents, top_k=512)
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
- Use `top_k` independently on each encoding call to cap the number of nonzero features **after pooling**. This is separate from the per-token top-32 operation and does not change the vector dimensionality.
 
 
225
 
226
  To reproduce the unpruned setting:
227
 
228
  ```python
229
- query_embeddings = model.encode_query(queries, top_k=0)
230
- document_embeddings = model.encode_document(documents, top_k=0)
231
  ```
232
 
233
- Omitting `top_k` also uses the saved unpruned defaults. To use the **q-full / d-512** setting, keep the query call at `top_k=0` and set the document call to `top_k=512`.
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
- "document_top_k": 0,
98
  "head_chunk_size": 2048,
99
  "pooling": "max_relu",
100
  "prompts": {
@@ -102,7 +102,7 @@
102
  "query": "query: "
103
  },
104
  "query_length": 8192,
105
- "query_top_k": 0,
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, query_top_k=0, document_top_k=0,
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, top_k=None, token_k=None, **kwargs):
 
 
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[task + "_top_k"] if top_k is None else top_k
148
  k = self.settings["token_k"] if token_k is None else token_k
149
- if not isinstance(cap, int) or not 0 <= cap <= n_latents:
150
- raise ValueError(f"top_k must be an integer between 0 and {n_latents}")
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
- values, indices = pooled.topk(cap, dim=-1)
 
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", "top_k", "token_k"}
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, top_k=None, token_k=None, **kwargs):
 
 
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, top_k=top_k, token_k=token_k))
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):