Skip to content

Commit 829fad6

Browse files
committed
feat: add skeletoken
1 parent 3c09887 commit 829fad6

12 files changed

Lines changed: 383 additions & 1177 deletions

File tree

model2vec/distill/distillation.py

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import logging
44
import os
55
import re
6-
from typing import Optional, cast
6+
from typing import cast
77

88
import numpy as np
99
from huggingface_hub import model_info
10-
from transformers import AutoModel, AutoTokenizer, PreTrainedModel, PreTrainedTokenizerFast
10+
from transformers import AutoModel, AutoTokenizer
11+
from transformers.modeling_utils import PreTrainedModel
12+
from transformers.tokenization_utils_fast import PreTrainedTokenizerFast
1113

1214
from model2vec.distill.inference import PCADimType, create_embeddings, post_process_embeddings
1315
from model2vec.distill.utils import select_optimal_device
@@ -24,11 +26,10 @@ def distill_from_model(
2426
vocabulary: list[str] | None = None,
2527
device: str | None = None,
2628
pca_dims: PCADimType = 256,
27-
apply_zipf: bool | None = None,
2829
sif_coefficient: float | None = 1e-4,
2930
token_remove_pattern: str | None = r"\[unused\d+\]",
3031
quantize_to: DType | str = DType.Float16,
31-
use_subword: bool | None = None,
32+
lower_case: bool = True,
3233
) -> StaticModel:
3334
"""
3435
Distill a staticmodel from a sentence transformer.
@@ -46,25 +47,20 @@ def distill_from_model(
4647
:param pca_dims: The number of components to use for PCA.
4748
If this is None, we don't apply PCA.
4849
If this is 'auto', we don't reduce dimensionality, but still apply PCA.
49-
:param apply_zipf: DEPRECATED: This parameter used to control whether Zipf is applied.
50-
Zipf weighting is now controlled by the sif_coefficient parameter. If this is set to None, no weighting is applied.
5150
:param sif_coefficient: The SIF coefficient to use. If this is None, no weighting is applied.
5251
Should be a value > 0 and < 1.0. A value of 1e-4 is a good default.
5352
:param token_remove_pattern: If this is set to a string, we compile this into a regex. Any tokens that conform to this regex pattern will be removed from the vocabulary.
5453
If the pattern is so general that it removes all tokens, we throw an error. If the pattern can't be compiled into a valid regex, we also throw an error.
5554
:param quantize_to: The data type to quantize to. Can be any of the DType enum members or their string equivalents.
56-
:param use_subword: DEPRECATED: If this is not set to None, we show a warning. It doesn't do anything.
55+
:param lower_case: If this is set, all tokens in the model vocabulary will be converted to lowercase, and
56+
a lowercase normalizer will be inserted. This almost always improves performance.
5757
:return: A StaticModel
5858
:raises: ValueError if the vocabulary is empty after preprocessing.
5959
6060
"""
61-
if use_subword is not None:
62-
logger.warning(
63-
"The `use_subword` parameter is deprecated and will be removed in the next release. It doesn't do anything."
64-
)
6561
quantize_to = DType(quantize_to)
6662
backend_tokenizer = tokenizer.backend_tokenizer
67-
sif_coefficient, token_remove_regex = _validate_parameters(apply_zipf, sif_coefficient, token_remove_pattern)
63+
sif_coefficient, token_remove_regex = _validate_parameters(sif_coefficient, token_remove_pattern)
6864

6965
if vocabulary is None:
7066
vocabulary = []
@@ -73,45 +69,37 @@ def distill_from_model(
7369

7470
n_tokens_before = len(vocabulary)
7571
# Clean the vocabulary by removing duplicate tokens and tokens that are in the internal vocabulary.
76-
all_tokens, backend_tokenizer = clean_and_create_vocabulary(
77-
tokenizer, vocabulary, token_remove_regex=token_remove_regex
72+
tokens, backend_tokenizer = clean_and_create_vocabulary(
73+
tokenizer, vocabulary, token_remove_regex=token_remove_regex, lower_case=lower_case
7874
)
79-
n_tokens_after = len([token for token in all_tokens if not token.is_internal])
75+
n_tokens_after = len([token for token in tokens if not token.is_internal])
8076
if n_tokens_before:
8177
logger.info(
8278
f"Adding {n_tokens_after} tokens to the vocabulary. Removed {n_tokens_before - n_tokens_after} tokens during preprocessing."
8379
)
8480

85-
if not all_tokens:
81+
if not tokens:
8682
raise ValueError("The vocabulary is empty after preprocessing. Please check your token_remove_pattern.")
8783

88-
unk_token = cast(Optional[str], tokenizer.special_tokens_map.get("unk_token"))
89-
pad_token = cast(Optional[str], tokenizer.special_tokens_map.get("pad_token"))
90-
91-
# Weird if to satsify mypy
92-
if pad_token is None:
93-
if unk_token is not None:
94-
pad_token = unk_token
95-
logger.warning(
96-
"The pad token is not set. Setting it to the unk token. This is a workaround for models that don't have a pad token."
97-
)
98-
else:
99-
pad_token = unk_token or all_tokens[0].form
100-
logger.warning(
101-
"The pad token is not set. Setting it to the first token in the vocabulary. This is a workaround for models that don't have a pad token."
102-
)
103-
10484
# Replace the vocabulary in the tokenizer with the new vocabulary.
105-
backend_tokenizer = replace_vocabulary(backend_tokenizer, all_tokens, unk_token=unk_token, pad_token=pad_token)
85+
backend_tokenizer = replace_vocabulary(backend_tokenizer, tokens)
10686

107-
logger.info(f"Creating embeddings for {len(all_tokens)} tokens")
87+
logger.info(f"Creating embeddings for {len(tokens)} tokens")
10888
# Convert tokens to IDs
109-
token_ids = turn_tokens_into_ids(all_tokens, tokenizer, unk_token)
89+
token_ids = turn_tokens_into_ids(tokens, tokenizer.backend_tokenizer)
11090

11191
# Create the embeddings
112-
embeddings = create_embeddings(
113-
tokenized=token_ids, model=model, device=device, pad_token_id=tokenizer.get_vocab()[pad_token]
114-
)
92+
pad_token = cast(str | None, tokenizer.special_tokens_map.get("pad_token", None))
93+
vocab = tokenizer.get_vocab()
94+
if pad_token is None:
95+
sep_token = cast(str | None, tokenizer.special_tokens_map.get("sep_token", None))
96+
if sep_token is None:
97+
pad_token_id = 0
98+
else:
99+
pad_token_id = vocab[sep_token]
100+
else:
101+
pad_token_id = vocab[pad_token]
102+
embeddings = create_embeddings(tokenized=token_ids, model=model, device=device, pad_token_id=pad_token_id)
115103

116104
# Post process the embeddings by applying PCA and Zipf weighting.
117105
embeddings = post_process_embeddings(np.asarray(embeddings), pca_dims, sif_coefficient=sif_coefficient)
@@ -125,7 +113,6 @@ def distill_from_model(
125113
"architectures": ["StaticModel"],
126114
"tokenizer_name": model_name,
127115
"apply_pca": pca_dims,
128-
"apply_zipf": apply_zipf,
129116
"sif_coefficient": sif_coefficient,
130117
"hidden_dim": embeddings.shape[1],
131118
"seq_length": 1000000, # Set this to a high value since we don't have a sequence length limit.
@@ -157,35 +144,19 @@ def distill_from_model(
157144

158145

159146
def _validate_parameters(
160-
apply_zipf: bool | None,
161147
sif_coefficient: float | None,
162148
token_remove_pattern: str | None,
163149
) -> tuple[float | None, re.Pattern | None]:
164150
"""
165151
Validate the parameters passed to the distillation function.
166152
167-
:param apply_zipf: DEPRECATED: This parameter used to control whether Zipf is applied.
168-
Zipf weighting is now controlled by the sif_coefficient parameter. If this is set to None, no weighting is applied.
169153
:param sif_coefficient: The SIF coefficient to use. If this is None, no weighting is applied.
170154
Should be a value >= 0 and < 1.0. A value of 1e-4 is a good default.
171155
:param token_remove_pattern: If this is set to a string, we compile this into a regex. Any tokens that conform to this regex pattern will be removed from the vocabulary.
172156
:return: The SIF coefficient to use.
173157
:raises: ValueError if the regex can't be compiled.
174158
175159
"""
176-
if apply_zipf is not None:
177-
logger.warning(
178-
"The `apply_zipf` parameter is deprecated and will be removed in the next release. "
179-
"Zipf weighting is applied based on the sif_coefficient parameter. If this is set to None, "
180-
"no weighting is applied."
181-
)
182-
if apply_zipf and sif_coefficient is None:
183-
logger.warning("You set apply_zipf to True, but sif_coefficient is None. Setting sif_coefficient to 1e-4.")
184-
sif_coefficient = 1e-4
185-
elif not apply_zipf:
186-
logger.warning("Because you set apply_zipf to False, we ignore the sif_coefficient parameter.")
187-
sif_coefficient = None
188-
189160
if sif_coefficient is not None:
190161
if not 0 < sif_coefficient < 1.0:
191162
raise ValueError("SIF coefficient must be a value > 0 and < 1.0.")
@@ -205,12 +176,11 @@ def distill(
205176
vocabulary: list[str] | None = None,
206177
device: str | None = None,
207178
pca_dims: PCADimType = 256,
208-
apply_zipf: bool | None = None,
209179
sif_coefficient: float | None = 1e-4,
210180
token_remove_pattern: str | None = r"\[unused\d+\]",
211181
trust_remote_code: bool = False,
212182
quantize_to: DType | str = DType.Float16,
213-
use_subword: bool | None = None,
183+
lower_case: bool = True,
214184
) -> StaticModel:
215185
"""
216186
Distill a staticmodel from a sentence transformer.
@@ -227,14 +197,13 @@ def distill(
227197
:param pca_dims: The number of components to use for PCA.
228198
If this is None, we don't apply PCA.
229199
If this is 'auto', we don't reduce dimenionality, but still apply PCA.
230-
:param apply_zipf: DEPRECATED: This parameter used to control whether Zipf is applied.
231-
Zipf weighting is now controlled by the sif_coefficient parameter. If this is set to None, no weighting is applied.
232200
:param sif_coefficient: The SIF coefficient to use. If this is None, no weighting is applied.
233201
Should be a value >= 0 and < 1.0. A value of 1e-4 is a good default.
234202
:param token_remove_pattern: If this is set to a string, we compile this into a regex. Any tokens that conform to this regex pattern will be removed from the vocabulary.
235203
:param trust_remote_code: Whether to trust the remote code. If this is False, we will only load components coming from `transformers`. If this is True, we will load all components.
236204
:param quantize_to: The data type to quantize to. Can be any of the DType enum members or their string equivalents.
237-
:param use_subword: DEPRECATED: If this is not set to None, we show a warning. It doesn't do anything.
205+
:param lower_case: If this is set, all tokens in the model vocabulary will be converted to lowercase, and
206+
a lowercase normalizer will be inserted. This almost always improves performance.
238207
:return: A StaticModel
239208
240209
"""
@@ -250,9 +219,8 @@ def distill(
250219
vocabulary=vocabulary,
251220
device=device,
252221
pca_dims=pca_dims,
253-
apply_zipf=apply_zipf,
254222
token_remove_pattern=token_remove_pattern,
255223
sif_coefficient=sif_coefficient,
256224
quantize_to=quantize_to,
257-
use_subword=use_subword,
225+
lower_case=lower_case,
258226
)

model2vec/distill/inference.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def create_embeddings(
4646
:param pad_token_id: The pad token id. Used to pad sequences.
4747
:return: The output embeddings.
4848
"""
49-
model = model.to(device)
49+
model = model.to(device) # type: ignore
5050

5151
out_weights: np.ndarray
5252
intermediate_weights: list[np.ndarray] = []
@@ -98,6 +98,7 @@ def _encode_mean_using_model(model: PreTrainedModel, encodings: dict[str, torch.
9898
"""
9999
encodings = {k: v.to(model.device) for k, v in encodings.items()}
100100
encoded: BaseModelOutputWithPoolingAndCrossAttentions = model(**encodings)
101+
assert encoded.last_hidden_state is not None
101102
out: torch.Tensor = encoded.last_hidden_state.cpu()
102103
# NOTE: If the dtype is bfloat 16, we convert to float32,
103104
# because numpy does not suport bfloat16

model2vec/tokenizer/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
from model2vec.tokenizer.tokenizer import (
66
clean_and_create_vocabulary,
7-
create_tokenizer,
87
replace_vocabulary,
98
turn_tokens_into_ids,
109
)
1110

12-
__all__ = ["clean_and_create_vocabulary", "create_tokenizer", "turn_tokens_into_ids", "replace_vocabulary"]
11+
__all__ = ["clean_and_create_vocabulary", "turn_tokens_into_ids", "replace_vocabulary"]

model2vec/tokenizer/datamodels.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
class Token:
66
"""A class to represent a token."""
77

8+
# The surface form: used for featurizing
89
form: str
9-
# The normalized and pretokenized form of the token
10+
# The normalized form: preprocessed by the new tokenizer
1011
normalized_form: str
1112
# Whether the word is a continuing subword.
1213
is_subword: bool
1314
# Whether the token is internal to the model.
1415
is_internal: bool
16+
# Whether the token is a multiword token
17+
is_multiword: bool = False

model2vec/tokenizer/model.py

Lines changed: 0 additions & 43 deletions
This file was deleted.

model2vec/tokenizer/normalizer.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

model2vec/tokenizer/pretokenizer.py

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)