Skip to content

Commit 35cca62

Browse files
committed
fix: refix tokenizer with added token shenanigans
1 parent 622078a commit 35cca62

6 files changed

Lines changed: 54 additions & 21 deletions

File tree

model2vec/distill/distillation.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99
from huggingface_hub.hf_api import model_info
1010
from skeletoken import TokenizerModel
11+
from skeletoken.external.transformers import reshape_embeddings
1112
from transformers import AutoModel, AutoTokenizer
1213
from transformers.modeling_utils import PreTrainedModel
1314
from transformers.tokenization_utils_fast import PreTrainedTokenizerFast
@@ -77,17 +78,19 @@ def distill_from_model(
7778

7879
device = select_optimal_device(device)
7980
original_tokenizer_model = TokenizerModel.from_transformers_tokenizer(tokenizer)
81+
original_tokenizer_model = original_tokenizer_model.prune_added_tokens()
8082

8183
# Clean the vocabulary by removing duplicate tokens and tokens that are in the internal vocabulary.
8284
# Copy the original tokenizer model.
83-
tokenizer_model = original_tokenizer_model.model_copy(deep=True)
85+
tokenizer_model = original_tokenizer_model.deep_copy()
8486
if tokenizer_model.adds_prefix_space is not None:
8587
tokenizer_model.adds_prefix_space = True
8688

8789
# Create the vocabulary in the new tokenizer.
8890
tokenizer_model = clean_and_create_vocabulary(tokenizer_model, vocabulary, token_remove_regex=token_remove_regex)
8991
# Remove the post processor, this is not necessary.
9092
tokenizer_model.post_processor = None
93+
# Reshape the model
9194

9295
# All tokens in a single list.
9396
all_tokens = tokenizer_model.sorted_vocabulary
@@ -97,12 +100,15 @@ def distill_from_model(
97100
# Turn all _new_ tokens into ids using the original tokenizer
98101
token_ids = turn_tokens_into_ids(all_tokens, original_tokenizer_model)
99102

103+
# Reshape the transformer
104+
model = reshape_embeddings(model, original_tokenizer_model)
105+
100106
# Create the embeddings using the ids from the original tokenizer.
101107
embeddings = create_embeddings(
102108
tokenized=token_ids,
103109
model=model,
104110
device=device,
105-
pad_token_id=tokenizer_model.pad_token_id or 0,
111+
pad_token_id=original_tokenizer_model.pad_token_id or 0,
106112
pooling=pooling,
107113
)
108114

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ dev = [
6060
"ruff",
6161
]
6262

63-
distill = ["torch", "transformers", "scikit-learn", "skeletoken>=0.3.0"]
63+
distill = ["torch", "transformers", "scikit-learn", "skeletoken @ https://github.com/stephantul/skeletoken.git"]
6464
onnx = ["onnx", "torch"]
6565
# train also installs inference
6666
train = ["torch", "lightning", "scikit-learn", "skops"]

tests/conftest.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,24 @@ def mock_tokenizermodel() -> TokenizerModel:
6262

6363

6464
@pytest.fixture
65-
def mock_transformer() -> PreTrainedModel:
65+
def mock_transformer(request: pytest.FixtureRequest) -> PreTrainedModel:
6666
"""Create a mock transformer model."""
67+
params = getattr(request, "param", {}) or {}
68+
# Default vocab size
69+
vocab_size: int = params.get("vocab_size", 30522)
70+
dim: int = params.get("dim", 768)
71+
with_pooler: bool = params.get("with_pooler", True)
72+
pooler_value: float = params.get("pooler_value", 7.0)
6773

6874
class MockPreTrainedModel:
69-
def __init__(self, dim: int = 768, with_pooler: bool = True, pooler_value: float = 7.0) -> None:
75+
def __init__(self, vocab_size: int, dim: int, with_pooler: bool, pooler_value: float) -> None:
7076
self.device = "cpu"
7177
self.name_or_path = "mock-model"
7278
self.dim = dim
7379
self.with_pooler = with_pooler
7480
self.pooler_value = pooler_value
81+
self.input_embs = torch.nn.Embedding(vocab_size, dim)
82+
self.config: dict[str, Any] = {}
7583

7684
def to(self, device: str) -> MockPreTrainedModel:
7785
self.device = device
@@ -91,7 +99,29 @@ def forward(self, *args: Any, **kwargs: Any) -> Any:
9199

92100
__call__ = forward
93101

94-
return cast(PreTrainedModel, MockPreTrainedModel())
102+
def get_input_embeddings(self) -> torch.nn.Embedding:
103+
return self.input_embs
104+
105+
def resize_token_embeddings(self, vocab_size: int) -> None:
106+
curr_size = len(self.input_embs.weight)
107+
if vocab_size == curr_size:
108+
return
109+
if vocab_size < curr_size:
110+
self.input_embs.weight.data = self.input_embs.weight.data[: vocab_size + 1]
111+
else:
112+
self.input_embs.weight.data = torch.cat(
113+
[self.input_embs.weight, torch.zeros(vocab_size - curr_size, self.dim)], dim=0
114+
)
115+
116+
return cast(
117+
PreTrainedModel,
118+
MockPreTrainedModel(
119+
dim=dim,
120+
with_pooler=with_pooler,
121+
pooler_value=pooler_value,
122+
vocab_size=vocab_size,
123+
),
124+
)
95125

96126

97127
@pytest.fixture(scope="session")

tests/test_distillation.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def test_distill_removal_pattern_all_tokens(
104104

105105
@patch.object(import_module("model2vec.distill.distillation"), "model_info")
106106
@patch("transformers.AutoModel.from_pretrained")
107+
@pytest.mark.parametrize("mock_transformer", [{"vocab_size": 35022}], indirect=True)
107108
def test_distill_removal_pattern(
108109
mock_auto_model: MagicMock,
109110
mock_model_info: MagicMock,
@@ -114,7 +115,8 @@ def test_distill_removal_pattern(
114115
mock_model_info.return_value = type("ModelInfo", (object,), {"cardData": {"language": "en"}})
115116
mock_auto_model.return_value = mock_transformer
116117

117-
expected_vocab_size = mock_berttokenizer.vocab_size
118+
# Because the added [MASK] gets removed
119+
expected_vocab_size = mock_berttokenizer.vocab_size - 1
118120

119121
static_model = distill_from_model(
120122
model=mock_transformer,
@@ -159,18 +161,19 @@ def test_distill_removal_pattern(
159161
@pytest.mark.parametrize(
160162
"vocabulary, pca_dims, sif_coefficient, expected_shape",
161163
[
162-
(None, 256, None, (30522, 256)), # PCA applied, SIF off
163-
(None, "auto", None, (30522, 768)), # PCA 'auto', SIF off
164-
(None, "auto", 1e-4, (30522, 768)), # PCA 'auto', SIF on
164+
(None, 256, None, (30521, 256)), # PCA applied, SIF off
165+
(None, "auto", None, (30521, 768)), # PCA 'auto', SIF off
166+
(None, "auto", 1e-4, (30521, 768)), # PCA 'auto', SIF on
165167
(None, "auto", 0, None), # invalid SIF (too low) -> raises
166168
(None, "auto", 1, None), # invalid SIF (too high) -> raises
167-
(None, 1024, None, (30522, 768)), # PCA set high (no reduction)
168-
(["wordA", "wordB"], 4, None, (30524, 4)), # Custom vocab, PCA applied
169-
(None, None, None, (30522, 768)), # No PCA, SIF off
169+
(None, 1024, None, (30521, 768)), # PCA set high (no reduction)
170+
(["wordA", "wordB"], 4, None, (30523, 4)), # Custom vocab, PCA applied
171+
(None, None, None, (30521, 768)), # No PCA, SIF off
170172
],
171173
)
172174
@patch.object(import_module("model2vec.distill.distillation"), "model_info")
173175
@patch("transformers.AutoModel.from_pretrained")
176+
@pytest.mark.parametrize("mock_transformer", [{"vocab_size": 30522}], indirect=True)
174177
def test_distill(
175178
mock_auto_model: MagicMock,
176179
mock_model_info: MagicMock,

tests/test_model.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from pathlib import Path
2-
from tempfile import TemporaryDirectory
32

43
import numpy as np
54
import pytest
6-
import safetensors
75
from tokenizers import Tokenizer
86

97
from model2vec import StaticModel

uv.lock

Lines changed: 2 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)