Download tokenizer.py from thenukegun10x/DivCenterer: direct link, hf CLI and curl.
- Browser
- Download file 5.56 kB
-
https://huggingface.co/thenukegun10x/DivCenterer/resolve/main/tokenizer.py
- Command line
-
hf download hf://thenukegun10x/DivCenterer/tokenizer.py
-
curl -L -o tokenizer.py https://huggingface.co/thenukegun10x/DivCenterer/resolve/main/tokenizer.py
5.56 kB
| from __future__ import annotations | |
| import json | |
| import re | |
| from pathlib import Path | |
| from typing import Iterable | |
| BASE_VOCAB_SIZE = 256 | |
| ATOM_RE = re.compile( | |
| r"<!--[\s\S]*?-->|<!doctype\s+html>|</?[A-Za-z][^<>]*>|" | |
| r"\b(?:display|place-items|align-items|justify-content|min-height|margin|font|padding|border|border-radius|background)\s*:\s*[^;{}]+;?|" | |
| r"\s+|[A-Za-z_$][A-Za-z0-9_$-]*|[0-9]+(?:\.[0-9]+)?(?:%|px|rem|em|vh|vw)?|[^\x00-\x7F]|[^\w\s]", | |
| re.IGNORECASE | re.ASCII, | |
| ) | |
| class ConstrainedBPE: | |
| """Byte-fallback BPE matching DivCenterer's syntax-aware Node tokenizer.""" | |
| def __init__(self, model: dict): | |
| if model.get("format") != "divcenterer-constrained-byte-bpe-v1": | |
| raise ValueError("Unrecognized tokenizer model format") | |
| self.special_tokens: list[str] = model["special_tokens"] | |
| self.special_to_id = { | |
| token: BASE_VOCAB_SIZE + index | |
| for index, token in enumerate(self.special_tokens) | |
| } | |
| self.special_by_id = {value: key for key, value in self.special_to_id.items()} | |
| self.merge_ranks: dict[tuple[int, int], int] = {} | |
| self.token_bytes: list[bytes | None] = [bytes([index]) for index in range(BASE_VOCAB_SIZE)] | |
| self.token_bytes.extend([None] * len(self.special_tokens)) | |
| for rank, (left, right) in enumerate(model["merges"]): | |
| if self.token_bytes[left] is None or self.token_bytes[right] is None: | |
| raise ValueError(f"Merge {rank} refers to a special or missing token") | |
| token_id = BASE_VOCAB_SIZE + len(self.special_tokens) + rank | |
| self.merge_ranks[(left, right)] = rank | |
| self.token_bytes.append(self.token_bytes[left] + self.token_bytes[right]) | |
| self._special_re = re.compile("(" + "|".join(re.escape(t) for t in self.special_tokens) + ")") | |
| self._atom_cache: dict[str, list[int]] = {} | |
| def from_file(cls, filename: str | Path) -> "ConstrainedBPE": | |
| with Path(filename).open("r", encoding="utf-8") as stream: | |
| return cls(json.load(stream)) | |
| def _atoms(self, text: str) -> list[str]: | |
| atoms: list[str] = [] | |
| cursor = 0 | |
| for match in ATOM_RE.finditer(text): | |
| if match.start() != cursor: | |
| raise ValueError(f"Pretokenizer left a gap at character {cursor}") | |
| atoms.append(match.group(0)) | |
| cursor = match.end() | |
| if cursor != len(text): | |
| raise ValueError(f"Pretokenizer left a gap at character {cursor}") | |
| return atoms | |
| def _apply_merge(ids: list[int], left: int, right: int, merged: int) -> list[int]: | |
| result: list[int] = [] | |
| index = 0 | |
| while index < len(ids): | |
| if index + 1 < len(ids) and ids[index] == left and ids[index + 1] == right: | |
| result.append(merged) | |
| index += 2 | |
| else: | |
| result.append(ids[index]) | |
| index += 1 | |
| return result | |
| def _encode_atom(self, atom: str) -> list[int]: | |
| cached = self._atom_cache.get(atom) | |
| if cached is not None: | |
| return cached.copy() | |
| ids = list(atom.encode("utf-8")) | |
| while len(ids) > 1: | |
| best_rank: int | None = None | |
| best_pair: tuple[int, int] | None = None | |
| for left, right in zip(ids, ids[1:]): | |
| rank = self.merge_ranks.get((left, right)) | |
| if rank is not None and (best_rank is None or rank < best_rank): | |
| best_rank = rank | |
| best_pair = (left, right) | |
| if best_pair is None or best_rank is None: | |
| break | |
| merged_id = BASE_VOCAB_SIZE + len(self.special_tokens) + best_rank | |
| ids = self._apply_merge(ids, best_pair[0], best_pair[1], merged_id) | |
| self._atom_cache[atom] = ids.copy() | |
| return ids | |
| def encode(self, text: str) -> list[int]: | |
| result: list[int] = [] | |
| for part in self._special_re.split(text): | |
| if not part: | |
| continue | |
| special_id = self.special_to_id.get(part) | |
| if special_id is not None: | |
| result.append(special_id) | |
| else: | |
| for atom in self._atoms(part): | |
| result.extend(self._encode_atom(atom)) | |
| return result | |
| def decode(self, ids: Iterable[int]) -> str: | |
| output: list[str] = [] | |
| byte_buffer = bytearray() | |
| def flush_bytes() -> None: | |
| if byte_buffer: | |
| output.append(bytes(byte_buffer).decode("utf-8", errors="replace")) | |
| byte_buffer.clear() | |
| for token_id in ids: | |
| special = self.special_by_id.get(int(token_id)) | |
| if special is not None: | |
| flush_bytes() | |
| output.append(special) | |
| elif 0 <= int(token_id) < len(self.token_bytes) and self.token_bytes[int(token_id)] is not None: | |
| byte_buffer.extend(self.token_bytes[int(token_id)]) | |
| else: | |
| raise ValueError(f"Unknown token id: {token_id}") | |
| flush_bytes() | |
| return "".join(output) | |
| def special_id(self, token: str) -> int: | |
| return self.special_to_id[token] | |
| def vocab_size(self) -> int: | |
| return len(self.token_bytes) | |
| def conversation_prefix(prompt: str) -> str: | |
| return f"<|user|>\n{prompt}\n<|assistant|>\n" | |
| def conversation_text(prompt: str, answer: str) -> str: | |
| return f"{conversation_prefix(prompt)}{answer}\n<|end|>" | |