Problem
In a hybrid Kubernetes deployment (on‑premise + AWS EKS) the Prometheus model service intermittently crashes during inference. The failure manifests as a RuntimeError: token ids out of range exception raised by the model’s PyTorch inference loop. The same input text that succeeds on a pure‑cloud cluster fails when the request is routed to a pod on the on‑premise node pool.
Observed symptoms
- HTTP 500 responses from
/v1/inferendpoint. - Container logs contain:
2026-09-28T12:34:56.789Z ERROR inference_worker.py:112 TokenizerEncodingError: Token ID 50257 out of vocab range (0-50256)
Traceback (most recent call last):
File "/app/inference_worker.py", line 98, in run_inference
input_ids = tokenizer.encode(text, return_tensors="pt")
File "/usr/local/lib/python3.9/site-packages/transformers/tokenization_utils_base.py", line 1234, in encode
return self._encode_plus(text, **kwargs)["input_ids"]
File "/usr/local/lib/python3.9/site-packages/transformers/tokenization_utils_base.py", line 1355, in _encode_plus
token_ids = self.convert_tokens_to_ids(tokens)
File "/usr/local/lib/python3.9/site-packages/transformers/tokenization_utils_base.py", line 1472, in convert_tokens_to_ids
return [self._convert_token_to_id(token) for token in tokens]
File "/usr/local/lib/python3.9/site-packages/transformers/tokenization_utils_base.py", line 1495, in _convert_token_to_id
raise TokenizerEncodingError(f"Token ID {token_id} out of vocab range ({self.vocab_size})")
TokenizerEncodingError: Token ID 50257 out of vocab range (0-50256)
- Prometheus metrics
model_inference_errors_total{reason="tokenizer"} = 42spiking on the on‑premise nodes. - CPU usage remains low; the crash is not caused by resource exhaustion.
Root Cause
The tokenizer artifact stored in the shared NFS volume was built with a newer version of the transformers library that introduced an additional special token (<extra_id_100>) expanding the vocabulary size from 50257 to 50258. The on‑premise nodes run an older container image (Python 3.9, transformers==4.26.0) that expects a vocab size of 50257. When the model pod on those nodes loads the newer tokenizer JSON, the internal vocab_size attribute is set to 50257 (derived from the older code path) while the token IDs in the file can be 50257 or higher. Consequently, any input that triggers the new special token is encoded to an ID outside the expected range, causing the TokenizerEncodingError and aborting inference.
Because the shared storage is mounted read‑only by all pods, the mismatch only appears on nodes that have not been rebuilt with the updated container image, leading to the hybrid‑cluster symptom.
Debug
1. Verify tokenizer version mismatch
# Inside a failing pod (on‑premise)
kubectl exec -it $(kubectl get pod -l app=prometheus-model -o name | grep onprem) -- bash
python -c "import transformers, json, pathlib;
t = transformers.AutoTokenizer.from_pretrained('/model/tokenizer');
print('transformers version:', transformers.__version__);
print('vocab size:', t.vocab_size);
print('special tokens:', t.special_tokens_map);"
Expected output on a healthy pod:
transformers version: 4.26.0
vocab size: 50257
special tokens: {'bos_token': '', 'eos_token': '', 'unk_token': '', 'pad_token': ''}
Output on the failing pod:
transformers version: 4.26.0
vocab size: 50257
special tokens: {'bos_token': '', 'eos_token': '', 'unk_token': '', 'pad_token': '', 'additional_special_tokens': ['']}
2. Inspect the tokenizer JSON file
kubectl exec -it $(kubectl get pod -l app=prometheus-model -o name | grep onprem) -- cat /model/tokenizer/tokenizer.json | jq '.model.vocab | length'
Result: 50258 (one token larger than expected).
3. Correlate container image tags with deployment manifests
kubectl get deployment prometheus-model -o yaml | grep image
image: registry.example.com/prometheus-model:2023-11-15 # older build
# vs
image: registry.example.com/prometheus-model:2024-02-01 # newer build (contains updated tokenizer)
4. Confirm that the shared storage is not version‑locked
kubectl describe pvc model-artifacts
Name: model-artifacts
StorageClass: nfs-storage
Capacity: 20Gi
Access Modes: RWX
VolumeMode: Filesystem
The PVC is mounted by both node pools, so a newer tokenizer can be consumed by older images.
Solution
Option A – Align container image with tokenizer version (recommended)
Rebuild the on‑premise image to use transformers>=4.30.0 which correctly reads the expanded vocab.
Before
# Dockerfile (old)
FROM python:3.9-slim
RUN pip install torch==1.13.1 transformers==4.26.0
COPY . /app
WORKDIR /app
CMD ["python", "serve.py"]
After
# Dockerfile (updated)
FROM python:3.9-slim
RUN pip install torch==2.1.0 transformers==4.31.0
COPY . /app
WORKDIR /app
CMD ["python", "serve.py"]
Re‑push the image and update the deployment:
docker build -t registry.example.com/prometheus-model:2024-09-28 .
docker push registry.example.com/prometheus-model:2024-09-28
kubectl set image deployment/prometheus-model prometheus-model=registry.example.com/prometheus-model:2024-09-28
Option B – Freeze tokenizer artifact to match older runtime
If rebuilding the image is not feasible, replace the tokenizer files with a version that matches the older vocab size.
# On a dev workstation
git checkout v1.2.0 # tag that contains original tokenizer
cp -r tokenizer/ /tmp/fixed-tokenizer/
# Upload to shared storage (overwrites the newer version)
kubectl cp /tmp/fixed-tokenizer/ $(kubectl get pod -l app=prometheus-model -o name | head -n1):/model/tokenizer
After the copy, restart the pods to reload the tokenizer.
Verify
Run a controlled inference request that exercises the previously failing token.
curl -X POST http://prometheus-model.default.svc.cluster.local/v1/infer \
-H "Content-Type: application/json" \
-d '{"text":"Please generate "}' | jq .
Expected successful response (HTTP 200) with a JSON payload containing generated_text. No TokenizerEncodingError should appear in the pod logs.
Confirm metrics reset:
curl http://prometheus-model.default.svc.cluster.local/metrics | grep model_inference_errors_total
# Should show zero or a stable baseline.
Prevent
- Version pinning policy: Enforce that the
transformerslibrary version is declared as a dependency of the model artifact. Store arequirements.txtalongside the model and have the container image install exactly those versions. - Artifact immutability: Use a separate PVC per model version or a version‑ed directory (e.g.,
/model/v1/,/model/v2/) to avoid accidental overwrites of tokenizer files. - CI validation step: Add a test that loads the tokenizer and encodes a corpus containing all special tokens, asserting that
max(token_ids) < tokenizer.vocab_size. Fail the pipeline if the assertion breaks. - Rolling upgrade strategy: When deploying a new tokenizer, first roll out the updated container image to a subset of nodes, verify compatibility, then update the shared artifact.
- Monitoring: Add an alert on
model_inference_errors_total{reason="tokenizer"}with a threshold of >5 per minute to catch regressions early.
Related Topic Hub: Observability Troubleshooting Hub
FAQ
- Why does the error only appear on on‑premise nodes?
Because those nodes run an older container image that expects a smaller vocab. The cloud nodes have been rebuilt with the newer image, so they correctly handle the expanded tokenizer. - Can I use the same tokenizer JSON with older
transformersversions?
No. Older versions hard‑code the vocab size based on the token list at import time. If the JSON contains extra tokens, the library will reject IDs beyond the hard‑coded limit. - Is it safe to overwrite the tokenizer file in shared storage?
Only if you are certain that all running pods use a compatible library version. Otherwise you risk breaking the other node pool; prefer versioned directories or immutable storage. - How do I detect a tokenizer‑vocab mismatch before it reaches production?
Add a startup health check that loads the tokenizer and assertslen(tokenizer.get_vocab()) == tokenizer.vocab_size. Fail the pod if the assertion fails. - What if I need to add new special tokens without rebuilding the image?
Create a custom wrapper that patchestokenizer.vocab_sizeat runtime after loading the JSON, e.g.:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("/model/tokenizer")
tokenizer.vocab_size = len(tokenizer.get_vocab())
Use this wrapper only as a temporary bridge; the proper fix is to align library versions.