mbpe 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mbpe/__init__.py ADDED
@@ -0,0 +1,251 @@
1
+ """Python wrapper around _mbpe (Mojo BPE tokenizer).
2
+
3
+ Adds Pythonic encode() with allowed_special/disallowed_special kwargs,
4
+ matching the tiktoken API surface. Everything else delegates to the
5
+ Mojo shared library via __getattr__.
6
+ """
7
+ from . import _mbpe
8
+ import os
9
+ import sys
10
+
11
+
12
+ class _BaseTokenizer:
13
+ """Wraps a _mbpe tokenizer adding encode() with special token params."""
14
+
15
+ def __init__(self, *args, **kwargs):
16
+ self._tok = self._make_tok(*args, **kwargs)
17
+
18
+ def _get_registered_specials(self):
19
+ return dict(self._tok._get_special_bytes())
20
+
21
+ def encode(self, text, *, allowed_special="all", disallowed_special="raise"):
22
+ """Encode text with special token handling.
23
+
24
+ Parameters
25
+ ----------
26
+ text : str
27
+ Input text to encode.
28
+ allowed_special : set, "all", or None
29
+ Set of special tokens allowed in the text. ``"all"`` (default)
30
+ allows every registered special token. An empty set/``None``
31
+ means no special tokens are recognised (equivalent to
32
+ :meth:`encode_ordinary` when combined with
33
+ ``disallowed_special="ignore"``).
34
+ disallowed_special : "raise" or "ignore"
35
+ What to do when a registered special token that is *not* in
36
+ *allowed_special* appears in *text*:
37
+
38
+ - ``"raise"`` / ``"error"`` — raise ``ValueError``.
39
+ - ``"ignore"`` — encode the special token as ordinary text
40
+ (do not treat it as a single token).
41
+
42
+ Returns
43
+ -------
44
+ list of int
45
+ Token IDs.
46
+ """
47
+ registered = self._get_registered_specials()
48
+
49
+ # ---- normalise allowed_special ----
50
+ if allowed_special == "all" or allowed_special is None:
51
+ allowed = set(registered)
52
+ elif isinstance(allowed_special, (set, frozenset, list, tuple)):
53
+ allowed = set(allowed_special)
54
+ else:
55
+ allowed = set(allowed_special) if allowed_special else set()
56
+
57
+ for s in allowed:
58
+ if s not in registered:
59
+ raise ValueError(
60
+ f"Special token {s!r} is not registered"
61
+ )
62
+
63
+ # ---- normalise disallowed_special ----
64
+ if disallowed_special not in ("raise", "error", "ignore"):
65
+ raise ValueError(
66
+ "disallowed_special must be 'raise', 'error', or 'ignore', "
67
+ f"got {disallowed_special!r}"
68
+ )
69
+
70
+ if not registered:
71
+ return self._tok.encode_ordinary(text)
72
+
73
+ # ---- disallowed_special == "raise" / "error" ----
74
+ if disallowed_special in ("raise", "error"):
75
+ for s in registered:
76
+ if s not in allowed and s in text:
77
+ raise ValueError(
78
+ f"Special token {s!r} is not allowed in the text"
79
+ )
80
+ return self._tok.encode(text)
81
+
82
+ # ---- disallowed_special == "ignore" ----
83
+ if not allowed:
84
+ return self._tok.encode_ordinary(text)
85
+
86
+ if allowed == set(registered):
87
+ return self._tok.encode(text)
88
+
89
+ # Subset: manually split on allowed specials using IDs from registered
90
+ result = []
91
+ pos = 0
92
+ while pos < len(text):
93
+ earliest = len(text)
94
+ earliest_tok = None
95
+ earliest_id = None
96
+ for tok in allowed:
97
+ if tok not in registered:
98
+ continue
99
+ tid = registered[tok]
100
+ idx = text.find(tok, pos)
101
+ if idx != -1 and idx < earliest:
102
+ earliest = idx
103
+ earliest_tok = tok
104
+ earliest_id = tid
105
+ if earliest_tok is not None:
106
+ if earliest > pos:
107
+ result.extend(self._tok.encode_ordinary(text[pos:earliest]))
108
+ result.append(earliest_id)
109
+ pos = earliest + len(earliest_tok)
110
+ else:
111
+ result.extend(self._tok.encode_ordinary(text[pos:]))
112
+ break
113
+ return result
114
+
115
+ def load_tiktoken(self, path):
116
+ """Load a .tiktoken file and sync special tokens."""
117
+ self._tok.load_tiktoken(path)
118
+
119
+ @property
120
+ def n_vocab(self):
121
+ return self._tok.n_vocab()
122
+
123
+ def __getattr__(self, name):
124
+ """Delegate everything else to the underlying _mbpe tokenizer."""
125
+ if name in ("encode", "_tok", "_make_tok", "n_vocab",
126
+ "load_tiktoken", "_get_registered_specials"):
127
+ raise AttributeError(name)
128
+ return getattr(self._tok, name)
129
+
130
+
131
+ class GPreTokenizer(_BaseTokenizer):
132
+ _TOK_CLS = _mbpe.GPreTokenizer
133
+ def _make_tok(self):
134
+ return self._TOK_CLS()
135
+
136
+
137
+ class GPT2Tokenizer(_BaseTokenizer):
138
+ _TOK_CLS = _mbpe.GPT2Tokenizer
139
+ def _make_tok(self):
140
+ return self._TOK_CLS()
141
+
142
+
143
+ class GPT4Tokenizer(_BaseTokenizer):
144
+ _TOK_CLS = _mbpe.GPT4Tokenizer
145
+ def _make_tok(self):
146
+ return self._TOK_CLS()
147
+
148
+
149
+ class GPT4oTokenizer(_BaseTokenizer):
150
+ _TOK_CLS = _mbpe.GPT4oTokenizer
151
+ def _make_tok(self):
152
+ return self._TOK_CLS()
153
+
154
+
155
+ # ── Module-level helpers ────────────────────────────────────────
156
+
157
+ def _find_data_dir():
158
+ # Use importlib.resources for installed packages (wheel)
159
+ try:
160
+ import importlib.resources as res
161
+ pkg = res.files("mbpe")
162
+ d = pkg / "data"
163
+ if d.is_dir():
164
+ return str(d)
165
+ except Exception:
166
+ pass
167
+ # Fallback for development (built .so inside package dir)
168
+ mod_dir = os.path.dirname(os.path.realpath(__file__))
169
+ d = os.path.join(mod_dir, "data")
170
+ if os.path.isdir(d):
171
+ return d
172
+ # Legacy fallback (built .so one level up)
173
+ return os.path.normpath(os.path.join(mod_dir, "..", "..", "data"))
174
+
175
+
176
+ _ENCODING_MAP = {
177
+ "gpt2": GPT2Tokenizer,
178
+ "cl100k": GPT4Tokenizer,
179
+ "o200k": GPT4oTokenizer,
180
+ }
181
+
182
+
183
+ def get_encoding(name):
184
+ """Load a pre-built encoding from a ``.tiktoken`` file.
185
+
186
+ Parameters
187
+ ----------
188
+ name : str
189
+ One of ``"gpt2"``, ``"cl100k"``, or ``"o200k"``.
190
+
191
+ Returns
192
+ -------
193
+ GPreTokenizer | GPT2Tokenizer | GPT4Tokenizer | GPT4oTokenizer
194
+ Loaded tokenizer instance.
195
+ """
196
+ cls = _ENCODING_MAP.get(name)
197
+ if cls is None:
198
+ raise ValueError(f"unknown encoding {name!r}; use one of {list(_ENCODING_MAP)}")
199
+ tok = cls()
200
+ tok._tok.load_tiktoken(os.path.join(_find_data_dir(), f"{name}.tiktoken"))
201
+ return tok
202
+
203
+
204
+ def train(texts, vocab_size):
205
+ """Train a GPreTokenizer wrapper instance.
206
+
207
+ Parameters
208
+ ----------
209
+ texts : list of str
210
+ Training corpus.
211
+ vocab_size : int
212
+ Target vocabulary size (>= 256).
213
+
214
+ Returns
215
+ -------
216
+ GPreTokenizer
217
+ Trained tokenizer.
218
+ """
219
+ return _train_impl(texts, vocab_size, "gpre")
220
+
221
+
222
+ _PT_MAP = {
223
+ "gpre": GPreTokenizer,
224
+ "gpt2": GPT2Tokenizer,
225
+ "gpt4": GPT4Tokenizer,
226
+ }
227
+
228
+
229
+ def _train_impl(texts, vocab_size, pretokenizer="gpre"):
230
+ """Train a tokenizer with the specified pretokenizer.
231
+
232
+ Parameters
233
+ ----------
234
+ texts : list of str
235
+ Training corpus.
236
+ vocab_size : int
237
+ Target vocabulary size.
238
+ pretokenizer : str
239
+ One of ``"gpre"``, ``"gpt2"``, or ``"gpt4"``.
240
+
241
+ Returns
242
+ -------
243
+ GPreTokenizer | GPT2Tokenizer | GPT4Tokenizer
244
+ Trained tokenizer wrapper.
245
+ """
246
+ cls = _PT_MAP.get(pretokenizer)
247
+ if cls is None:
248
+ raise ValueError(f"unknown pretokenizer {pretokenizer!r}")
249
+ tok = cls()
250
+ tok._tok.train(texts, vocab_size)
251
+ return tok
mbpe/_mbpe.so ADDED
Binary file