[None][fix] Force slow tokenizer for Kimi K2.5 (TikTokenTokenizer) - #14416
[None][fix] Force slow tokenizer for Kimi K2.5 (TikTokenTokenizer)#14416qiaoxj07 wants to merge 6 commits into
Conversation
Kimi K2.5 ships only the slow `TikTokenTokenizer` (model repo's
`tokenization_kimi.py`). transformers >= 5 tries to auto-convert it
to a Fast tokenizer via a SentencePiece-then-TikToken fallback that
does not honor the custom CJK-aware `pat_str`. The fallback Fast
tokenizer mis-segments multilingual content, producing ~+20% more
tokens than the slow path for the same input string. Concretely,
benchmark prompts that the dataset generator produced at <= 8192
tokens get re-tokenized at the bench to up to ~9820 tokens.
Two fixes:
1. `tensorrt_llm/bench/utils/data.py`: in `initialize_tokenizer`,
inspect `tokenizer_class` from `tokenizer_config.json` (without
instantiating) and pass `use_fast=False` to `AutoTokenizer` for
classes in `_REQUIRES_SLOW_TOKENIZER` (currently
`{"TikTokenTokenizer"}`). Behavior is unchanged for all other
models.
2. `tensorrt_llm/_torch/models/modeling_kimi_k25.py`: in
`KimiK25InputProcessor.__init__`, hardcode `use_fast=False` for
the `AutoTokenizer` call (the file is K2.5-specific) and pin
`self._processor.tokenizer = self._tokenizer` so any text path
through `self._processor(text=...)` uses the same slow tokenizer.
`AutoProcessor`'s `use_fast` is left as-is because it only
affects image processor selection.
References:
- https://huggingface.co/moonshotai/Kimi-K2.5/discussions/7
- https://huggingface.co/moonshotai/Kimi-K2.6 (transformers < 5.0.0)
Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
📝 WalkthroughWalkthroughThe PR enforces slow tokenizer usage in two complementary locations: a general tokenizer initialization utility that detects when slow tokenizers are required via configuration inspection, and the KimiK25 model which explicitly uses slow tokenizers and synchronizes processor and text tokenization paths. ChangesSlow Tokenizer Enforcement
🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tensorrt_llm/bench/utils/data.py`:
- Around line 46-51: The docstring for the function with signature ending in
"model_name_or_path: str) -> Optional[str]:" must be reformatted to satisfy Ruff
D205: make the summary a single line (one-sentence short description), add one
blank line, then put the expanded description/details on subsequent lines;
adjust the existing triple-quoted string so the first line is the short summary,
followed by a blank line, then the longer explanation about reading
"tokenizer_class" from "tokenizer_config.json" and the return behavior.
- Around line 58-65: The hf_hub_download call can raise several
huggingface_hub-specific exceptions that aren’t covered by the current except
tuple, causing the tokenizer fallback to fail; update the try/except around
hf_hub_download (the call using hf_hub_download(repo_id=model_name_or_path,
filename="tokenizer_config.json")) to also catch
huggingface_hub.utils.EntryNotFoundError, LocalEntryNotFoundError,
RepositoryNotFoundError, RevisionNotFoundError and
huggingface_hub.errors.GatedRepoError (either by importing and adding them to
the except tuple or by catching the broader huggingface_hub errors namespace) so
that any of those failures return None as the fallback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: d268af38-52ee-4d87-89f6-329cb060e019
📒 Files selected for processing (2)
tensorrt_llm/_torch/models/modeling_kimi_k25.pytensorrt_llm/bench/utils/data.py
… no-op) In transformers 5.x, ``AutoTokenizer.from_pretrained(..., use_fast=False)`` is silently ignored for Kimi-K2.5's ``TikTokenTokenizer`` -- the returned tokenizer is still ``TokenizersBackend`` with ``is_fast=True``, built via the new generic SentencePiece-then-TikToken fast converter that does not honor the custom CJK-aware ``pat_str``. Bench-side empirical verification on a transformers 5.5.3 container: even with ``use_fast=False`` set, the dataset percentile MAX stayed at 9820 (the inflated number); switching to direct class load brought it back to 8156, matching the transformers 4.57.3 baseline exactly. Instead of toggling ``use_fast``, load the slow class directly via ``transformers.dynamic_module_utils.get_class_from_dynamic_module`` from the model repo's remote code: 1. ``tensorrt_llm/bench/utils/data.py``: replace ``_REQUIRES_SLOW_TOKENIZER`` with ``_DYNAMIC_TOKENIZER_LOAD`` mapping ``tokenizer_class`` -> ``"module.Class"``; for hits, instantiate the slow class directly. Fall back to ``AutoTokenizer`` for everything else and on dynamic-load failure. 2. ``tensorrt_llm/_torch/models/modeling_kimi_k25.py``: same direct-load trick in ``KimiK25InputProcessor.__init__``, with ``AutoTokenizer`` fallback. ``self._processor.tokenizer = self._tokenizer`` pinning is retained so any text path through ``self._processor(text=...)`` uses the same slow tokenizer. Verified on OCI GB200 with ``trtllm-bench throughput`` against the ``kimi-k25-8192-1-20000-ratio-08`` dataset on a transformers 5.5.3 container -- dataset percentiles: MIN 6571, MAX 8156, AVG 7415, P99 8155 (matches the 2026-05-19 transformers 4.57.3 baseline). Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
|
Updated approach: Switched to loading the slow |
…ixes trtllm-serve) Previous iteration only patched ``tensorrt_llm/bench/utils/data.py`` and ``tensorrt_llm/_torch/models/modeling_kimi_k25.py``. That fixes the bench-side dataset percentile path, but leaves trtllm-serve (and the LLM API runtime in general) still using the broken Fast-converted tokenizer: LLM.__init__ -> _try_load_tokenizer -> ModelLoader.load_hf_tokenizer -> tensorrt_llm.tokenizer.tokenizer.load_hf_tokenizer -> TransformersTokenizer.from_pretrained -> AutoTokenizer.from_pretrained(..., use_fast=True_or_False) -> [broken Fast TokenizersBackend under transformers >= 5] That ``self._tokenizer`` is then passed in to ``create_input_processor`` which calls ``KimiK25InputProcessor(model_path, config, tokenizer, ...)`` with the broken tokenizer; the input processor stores it as ``self._tokenizer`` and uses it for every incoming request's encoding, producing the same +20% token-count inflation we saw in the bench dataset stats. This is what caused the original CTX worker's ``default_max_tokens (-746) = max_seq_len (8232) - splited_prompt_len (8978)`` 400 Bad Request errors at the very top of this thread. Consolidate the fix into one place at the runtime tokenizer layer: 1. ``tensorrt_llm/tokenizer/tokenizer.py``: add public ``DYNAMIC_SLOW_TOKENIZER_MAP`` and ``try_load_dynamic_slow_tokenizer``. Wire the helper into ``TransformersTokenizer.from_pretrained`` so it is tried first; on hit, wrap the slow class in TransformersTokenizer and return; on miss / failure, fall through to the existing AutoTokenizer path unchanged. 2. ``tensorrt_llm/bench/utils/data.py``: replace the inline detect-and- load logic with a call to the shared helper. Returns the raw HF tokenizer (PreTrainedTokenizer subclass) as before. 3. ``tensorrt_llm/_torch/models/modeling_kimi_k25.py``: revert the AutoTokenizer call to its original form (the runtime fix now loads the slow tokenizer upstream). Keep ``self._processor.tokenizer = self._tokenizer`` so any text path through ``self._processor(text=...)`` uses the same tokenizer the rest of the class uses (the processor builds its own internal tokenizer copy via AutoProcessor that is independently mis-converted). Verified on OCI GB200 with the same transformers 5.5.3 container via ``trtllm-bench throughput`` on the Kimi K2.5-NVFP4 ctx-only sweep: dataset MIN/MAX/AVG/P99 = 6571 / 8156 / 7415 / 8155, identical to the 2026-05-19 transformers 4.57.3 baseline, and zero ``400 Bad Request`` errors during the run. References: - https://huggingface.co/moonshotai/Kimi-K2.5/discussions/7 - https://huggingface.co/moonshotai/Kimi-K2.6 (pins transformers < 5.0.0) - https://github.com/huggingface/transformers/blob/v5.1.0/src/transformers/tokenization_utils_tokenizers.py Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
|
v3: moved the fix into the runtime tokenizer layer so trtllm-serve is also covered. The previous v2 only fixed the bench-side dataset prep path ( v3 consolidates:
Re-verified on OCI GB200 / transformers 5.5.3 via |
…rt + yapf Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
…209) Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
|
/bot run --disable-fail-fast |
|
PR_Github #49727 [ run ] triggered by Bot. Commit: |
|
PR_Github #49727 [ run ] completed with state |
Detect auto_map.AutoTokenizer = [SlowClass, null] after AutoTokenizer returns Fast, then reload the slow class via get_class_from_dynamic_module. Avoids transformers 5.x mis-converting Kimi K2/K2.5's TikTokenTokenizer to a Fast tokenizer with a generic regex that drops the model's pat_str. References NVIDIA#14416. Signed-off-by: Zhenhuan Chen <zhenhuanc@nvidia.com>
…enizer
initialize_tokenizer in bench/utils/data.py called AutoTokenizer
directly, bypassing the post-load fixes wired into
TransformersTokenizer.from_pretrained -- in particular
maybe_fix_byte_level_tokenizer, which prevents DeepSeek-V3-family
models from loading with a Metaspace pre-tokenizer that silently
strips spaces ('hello world' -> 'helloworld') on transformers >= 5.x.
Route through the shared wrapper and peel off .tokenizer to return
the raw HF tokenizer the rest of bench code expects. Adds a unit
test that pins the routing.
References NVIDIA#14416.
Signed-off-by: Zhenhuan Chen <zhenhuanc@nvidia.com>
…h TransformersTokenizer
Two entry points were calling AutoTokenizer.from_pretrained directly,
bypassing the post-load fixes wired into
TransformersTokenizer.from_pretrained -- in particular
maybe_fix_byte_level_tokenizer (DeepSeek-V3 Metaspace bug that silently
strips spaces) and _fallback_to_fast_tokenizer (DeepSeek-V3.2
AttributeError on transformers >= 5.x):
* bench/utils/data.py:initialize_tokenizer (used by trtllm-bench)
* serve/router.py:BlockHashMixin._get_tokenizer (used by trtllm-serve
block-hash KV-cache routing -- it hand-inlined the byte-level fix
only, missing the DeepSeek-V3.2 fallback)
Route both through TransformersTokenizer.from_pretrained(...).tokenizer
so they inherit the canonical fix list. Adds two guard tests.
References NVIDIA#14416.
Signed-off-by: Zhenhuan Chen <zhenhuanc@nvidia.com>
…h TransformersTokenizer
Two entry points were calling AutoTokenizer.from_pretrained directly,
bypassing the post-load fixes wired into
TransformersTokenizer.from_pretrained -- in particular
maybe_fix_byte_level_tokenizer (DeepSeek-V3 Metaspace bug that silently
strips spaces) and _fallback_to_fast_tokenizer (DeepSeek-V3.2
AttributeError on transformers >= 5.x):
* bench/utils/data.py:initialize_tokenizer (used by trtllm-bench)
* serve/router.py:BlockHashMixin._get_tokenizer (used by trtllm-serve
block-hash KV-cache routing -- it hand-inlined the byte-level fix
only, missing the DeepSeek-V3.2 fallback)
Route both through TransformersTokenizer.from_pretrained(...).tokenizer
so they inherit the canonical fix list. Adds two guard tests.
References NVIDIA#14416.
Signed-off-by: Zhenhuan Chen <zhenhuanc@nvidia.com>
Summary
use_fast=Falsewhen loading the Kimi K2.5 tokenizer so the model's own slowTikTokenTokenizer(tokenization_kimi.py) is used instead of transformers 5.x's auto-converted Fast tokenizer.tensorrt_llm/bench/utils/data.py:initialize_tokenizer, gateuse_fast=Falseontokenizer_class == "TikTokenTokenizer"via a new helper that readstokenizer_config.jsonwithout instantiating, so behavior is unchanged for every other model.tensorrt_llm/_torch/models/modeling_kimi_k25.py:KimiK25InputProcessor.__init__, hardcodeuse_fast=FalseforAutoTokenizer(the file is K2.5-specific) and pinself._processor.tokenizer = self._tokenizerso any text path throughself._processor(text=...)uses the same slow tokenizer.AutoProcessor'suse_fastis left as-is because it only controls image processor selection.Why
Kimi K2.5 ships only a slow
TikTokenTokenizer(PreTrainedTokenizer)with a custom CJK-awarepat_strregex; there is no Fast equivalent and notokenizer.jsonupstream. transformers >= 5 newly tries to auto-convert it to a Fast tokenizer viaTokenizersBackend.convert_to_native_format, which:vocab_file = tiktoken.modelexists.Could not extract SentencePiece model … Falling back to TikToken extractor.pat_str).The resulting Fast tokenizer mis-segments multilingual / CJK content, producing roughly +20% more tokens for the same input string compared to the slow path. In one benchmark sweep (kimi-k2-sol-8192-1024-ratio08), this caused dataset prompts that were generated at <=8192 tokens to be re-tokenized by
trtllm-benchat up to 9820 tokens, overflowing the server'smax_seq_lenand rejecting requests with HTTP 400.Upstream's own constraints corroborate this:
moonshotai/Kimi-K2.5README pinstransformers >= 4.57.1, and the siblingKimi-K2.6(same architecture) caps to< 5.0.0. This PR makes trtllm work correctly under either transformers 4.x or 5.x without requiring a transformers downgrade.Test plan
trtllm-benchthroughput on a Kimi-K2.5 checkpoint under transformers >= 5: confirm the DATASET DETAILS percentiles match the slow tokenizer (e.g. for the random-v2 ratio-0.8 ISL=8192 dataset, P100 should be <= 8192 + chat-template overhead, not ~9820).trtllm-benchthroughput on a non-Kimi model (e.g. Llama, DeepSeek-V3) under transformers >= 5: confirmuse_fast=Trueis still selected (no perf regression for the Fast-tokenizer path) by inspecting the tokenizer type after init.self._processor(text=...)andself._tokenizer(text)yield the sameinput_idsfor a sample prompt.References
transformers >= 4.57.1transformers < 5.0.0convert_to_native_formatSentencePiece-then-TikToken fallbackSummary by CodeRabbit