abyss-api / ml /tokenizer /tokenizer.py
KyleEastwood's picture
Upload folder using huggingface_hub
cc15101 verified
Raw History Blame Contribute Delete
3.15 kB
"""AbyssTokenizer: byte-level BPE encode/decode.
Source of truth on disk is merges.txt (integer id pairs, in priority
order). The vocab (id -> bytes) is rebuilt by replaying those merges
over the 256 base bytes."""
import os
import json
import regex as re
from .regex_split import split
from .special_tokens import SPECIAL_TOKENS, special_token_ids
def merge_seq(ids, pair, new_id):
"""Replace every adjacent occurence of 'pair' in 'ids' with new_id."""
out = []
i = 0
while i < len(ids):
if i < len(ids) - 1 and ids[i] == pair[0] and ids[i + 1] == pair[1]:
out.append(new_id)
i += 2
else:
out.append(ids[i])
i += 1
return out
class AbyssTokenizer:
def __init__(self, merges, vocab_size):
# merges: ordered list of (a, b) integer id pairs
self.vocab_size = vocab_size
self.merges = merges
# rank[(a, b)] = priority index (lower = merged earlier)
self.ranks = {pair: i for i, pair in enumerate(merges)}
# rebuild id -> bytes
self.vocab = {i: bytes([i]) for i in range(256)}
for i, (a, b) in enumerate(merges):
self.vocab[256 + i] = self.vocab[a] + self.vocab[b]
# special tokens
self.special = special_token_ids(vocab_size)
self.inv_special = {v: k for k, v in self.special.items()}
self._special_pat = re.compile(
"(" + "|".join(re.escape(t) for t in SPECIAL_TOKENS) + ")"
)
# ---- Encode ----
def _encode_chunk(self, ids):
while len(ids) >= 2:
pairs = set(zip(ids, ids[1:]))
best = min(pairs, key=lambda p: self.ranks.get(p, float("inf")))
if best not in self.ranks:
break
new_id = 256 + self.ranks[best]
ids = merge_seq(ids, best, new_id)
return ids
def encode(self, text):
out = []
for piece in self._special_pat.split(text):
if piece == "":
continue
if piece in self.special:
out.append(self.special[piece])
else:
for chunk in split(piece):
ids = list(chunk.encode("utf-8"))
out.extend(self._encode_chunk(ids))
return out
# ---- Decode ----
def decode(self, ids):
parts = []
for i in ids:
if i in self.inv_special:
parts.append(self.inv_special[i].encode("utf-8"))
else:
parts.append(self.vocab[i])
return b"".join(parts).decode("utf-8", errors="replace")
# ---- load ----
@classmethod
def load(cls, in_dir):
with open(os.path.join(in_dir, "config.json")) as f:
vocab_size = json.load(f)["vocab_size"]
merges = []
with open(os.path.join(in_dir, "merges.txt")) as f:
for line in f:
a, b = line.split()
merges.append((int(a), int(b)))
return cls(merges, vocab_size)