From 347112fdf8bda39d51839b9ffde4e91b2c245043 Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Wed, 29 Apr 2026 20:21:49 -0400 Subject: [PATCH 1/8] draft Signed-off-by: Yifan Zong --- vllm/multimodal/processing/context.py | 22 ----------------- vllm/renderers/base.py | 13 ++-------- vllm/tokenizers/deepseek_v32.py | 3 ++- vllm/tokenizers/deepseek_v4.py | 3 ++- vllm/tokenizers/hf.py | 35 ++++++++++++++++++++++++++- vllm/tokenizers/qwen_vl.py | 3 ++- 6 files changed, 42 insertions(+), 37 deletions(-) diff --git a/vllm/multimodal/processing/context.py b/vllm/multimodal/processing/context.py index ef9710374d81..bed66d0a4e9d 100644 --- a/vllm/multimodal/processing/context.py +++ b/vllm/multimodal/processing/context.py @@ -268,28 +268,6 @@ def call_hf_processor( try: output = hf_processor(**data, **allowed_kwargs) except Exception as exc: - # See https://github.com/huggingface/tokenizers/issues/537 - if ( - isinstance(exc, RuntimeError) - and exc - and exc.args[0] == "Already borrowed" - and num_tries < max_tries - ): - logger.warning( - "Failed to acquire tokenizer in current thread. " - "Retrying (%d/%d)...", - num_tries, - max_tries, - ) - time.sleep(0.5) - return self.call_hf_processor( - hf_processor, - data, - kwargs, - num_tries=num_tries + 1, - max_tries=max_tries, - ) - msg = ( f"Failed to apply {type(hf_processor).__name__} " f"on data={data} with kwargs={allowed_kwargs}" diff --git a/vllm/renderers/base.py b/vllm/renderers/base.py index 2f10302c0268..cb9c2cef8f78 100644 --- a/vllm/renderers/base.py +++ b/vllm/renderers/base.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import asyncio -import copy import time from abc import ABC, abstractmethod from collections.abc import Mapping, Sequence @@ -108,17 +107,10 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: if config.model_config.is_multimodal_model: mm_processor_cache = mm_registry.processor_cache_from_config(config) - # Deep-copy the tokenizer so the multimodal processor gets its - # own Rust tokenizer backend. Without this, concurrent access - # from AsyncMicrobatchTokenizer and call_hf_processor causes - # "RuntimeError: Already borrowed" from the Rust RefCell. - # See: https://github.com/huggingface/tokenizers/issues/537 - mm_tokenizer = copy.deepcopy(tokenizer) - with set_default_torch_num_threads(): self.mm_processor = mm_registry.create_processor( config.model_config, - tokenizer=mm_tokenizer, + tokenizer=tokenizer, cache=mm_processor_cache, ) @@ -130,11 +122,10 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: # requests don't pollute the sender cache. ro_cache = mm_registry.processor_only_cache_from_config(config) if ro_cache is not None: - ro_tokenizer = copy.deepcopy(tokenizer) with set_default_torch_num_threads(): self._readonly_mm_processor = mm_registry.create_processor( config.model_config, - tokenizer=ro_tokenizer, + tokenizer=tokenizer, cache=ro_cache, ) diff --git a/vllm/tokenizers/deepseek_v32.py b/vllm/tokenizers/deepseek_v32.py index 51199de5c47e..2f916ef33546 100644 --- a/vllm/tokenizers/deepseek_v32.py +++ b/vllm/tokenizers/deepseek_v32.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v32_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer +from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local from .protocol import TokenizerLike @@ -86,4 +86,5 @@ class DeepseekV32Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) + tokenizer = make_backend_thread_local(tokenizer) return get_cached_tokenizer(get_deepseek_v32_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/deepseek_v4.py b/vllm/tokenizers/deepseek_v4.py index 2a6aaaf73975..2b9e062c3122 100644 --- a/vllm/tokenizers/deepseek_v4.py +++ b/vllm/tokenizers/deepseek_v4.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v4_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer +from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local from .protocol import TokenizerLike @@ -93,4 +93,5 @@ class DeepseekV4Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) + tokenizer = make_backend_thread_local(tokenizer) return get_cached_tokenizer(get_deepseek_v4_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index 85c812398529..0e82e05dc8d8 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -2,6 +2,7 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import contextlib import copy +import threading from pathlib import Path from typing import TypeAlias @@ -14,6 +15,38 @@ HfTokenizer: TypeAlias = PreTrainedTokenizer | PreTrainedTokenizerFast +def make_backend_thread_local(tokenizer: HfTokenizer) -> HfTokenizer: + """Route operations through a per-thread deep-copied backend tokenizer.""" + if not isinstance(tokenizer, PreTrainedTokenizerFast): + return tokenizer + + thread_safe_tokenizer = copy.copy(tokenizer) + + backend_tokenizer = thread_safe_tokenizer._tokenizer + + # Concurrent dict insertion is safe here thanks to the GIL. + thread_local = {threading.get_ident(): copy.deepcopy(backend_tokenizer)} + + class ThreadLocalTokenizer(tokenizer.__class__): # type: ignore + @property + def _tokenizer(self): + current_thread_id = threading.get_ident() + try: + return thread_local[current_thread_id] + except KeyError: + backend_copy = copy.deepcopy(backend_tokenizer) + thread_local[current_thread_id] = backend_copy + return backend_copy + + def __reduce__(self): + return make_backend_thread_local, (tokenizer,) + + ThreadLocalTokenizer.__name__ = f"ThreadLocal{tokenizer.__class__.__name__}" + + thread_safe_tokenizer.__class__ = ThreadLocalTokenizer + return thread_safe_tokenizer + + def get_cached_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer: """ By default, transformers will recompute multiple tokenizer properties @@ -122,4 +155,4 @@ def from_pretrained( } tokenizer.add_special_tokens(special_tokens_map) - return get_cached_tokenizer(tokenizer) + return get_cached_tokenizer(make_backend_thread_local(tokenizer)) diff --git a/vllm/tokenizers/qwen_vl.py b/vllm/tokenizers/qwen_vl.py index f36a22b02545..afc5ebfa9158 100644 --- a/vllm/tokenizers/qwen_vl.py +++ b/vllm/tokenizers/qwen_vl.py @@ -6,7 +6,7 @@ from transformers import AutoTokenizer -from .hf import HfTokenizer, get_cached_tokenizer +from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local from .protocol import TokenizerLike @@ -68,4 +68,5 @@ class QwenVLTokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = AutoTokenizer.from_pretrained(*args, **kwargs) + tokenizer = make_backend_thread_local(tokenizer) return get_cached_tokenizer(get_qwen_vl_tokenizer(tokenizer)) From 21851d33747266e7ebde1d94982033f39c7b8113 Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Thu, 30 Apr 2026 14:11:46 -0400 Subject: [PATCH 2/8] Use queue based pool Signed-off-by: Yifan Zong --- vllm/tokenizers/hf.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index 0e82e05dc8d8..de488156d10c 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import contextlib import copy -import threading +import queue from pathlib import Path from typing import TypeAlias @@ -16,27 +16,41 @@ def make_backend_thread_local(tokenizer: HfTokenizer) -> HfTokenizer: - """Route operations through a per-thread deep-copied backend tokenizer.""" + """Make `PreTrainedTokenizerFast` thread-safe. + Make `encode`, `batch_encode`, and `__call__` methods, which may mutably + borrow the Rust backend tokenizer, thread-safe by routing them through a + deep-copied tokenizer pool. + """ if not isinstance(tokenizer, PreTrainedTokenizerFast): return tokenizer thread_safe_tokenizer = copy.copy(tokenizer) - backend_tokenizer = thread_safe_tokenizer._tokenizer + encoder_pool: queue.Queue[PreTrainedTokenizerFast] = queue.Queue() - # Concurrent dict insertion is safe here thanks to the GIL. - thread_local = {threading.get_ident(): copy.deepcopy(backend_tokenizer)} + @contextlib.contextmanager + def _borrow_from_pool(): + try: + tok = encoder_pool.get_nowait() + yield tok + except queue.Empty: + tok = copy.deepcopy(tokenizer) + yield tok + finally: + encoder_pool.put(tok) class ThreadLocalTokenizer(tokenizer.__class__): # type: ignore - @property - def _tokenizer(self): - current_thread_id = threading.get_ident() - try: - return thread_local[current_thread_id] - except KeyError: - backend_copy = copy.deepcopy(backend_tokenizer) - thread_local[current_thread_id] = backend_copy - return backend_copy + def encode(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.encode(*args, **kwargs) + + def batch_encode(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.batch_encode(*args, **kwargs) + + def __call__(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok(*args, **kwargs) def __reduce__(self): return make_backend_thread_local, (tokenizer,) From 0ed7614f9dc9639862a885815f0afdf22978c131 Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Fri, 1 May 2026 14:20:21 -0400 Subject: [PATCH 3/8] Addr comments Signed-off-by: Yifan Zong --- vllm/tokenizers/deepseek_v32.py | 4 +- vllm/tokenizers/deepseek_v4.py | 4 +- vllm/tokenizers/hf.py | 65 +++++++++++++++++++++++++-------- vllm/tokenizers/qwen_vl.py | 4 +- vllm/tokenizers/registry.py | 8 +++- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/vllm/tokenizers/deepseek_v32.py b/vllm/tokenizers/deepseek_v32.py index 2f916ef33546..db22020f858d 100644 --- a/vllm/tokenizers/deepseek_v32.py +++ b/vllm/tokenizers/deepseek_v32.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v32_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local +from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool from .protocol import TokenizerLike @@ -86,5 +86,5 @@ class DeepseekV32Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) - tokenizer = make_backend_thread_local(tokenizer) + tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_deepseek_v32_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/deepseek_v4.py b/vllm/tokenizers/deepseek_v4.py index 2b9e062c3122..d21dea3ae82a 100644 --- a/vllm/tokenizers/deepseek_v4.py +++ b/vllm/tokenizers/deepseek_v4.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v4_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local +from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool from .protocol import TokenizerLike @@ -93,5 +93,5 @@ class DeepseekV4Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) - tokenizer = make_backend_thread_local(tokenizer) + tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_deepseek_v4_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index de488156d10c..ae6ed7839b6f 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -15,49 +15,84 @@ HfTokenizer: TypeAlias = PreTrainedTokenizer | PreTrainedTokenizerFast -def make_backend_thread_local(tokenizer: HfTokenizer) -> HfTokenizer: - """Make `PreTrainedTokenizerFast` thread-safe. - Make `encode`, `batch_encode`, and `__call__` methods, which may mutably - borrow the Rust backend tokenizer, thread-safe by routing them through a - deep-copied tokenizer pool. +def make_tokenizer_pool(tokenizer: HfTokenizer) -> HfTokenizer: + """Ensure the public interface of ``TokenizerLike`` is thread-safe. + Route calls to ``PreTrainedTokenizerFast`` through a deep-copied + tokenizer pool. + + Note that: + - Mutation is not propagated to tokenizers in the pool. + - Adjacent method calls could happen on different deep copies. + - ``_tokenizer`` property is not protected by tokenizer pool. """ if not isinstance(tokenizer, PreTrainedTokenizerFast): return tokenizer thread_safe_tokenizer = copy.copy(tokenizer) - encoder_pool: queue.Queue[PreTrainedTokenizerFast] = queue.Queue() + tokenizer_pool: queue.Queue[PreTrainedTokenizerFast] = queue.Queue() @contextlib.contextmanager def _borrow_from_pool(): try: - tok = encoder_pool.get_nowait() + tok = tokenizer_pool.get_nowait() yield tok except queue.Empty: tok = copy.deepcopy(tokenizer) yield tok finally: - encoder_pool.put(tok) + tokenizer_pool.put(tok) - class ThreadLocalTokenizer(tokenizer.__class__): # type: ignore - def encode(self, *args, **kwargs): + class TokenizerPool(tokenizer.__class__): # type: ignore + def apply_chat_template(self, *args, **kwargs): with _borrow_from_pool() as tok: - return tok.encode(*args, **kwargs) + return tok.apply_chat_template(*args, **kwargs) + + def batch_decode(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.batch_decode(*args, **kwargs) def batch_encode(self, *args, **kwargs): with _borrow_from_pool() as tok: return tok.batch_encode(*args, **kwargs) + def convert_tokens_to_ids(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.convert_tokens_to_ids(*args, **kwargs) + + def convert_ids_to_tokens(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.convert_ids_to_tokens(*args, **kwargs) + + def convert_tokens_to_string(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.convert_tokens_to_string(*args, **kwargs) + + def decode(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.decode(*args, **kwargs) + + def encode(self, *args, **kwargs): + with _borrow_from_pool() as tok: + return tok.encode(*args, **kwargs) + def __call__(self, *args, **kwargs): with _borrow_from_pool() as tok: return tok(*args, **kwargs) def __reduce__(self): - return make_backend_thread_local, (tokenizer,) + return make_tokenizer_pool, (tokenizer,) + + @staticmethod + def _reserve_pool(copies: int): + # Ensure tokenizer pool contains at least `copies` tokenizers. + num_to_add = copies - tokenizer_pool.qsize() + for _ in range(num_to_add): + tokenizer_pool.put(copy.deepcopy(tokenizer)) - ThreadLocalTokenizer.__name__ = f"ThreadLocal{tokenizer.__class__.__name__}" + TokenizerPool.__name__ = f"TokenizerPool{tokenizer.__class__.__name__}" - thread_safe_tokenizer.__class__ = ThreadLocalTokenizer + thread_safe_tokenizer.__class__ = TokenizerPool return thread_safe_tokenizer @@ -169,4 +204,4 @@ def from_pretrained( } tokenizer.add_special_tokens(special_tokens_map) - return get_cached_tokenizer(make_backend_thread_local(tokenizer)) + return get_cached_tokenizer(make_tokenizer_pool(tokenizer)) diff --git a/vllm/tokenizers/qwen_vl.py b/vllm/tokenizers/qwen_vl.py index afc5ebfa9158..899c813d0b0c 100644 --- a/vllm/tokenizers/qwen_vl.py +++ b/vllm/tokenizers/qwen_vl.py @@ -6,7 +6,7 @@ from transformers import AutoTokenizer -from .hf import HfTokenizer, get_cached_tokenizer, make_backend_thread_local +from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool from .protocol import TokenizerLike @@ -68,5 +68,5 @@ class QwenVLTokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = AutoTokenizer.from_pretrained(*args, **kwargs) - tokenizer = make_backend_thread_local(tokenizer) + tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_qwen_vl_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/registry.py b/vllm/tokenizers/registry.py index 8778aa9d691f..73f81e75969f 100644 --- a/vllm/tokenizers/registry.py +++ b/vllm/tokenizers/registry.py @@ -258,7 +258,7 @@ def cached_tokenizer_from_config(model_config: "ModelConfig", **kwargs): if model_config.skip_tokenizer_init: return None - return cached_get_tokenizer( + tok = cached_get_tokenizer( model_config.tokenizer, runner_type=model_config.runner_type, tokenizer_mode=model_config.tokenizer_mode, @@ -266,3 +266,9 @@ def cached_tokenizer_from_config(model_config: "ModelConfig", **kwargs): trust_remote_code=model_config.trust_remote_code, **kwargs, ) + + # HF fast tokenizer specific method to reserve pool of tokenizers. + if hasattr(tok, "_reserve_pool"): + tok._reserve_pool(model_config.renderer_num_workers) + + return tok From 04d9363ee4a39522904f33414d3ee20f00cdeaca Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Mon, 4 May 2026 17:29:37 -0400 Subject: [PATCH 4/8] Make threadpool after renderer init Signed-off-by: Yifan Zong --- vllm/renderers/base.py | 7 ++++--- vllm/renderers/hf.py | 28 +++++++++++++++++++--------- vllm/tokenizers/__init__.py | 2 ++ vllm/tokenizers/deepseek_v32.py | 3 +-- vllm/tokenizers/deepseek_v4.py | 3 +-- vllm/tokenizers/hf.py | 33 +++++++++++++++------------------ vllm/tokenizers/qwen_vl.py | 3 +-- vllm/tokenizers/registry.py | 4 ---- 8 files changed, 43 insertions(+), 40 deletions(-) diff --git a/vllm/renderers/base.py b/vllm/renderers/base.py index 263d593d678c..c7cf188a4e5d 100644 --- a/vllm/renderers/base.py +++ b/vllm/renderers/base.py @@ -37,7 +37,7 @@ from vllm.multimodal.processing import BaseMultiModalProcessor from vllm.multimodal.processing import ProcessorInputs as MMProcessorInputs from vllm.multimodal.registry import MultiModalTimingRegistry -from vllm.tokenizers import TokenizerLike +from vllm.tokenizers import TokenizerLike, maybe_make_thread_pool from vllm.utils.async_utils import ( AsyncMicrobatchTokenizer, make_async, @@ -79,8 +79,6 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: self.model_config = config.model_config self.api_process_rank = config.parallel_config._api_process_rank - self.tokenizer = tokenizer - # Shared thread pool executor for blocking tokenizer and # multimodal preprocessing operations. The multimodal processor # receives a deep-copied tokenizer (see #36557) so it is safe to @@ -136,6 +134,9 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: config.observability_config ) + # Make HF fast tokenizer thread-safe by dispatching calls to tokenizer pool + self.tokenizer = maybe_make_thread_pool(tokenizer, pool_workers + 1) + def get_tokenizer(self) -> _T: tokenizer = self.tokenizer if tokenizer is None: diff --git a/vllm/renderers/hf.py b/vllm/renderers/hf.py index bff473fa6417..5341edbaa10d 100644 --- a/vllm/renderers/hf.py +++ b/vllm/renderers/hf.py @@ -99,12 +99,11 @@ ) -def _ensure_prompt_embeds_placeholder_token(tokenizer: HfTokenizer) -> int: - """Register `PROMPT_EMBEDS_PLACEHOLDER_TOKEN` as a special token and return - its token ID.""" - cached = _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE.get(tokenizer) - if cached is not None: - return cached +def _maybe_register_prompt_embeds_placeholder_token( + model_config: ModelConfig, tokenizer: HfTokenizer +): + if not model_config.enable_prompt_embeds: + return tokenizer.add_special_tokens( {"additional_special_tokens": [PROMPT_EMBEDS_PLACEHOLDER_TOKEN]} @@ -122,7 +121,17 @@ def _ensure_prompt_embeds_placeholder_token(tokenizer: HfTokenizer) -> int: token_id = ids[0] _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE[tokenizer] = token_id - return token_id + + +def _get_prompt_embeds_placeholder_token_id(tokenizer: HfTokenizer) -> int: + """Get the token ID of `PROMPT_EMBEDS_PLACEHOLDER_TOKEN`.""" + cached = _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE.get(tokenizer) + if cached is None: + raise RuntimeError( + "Prompt embeds placeholder token ID not found for tokenizer %s", + tokenizer.name_or_path, + ) + return cached def _build_prompt_embeds_updates( @@ -785,6 +794,7 @@ def __init__( config: VllmConfig, tokenizer: HfTokenizer | None, ) -> None: + _maybe_register_prompt_embeds_placeholder_token(config.model_config, tokenizer) super().__init__(config, tokenizer) self.use_unified_vision_chunk = getattr( @@ -806,7 +816,7 @@ def render_messages( prompt_embeds_placeholder_token_id: int | None = None if model_config.enable_prompt_embeds: prompt_embeds_placeholder_token_id = ( - _ensure_prompt_embeds_placeholder_token(tokenizer) + _get_prompt_embeds_placeholder_token_id(tokenizer) ) conversation, mm_data, mm_uuids = parse_chat_messages( @@ -913,7 +923,7 @@ async def render_messages_async( prompt_embeds_placeholder_token_id: int | None = None if model_config.enable_prompt_embeds: prompt_embeds_placeholder_token_id = ( - _ensure_prompt_embeds_placeholder_token(tokenizer) + _get_prompt_embeds_placeholder_token_id(tokenizer) ) conversation, mm_data, mm_uuids = await parse_chat_messages_async( diff --git a/vllm/tokenizers/__init__.py b/vllm/tokenizers/__init__.py index 2daba409881f..6531989a9f35 100644 --- a/vllm/tokenizers/__init__.py +++ b/vllm/tokenizers/__init__.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project +from .hf import maybe_make_thread_pool from .protocol import TokenizerLike from .registry import ( TokenizerRegistry, @@ -15,4 +16,5 @@ "cached_get_tokenizer", "get_tokenizer", "cached_tokenizer_from_config", + "maybe_make_thread_pool", ] diff --git a/vllm/tokenizers/deepseek_v32.py b/vllm/tokenizers/deepseek_v32.py index db22020f858d..51199de5c47e 100644 --- a/vllm/tokenizers/deepseek_v32.py +++ b/vllm/tokenizers/deepseek_v32.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v32_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool +from .hf import HfTokenizer, get_cached_tokenizer from .protocol import TokenizerLike @@ -86,5 +86,4 @@ class DeepseekV32Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) - tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_deepseek_v32_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/deepseek_v4.py b/vllm/tokenizers/deepseek_v4.py index d21dea3ae82a..2a6aaaf73975 100644 --- a/vllm/tokenizers/deepseek_v4.py +++ b/vllm/tokenizers/deepseek_v4.py @@ -8,7 +8,7 @@ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam from .deepseek_v4_encoding import encode_messages -from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool +from .hf import HfTokenizer, get_cached_tokenizer from .protocol import TokenizerLike @@ -93,5 +93,4 @@ class DeepseekV4Tokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) - tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_deepseek_v4_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index ae6ed7839b6f..877f55e22a89 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -15,22 +15,26 @@ HfTokenizer: TypeAlias = PreTrainedTokenizer | PreTrainedTokenizerFast -def make_tokenizer_pool(tokenizer: HfTokenizer) -> HfTokenizer: - """Ensure the public interface of ``TokenizerLike`` is thread-safe. - Route calls to ``PreTrainedTokenizerFast`` through a deep-copied - tokenizer pool. +def maybe_make_thread_pool(tokenizer: TokenizerLike, copies: int = 1) -> TokenizerLike: + """ + If `tokenizer` is a `PreTrainedTokenizerFast`, modify the tokenizer + in place to make the public interface thread-safe by routing calls + through a deep-copied tokenizer pool. Note that: - - Mutation is not propagated to tokenizers in the pool. + - Only ``TokenizerLike``'s public interface is thread-safe. + This doesn't include ``_tokenizer`` property nor any mutation + methods like ``add_special_tokens`` or ``add_tokens``. - Adjacent method calls could happen on different deep copies. - - ``_tokenizer`` property is not protected by tokenizer pool. """ if not isinstance(tokenizer, PreTrainedTokenizerFast): return tokenizer - thread_safe_tokenizer = copy.copy(tokenizer) + og_tokenizer = copy.copy(tokenizer) tokenizer_pool: queue.Queue[PreTrainedTokenizerFast] = queue.Queue() + for _ in range(copies): + tokenizer_pool.put(copy.deepcopy(og_tokenizer)) @contextlib.contextmanager def _borrow_from_pool(): @@ -81,19 +85,12 @@ def __call__(self, *args, **kwargs): return tok(*args, **kwargs) def __reduce__(self): - return make_tokenizer_pool, (tokenizer,) - - @staticmethod - def _reserve_pool(copies: int): - # Ensure tokenizer pool contains at least `copies` tokenizers. - num_to_add = copies - tokenizer_pool.qsize() - for _ in range(num_to_add): - tokenizer_pool.put(copy.deepcopy(tokenizer)) + return maybe_make_thread_pool, (og_tokenizer, copies) TokenizerPool.__name__ = f"TokenizerPool{tokenizer.__class__.__name__}" - thread_safe_tokenizer.__class__ = TokenizerPool - return thread_safe_tokenizer + tokenizer.__class__ = TokenizerPool + return tokenizer def get_cached_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer: @@ -204,4 +201,4 @@ def from_pretrained( } tokenizer.add_special_tokens(special_tokens_map) - return get_cached_tokenizer(make_tokenizer_pool(tokenizer)) + return get_cached_tokenizer(tokenizer) diff --git a/vllm/tokenizers/qwen_vl.py b/vllm/tokenizers/qwen_vl.py index 899c813d0b0c..f36a22b02545 100644 --- a/vllm/tokenizers/qwen_vl.py +++ b/vllm/tokenizers/qwen_vl.py @@ -6,7 +6,7 @@ from transformers import AutoTokenizer -from .hf import HfTokenizer, get_cached_tokenizer, make_tokenizer_pool +from .hf import HfTokenizer, get_cached_tokenizer from .protocol import TokenizerLike @@ -68,5 +68,4 @@ class QwenVLTokenizer(TokenizerLike): @classmethod def from_pretrained(cls, *args, **kwargs) -> HfTokenizer: tokenizer = AutoTokenizer.from_pretrained(*args, **kwargs) - tokenizer = make_tokenizer_pool(tokenizer) return get_cached_tokenizer(get_qwen_vl_tokenizer(tokenizer)) diff --git a/vllm/tokenizers/registry.py b/vllm/tokenizers/registry.py index 73f81e75969f..552902d285ea 100644 --- a/vllm/tokenizers/registry.py +++ b/vllm/tokenizers/registry.py @@ -267,8 +267,4 @@ def cached_tokenizer_from_config(model_config: "ModelConfig", **kwargs): **kwargs, ) - # HF fast tokenizer specific method to reserve pool of tokenizers. - if hasattr(tok, "_reserve_pool"): - tok._reserve_pool(model_config.renderer_num_workers) - return tok From e324328eae2143a105838a7746e624b0196c4bfa Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Mon, 4 May 2026 17:36:47 -0400 Subject: [PATCH 5/8] Revert registry changes Signed-off-by: Yifan Zong --- vllm/tokenizers/registry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vllm/tokenizers/registry.py b/vllm/tokenizers/registry.py index 552902d285ea..8778aa9d691f 100644 --- a/vllm/tokenizers/registry.py +++ b/vllm/tokenizers/registry.py @@ -258,7 +258,7 @@ def cached_tokenizer_from_config(model_config: "ModelConfig", **kwargs): if model_config.skip_tokenizer_init: return None - tok = cached_get_tokenizer( + return cached_get_tokenizer( model_config.tokenizer, runner_type=model_config.runner_type, tokenizer_mode=model_config.tokenizer_mode, @@ -266,5 +266,3 @@ def cached_tokenizer_from_config(model_config: "ModelConfig", **kwargs): trust_remote_code=model_config.trust_remote_code, **kwargs, ) - - return tok From 22fab679bbc7de3e3cb7e78f689fdbb8d75d0ed4 Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Mon, 4 May 2026 17:57:12 -0400 Subject: [PATCH 6/8] Fix pre-commit Signed-off-by: Yifan Zong --- vllm/renderers/base.py | 9 +++++++-- vllm/tokenizers/hf.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/vllm/renderers/base.py b/vllm/renderers/base.py index c7cf188a4e5d..44b598eb1543 100644 --- a/vllm/renderers/base.py +++ b/vllm/renderers/base.py @@ -134,8 +134,13 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: config.observability_config ) - # Make HF fast tokenizer thread-safe by dispatching calls to tokenizer pool - self.tokenizer = maybe_make_thread_pool(tokenizer, pool_workers + 1) + # Make HF fast tokenizer thread-safe by dispatching calls to tokenizer + # pool when a tokenizer is available. + self.tokenizer = ( + None + if tokenizer is None + else maybe_make_thread_pool(tokenizer, pool_workers + 1) + ) def get_tokenizer(self) -> _T: tokenizer = self.tokenizer diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index 877f55e22a89..4cd7c96ae7a1 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -4,7 +4,7 @@ import copy import queue from pathlib import Path -from typing import TypeAlias +from typing import TypeAlias, TypeVar from transformers import AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast @@ -13,9 +13,10 @@ from .protocol import TokenizerLike HfTokenizer: TypeAlias = PreTrainedTokenizer | PreTrainedTokenizerFast +_T = TypeVar("_T", bound=TokenizerLike) -def maybe_make_thread_pool(tokenizer: TokenizerLike, copies: int = 1) -> TokenizerLike: +def maybe_make_thread_pool(tokenizer: _T, copies: int = 1) -> _T: """ If `tokenizer` is a `PreTrainedTokenizerFast`, modify the tokenizer in place to make the public interface thread-safe by routing calls From c5c0cd1649f0a0c88a1ebb067abb9455200700fc Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Mon, 4 May 2026 22:37:02 -0400 Subject: [PATCH 7/8] Fix tests Signed-off-by: Yifan Zong --- .../openai/chat_completion/test_chat_error.py | 1 + .../completion/test_completion_error.py | 1 + .../openai/completion/test_lora_resolvers.py | 1 + vllm/renderers/hf.py | 33 ++++++++----------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/tests/entrypoints/openai/chat_completion/test_chat_error.py b/tests/entrypoints/openai/chat_completion/test_chat_error.py index 582e0792156c..c6aaba109e6d 100644 --- a/tests/entrypoints/openai/chat_completion/test_chat_error.py +++ b/tests/entrypoints/openai/chat_completion/test_chat_error.py @@ -56,6 +56,7 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 + enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/tests/entrypoints/openai/completion/test_completion_error.py b/tests/entrypoints/openai/completion/test_completion_error.py index c95e47fa1b16..fdb4f3c44e86 100644 --- a/tests/entrypoints/openai/completion/test_completion_error.py +++ b/tests/entrypoints/openai/completion/test_completion_error.py @@ -55,6 +55,7 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 + enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/tests/entrypoints/openai/completion/test_lora_resolvers.py b/tests/entrypoints/openai/completion/test_lora_resolvers.py index 6a0bec92516d..fd7bda3ee8b9 100644 --- a/tests/entrypoints/openai/completion/test_lora_resolvers.py +++ b/tests/entrypoints/openai/completion/test_lora_resolvers.py @@ -55,6 +55,7 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 + enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/vllm/renderers/hf.py b/vllm/renderers/hf.py index 5341edbaa10d..51d90c17caa3 100644 --- a/vllm/renderers/hf.py +++ b/vllm/renderers/hf.py @@ -99,11 +99,12 @@ ) -def _maybe_register_prompt_embeds_placeholder_token( - model_config: ModelConfig, tokenizer: HfTokenizer -): - if not model_config.enable_prompt_embeds: - return +def _ensure_prompt_embeds_placeholder_token(tokenizer: HfTokenizer) -> int: + """Register `PROMPT_EMBEDS_PLACEHOLDER_TOKEN` as a special token and return + its token ID.""" + cached = _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE.get(tokenizer) + if cached is not None: + return cached tokenizer.add_special_tokens( {"additional_special_tokens": [PROMPT_EMBEDS_PLACEHOLDER_TOKEN]} @@ -121,17 +122,7 @@ def _maybe_register_prompt_embeds_placeholder_token( token_id = ids[0] _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE[tokenizer] = token_id - - -def _get_prompt_embeds_placeholder_token_id(tokenizer: HfTokenizer) -> int: - """Get the token ID of `PROMPT_EMBEDS_PLACEHOLDER_TOKEN`.""" - cached = _PROMPT_EMBEDS_PLACEHOLDER_TOKEN_ID_CACHE.get(tokenizer) - if cached is None: - raise RuntimeError( - "Prompt embeds placeholder token ID not found for tokenizer %s", - tokenizer.name_or_path, - ) - return cached + return token_id def _build_prompt_embeds_updates( @@ -794,7 +785,11 @@ def __init__( config: VllmConfig, tokenizer: HfTokenizer | None, ) -> None: - _maybe_register_prompt_embeds_placeholder_token(config.model_config, tokenizer) + if ( + config.model_config.enable_prompt_embeds + and isinstance(tokenizer, HfTokenizer) # skip for mock tokenizers + ): + _ensure_prompt_embeds_placeholder_token(tokenizer) super().__init__(config, tokenizer) self.use_unified_vision_chunk = getattr( @@ -816,7 +811,7 @@ def render_messages( prompt_embeds_placeholder_token_id: int | None = None if model_config.enable_prompt_embeds: prompt_embeds_placeholder_token_id = ( - _get_prompt_embeds_placeholder_token_id(tokenizer) + _ensure_prompt_embeds_placeholder_token(tokenizer) ) conversation, mm_data, mm_uuids = parse_chat_messages( @@ -923,7 +918,7 @@ async def render_messages_async( prompt_embeds_placeholder_token_id: int | None = None if model_config.enable_prompt_embeds: prompt_embeds_placeholder_token_id = ( - _get_prompt_embeds_placeholder_token_id(tokenizer) + _ensure_prompt_embeds_placeholder_token(tokenizer) ) conversation, mm_data, mm_uuids = await parse_chat_messages_async( From b488792721ee3da70d56f955ec3b574e25643ef2 Mon Sep 17 00:00:00 2001 From: Yifan Zong Date: Tue, 5 May 2026 01:51:42 -0400 Subject: [PATCH 8/8] Simplify tests and fix expensive ecursive copy Signed-off-by: Yifan Zong --- .../openai/chat_completion/test_chat_error.py | 1 - .../completion/test_completion_error.py | 1 - .../openai/completion/test_lora_resolvers.py | 1 - vllm/renderers/base.py | 16 +++++--------- vllm/renderers/hf.py | 15 ++++++++++--- vllm/tokenizers/hf.py | 21 ++++++++++++------- 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/tests/entrypoints/openai/chat_completion/test_chat_error.py b/tests/entrypoints/openai/chat_completion/test_chat_error.py index c6aaba109e6d..582e0792156c 100644 --- a/tests/entrypoints/openai/chat_completion/test_chat_error.py +++ b/tests/entrypoints/openai/chat_completion/test_chat_error.py @@ -56,7 +56,6 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 - enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/tests/entrypoints/openai/completion/test_completion_error.py b/tests/entrypoints/openai/completion/test_completion_error.py index fdb4f3c44e86..c95e47fa1b16 100644 --- a/tests/entrypoints/openai/completion/test_completion_error.py +++ b/tests/entrypoints/openai/completion/test_completion_error.py @@ -55,7 +55,6 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 - enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/tests/entrypoints/openai/completion/test_lora_resolvers.py b/tests/entrypoints/openai/completion/test_lora_resolvers.py index fd7bda3ee8b9..6a0bec92516d 100644 --- a/tests/entrypoints/openai/completion/test_lora_resolvers.py +++ b/tests/entrypoints/openai/completion/test_lora_resolvers.py @@ -55,7 +55,6 @@ class MockModelConfig: is_encoder_decoder: bool = False is_multimodal_model: bool = False renderer_num_workers: int = 1 - enable_prompt_embeds: bool = False def get_diff_sampling_param(self): return self.diff_sampling_param or {} diff --git a/vllm/renderers/base.py b/vllm/renderers/base.py index 44b598eb1543..41d8c0075fb1 100644 --- a/vllm/renderers/base.py +++ b/vllm/renderers/base.py @@ -37,7 +37,7 @@ from vllm.multimodal.processing import BaseMultiModalProcessor from vllm.multimodal.processing import ProcessorInputs as MMProcessorInputs from vllm.multimodal.registry import MultiModalTimingRegistry -from vllm.tokenizers import TokenizerLike, maybe_make_thread_pool +from vllm.tokenizers import TokenizerLike from vllm.utils.async_utils import ( AsyncMicrobatchTokenizer, make_async, @@ -79,6 +79,8 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: self.model_config = config.model_config self.api_process_rank = config.parallel_config._api_process_rank + self.tokenizer = tokenizer + # Shared thread pool executor for blocking tokenizer and # multimodal preprocessing operations. The multimodal processor # receives a deep-copied tokenizer (see #36557) so it is safe to @@ -108,7 +110,7 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: with set_default_torch_num_threads(): self.mm_processor = mm_registry.create_processor( config.model_config, - tokenizer=tokenizer, + tokenizer=self.tokenizer, cache=mm_processor_cache, ) @@ -123,7 +125,7 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: with set_default_torch_num_threads(): self._readonly_mm_processor = mm_registry.create_processor( config.model_config, - tokenizer=tokenizer, + tokenizer=self.tokenizer, cache=ro_cache, ) @@ -134,14 +136,6 @@ def __init__(self, config: "VllmConfig", tokenizer: _T | None) -> None: config.observability_config ) - # Make HF fast tokenizer thread-safe by dispatching calls to tokenizer - # pool when a tokenizer is available. - self.tokenizer = ( - None - if tokenizer is None - else maybe_make_thread_pool(tokenizer, pool_workers + 1) - ) - def get_tokenizer(self) -> _T: tokenizer = self.tokenizer if tokenizer is None: diff --git a/vllm/renderers/hf.py b/vllm/renderers/hf.py index 51d90c17caa3..6425bc647a1c 100644 --- a/vllm/renderers/hf.py +++ b/vllm/renderers/hf.py @@ -2,6 +2,7 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project from __future__ import annotations +import copy import inspect import itertools import weakref @@ -42,7 +43,7 @@ apply_token_matches, find_mm_placeholders, ) -from vllm.tokenizers.hf import HfTokenizer +from vllm.tokenizers.hf import HfTokenizer, maybe_make_thread_pool from vllm.transformers_utils.chat_templates import get_chat_template_fallback_path from vllm.transformers_utils.processor import cached_get_processor from vllm.utils.async_utils import make_async @@ -785,9 +786,12 @@ def __init__( config: VllmConfig, tokenizer: HfTokenizer | None, ) -> None: + # Ensure the og tokenizer is never modified by maybe_make_thread_pool + tokenizer = copy.copy(tokenizer) if ( - config.model_config.enable_prompt_embeds - and isinstance(tokenizer, HfTokenizer) # skip for mock tokenizers + # Skip for mock configs and tokenizers + getattr(config.model_config, "enable_prompt_embeds", False) + and isinstance(tokenizer, HfTokenizer) ): _ensure_prompt_embeds_placeholder_token(tokenizer) super().__init__(config, tokenizer) @@ -800,6 +804,11 @@ def __init__( safe_apply_chat_template, executor=self._executor ) + if self.tokenizer is not None: + maybe_make_thread_pool( + self.tokenizer, config.model_config.renderer_num_workers + 1 + ) + def render_messages( self, messages: list[ChatCompletionMessageParam], diff --git a/vllm/tokenizers/hf.py b/vllm/tokenizers/hf.py index 4cd7c96ae7a1..03c71adb8d65 100644 --- a/vllm/tokenizers/hf.py +++ b/vllm/tokenizers/hf.py @@ -16,10 +16,16 @@ _T = TypeVar("_T", bound=TokenizerLike) -def maybe_make_thread_pool(tokenizer: _T, copies: int = 1) -> _T: +class ThreadSafeHFTokenizerMixin: + """Mixin class for thread-safe HF fast tokenizers.""" + + pass + + +def maybe_make_thread_pool(tokenizer: _T, copies: int = 1): """ If `tokenizer` is a `PreTrainedTokenizerFast`, modify the tokenizer - in place to make the public interface thread-safe by routing calls + in-place to make the public interface thread-safe by routing calls through a deep-copied tokenizer pool. Note that: @@ -28,7 +34,9 @@ def maybe_make_thread_pool(tokenizer: _T, copies: int = 1) -> _T: methods like ``add_special_tokens`` or ``add_tokens``. - Adjacent method calls could happen on different deep copies. """ - if not isinstance(tokenizer, PreTrainedTokenizerFast): + if not isinstance(tokenizer, PreTrainedTokenizerFast) or isinstance( + tokenizer, ThreadSafeHFTokenizerMixin + ): return tokenizer og_tokenizer = copy.copy(tokenizer) @@ -43,12 +51,12 @@ def _borrow_from_pool(): tok = tokenizer_pool.get_nowait() yield tok except queue.Empty: - tok = copy.deepcopy(tokenizer) + tok = copy.deepcopy(og_tokenizer) yield tok finally: tokenizer_pool.put(tok) - class TokenizerPool(tokenizer.__class__): # type: ignore + class TokenizerPool(tokenizer.__class__, ThreadSafeHFTokenizerMixin): # type: ignore def apply_chat_template(self, *args, **kwargs): with _borrow_from_pool() as tok: return tok.apply_chat_template(*args, **kwargs) @@ -88,10 +96,9 @@ def __call__(self, *args, **kwargs): def __reduce__(self): return maybe_make_thread_pool, (og_tokenizer, copies) - TokenizerPool.__name__ = f"TokenizerPool{tokenizer.__class__.__name__}" + TokenizerPool.__name__ = f"TokenizerPool{og_tokenizer.__class__.__name__}" tokenizer.__class__ = TokenizerPool - return tokenizer def get_cached_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer: