RWKV7-G1k-2.9B-20260930-FLA / tokenization_rwkv7.py
shoumenchougou's picture
Publish verified RWKV7 G1k 2.9B release
3a89d27 verified
Raw History Blame Contribute Delete
14 kB
# coding=utf-8
"""Exact linear-time RWKV World tokenizer for Transformers AutoTokenizer."""
from __future__ import annotations
import json
import os
import shutil
from typing import Optional
from transformers.tokenization_utils import PreTrainedTokenizer
VOCAB_FILES_NAMES = {"tokenizer_file": "tokenizer.json"}
END_TOKEN = "<|endoftext|>"
INVALID_TOKEN_PREFIX = "\ue000"
INVALID_TOKEN_SUFFIX = "\ue001"
class _CharEncoding:
"""Minimal Encoding surface used by BatchEncoding.char_to_token."""
def __init__(self, offsets):
self.offsets = offsets
def char_to_token(self, char_index, sequence_index=0):
if sequence_index != 0:
return None
for token_index, offset in enumerate(self.offsets):
if offset is not None and offset[0] <= char_index < offset[1]:
return token_index
return None
def _bytes_to_unicode():
values = list(range(ord("!"), ord("~") + 1))
values += list(range(ord("¡"), ord("¬") + 1))
values += list(range(ord("®"), ord("ÿ") + 1))
characters = list(values)
extra = 0
for byte in range(256):
if byte not in values:
values.append(byte)
characters.append(256 + extra)
extra += 1
return dict(zip(values, map(chr, characters)))
class Rwkv7Tokenizer(PreTrainedTokenizer):
"""RWKV World longest-prefix tokenizer backed only by tokenizer.json."""
vocab_files_names = VOCAB_FILES_NAMES
model_input_names = ["input_ids", "attention_mask"]
padding_side = "left"
def __init__(
self,
tokenizer_file,
eos_token=END_TOKEN,
pad_token=END_TOKEN,
unk_token=END_TOKEN,
add_bos_token=False,
**kwargs,
):
if not tokenizer_file or not os.path.isfile(tokenizer_file):
raise ValueError(f"tokenizer.json does not exist: {tokenizer_file}")
if add_bos_token:
raise ValueError("RWKV World has no BOS token")
self.tokenizer_file = tokenizer_file
self.add_bos_token = False
value = json.loads(open(tokenizer_file, encoding="utf-8").read())
model = value.get("model")
if not isinstance(model, dict) or model.get("type") != "WordPiece":
raise ValueError("tokenizer.json is not the locked RWKV WordPiece model")
if (
model.get("unk_token") != END_TOKEN
or model.get("continuing_subword_prefix") != ""
or value.get("normalizer") is not None
):
raise ValueError("tokenizer.json does not use exact RWKV semantics")
pre_tokenizer = value.get("pre_tokenizer")
decoder = value.get("decoder")
if (
not isinstance(pre_tokenizer, dict)
or pre_tokenizer.get("type") != "ByteLevel"
or pre_tokenizer.get("add_prefix_space") is not False
or pre_tokenizer.get("use_regex") is not False
or not isinstance(decoder, dict)
or decoder.get("type") != "ByteLevel"
):
raise ValueError("tokenizer.json does not use exact ByteLevel semantics")
vocabulary = model.get("vocab")
if not isinstance(vocabulary, dict) or not vocabulary:
raise ValueError("tokenizer.json has no vocabulary")
if any(not isinstance(token, str) or not isinstance(index, int) for token, index in vocabulary.items()):
raise TypeError("tokenizer.json vocabulary entry is invalid")
if set(vocabulary.values()) != set(range(len(vocabulary))):
raise ValueError("tokenizer.json vocabulary ids are not contiguous")
if vocabulary.get(END_TOKEN) != 0:
raise ValueError("tokenizer.json must reserve id 0 for end-of-text")
byte_decoder = {character: byte for byte, character in _bytes_to_unicode().items()}
self.encoder = dict(vocabulary)
self.decoder = {index: token for token, index in vocabulary.items()}
self._token_bytes = {}
reverse = {}
for token, index in vocabulary.items():
if index == 0:
continue
placeholder = f"{INVALID_TOKEN_PREFIX}{index}{INVALID_TOKEN_SUFFIX}"
if token == placeholder:
continue
try:
raw = bytes(byte_decoder[character] for character in token)
except KeyError as error:
raise ValueError("tokenizer.json vocabulary is not ByteLevel encoded") from error
if raw in reverse:
raise ValueError("tokenizer.json contains duplicate byte tokens")
reverse[raw] = index
self._token_bytes[token] = raw
missing = [byte for byte in range(256) if bytes([byte]) not in reverse]
if missing:
raise ValueError(f"tokenizer.json is missing singleton bytes: {missing}")
for raw, index in reverse.items():
for endpoint in range(1, len(raw)):
prefix = reverse.get(raw[:endpoint])
if prefix is not None and prefix > index:
raise ValueError("token ranks do not support longest-prefix encoding")
self._children = [{}]
self._terminal = [None]
self._max_token_bytes = 1
for raw, index in sorted(reverse.items(), key=lambda item: item[1]):
node = 0
for byte in raw:
child = self._children[node].get(byte)
if child is None:
child = len(self._children)
self._children[node][byte] = child
self._children.append({})
self._terminal.append(None)
node = child
self._terminal[node] = index
self._max_token_bytes = max(self._max_token_bytes, len(raw))
if "additional_special_tokens" not in kwargs:
appended = []
for item in sorted(value.get("added_tokens", []), key=lambda item: item.get("id", -1)):
if not isinstance(item, dict):
raise TypeError("tokenizer.json added token is invalid")
content = item.get("content")
index = item.get("id")
if not isinstance(content, str) or not isinstance(index, int):
raise TypeError("tokenizer.json added token is invalid")
if content != END_TOKEN:
if not item.get("special") or index < len(vocabulary):
raise ValueError("only append-only special tokens are supported")
appended.append(content)
if appended:
kwargs["additional_special_tokens"] = appended
super().__init__(
eos_token=eos_token,
pad_token=pad_token,
unk_token=unk_token,
add_bos_token=self.add_bos_token,
**kwargs,
)
@property
def vocab_size(self):
return len(self.encoder)
def get_vocab(self):
vocabulary = dict(self.encoder)
vocabulary.update(self.added_tokens_encoder)
return vocabulary
def _encode_bytes(self, data):
output = []
position = 0
while position < len(data):
node = 0
cursor = position
best_id = None
best_end = position
limit = min(len(data), position + self._max_token_bytes)
while cursor < limit:
child = self._children[node].get(data[cursor])
if child is None:
break
node = child
cursor += 1
token_id = self._terminal[node]
if token_id is not None and (best_id is None or token_id > best_id):
best_id = token_id
best_end = cursor
if best_id is None:
raise RuntimeError("singleton-byte vocabulary invariant was violated")
output.append(best_id)
position = best_end
return output
def _encode_bytes_with_offsets(self, data, char_offset, byte_to_char):
output = []
position = 0
while position < len(data):
node = 0
cursor = position
best_id = None
best_end = position
limit = min(len(data), position + self._max_token_bytes)
while cursor < limit:
child = self._children[node].get(data[cursor])
if child is None:
break
node = child
cursor += 1
token_id = self._terminal[node]
if token_id is not None and (best_id is None or token_id > best_id):
best_id = token_id
best_end = cursor
if best_id is None:
raise RuntimeError("singleton-byte vocabulary invariant was violated")
start_char = char_offset + byte_to_char[position]
end_char = char_offset + byte_to_char[best_end - 1] + 1
output.append((best_id, (start_char, end_char)))
position = best_end
return output
def _encode_with_offsets(self, text):
special = sorted(self.added_tokens_encoder, key=lambda token: (-len(token), token))
output = []
ordinary_start = 0
position = 0
def append_ordinary(segment, char_offset):
if not segment:
return
data = segment.encode("utf-8")
byte_to_char = []
for char_index, character in enumerate(segment):
byte_to_char.extend([char_index] * len(character.encode("utf-8")))
output.extend(
self._encode_bytes_with_offsets(data, char_offset, byte_to_char)
)
while position < len(text):
matched = next(
(token for token in special if text.startswith(token, position)), None
)
if matched is None:
position += 1
continue
append_ordinary(text[ordinary_start:position], ordinary_start)
output.append(
(
self.added_tokens_encoder[matched],
(position, position + len(matched)),
)
)
position += len(matched)
ordinary_start = position
append_ordinary(text[ordinary_start:], ordinary_start)
return output
def _tokenize(self, text, **kwargs):
del kwargs
return [self.decoder[index] for index in self._encode_bytes(text.encode("utf-8"))]
def __call__(self, text=None, text_pair=None, **kwargs):
output = super().__call__(text=text, text_pair=text_pair, **kwargs)
if text_pair is not None or text is None:
return output
texts = [text] if isinstance(text, str) else list(text)
if any(not isinstance(item, str) for item in texts):
return output
input_ids = output["input_ids"]
attention_mask = output.get("attention_mask")
raw_ids = input_ids.tolist() if hasattr(input_ids, "tolist") else input_ids
if raw_ids and isinstance(raw_ids[0], int):
rows = [raw_ids]
masks = [attention_mask] if attention_mask is not None else None
else:
rows = raw_ids
if attention_mask is None:
masks = None
else:
masks = (
attention_mask.tolist()
if hasattr(attention_mask, "tolist")
else attention_mask
)
encodings = []
for index, source in enumerate(texts):
exact = self._encode_with_offsets(source)
row = rows[index]
active = len(row) if masks is None else sum(int(item) for item in masks[index])
exact = exact[:active]
offsets = [offset for _token_id, offset in exact]
missing = len(row) - len(offsets)
if self.padding_side == "left":
offsets = [None] * missing + offsets
else:
offsets.extend([None] * missing)
encodings.append(_CharEncoding(offsets))
output._encodings = encodings
return output
def _convert_token_to_id(self, token):
return self.encoder.get(token, 0)
def _convert_id_to_token(self, index):
return self.decoder.get(index, END_TOKEN)
def convert_tokens_to_string(self, tokens):
decoded = bytearray()
for token in tokens:
raw = self._token_bytes.get(token)
if raw is not None:
decoded.extend(raw)
else:
decoded.extend(str(token).encode("utf-8"))
return bytes(decoded).decode("utf-8", errors="replace")
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
bos = [self.bos_token_id] if self.add_bos_token else []
output = bos + list(token_ids_0)
if token_ids_1 is not None:
output.extend(bos + list(token_ids_1))
return output
def save_vocabulary(self, save_directory, filename_prefix: Optional[str] = None):
if not os.path.isdir(save_directory):
raise ValueError("save directory does not exist")
filename = (f"{filename_prefix}-" if filename_prefix else "") + "tokenizer.json"
destination = os.path.join(save_directory, filename)
if os.path.abspath(destination) != os.path.abspath(self.tokenizer_file):
shutil.copyfile(self.tokenizer_file, destination)
return (destination,)
__all__ = ["Rwkv7Tokenizer"]