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 +251 -0
- mbpe/_mbpe.so +0 -0
- mbpe/data/cl100k.tiktoken +100256 -0
- mbpe/data/gpt2.tiktoken +50256 -0
- mbpe/data/o200k.tiktoken +199998 -0
- mbpe-0.1.0.dist-info/METADATA +21 -0
- mbpe-0.1.0.dist-info/RECORD +9 -0
- mbpe-0.1.0.dist-info/WHEEL +5 -0
- mbpe-0.1.0.dist-info/top_level.txt +1 -0
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
|