import importlib
import inspect
import json
import os
import shutil
import subprocess
import sys
from typing import Dict, Iterable, List, Optional, Sequence, Tuple
import numpy as np
from transformers import PreTrainedTokenizer
from transformers.utils import cached_file
def _prepare_tokenmonster_runtime(tokenmonster_module) -> None:
candidate_roots = [
os.environ.get("STENTOR_TOKENMONSTER_DIR"),
"/root/.cache" if os.path.isdir("/root/.cache") else None,
os.getcwd(),
os.environ.get("HOME"),
os.environ.get("TMPDIR"),
tempfile.gettempdir(),
]
runtime_dir = None
for root in candidate_roots:
if not root:
continue
try:
os.makedirs(root, exist_ok=True)
except Exception:
continue
if not (os.access(root, os.W_OK) and os.access(root, os.X_OK)):
continue
runtime_dir = os.path.join(root, "stentor_tokenmonster_runtime")
break
if runtime_dir is None:
raise SystemExit(
"Unable to find a writable/executable directory for TokenMonster runtime files."
)
rank_suffix = os.environ.get("LOCAL_RANK") or os.environ.get("RANK")
if rank_suffix is not None and str(rank_suffix).strip() != "":
runtime_dir = os.path.join(runtime_dir, f"rank_{rank_suffix}")
os.makedirs(runtime_dir, exist_ok=True)
if hasattr(tokenmonster_module, "set_local_directory"):
tokenmonster_module.set_local_directory(runtime_dir)
server_name = "tokenmonsterserver"
os_name = None
if hasattr(tokenmonster_module, "_get_binary_filename"):
try:
os_name, server_name = tokenmonster_module._get_binary_filename()
except Exception:
os_name, server_name = None, "tokenmonsterserver"
server_path = os.path.join(runtime_dir, server_name)
if (not os.path.exists(server_path)) and os_name:
try:
cached_server = hf_hub_download(
repo_id=DEFAULT_TOKENMONSTER_REPO,
filename=f"binaries/{os_name}/{server_name}",
token=resolve_hf_token(),
)
shutil.copy2(cached_server, server_path)
except Exception:
pass
if os.path.exists(server_path):
try:
os.chmod(server_path, 0o755)
except Exception:
pass
if not os.access(server_path, os.X_OK):
raise SystemExit(
f"TokenMonster server binary is not executable after chmod: {server_path}"
)
def _repair_tokenmonster_server(tokenmonster_module) -> None:
_prepare_tokenmonster_runtime(tokenmonster_module)
vocab_cls = getattr(tokenmonster_module, "Vocab", None)
if vocab_cls is None:
return
runtime_dir = getattr(vocab_cls, "_dir", None)
server_name = None
os_name = None
if hasattr(tokenmonster_module, "_get_binary_filename"):
try:
os_name, server_name = tokenmonster_module._get_binary_filename()
except Exception:
os_name, server_name = None, None
if not runtime_dir or not server_name:
return
server_path = os.path.join(runtime_dir, server_name)
try:
if os.path.exists(server_path):
os.remove(server_path)
except Exception:
pass
if os_name:
try:
cached_server = hf_hub_download(
repo_id=DEFAULT_TOKENMONSTER_REPO,
filename=f"binaries/{os_name}/{server_name}",
token=resolve_hf_token(),
)
shutil.copy2(cached_server, server_path)
except Exception:
pass
if os.path.exists(server_path):
try:
os.chmod(server_path, 0o755)
except Exception:
pass
def _ensure_tokenmonster_available():
global tokenmonster
if tokenmonster is not None:
_prepare_tokenmonster_runtime(tokenmonster)
return tokenmonster
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "tokenmonster"])
tokenmonster = importlib.import_module("tokenmonster")
_prepare_tokenmonster_runtime(tokenmonster)
return tokenmonster
except Exception as exc:
raise SystemExit(
"tokenmonster is required to use TokenMonster vocab files. "
"Automatic install failed. Install with `pip install tokenmonster`."
) from exc
def _tokenmonster_remote_module_source() -> str:
imports = [
"import importlib",
"import inspect",
"import json",
"import os",
"import shutil",
"import subprocess",
"import sys",
"from typing import Dict, Iterable, List, Optional, Sequence, Tuple",
"",
"import numpy as np",
"from transformers import PreTrainedTokenizer",
"from transformers.utils import cached_file",
]
try:
prepare_source = inspect.getsource(_prepare_tokenmonster_runtime)
repair_source = inspect.getsource(_repair_tokenmonster_server)
helper_source = inspect.getsource(_ensure_tokenmonster_available)
remote_source = inspect.getsource(_tokenmonster_remote_module_source)
class_source = inspect.getsource(TokenMonsterTokenizer)
except (OSError, TypeError):
source_text = _load_current_source_text()
prepare_source = _extract_source_block(
source_text,
"def _prepare_tokenmonster_runtime(tokenmonster_module) -> None:",
["def _repair_tokenmonster_server(", "def print_hardware_diagnostic(", "def _ensure_tokenmonster_available("],
)
repair_source = _extract_source_block(
source_text,
"def _repair_tokenmonster_server(tokenmonster_module) -> None:",
["def print_hardware_diagnostic(", "def _ensure_tokenmonster_available("],
)
helper_source = _extract_source_block(
source_text,
"def _ensure_tokenmonster_available():",
["def _extract_source_block(", "def _tokenmonster_remote_module_source("],
)
extract_source = _extract_source_block(
source_text,
"def _extract_source_block(",
["def _load_current_source_text(", "def _tokenmonster_remote_module_source("],
)
load_source = _extract_source_block(
source_text,
"def _load_current_source_text(",
["def _tokenmonster_remote_module_source(", "class TokenMonsterTokenizer("],
)
remote_source = _extract_source_block(
source_text,
"def _tokenmonster_remote_module_source() -> str:",
["class TokenMonsterTokenizer("],
)
class_source = _extract_source_block(
source_text,
"class TokenMonsterTokenizer(PreTrainedTokenizer):",
["@dataclass", "\n\ndef parse_args("],
)
return "\n\n".join(
[
"\n".join(imports),
prepare_source.strip(),
repair_source.strip(),
helper_source.strip(),
extract_source.strip(),
load_source.strip(),
remote_source.strip(),
class_source.strip(),
]
) + "\n"
return "\n\n".join(
[
"\n".join(imports),
prepare_source,
repair_source,
helper_source,
remote_source,
class_source,
]
) + "\n"
class TokenMonsterTokenizer(PreTrainedTokenizer):
vocab_files_names = {"vocab_file": "tokenmonster.vocab"}
model_input_names = ["input_ids", "attention_mask"]
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path, *inputs, **kwargs):
if "vocab_file" not in kwargs:
resolved_vocab = None
if os.path.isdir(pretrained_model_name_or_path):
candidate = os.path.join(
pretrained_model_name_or_path,
cls.vocab_files_names["vocab_file"],
)
if os.path.exists(candidate):
resolved_vocab = candidate
if resolved_vocab is None:
cached_kwargs = {
key: kwargs[key]
for key in (
"cache_dir",
"force_download",
"local_files_only",
"proxies",
"resume_download",
"revision",
"subfolder",
"token",
)
if key in kwargs
}
try:
resolved_vocab = cached_file(
pretrained_model_name_or_path,
cls.vocab_files_names["vocab_file"],
**cached_kwargs,
)
except Exception:
resolved_vocab = None
if resolved_vocab is not None:
kwargs["vocab_file"] = resolved_vocab
return super().from_pretrained(pretrained_model_name_or_path, *inputs, **kwargs)
def __init__(self, vocab_file: str, model_max_length: int = 1_000_000, **kwargs) -> None:
name_or_path = kwargs.get("name_or_path")
if (
vocab_file
and not os.path.isabs(vocab_file)
and not os.path.exists(vocab_file)
and name_or_path
and os.path.isdir(name_or_path)
):
candidate = os.path.join(name_or_path, vocab_file)
if os.path.exists(candidate):
vocab_file = candidate
self.vocab_file_path = os.path.abspath(vocab_file)
self._tm = _ensure_tokenmonster_available()
try:
self._vocab = self._tm.load(self.vocab_file_path)
except RuntimeError as exc:
if "enable execute permissions" not in str(exc):
raise
_repair_tokenmonster_server(self._tm)
self._vocab = self._tm.load(self.vocab_file_path)
self._token_to_id: Dict[str, int] = {}
self._id_to_token: Dict[int, str] = {}
self._vocab_size_with_gaps = 0
self._refresh_dictionary()
kwargs.setdefault("bos_token", "")
kwargs.setdefault("eos_token", "")
kwargs.setdefault("unk_token", "")
kwargs.setdefault("pad_token", "")
kwargs.setdefault("model_max_length", int(model_max_length))
super().__init__(vocab_file=self.vocab_file_path, **kwargs)
if not hasattr(self, "additional_special_tokens"):
self.additional_special_tokens = []
elif self.additional_special_tokens is None:
self.additional_special_tokens = []
@property
def vocab_size(self) -> int:
return self._vocab_size_with_gaps
def __len__(self) -> int:
return self._vocab_size_with_gaps
def get_vocab(self) -> Dict[str, int]:
return dict(self._token_to_id)
def prepare_for_tokenization(self, text, is_split_into_words: bool = False, **kwargs):
return (text, kwargs)
def _refresh_dictionary(self) -> None:
token_to_id: Dict[str, int] = {}
id_to_token: Dict[int, str] = {}
max_token_id = -1
entries = self._vocab.get_dictionary()
if isinstance(entries, dict):
entries = entries.values()
for entry in entries:
token = entry["token"]
if isinstance(token, bytes):
token = token.decode("utf-8", errors="replace")
token_id = int(entry["id"])
token_to_id[str(token)] = token_id
id_to_token[token_id] = str(token)
if token_id > max_token_id:
max_token_id = token_id
self._token_to_id = token_to_id
self._id_to_token = id_to_token
self._vocab_size_with_gaps = max(0, max_token_id + 1)
def _clear_added_token_state(self) -> None:
# Intentionally left as a no-op. Clearing HF's added-token state here causes
# additional/special tokens to disappear from saved checkpoints.
return
@staticmethod
def _normalize_token_value(token) -> Optional[str]:
if token is None:
return None
if hasattr(token, "content"):
return str(token.content)
return str(token)
@staticmethod
def _normalize_token_ids(token_ids) -> List[int]:
if isinstance(token_ids, np.ndarray):
return [int(x) for x in token_ids.tolist()]
if isinstance(token_ids, (list, tuple)):
return [int(x) for x in token_ids]
return [int(token_ids)]
def _add_backend_token(self, token: Optional[str], special: bool) -> bool:
if not token:
return False
if token in self._token_to_id:
return False
if special:
self._vocab.add_special_token(token)
else:
self._vocab.add_token(token)
self._refresh_dictionary()
return token in self._token_to_id
def _tokenize(self, text: str) -> List[str]:
ids = self._normalize_token_ids(self._vocab.tokenize(text))
unk_token = self.unk_token or ""
return [self._id_to_token.get(int(i), unk_token) for i in ids]
def _convert_token_to_id(self, token: str) -> Optional[int]:
if token is None:
return None
token_id = self._token_to_id.get(token)
if token_id is not None:
return int(token_id)
if self.unk_token is not None:
return self._token_to_id.get(self.unk_token)
return None
def _convert_id_to_token(self, index: int) -> str:
token = self._id_to_token.get(int(index))
if token is not None:
return token
return self.unk_token or ""
def convert_tokens_to_string(self, tokens: Sequence[str]) -> str:
token_ids: List[int] = []
for token in tokens:
token_id = self._convert_token_to_id(token)
if token_id is not None:
token_ids.append(int(token_id))
if not token_ids:
return ""
return self._vocab.decode(token_ids)
def build_inputs_with_special_tokens(
self,
token_ids_0: List[int],
token_ids_1: Optional[List[int]] = None,
) -> List[int]:
output = list(token_ids_0)
if self.bos_token_id is not None:
output = [self.bos_token_id] + output
if self.eos_token_id is not None:
output = output + [self.eos_token_id]
if token_ids_1 is None:
return output
second = list(token_ids_1)
if self.eos_token_id is not None:
second = second + [self.eos_token_id]
return output + second
def get_special_tokens_mask(
self,
token_ids_0: List[int],
token_ids_1: Optional[List[int]] = None,
already_has_special_tokens: bool = False,
) -> List[int]:
special_ids = set(self.all_special_ids)
if already_has_special_tokens:
combined = list(token_ids_0)
return [1 if token in special_ids else 0 for token in combined]
mask = [0] * len(token_ids_0)
if self.bos_token_id is not None:
mask = [1] + mask
if self.eos_token_id is not None:
mask = mask + [1]
if token_ids_1 is None:
return mask
second_mask = [0] * len(token_ids_1)
if self.eos_token_id is not None:
second_mask = second_mask + [1]
return mask + second_mask
def create_token_type_ids_from_sequences(
self,
token_ids_0: List[int],
token_ids_1: Optional[List[int]] = None,
) -> List[int]:
length = len(self.build_inputs_with_special_tokens(token_ids_0, token_ids_1))
return [0] * length
def add_tokens(self, new_tokens: Iterable[str], special_tokens: bool = False) -> int:
if isinstance(new_tokens, str):
new_tokens = [new_tokens]
added = 0
for token in new_tokens:
if self._add_backend_token(self._normalize_token_value(token), special_tokens):
added += 1
return added
def add_special_tokens(
self,
special_tokens_dict: Dict[str, object],
replace_additional_special_tokens: bool = True,
) -> int:
added = 0
single_token_keys = (
"unk_token",
"bos_token",
"eos_token",
"pad_token",
"sep_token",
"cls_token",
"mask_token",
)
for key in single_token_keys:
token = self._normalize_token_value(special_tokens_dict.get(key))
if token is None:
continue
if self._add_backend_token(token, special=True):
added += 1
setattr(self, key, token)
extras = special_tokens_dict.get("additional_special_tokens")
if extras is not None:
normalized_extras: List[str] = []
for token in extras:
normalized = self._normalize_token_value(token)
if normalized is None:
continue
if self._add_backend_token(normalized, special=True):
added += 1
if normalized not in normalized_extras:
normalized_extras.append(normalized)
if replace_additional_special_tokens:
self.additional_special_tokens = normalized_extras
else:
merged = list(getattr(self, "additional_special_tokens", []) or [])
for token in normalized_extras:
if token not in merged:
merged.append(token)
self.additional_special_tokens = merged
return added
def _decode(
self,
token_ids,
skip_special_tokens: bool = False,
clean_up_tokenization_spaces: Optional[bool] = None,
spaces_between_special_tokens: bool = False,
**kwargs,
) -> str:
ids = self._normalize_token_ids(token_ids)
if skip_special_tokens:
special_ids = set(self.all_special_ids)
ids = [token_id for token_id in ids if token_id not in special_ids]
if not ids:
return ""
return self._vocab.decode(ids)
def save_vocabulary(
self,
save_directory: str,
filename_prefix: Optional[str] = None,
) -> Tuple[str]:
os.makedirs(save_directory, exist_ok=True)
prefix = f"{filename_prefix}-" if filename_prefix else ""
vocab_path = os.path.join(save_directory, f"{prefix}tokenmonster.vocab")
vocab_json_path = os.path.join(save_directory, f"{prefix}vocab.json")
self._vocab.save(vocab_path)
with open(vocab_json_path, "w", encoding="utf-8") as handle:
json.dump(self.get_vocab(), handle, ensure_ascii=False, indent=2, sort_keys=True)
return (vocab_path,)
def save_pretrained(
self,
save_directory: str,
legacy_format: Optional[bool] = None,
filename_prefix: Optional[str] = None,
push_to_hub: bool = False,
**kwargs,
):
saved_files = super().save_pretrained(
save_directory,
legacy_format=legacy_format,
filename_prefix=filename_prefix,
push_to_hub=push_to_hub,
**kwargs,
)
module_out = os.path.join(save_directory, "tokenmonster_hf.py")
try:
module_path = inspect.getsourcefile(type(self))
except (OSError, TypeError):
module_path = None
if module_path and os.path.isfile(module_path) and os.path.basename(module_path) == "tokenmonster_hf.py":
shutil.copy(module_path, module_out)
else:
with open(module_out, "w", encoding="utf-8") as handle:
handle.write(_tokenmonster_remote_module_source())
prefix = f"{filename_prefix}-" if filename_prefix else ""
config_path = os.path.join(save_directory, f"{prefix}tokenizer_config.json")
config = {}
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as handle:
config = json.load(handle)
config.update(
{
"tokenizer_class": "TokenMonsterTokenizer",
"auto_map": {
"AutoTokenizer": ["tokenmonster_hf.TokenMonsterTokenizer", None]
},
"vocab_file": self.vocab_files_names["vocab_file"],
"bos_token": self.bos_token,
"eos_token": self.eos_token,
"unk_token": self.unk_token,
"pad_token": self.pad_token,
}
)
with open(config_path, "w", encoding="utf-8") as handle:
json.dump(config, handle, ensure_ascii=False, indent=2, sort_keys=True)
added_tokens_path = os.path.join(save_directory, f"{prefix}added_tokens.json")
added_tokens = {}
for token in list(getattr(self, "additional_special_tokens", []) or []):
token_id = self._convert_token_to_id(token)
if token_id is not None:
added_tokens[str(token)] = int(token_id)
if added_tokens:
with open(added_tokens_path, "w", encoding="utf-8") as handle:
json.dump(added_tokens, handle, ensure_ascii=False, indent=2, sort_keys=True)
return saved_files