| |
| """ |
| Loader for the LOCKED byte-level BPE tokenizer (`bpe_6000.json`, vocab=6000). |
| |
| This tokenizer was trained once, outside this repository, and must never be |
| retrained or regenerated. This module only deserializes the finished merge |
| table and performs encode/decode. There is no training method here by design. |
| |
| On-disk schema (BPT_V1): |
| { |
| "vocab_size": 6000, |
| "merge_order": [ |
| [[a_id, b_id], new_id], |
| ... |
| ] |
| } |
| """ |
|
|
| import json |
|
|
|
|
| class BPETokenizer: |
| def __init__(self, path): |
| self.path = path |
| with open(path, "r", encoding="utf-8") as f: |
| data = json.load(f) |
|
|
| if "merge_order" not in data: |
| raise KeyError( |
| f"Unrecognized tokenizer file at {path}: expected a 'merge_order' key " |
| f"(BPT_V1 schema). Found top-level keys: {list(data.keys())}. " |
| "Do not attempt to regenerate this tokenizer." |
| ) |
|
|
| merge_order = data["merge_order"] |
|
|
| |
| self.id_to_bytes = {i: (i,) for i in range(256)} |
| self.bpe_ranks = {} |
|
|
| for rank, entry in enumerate(merge_order): |
| a_id, b_id, new_id = self._normalize_merge_entry(entry) |
| self.bpe_ranks[(a_id, b_id)] = rank |
| self.id_to_bytes[new_id] = (a_id, b_id) |
|
|
| self.bytes_to_id = {v: k for k, v in self.id_to_bytes.items()} |
|
|
| declared_vocab_size = data.get("vocab_size") |
| computed_vocab_size = max(self.id_to_bytes.keys()) + 1 |
| if declared_vocab_size is not None and declared_vocab_size != computed_vocab_size: |
| raise ValueError( |
| f"Tokenizer mismatch: declared vocab_size={declared_vocab_size}, " |
| f"computed={computed_vocab_size}. The tokenizer artifact is locked; " |
| "do not regenerate it." |
| ) |
| self.vocab_size = computed_vocab_size |
|
|
| @staticmethod |
| def _normalize_merge_entry(entry): |
| if ( |
| isinstance(entry, (list, tuple)) |
| and len(entry) == 2 |
| and isinstance(entry[0], (list, tuple)) |
| and len(entry[0]) == 2 |
| ): |
| return int(entry[0][0]), int(entry[0][1]), int(entry[1]) |
| raise KeyError(f"Unrecognized merge entry: {entry!r}") |
|
|
| def _get_pairs(self, seq): |
| return set(zip(seq[:-1], seq[1:])) |
|
|
| def _bpe_merge(self, seq): |
| seq = list(seq) |
| if len(seq) < 2: |
| return seq |
| while True: |
| pairs = self._get_pairs(seq) |
| ranked = [(self.bpe_ranks[p], p) for p in pairs if p in self.bpe_ranks] |
| if not ranked: |
| break |
| _, best = min(ranked) |
| new_seq, i = [], 0 |
| while i < len(seq): |
| if i < len(seq) - 1 and (seq[i], seq[i + 1]) == best: |
| new_seq.append(self.bytes_to_id[(seq[i], seq[i + 1])]) |
| i += 2 |
| else: |
| new_seq.append(seq[i]) |
| i += 1 |
| seq = new_seq |
| return seq |
|
|
| def encode(self, text): |
| raw_bytes = text.encode("utf-8") |
| return self._bpe_merge(list(raw_bytes)) |
|
|
| def _expand(self, token_id): |
| if token_id < 256: |
| return bytes([token_id]) |
| out = bytearray() |
| for part in self.id_to_bytes[token_id]: |
| out.extend(self._expand(part) if part >= 256 else bytes([part])) |
| return bytes(out) |
|
|
| def decode(self, ids): |
| out = bytearray() |
| for token_id in ids: |
| out.extend(self._expand(int(token_id))) |
| return bytes(out).decode("utf-8", errors="replace") |
|
|