Skip to content

Commit 98bcf3d

Browse files
committed
Annotate Gigatoken adapter and regression tests
Add annotations to the adapter interfaces and focused tokenizer tests. Keep optional native imports behind TYPE_CHECKING. Validation: repository autoformatter check, 38 tokenizer tests, and exact-HF chat-handler checks pass. Mypy reports the same 15 diagnostics as untouched upstream main. Signed-off-by: Chenshu Zhu <chenshuz@nvidia.com>
1 parent 12abeeb commit 98bcf3d

2 files changed

Lines changed: 72 additions & 21 deletions

File tree

‎megatron/core/tokenizers/text/libraries/huggingface_tokenizer.py‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import logging
55
import re
66
from functools import cached_property
7-
from typing import List, Optional
7+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
88

99
try:
1010
from transformers import AutoTokenizer, BatchEncoding, PreTrainedTokenizerFast
@@ -18,6 +18,12 @@
1818

1919
from .abstract_tokenizer import MegatronTokenizerTextAbstract
2020

21+
if TYPE_CHECKING:
22+
from gigatoken import HFCompat
23+
from numpy.typing import NDArray
24+
from torch import Tensor
25+
26+
2127
logger = logging.getLogger(__name__)
2228

2329

@@ -456,7 +462,7 @@ class _GigatokenTokenizer(PreTrainedTokenizerFast):
456462
r'\U0002b73a-\U0002b73f\U0002cea2-\U0002cead\U000323b0-\U00033479]'
457463
)
458464

459-
def __init__(self, tokenizer):
465+
def __init__(self, tokenizer: PreTrainedTokenizerFast) -> None:
460466
from megatron.core.tokenizers.utils import init_gigatoken_from_hf
461467

462468
# Preserve the loaded HF configuration and native decoder for DecodeStream.
@@ -465,15 +471,19 @@ def __init__(self, tokenizer):
465471
self._gigatoken = init_gigatoken_from_hf(tokenizer, tokenizer.name_or_path)
466472
self.tokenizer = self._gigatoken.tokenizer
467473

468-
def __reduce__(self):
474+
def __reduce__(
475+
self,
476+
) -> tuple[
477+
Callable[..., "_GigatokenTokenizer"], tuple[PreTrainedTokenizerFast], dict[str, Any]
478+
]:
469479
# Spawned serving workers rebuild native encoders from the retained HF tokenizer.
470480
state = self.__dict__.copy()
471481
for name in ('tokenizer', '_gigatoken', '_split_special_tokenizer'):
472482
state.pop(name, None)
473483
return type(self), (self._hf_tokenizer,), state
474484

475485
@cached_property
476-
def _split_special_tokenizer(self):
486+
def _split_special_tokenizer(self) -> "HFCompat":
477487
import gigatoken as gt
478488

479489
# Gigatoken's matcher is immutable. Ordinary text needs a separate matcher
@@ -484,7 +494,9 @@ def _split_special_tokenizer(self):
484494
]
485495
return gt.Tokenizer.from_json(json.dumps(config)).as_hf()
486496

487-
def __call__(self, text=None, text_pair=None, *args, **kwargs):
497+
def __call__(
498+
self, text: Any = None, text_pair: Any = None, *args: Any, **kwargs: Any
499+
) -> BatchEncoding:
488500
supported = {
489501
'add_special_tokens',
490502
'split_special_tokens',
@@ -536,7 +548,11 @@ def __call__(self, text=None, text_pair=None, *args, **kwargs):
536548
prepend_batch_axis=isinstance(text, str),
537549
)
538550

539-
def apply_chat_template(self, *args, **kwargs):
551+
def apply_chat_template(
552+
self, *args: Any, **kwargs: Any
553+
) -> Union[
554+
str, List[str], List[int], List[List[int]], BatchEncoding, "Tensor", "NDArray[Any]"
555+
]:
540556
if kwargs.get('return_assistant_tokens_mask'):
541557
# Assistant masks require HF's character-to-token alignment data.
542558
return self._hf_tokenizer.apply_chat_template(*args, **kwargs)

‎tests/unit_tests/tokenizers/test_tokenizer.py‎

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import json
44
import pickle
5+
from pathlib import Path
56
from types import SimpleNamespace
7+
from typing import Any
68
from unittest.mock import MagicMock, patch
79

810
import numpy as np
@@ -138,9 +140,16 @@ def test_hf_tokenizer():
138140

139141

140142
@pytest.fixture
141-
def gigatoken_hf_pair(tmp_path):
143+
def gigatoken_hf_pair(tmp_path: Path) -> tuple[HuggingFaceTokenizer, HuggingFaceTokenizer]:
142144
pytest.importorskip('gigatoken')
143-
from tokenizers import Tokenizer, decoders, models, pre_tokenizers, processors, trainers
145+
from tokenizers import ( # type: ignore[import-untyped]
146+
Tokenizer,
147+
decoders,
148+
models,
149+
pre_tokenizers,
150+
processors,
151+
trainers,
152+
)
144153
from transformers import PreTrainedTokenizerFast
145154

146155
backend = Tokenizer(models.BPE())
@@ -177,7 +186,9 @@ def gigatoken_hf_pair(tmp_path):
177186
return baseline, native
178187

179188

180-
def test_gigatoken_user_text_and_special_filtering(gigatoken_hf_pair):
189+
def test_gigatoken_user_text_and_special_filtering(
190+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
191+
) -> None:
181192
hf, native = gigatoken_hf_pair
182193
texts = ['Invoice 1234567', 'x<eos>12345<extra>y', '<ordinary> café 中文 ١٢٣٤٥٦٧']
183194
with patch.object(
@@ -211,7 +222,9 @@ def test_gigatoken_user_text_and_special_filtering(gigatoken_hf_pair):
211222
'character',
212223
['\u0295', '\ua7cf', '\u1acf', '\U00011de0', '\U00016ff4', '\U0002b73a', '\U00033479'],
213224
)
214-
def test_gigatoken_unicode_fallback(gigatoken_hf_pair, character):
225+
def test_gigatoken_unicode_fallback(
226+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer], character: str
227+
) -> None:
215228
hf, native = gigatoken_hf_pair
216229
with (
217230
patch.object(native.tokenizer, '_gigatoken') as encode,
@@ -239,7 +252,9 @@ def test_gigatoken_unicode_fallback(gigatoken_hf_pair, character):
239252

240253

241254
@pytest.mark.parametrize('split_special_tokens', [False, True])
242-
def test_gigatoken_unicode_batch_fallback(gigatoken_hf_pair, split_special_tokens):
255+
def test_gigatoken_unicode_batch_fallback(
256+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer], split_special_tokens: bool
257+
) -> None:
243258
hf, native = gigatoken_hf_pair
244259
batch = ['Invoice 1234567 café 中文', '12345\U00011de06789 <extra><ordinary>']
245260
name = '_split_special_tokenizer' if split_special_tokens else '_gigatoken'
@@ -250,7 +265,9 @@ def test_gigatoken_unicode_batch_fallback(gigatoken_hf_pair, split_special_token
250265
encode.assert_not_called()
251266

252267

253-
def test_gigatoken_unicode_chat_fallback(gigatoken_hf_pair):
268+
def test_gigatoken_unicode_chat_fallback(
269+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
270+
) -> None:
254271
hf, native = gigatoken_hf_pair
255272
conversation = [{'role': 'user', 'content': 'Invoice 1234567'}]
256273
# The rendered template can introduce an affected character absent from messages.
@@ -263,7 +280,9 @@ def test_gigatoken_unicode_chat_fallback(gigatoken_hf_pair):
263280

264281

265282
@pytest.mark.parametrize('text', ['Invoice 1234567', 'café e\u0301 中文 ١٢٣٤٥٦٧ 12345 😀'])
266-
def test_gigatoken_compatible_unicode_stays_native(gigatoken_hf_pair, text):
283+
def test_gigatoken_compatible_unicode_stays_native(
284+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer], text: str
285+
) -> None:
267286
hf, native = gigatoken_hf_pair
268287
for split in (False, True):
269288
name = '_split_special_tokenizer' if split else '_gigatoken'
@@ -277,9 +296,14 @@ def test_gigatoken_compatible_unicode_stays_native(gigatoken_hf_pair, text):
277296
@pytest.mark.parametrize('batched', [False, True])
278297
@pytest.mark.parametrize('return_dict', [False, True, None])
279298
@pytest.mark.parametrize('tensor_type', [None, 'np', 'pt'])
280-
def test_gigatoken_chat_template_parity(gigatoken_hf_pair, batched, return_dict, tensor_type):
299+
def test_gigatoken_chat_template_parity(
300+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
301+
batched: bool,
302+
return_dict: bool | None,
303+
tensor_type: str | None,
304+
) -> None:
281305
hf, native = gigatoken_hf_pair
282-
conversation = [{'role': 'user', 'content': 'Invoice 1234567 <extra> café'}]
306+
conversation: list[Any] = [{'role': 'user', 'content': 'Invoice 1234567 <extra> café'}]
283307
if batched:
284308
conversation = [conversation, [{'role': 'user', 'content': '中文 ١٢٣٤٥٦٧'}]]
285309
kwargs = {'tokenize': True, 'add_generation_prompt': True, 'return_tensors': tensor_type}
@@ -314,7 +338,11 @@ def test_gigatoken_chat_template_parity(gigatoken_hf_pair, batched, return_dict,
314338

315339
@pytest.mark.parametrize('side', ['left', 'right'])
316340
@pytest.mark.parametrize('truncation', [False, True])
317-
def test_gigatoken_chat_padding_and_truncation(gigatoken_hf_pair, side, truncation):
341+
def test_gigatoken_chat_padding_and_truncation(
342+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
343+
side: str,
344+
truncation: bool,
345+
) -> None:
318346
hf, native = gigatoken_hf_pair
319347
for tokenizer in (hf.tokenizer, native.tokenizer):
320348
tokenizer.padding_side = tokenizer.truncation_side = side
@@ -325,7 +353,9 @@ def test_gigatoken_chat_padding_and_truncation(gigatoken_hf_pair, side, truncati
325353
) == hf.apply_chat_template(messages, chat_template=None, **kwargs)
326354

327355

328-
def test_gigatoken_chat_rendering_and_assistant_masks(gigatoken_hf_pair):
356+
def test_gigatoken_chat_rendering_and_assistant_masks(
357+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
358+
) -> None:
329359
hf, native = gigatoken_hf_pair
330360
conversation = [
331361
{'role': 'user', 'content': 'Invoice 1234567'},
@@ -336,7 +366,7 @@ def test_gigatoken_chat_rendering_and_assistant_masks(gigatoken_hf_pair):
336366
'{% if m.role == "assistant" %}{% generation %}{{ m.content }}{% endgeneration %}'
337367
'{% else %}{{ m.content }}{% endif %}<eos>{% endfor %}'
338368
)
339-
for kwargs in [
369+
cases: list[dict[str, Any]] = [
340370
{'tokenize': False},
341371
{'tokenize': True, 'return_dict': False, 'continue_final_message': True},
342372
{'tokenize': True, 'return_dict': True, 'return_assistant_tokens_mask': True},
@@ -345,13 +375,16 @@ def test_gigatoken_chat_rendering_and_assistant_masks(gigatoken_hf_pair):
345375
'return_dict': True,
346376
'tokenizer_kwargs': {'return_offsets_mapping': True},
347377
},
348-
]:
378+
]
379+
for kwargs in cases:
349380
assert native.apply_chat_template(
350381
conversation, chat_template=template, **kwargs
351382
) == hf.apply_chat_template(conversation, chat_template=template, **kwargs)
352383

353384

354-
def test_gigatoken_serving_worker_serialization(gigatoken_hf_pair):
385+
def test_gigatoken_serving_worker_serialization(
386+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
387+
) -> None:
355388
hf, native = gigatoken_hf_pair
356389
text = 'Invoice 1234567 <extra> café'
357390
native.tokenizer(
@@ -375,7 +408,9 @@ def test_gigatoken_serving_worker_serialization(gigatoken_hf_pair):
375408
) == hf.apply_chat_template(messages, chat_template=None, return_dict=False)
376409

377410

378-
def test_gigatoken_streaming_preserves_hf_interface(gigatoken_hf_pair):
411+
def test_gigatoken_streaming_preserves_hf_interface(
412+
gigatoken_hf_pair: tuple[HuggingFaceTokenizer, HuggingFaceTokenizer],
413+
) -> None:
379414
from megatron.core.inference.text_generation_server.dynamic_text_gen_server.incremental_detokenizer import (
380415
HuggingFaceFastIncrementalDetokenizer,
381416
)

0 commit comments

Comments
 (0)