immich-app/immich · error · ValueError
Pad token '{pad_token}' not found in tokenizer vocab
Error message
Pad token '{pad_token}' not found in tokenizer vocab What it means
Raised by OpenClipTextualEncoder._load_tokenizer when the pad_token string declared in tokenizer_config.json has no corresponding entry in the vocab of tokenizer.json (Tokenizer.token_to_id returns None). The pad token must exist so enable_padding can emit valid pad_id values for every position it inserts.
Source
Thrown at machine-learning/immich_ml/models/clip/textual.py:94
@cached_property
def tokenizer_cfg(self) -> dict[str, Any]:
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
return tokenizer_cfg
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
def _load_tokenizer(self) -> Tokenizer:
context_length: int = self.text_cfg.get("context_length", 77)
pad_token: str = self.tokenizer_cfg["pad_token"]
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
pad_id = tokenizer.token_to_id(pad_token)
if pad_id is None:
raise ValueError(f"Pad token '{pad_token}' not found in tokenizer vocab")
tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
tokenizer.enable_truncation(max_length=context_length)
return tokenizer
def tokenize(self, text: str, language: str | None = None) -> dict[str, NDArray[np.int32]]:
text = clean_text(text, canonicalize=self.canonicalize)
if self.is_nllb and language is not None:
flores_code = WEBLATE_TO_FLORES200.get(language)
if flores_code is None:
no_country = language.split("-")[0]
flores_code = WEBLATE_TO_FLORES200.get(no_country)
if flores_code is None:
log.warning(f"Language '{language}' not found, defaulting to 'en'")
flores_code = "eng_Latn"
text = f"{flores_code}{text}"
tokens: Encoding = self.tokenizer.encode(text)
return {"text": np.array([tokens.ids], dtype=np.int32)}View on GitHub (pinned to 199723261c)
Solutions
- Open tokenizer_config.json and note the exact pad_token string (including angle brackets and spaces).
- Open tokenizer.json's vocab and confirm that exact string is a key; check for case or whitespace mismatch.
- Call clear_cache() and re-download to get a consistent pair of tokenizer files from the same revision.
- If the repo is genuinely inconsistent, pin a known-good revision of the model or override tokenizer_cfg with a corrected pad_token present in the vocab.
- Add a startup self-check in your deployment that asserts token_to_id(pad_token) is not None before serving requests.
Example fix
# before
pad_token: str = self.tokenizer_cfg['pad_token'] # e.g. '<pad >' (typo in config)
pad_id = tokenizer.token_to_id(pad_token) # None -> ValueError
# after
pad_token: str = self.tokenizer_cfg['pad_token']
pad_id = tokenizer.token_to_id(pad_token)
if pad_id is None:
# fall back to a vocab-known pad token before failing
for candidate in ('<|endoftext|>', '</s>', '<pad>'):
pad_id = tokenizer.token_to_id(candidate)
if pad_id is not None:
pad_token = candidate
break
if pad_id is None:
raise ValueError(f"Pad token '{pad_token}' not found in tokenizer vocab") Defensive patterns
Strategy: validation
Validate before calling
from tokenizers import Tokenizer
def validate_pad_token(tokenizer_file_path: str, pad_token: str) -> int:
tok = Tokenizer.from_file(tokenizer_file_path)
pad_id = tok.token_to_id(pad_token)
if pad_id is None:
raise ValueError(
f"Pad token {pad_token!r} not in vocab of {tokenizer_file_path}; "
f"check tokenizer_config.json vs tokenizer.json"
)
return pad_id
# call before instantiating the encoder:
validate_pad_token(str(tokenizer_file_path), tokenizer_cfg['pad_token']) Type guard
def tokenizer_pad_token_is_known(tokenizer_file_path: str, pad_token: str) -> bool:
from tokenizers import Tokenizer
return Tokenizer.from_file(tokenizer_file_path).token_to_id(pad_token) is not None Try / catch
try:
self.tokenizer = self._load_tokenizer()
except ValueError as e:
if 'not found in tokenizer vocab' in str(e):
log.error("Tokenizer/config mismatch for %s; re-downloading model cache", self.model_name)
self.clear_cache()
self.download()
self.tokenizer = self._load_tokenizer()
else:
raise Prevention
- Treat tokenizer_config.json and tokenizer.json as a pair: never replace one without the other.
- Pin model revisions in snapshot_download so a later repo change cannot desync the files.
- Add a startup health check that loads every CLIP encoder's tokenizer and asserts the pad token resolves.
When it happens
Trigger: Loading a CLIP/OpenCLIP textual encoder whose tokenizer_config.json and tokenizer.json come from different model revisions or were swapped; a custom tokenizer.json whose vocab was pruned; a pad_token value with whitespace/casing differences versus the vocab (e.g. '<pad>' vs '<pad >'); pointing cache_dir at a hand-edited tokenizer file.
Common situations: Partial or mixed snapshot_download leaving an old tokenizer_config.json with a new tokenizer.json; using a community/fine-tuned model whose tokenizer files are inconsistent; encoding artifacts (BOM, escaped quotes) in tokenizer_config.json changing the pad_token string; model repo renamed the pad token between releases.
Related errors
- Invalid CLIP dimension size: ${dimSize}
- Machine learning request '${JSON.stringify(config)}' failed
- Smart search is not enabled
- Asset ${dto.queryAssetId} has no embedding
- Unknown CLIP model: ${newConfig.machineLearning.clip.modelNa
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/df182870665947ba.
Report an issue: GitHub.