@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 54e2f83d0a
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 7, 22:45 UTC

[Feature] Lazy import for the "mistral" tokenizer module. (#34651)

Signed-off-by: Neil Schemenauer <nas@arctrix.com>

Neil Schemenauer committed Feb 23, 2026 at 00:43 UTC 54e2f83d0a82462e0128e5d852e3d46fbb566a7f
14 files changed +68 -48
tests/models/multimodal/processing/test_common.py
+2 -2
@@ -23,7 +23,7 @@ from vllm.multimodal.cache import MultiModalProcessorOnlyCache
23 from vllm.multimodal.inputs import MultiModalInputs, batched_tensors_equal
24 from vllm.multimodal.processing import BaseMultiModalProcessor, InputProcessingContext
25 from vllm.tokenizers import TokenizerLike, cached_tokenizer_from_config
26 -from vllm.tokenizers.mistral import MistralTokenizer
26 +from vllm.utils.mistral import is_mistral_tokenizer
27
28 from ....multimodal.utils import random_audio, random_image, random_video
29 from ...registry import (
@@ -183,7 +183,7 @@ def get_text_token_prompts(
183
184 text_prompt: str | None
185 token_prompt: list[int]
186 - if isinstance(tokenizer, MistralTokenizer):
186 + if is_mistral_tokenizer(tokenizer):
187 # ChatCompletionRequest only supports ImageChunk natively;
188 # for other modalities (e.g. audio), fall back to the model's
189 # own dummy inputs builder which knows the right placeholders.
tests/reasoning/utils.py
+3 -3
@@ -4,7 +4,7 @@
4 from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
5 from vllm.entrypoints.openai.engine.protocol import DeltaMessage
6 from vllm.reasoning import ReasoningParser
7 -from vllm.tokenizers.mistral import MistralTokenizer
7 +from vllm.utils.mistral import is_mistral_tokenizer
8
9
10 class StreamingReasoningReconstructor:
@@ -59,7 +59,7 @@ def run_reasoning_extraction_mistral(
59 request: ChatCompletionRequest | None = None,
60 streaming: bool = False,
61 ) -> tuple[str | None, str | None]:
62 - assert isinstance(reasoning_parser.model_tokenizer, MistralTokenizer), type(
62 + assert is_mistral_tokenizer(reasoning_parser.model_tokenizer), type(
63 reasoning_parser.model_tokenizer
64 )
65 if streaming:
@@ -130,7 +130,7 @@ def run_reasoning_extraction_streaming_mistral(
130 model_deltas: list[int],
131 request: ChatCompletionRequest | None = None,
132 ) -> StreamingReasoningReconstructor:
133 - assert isinstance(reasoning_parser.model_tokenizer, MistralTokenizer), type(
133 + assert is_mistral_tokenizer(reasoning_parser.model_tokenizer), type(
134 reasoning_parser.model_tokenizer
135 )
136 request = request or ChatCompletionRequest(messages=[], model="test-model")
vllm/entrypoints/llm.py
+3 -3
@@ -83,9 +83,9 @@ from vllm.renderers.inputs.preprocess import (
83 from vllm.sampling_params import BeamSearchParams, RequestOutputKind, SamplingParams
84 from vllm.tasks import PoolingTask
85 from vllm.tokenizers import TokenizerLike
86 -from vllm.tokenizers.mistral import MistralTokenizer
86 from vllm.usage.usage_lib import UsageContext
87 from vllm.utils.counter import Counter
88 +from vllm.utils.mistral import is_mistral_tokenizer
89 from vllm.utils.tqdm_utils import maybe_tqdm
90 from vllm.v1.engine.llm_engine import LLMEngine
91 from vllm.v1.sample.logits_processor import LogitsProcessor
@@ -891,7 +891,7 @@ class LLM:
891 add_generation_prompt=add_generation_prompt,
892 continue_final_message=continue_final_message,
893 tools=tools,
894 - tokenize=isinstance(renderer.tokenizer, MistralTokenizer),
894 + tokenize=is_mistral_tokenizer(renderer.tokenizer),
895 ),
896 ),
897 )
@@ -1458,7 +1458,7 @@ class LLM:
1458 model_config = self.model_config
1459 tokenizer = self.get_tokenizer()
1460
1461 - if isinstance(tokenizer, MistralTokenizer):
1461 + if is_mistral_tokenizer(tokenizer):
1462 raise ValueError("Score API is not supported for Mistral tokenizer")
1463
1464 if len(data_1) == 1:
vllm/entrypoints/openai/chat_completion/serving.py
+10 -16
@@ -75,16 +75,12 @@ from vllm.parser import ParserManager
75 from vllm.reasoning import ReasoningParser
76 from vllm.sampling_params import BeamSearchParams, SamplingParams
77 from vllm.tokenizers import TokenizerLike
78 -from vllm.tokenizers.mistral import (
79 - MistralTokenizer,
80 - maybe_serialize_tool_calls,
81 - truncate_tool_call_ids,
82 - validate_request_params,
83 -)
78 from vllm.tool_parsers import ToolParser
79 from vllm.tool_parsers.mistral_tool_parser import MistralToolCall
80 from vllm.tool_parsers.utils import partial_json_loads
81 from vllm.utils.collection_utils import as_list
82 +from vllm.utils.mistral import is_mistral_tokenizer
83 +from vllm.utils.mistral import mt as _mt
84
85 logger = init_logger(__name__)
86
@@ -244,18 +240,18 @@ class OpenAIServingChat(OpenAIServing):
240
241 tool_parser = self.tool_parser
242
247 - if isinstance(tokenizer, MistralTokenizer):
243 + if is_mistral_tokenizer(tokenizer):
244 # because of issues with pydantic we need to potentially
245 # re-serialize the tool_calls field of the request
246 # for more info: see comment in `maybe_serialize_tool_calls`
251 - maybe_serialize_tool_calls(request) # type: ignore[arg-type]
252 - truncate_tool_call_ids(request) # type: ignore[arg-type]
253 - validate_request_params(request)
247 + _mt.maybe_serialize_tool_calls(request) # type: ignore[arg-type]
248 + _mt.truncate_tool_call_ids(request) # type: ignore[arg-type]
249 + _mt.validate_request_params(request)
250
251 # Check if tool parsing is unavailable (common condition)
252 tool_parsing_unavailable = (
253 tool_parser is None
258 - and not isinstance(tokenizer, MistralTokenizer)
254 + and not is_mistral_tokenizer(tokenizer)
255 and not self.use_harmony
256 )
257
@@ -639,8 +635,6 @@ class OpenAIServingChat(OpenAIServing):
635 request_metadata: RequestResponseMetadata,
636 reasoning_parser: ReasoningParser | None = None,
637 ) -> AsyncGenerator[str, None]:
642 - from vllm.tokenizers.mistral import MistralTokenizer
643 -
638 created_time = int(time.time())
639 chunk_object_type: Final = "chat.completion.chunk"
640 first_iteration = True
@@ -955,7 +949,7 @@ class OpenAIServingChat(OpenAIServing):
949 )
950 else:
951 # Generate ID based on tokenizer type
958 - if isinstance(tokenizer, MistralTokenizer):
952 + if is_mistral_tokenizer(tokenizer):
953 tool_call_id = MistralToolCall.generate_random_id()
954 else:
955 tool_call_id = make_tool_call_id(
@@ -1516,7 +1510,7 @@ class OpenAIServingChat(OpenAIServing):
1510 tool_parser_cls=self.tool_parser,
1511 )
1512 tool_call_class = (
1519 - MistralToolCall if isinstance(tokenizer, MistralTokenizer) else ToolCall
1513 + MistralToolCall if is_mistral_tokenizer(tokenizer) else ToolCall
1514 )
1515 if self.use_harmony:
1516 # Harmony models already have parsed content and tool_calls
@@ -1951,7 +1945,7 @@ class OpenAIServingChat(OpenAIServing):
1945 # because of issues with pydantic we need to potentially
1946 # re-serialize the tool_calls field of the request
1947 # for more info: see comment in `maybe_serialize_tool_calls`
1954 - maybe_serialize_tool_calls(request) # type: ignore[arg-type]
1948 + _mt.maybe_serialize_tool_calls(request) # type: ignore[arg-type]
1949
1950 # Add system message.
1951 # NOTE: In Chat Completion API, browsing is enabled by default
vllm/entrypoints/openai/engine/serving.py
+2 -3
@@ -128,6 +128,7 @@ from vllm.utils.async_utils import (
128 collect_from_async_generator,
129 merge_async_iterators,
130 )
131 +from vllm.utils.mistral import is_mistral_tokenizer
132
133
134 class GenerationError(Exception):
@@ -976,15 +977,13 @@ class OpenAIServing:
977 tool_dicts: list[dict[str, Any]] | None = None,
978 tool_parser: Callable[[TokenizerLike], ToolParser] | None = None,
979 ) -> tuple[list[ConversationMessage], list[ProcessorInputs]]:
979 - from vllm.tokenizers.mistral import MistralTokenizer
980 -
980 renderer = self.renderer
981
982 default_template_kwargs = merge_kwargs(
983 default_template_kwargs,
984 dict(
985 tools=tool_dicts,
987 - tokenize=isinstance(renderer.tokenizer, MistralTokenizer),
986 + tokenize=is_mistral_tokenizer(renderer.tokenizer),
987 ),
988 )
989
vllm/entrypoints/pooling/score/serving.py
+2 -2
@@ -41,8 +41,8 @@ from vllm.logger import init_logger
41 from vllm.lora.request import LoRARequest
42 from vllm.outputs import PoolingRequestOutput, ScoringRequestOutput
43 from vllm.tokenizers import TokenizerLike
44 -from vllm.tokenizers.mistral import MistralTokenizer
44 from vllm.utils.async_utils import make_async, merge_async_iterators
45 +from vllm.utils.mistral import is_mistral_tokenizer
46
47 logger = init_logger(__name__)
48
@@ -348,7 +348,7 @@ class ServingScores(OpenAIServing):
348 trace_headers: Mapping[str, str] | None = None,
349 ) -> list[PoolingRequestOutput] | ErrorResponse:
350 tokenizer = self.renderer.get_tokenizer()
351 - if isinstance(tokenizer, MistralTokenizer):
351 + if is_mistral_tokenizer(tokenizer):
352 raise ValueError("MistralTokenizer not supported for cross-encoding")
353
354 model_config = self.model_config
vllm/multimodal/processing/context.py
+2 -3
@@ -26,6 +26,7 @@ from vllm.tokenizers import TokenizerLike
26 from vllm.transformers_utils.processor import cached_processor_from_config
27 from vllm.utils.func_utils import get_allowed_kwarg_only_overrides
28 from vllm.utils.jsontree import JSONTree, json_map_leaves
29 +from vllm.utils.mistral import is_mistral_tokenizer
30
31 if TYPE_CHECKING:
32 from transformers.configuration_utils import PretrainedConfig
@@ -260,10 +261,8 @@ class InputProcessingContext:
261
262 typ = ProcessorMixin
263
263 - from vllm.tokenizers.mistral import MistralTokenizer
264 -
264 tokenizer = self.tokenizer
266 - if isinstance(tokenizer, MistralTokenizer):
265 + if is_mistral_tokenizer(tokenizer):
266 tokenizer = tokenizer.transformers_tokenizer
267
268 merged_kwargs = self.get_merged_mm_kwargs(kwargs)
vllm/sampling_params.py
+4 -4
@@ -16,6 +16,7 @@ from vllm.config import ModelConfig, SpeculativeConfig, StructuredOutputsConfig
16 from vllm.exceptions import VLLMValidationError
17 from vllm.logger import init_logger
18 from vllm.tokenizers import TokenizerLike
19 +from vllm.utils.mistral import is_mistral_tokenizer
20 from vllm.v1.serial_utils import PydanticMsgspecMixin
21
22 logger = init_logger(__name__)
@@ -731,7 +732,6 @@ class SamplingParams(
732 ):
733 raise ValueError("structured_outputs.grammar cannot be an empty string")
734
734 - from vllm.tokenizers.mistral import MistralTokenizer
735 from vllm.v1.structured_output.backend_guidance import (
736 has_guidance_unsupported_json_features,
737 validate_guidance_grammar,
@@ -752,7 +752,7 @@ class SamplingParams(
752 # allows <|special_token|> and similar, see
753 # https://github.com/guidance-ai/llguidance/blob/main/docs/syntax.md#special-tokens
754 # Without tokenizer these are disallowed in grammars.
755 - if isinstance(tokenizer, MistralTokenizer):
755 + if is_mistral_tokenizer(tokenizer):
756 raise ValueError(
757 "Mistral tokenizer is not supported for the 'guidance' "
758 "structured output backend. Please use ['xgrammar', 'outlines'] "
@@ -764,7 +764,7 @@ class SamplingParams(
764 validate_structured_output_request_outlines(self)
765 elif backend == "lm-format-enforcer":
766 # lm format enforcer backend
767 - if isinstance(tokenizer, MistralTokenizer):
767 + if is_mistral_tokenizer(tokenizer):
768 raise ValueError(
769 "Mistral tokenizer is not supported for the 'lm-format-enforcer' "
770 "structured output backend. Please use ['xgrammar', 'outlines'] "
@@ -796,7 +796,7 @@ class SamplingParams(
796 schema = so_params.json
797 skip_guidance = has_guidance_unsupported_json_features(schema)
798
799 - if isinstance(tokenizer, MistralTokenizer) or skip_guidance:
799 + if is_mistral_tokenizer(tokenizer) or skip_guidance:
800 # Fall back to outlines if the tokenizer is Mistral
801 # or if schema contains features unsupported by guidance
802 validate_structured_output_request_outlines(self)
vllm/tokenizers/mistral.py
+2
@@ -210,6 +210,8 @@ def _tekken_token_to_id(tokenizer: "Tekkenizer", t: str | bytes) -> int:
210
211
212 class MistralTokenizer(TokenizerLike):
213 + IS_MISTRAL_TOKENIZER = True # used by vllm.utils.mistral
214 +
215 @classmethod
216 def from_pretrained(
217 cls,
vllm/tool_parsers/hermes_tool_parser.py
+2 -2
@@ -22,10 +22,10 @@ from vllm.entrypoints.openai.engine.protocol import (
22 )
23 from vllm.logger import init_logger
24 from vllm.tokenizers import TokenizerLike
25 -from vllm.tokenizers.mistral import MistralTokenizer
25 from vllm.tool_parsers.abstract_tool_parser import (
26 ToolParser,
27 )
28 +from vllm.utils.mistral import is_mistral_tokenizer
29
30 logger = init_logger(__name__)
31
@@ -34,7 +34,7 @@ class Hermes2ProToolParser(ToolParser):
34 def __init__(self, tokenizer: TokenizerLike):
35 super().__init__(tokenizer)
36
37 - if isinstance(tokenizer, MistralTokenizer):
37 + if is_mistral_tokenizer(tokenizer):
38 logger.error("Detected Mistral tokenizer when using a Hermes model")
39 self.model_tokenizer = tokenizer.tokenizer
40
vllm/tool_parsers/jamba_tool_parser.py
+2 -2
@@ -22,9 +22,9 @@ from vllm.entrypoints.openai.engine.protocol import (
22 )
23 from vllm.logger import init_logger
24 from vllm.tokenizers import TokenizerLike
25 -from vllm.tokenizers.mistral import MistralTokenizer
25 from vllm.tool_parsers import ToolParser
26 from vllm.tool_parsers.utils import extract_intermediate_diff
27 +from vllm.utils.mistral import is_mistral_tokenizer
28
29 logger = init_logger(__name__)
30
@@ -33,7 +33,7 @@ class JambaToolParser(ToolParser):
33 def __init__(self, tokenizer: TokenizerLike):
34 super().__init__(tokenizer)
35
36 - if isinstance(self.model_tokenizer, MistralTokenizer):
36 + if is_mistral_tokenizer(self.model_tokenizer):
37 raise ValueError(
38 "Detected a MistralTokenizer tokenizer when using a Jamba model"
39 )
vllm/tool_parsers/mistral_tool_parser.py
+4 -6
@@ -25,10 +25,10 @@ from vllm.entrypoints.openai.engine.protocol import (
25 )
26 from vllm.logger import init_logger
27 from vllm.tokenizers import TokenizerLike
28 -from vllm.tokenizers.mistral import MistralTokenizer
28 from vllm.tool_parsers.abstract_tool_parser import (
29 ToolParser,
30 )
31 +from vllm.utils.mistral import is_mistral_tokenizer
32
33 logger = init_logger(__name__)
34
@@ -66,9 +66,7 @@ class MistralToolCall(ToolCall):
66
67
68 def _is_pre_v11_tokeniser(model_tokenizer: TokenizerLike) -> bool:
69 - return not (
70 - isinstance(model_tokenizer, MistralTokenizer) and model_tokenizer.version >= 11
71 - )
69 + return not (is_mistral_tokenizer(model_tokenizer) and model_tokenizer.version >= 11)
70
71
72 class MistralToolParser(ToolParser):
@@ -83,7 +81,7 @@ class MistralToolParser(ToolParser):
81 def __init__(self, tokenizer: TokenizerLike):
82 super().__init__(tokenizer)
83
86 - if not isinstance(self.model_tokenizer, MistralTokenizer):
84 + if not is_mistral_tokenizer(self.model_tokenizer):
85 logger.info("Non-Mistral tokenizer detected when using a Mistral model...")
86
87 # initialize properties used for state when parsing tool calls in
@@ -115,7 +113,7 @@ class MistralToolParser(ToolParser):
113 def adjust_request(self, request: ChatCompletionRequest) -> ChatCompletionRequest:
114 request = super().adjust_request(request)
115 if (
118 - not isinstance(self.model_tokenizer, MistralTokenizer)
116 + not is_mistral_tokenizer(self.model_tokenizer)
117 and request.tools
118 and request.tool_choice != "none"
119 ):
vllm/utils/mistral.py new
+28
@@ -0,0 +1,28 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Provides lazy import of the vllm.tokenizers.mistral module."""
4 +
5 +from __future__ import annotations
6 +
7 +from typing import TYPE_CHECKING, TypeGuard
8 +
9 +from vllm.tokenizers import TokenizerLike
10 +from vllm.utils.import_utils import LazyLoader
11 +
12 +if TYPE_CHECKING:
13 + # if type checking, eagerly import the module
14 + import vllm.tokenizers.mistral as mt
15 +else:
16 + mt = LazyLoader("mt", globals(), "vllm.tokenizers.mistral")
17 +
18 +
19 +def is_mistral_tokenizer(obj: TokenizerLike | None) -> TypeGuard[mt.MistralTokenizer]:
20 + """Return true if the tokenizer is a MistralTokenizer instance."""
21 + cls = type(obj)
22 + # Check for special class attribute, this avoids importing the class to
23 + # do an isinstance() check. If the attribute is True, do an isinstance
24 + # check to be sure we have the correct type.
25 + return bool(
26 + getattr(cls, "IS_MISTRAL_TOKENIZER", False)
27 + and isinstance(obj, mt.MistralTokenizer)
28 + )
vllm/v1/structured_output/backend_xgrammar.py
+2 -2
@@ -10,8 +10,8 @@ import torch
10 import vllm.envs
11 from vllm.logger import init_logger
12 from vllm.sampling_params import SamplingParams
13 -from vllm.tokenizers.mistral import MistralTokenizer
13 from vllm.utils.import_utils import LazyLoader
14 +from vllm.utils.mistral import is_mistral_tokenizer
15 from vllm.v1.structured_output.backend_types import (
16 StructuredOutputBackend,
17 StructuredOutputGrammar,
@@ -38,7 +38,7 @@ class XgrammarBackend(StructuredOutputBackend):
38 self.vllm_config.structured_outputs_config.disable_any_whitespace
39 )
40
41 - if isinstance(self.tokenizer, MistralTokenizer):
41 + if is_mistral_tokenizer(self.tokenizer):
42 # NOTE: ideally, xgrammar should handle this accordingly.
43 # refer to https://github.com/mlc-ai/xgrammar/blob/d77c0a0173ef14779c918e3be7966ba852f7910f/python/xgrammar/tokenizer_info.py#L98
44 stop_token_ids = [self.tokenizer.eos_token_id]