SlideOn / slideon.py
Paulo de Moura
Use max_active_dims for pooled latent pruning
450a2c8
Raw History Blame Contribute Delete
4.65 kB
"""Sentence Transformers adapter for the complete, flat SlideOn checkpoint."""
from pathlib import Path
import torch
from sentence_transformers.base.modules import InputModule
from transformers import AutoConfig, AutoModel, AutoTokenizer
class SlideOn(InputModule):
config_file_name = "config.json"
forward_kwargs = {"task", "max_active_dims", "token_k"}
save_in_root = True
def __init__(self, checkpoint, model_kwargs=None, processor_kwargs=None,
config_kwargs=None, **hub_kwargs):
super().__init__()
config = AutoConfig.from_pretrained(
checkpoint, trust_remote_code=True, **hub_kwargs, **(config_kwargs or {}))
if config.model_type != "slideon":
raise ValueError("Expected a flat SlideOn checkpoint; use slideon.export for legacy checkpoints")
self.model = AutoModel.from_pretrained(
checkpoint, config=config, trust_remote_code=True,
**hub_kwargs, **(model_kwargs or {}))
self.tokenizer = AutoTokenizer.from_pretrained(
checkpoint, trust_remote_code=True, **hub_kwargs, **(processor_kwargs or {}))
if self.tokenizer.mask_token_id is not None:
self.tokenizer.pad_token_id = self.tokenizer.mask_token_id
elif self.tokenizer.eos_token_id is not None:
self.tokenizer.pad_token_id = self.tokenizer.eos_token_id
self.settings = self.model.config.inference
self.encoder_config = self.model.config.tokenizer_settings
self.max_seq_length = max(self.settings["query_length"], self.settings["document_length"])
def get_embedding_dimension(self):
return self.model.get_embedding_dimension()
@staticmethod
def _task(task):
task = task or "document"
if task not in ("query", "document"):
raise ValueError("task must be 'query' or 'document'")
return task
def preprocess(
self, inputs, prompt=None, task=None, processing_kwargs=None, **kwargs
):
task = self._task(task)
if not all(isinstance(text, str) for text in inputs):
raise TypeError("SlideOn accepts text strings only")
if prompt is None:
prompt = self.settings["prompts"][task]
texts = [(prompt + text).strip() for text in inputs]
prefix = self.encoder_config.get(task + "_prefix", "")
length = min(self.max_seq_length, self.settings[task + "_length"])
options = dict(
padding=True,
truncation=True,
max_length=length - bool(prefix),
return_tensors="pt",
)
options.update(processing_kwargs or {})
features = dict(self.tokenizer(texts, **options))
if prefix:
prefix_id = self.tokenizer.convert_tokens_to_ids(prefix)
for name, value in list(features.items()):
fill = (
prefix_id
if name == "input_ids"
else (1 if name == "attention_mask" else 0)
)
marker = torch.full(
(value.shape[0], 1), fill, dtype=value.dtype, device=value.device
)
features[name] = torch.cat((value[:, :1], marker, value[:, 1:]), dim=1)
features["slideon_task"] = task
return features
def forward(self, features, task=None, max_active_dims=None, token_k=None, **kwargs):
if "top_k" in kwargs:
raise TypeError("top_k has been removed; use max_active_dims (None for unpruned)")
inputs = {key: features[key] for key in
("input_ids", "attention_mask", "token_type_ids", "slideon_task") if key in features}
features.update(self.model(**inputs, task=task, max_active_dims=max_active_dims, token_k=token_k))
return features
def save(self, output_path, safe_serialization=True, **kwargs):
self.model.save_pretrained(output_path, safe_serialization=safe_serialization)
self.tokenizer.save_pretrained(output_path)
@classmethod
def load(cls, model_name_or_path, subfolder="", token=None, cache_folder=None,
revision=None, local_files_only=False, model_kwargs=None,
processor_kwargs=None, config_kwargs=None, backend="torch", **kwargs):
if backend != "torch":
raise ValueError("SlideOn currently supports the torch backend only")
return cls(model_name_or_path, model_kwargs, processor_kwargs, config_kwargs,
subfolder=subfolder, token=token, cache_dir=cache_folder,
revision=revision, local_files_only=local_files_only)