File size: 4,429 Bytes
31e0f19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""
GembyTiny Tokenizer — HuggingFace PreTrainedTokenizer compatible.
Works with pipeline(), Trainer, AutoTokenizer, push_to_hub().

For production: swap the character-level encoding with a SentencePiece model.
"""

import os
import json
from typing import Dict, List, Optional, Tuple
from transformers import PreTrainedTokenizer


VOCAB_FILES_NAMES = {"vocab_file": "vocab.json"}

SPECIAL_TOKENS = {
    "<|pad|>":       0,
    "<|bos|>":       1,
    "<|eos|>":       2,
    "<|sep|>":       3,
    "<|user|>":      4,
    "<|assistant|>": 5,
    "<|system|>":    6,
    "<|unk|>":       7,
}

# Default chat template (Jinja2 format — used by apply_chat_template)
CHAT_TEMPLATE = (
    "{% if messages[0]['role'] == 'system' %}"
    "<|bos|><|system|>{{ messages[0]['content'] }}<|sep|>"
    "{% set messages = messages[1:] %}"
    "{% else %}"
    "<|bos|><|system|>You are GembyTiny, a helpful AI assistant.<|sep|>"
    "{% endif %}"
    "{% for message in messages %}"
    "{% if message['role'] == 'user' %}"
    "<|user|>{{ message['content'] }}<|sep|>"
    "{% elif message['role'] == 'assistant' %}"
    "<|assistant|>{{ message['content'] }}<|sep|>"
    "{% endif %}"
    "{% endfor %}"
    "{% if add_generation_prompt %}<|assistant|>{% endif %}"
)


class GembyTinyTokenizer(PreTrainedTokenizer):
    """
    HuggingFace-compatible tokenizer for GembyTiny.

    Special tokens:
      <|pad|>       pad token
      <|bos|>       beginning of sequence
      <|eos|>       end of sequence
      <|sep|>       turn separator
      <|user|>      user turn
      <|assistant|> assistant turn
      <|system|>    system prompt
      <|unk|>       unknown token
    """

    vocab_files_names = VOCAB_FILES_NAMES
    model_input_names = ["input_ids", "attention_mask"]

    def __init__(
        self,
        vocab_file: Optional[str] = None,
        pad_token: str = "<|pad|>",
        bos_token: str = "<|bos|>",
        eos_token: str = "<|eos|>",
        unk_token: str = "<|unk|>",
        sep_token: str = "<|sep|>",
        **kwargs,
    ):
        self.vocab_file = vocab_file
        self._vocab: Dict[str, int] = {}
        self._ids_to_tokens: Dict[int, str] = {}

        if vocab_file and os.path.exists(vocab_file):
            with open(vocab_file) as f:
                self._vocab = json.load(f)
        else:
            self._build_char_vocab()

        self._ids_to_tokens = {v: k for k, v in self._vocab.items()}

        kwargs.setdefault("chat_template", CHAT_TEMPLATE)

        super().__init__(
            pad_token=pad_token,
            bos_token=bos_token,
            eos_token=eos_token,
            unk_token=unk_token,
            sep_token=sep_token,
            **kwargs,
        )

    def _build_char_vocab(self):
        """Build a character-level vocabulary (bootstrap / dev)."""
        self._vocab = dict(SPECIAL_TOKENS)
        chars = (
            list(" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
            + list("0123456789")
            + list(".,!?;:'\"-()[]{}@#$%^&*+=/<>\\|`~\n\t")
        )
        offset = max(SPECIAL_TOKENS.values()) + 1
        for i, ch in enumerate(chars):
            self._vocab[ch] = offset + i
        self._ids_to_tokens = {v: k for k, v in self._vocab.items()}

    @property
    def vocab_size(self) -> int:
        return 32000  # fixed vocab size matching model config

    def get_vocab(self) -> Dict[str, int]:
        return dict(self._vocab)

    def _tokenize(self, text: str) -> List[str]:
        """Character-level tokenization. Replace with BPE/SPM in production."""
        return list(text)

    def _convert_token_to_id(self, token: str) -> int:
        return self._vocab.get(token, self._vocab.get("<|unk|>", 7))

    def _convert_id_to_token(self, index: int) -> str:
        return self._ids_to_tokens.get(index, "<|unk|>")

    def convert_tokens_to_string(self, tokens: List[str]) -> str:
        return "".join(tokens)

    def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
        os.makedirs(save_directory, exist_ok=True)
        vocab_file = os.path.join(
            save_directory,
            (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"],
        )
        with open(vocab_file, "w", encoding="utf-8") as f:
            json.dump(self._vocab, f, ensure_ascii=False, indent=2)
        return (vocab_file,)