Download tiny_gdn/detokenize.py from KerzgrAI/Monody-Exp: direct link, hf CLI and curl.
- Browser
- Download file 3.93 kB
-
https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/detokenize.py
- Command line
-
hf download hf://KerzgrAI/Monody-Exp/tiny_gdn/detokenize.py
-
curl -L -o detokenize.py https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/detokenize.py
3.93 kB
| """Byte-accurate incremental decode for GPT-2-style byte-level BPE. | |
| `tokenizer.decode(ids)` UTF-8-replaces incomplete sequences. Streaming that | |
| as a string prefix then breaks on the next byte: decode([20492]) is a | |
| replacement character and decode([20492, 294]) is `` ≈``, so the | |
| accumulated text is no longer a prefix and the UI re-emits the whole | |
| think block. | |
| This decoder concatenates raw BPE bytes and only emits complete UTF-8 | |
| characters. The completing byte of ``≈`` yields `` ≈``; nothing is rewound. | |
| """ | |
| from __future__ import annotations | |
| from collections.abc import Iterable, Mapping, Sequence | |
| from typing import Any | |
| def gpt2_bytes_to_unicode() -> dict[int, str]: | |
| raw = ( | |
| list(range(ord("!"), ord("~") + 1)) | |
| + list(range(ord("¡"), ord("¬") + 1)) | |
| + list(range(ord("®"), ord("ÿ") + 1)) | |
| ) | |
| mapped = raw[:] | |
| extra = 0 | |
| for byte in range(256): | |
| if byte not in raw: | |
| raw.append(byte) | |
| mapped.append(256 + extra) | |
| extra += 1 | |
| return dict(zip(raw, [chr(code) for code in mapped], strict=True)) | |
| def unicode_to_gpt2_bytes() -> dict[str, int]: | |
| return {char: byte for byte, char in gpt2_bytes_to_unicode().items()} | |
| def utf8_char_length(lead: int) -> int | None: | |
| if lead < 0x80: | |
| return 1 | |
| if 0xC2 <= lead <= 0xDF: | |
| return 2 | |
| if 0xE0 <= lead <= 0xEF: | |
| return 3 | |
| if 0xF0 <= lead <= 0xF4: | |
| return 4 | |
| return None | |
| def split_complete_utf8(buffer: bytes) -> tuple[str, bytes]: | |
| """Return (decoded prefix, leftover incomplete or invalid-lead bytes).""" | |
| index = 0 | |
| end = len(buffer) | |
| while index < end: | |
| size = utf8_char_length(buffer[index]) | |
| if size is None: | |
| break | |
| if index + size > end: | |
| break | |
| chunk = buffer[index : index + size] | |
| try: | |
| chunk.decode("utf-8") | |
| except UnicodeDecodeError: | |
| break | |
| index += size | |
| if index == 0: | |
| return "", buffer | |
| return buffer[:index].decode("utf-8"), buffer[index:] | |
| def piece_to_bytes(piece: str, unicode_to_byte: Mapping[str, int]) -> bytes: | |
| raw = bytearray() | |
| for char in piece: | |
| mapped = unicode_to_byte.get(char) | |
| if mapped is None: | |
| raw.extend(char.encode("utf-8")) | |
| else: | |
| raw.append(mapped) | |
| return bytes(raw) | |
| class IncrementalUtf8Decoder: | |
| """Push token ids; read only complete Unicode as it becomes available.""" | |
| def __init__( | |
| self, | |
| tokenizer: Any, | |
| *, | |
| skip_special_ids: Iterable[int] | None = None, | |
| ) -> None: | |
| vocab = tokenizer.get_vocab() | |
| self._id_to_piece = {tid: piece for piece, tid in vocab.items()} | |
| self._unicode_to_byte = unicode_to_gpt2_bytes() | |
| self._skip_special_ids = set(skip_special_ids or ()) | |
| self._pending = b"" | |
| self._text = "" | |
| def text(self) -> str: | |
| return self._text | |
| def reset(self) -> None: | |
| self._pending = b"" | |
| self._text = "" | |
| def token_bytes(self, token_id: int) -> bytes: | |
| piece = self._id_to_piece.get(token_id, "") | |
| return piece_to_bytes(piece, self._unicode_to_byte) | |
| def push(self, token_id: int) -> str: | |
| if token_id in self._skip_special_ids: | |
| return "" | |
| self._pending += self.token_bytes(token_id) | |
| complete, self._pending = split_complete_utf8(self._pending) | |
| self._text += complete | |
| return complete | |
| def push_many(self, token_ids: Sequence[int]) -> str: | |
| delta = [] | |
| for token_id in token_ids: | |
| piece = self.push(token_id) | |
| if piece: | |
| delta.append(piece) | |
| return "".join(delta) | |
| def finalize(self) -> str: | |
| if self._pending: | |
| self._text += self._pending.decode("utf-8", errors="replace") | |
| self._pending = b"" | |
| return self._text | |