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 12, 18:30 UTC
Default model load/config/tokenizer to `mistral` format if relevant files exist (#28659)
Signed-off-by: Julien Denize <julien.denize@mistral.ai> Signed-off-by: Julien Denize <40604584+juliendenize@users.noreply.github.com> Signed-off-by: mgoin <mgoin64@gmail.com> Signed-off-by: Michael Goin <mgoin64@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: mgoin <mgoin64@gmail.com>
Julien Denize committed
Nov 21, 2025 at 22:58 UTC
57430fc95c8a94a7c68b3d525e3b8823b0f2433f
15 files changed
+230
-34
docs/features/tool_calling.md
+18
-5
@@ -142,7 +142,7 @@ Flags: `--tool-call-parser hermes`
142
Supported models:
143
144
* `mistralai/Mistral-7B-Instruct-v0.3` (confirmed)
145
-* Additional mistral function-calling models are compatible as well.
145
+* Additional Mistral function-calling models are compatible as well.
146
147
Known issues:
148
@@ -158,12 +158,25 @@ Known issues:
158
159
Recommended flags:
160
161
-1. To use [mistral-common](https://github.com/mistralai/mistral-common) the official Mistral tokenization backend:
161
+1. To use the official Mistral AI's format:
162
163
- `--tokenizer_mode mistral --config_format mistral --load_format mistral --tool-call-parser mistral`
163
+ `--tool-call-parser mistral`
164
165
-2. To use the default Transformers tokenization backend:
166
- `--tool-call-parser mistral --chat-template examples/tool_chat_template_mistral_parallel.jinja`
165
+2. To use the Transformers format when available:
166
+
167
+ `--tokenizer_mode hf --config_format hf --load_format hf --tool-call-parser mistral --chat-template examples/tool_chat_template_mistral_parallel.jinja`
168
+
169
+!!! note
170
+ Models officially released by Mistral AI have two possible formats:
171
+
172
+ 1. The official format that is used by default with `auto` or `mistral` arguments:
173
+
174
+ `--tokenizer_mode mistral --config_format mistral --load_format mistral`
175
+ This format uses [mistral-common](https://github.com/mistralai/mistral-common), the Mistral AI's tokenizer backend.
176
+
177
+ 2. The Transformers format, when available, that is used with `hf` arguments:
178
+
179
+ `--tokenizer_mode hf --config_format hf --load_format hf --chat-template examples/tool_chat_template_mistral_parallel.jinja`
180
181
### Llama Models (`llama3_json`)
182
tests/models/language/generation/test_mistral.py
+1
-1
@@ -208,7 +208,7 @@ def test_mistral_format(
208
with vllm_runner(
209
model,
210
dtype=dtype,
211
- tokenizer_mode="auto",
211
+ tokenizer_mode="hf",
212
load_format="safetensors",
213
config_format="hf",
214
) as hf_format_model:
tests/models/multimodal/test_mapping.py
+13
-1
@@ -50,12 +50,24 @@ def test_hf_model_weights_mapper(model_arch: str):
50
model_info.check_available_online(on_fail="skip")
51
model_info.check_transformers_version(on_fail="skip")
52
53
+ is_mistral_model = model_arch in [
54
+ "Mistral3ForConditionalGeneration",
55
+ "PixtralForConditionalGeneration",
56
+ "VoxtralForConditionalGeneration",
57
+ ]
58
+
59
+ if not is_mistral_model or model_info.tokenizer_mode == "mistral":
60
+ tokenizer_mode = model_info.tokenizer_mode
61
+ else:
62
+ tokenizer_mode = "hf"
63
+
64
model_id = model_info.default
65
66
model_config = ModelConfig(
67
model_id,
68
tokenizer=model_info.tokenizer or model_id,
58
- tokenizer_mode=model_info.tokenizer_mode,
69
+ tokenizer_mode=tokenizer_mode,
70
+ config_format="hf",
71
revision=model_info.revision,
72
trust_remote_code=model_info.trust_remote_code,
73
hf_overrides=model_info.hf_overrides,
tests/models/quantization/test_bitsandbytes.py
+3
@@ -259,6 +259,9 @@ def validate_generated_texts(
259
tensor_parallel_size=vllm_tp_size,
260
enforce_eager=False,
261
default_torch_num_threads=1,
262
+ tokenizer_mode="hf",
263
+ load_format="hf",
264
+ config_format="hf",
265
) as llm:
266
vllm_outputs = llm.generate_greedy(prompts, max_tokens)
267
vllm_logs = log_generated_texts(prompts, vllm_outputs, "VllmRunner")
tests/tool_use/utils.py
+6
@@ -128,6 +128,12 @@ CONFIGS: dict[str, ServerConfig] = {
128
"arguments": [
129
"--enforce-eager",
130
"--no-enable-prefix-caching",
131
+ "--tokenizer_mode",
132
+ "hf",
133
+ "--load_format",
134
+ "hf",
135
+ "--config_format",
136
+ "hf",
137
"--tool-call-parser",
138
"mistral",
139
"--chat-template",
tests/transformers_utils/test_config.py
new
+62
@@ -0,0 +1,62 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+
5
+import tempfile
6
+from pathlib import Path
7
+from unittest.mock import MagicMock, call, patch
8
+
9
+import pytest
10
+
11
+from vllm.transformers_utils.config import list_filtered_repo_files
12
+
13
+
14
+@pytest.mark.parametrize(
15
+ "allow_patterns,expected_relative_files",
16
+ [
17
+ (
18
+ ["*.json", "correct*.txt"],
19
+ ["json_file.json", "subfolder/correct.txt", "correct_2.txt"],
20
+ ),
21
+ ],
22
+)
23
+def test_list_filtered_repo_files(
24
+ allow_patterns: list[str], expected_relative_files: list[str]
25
+):
26
+ with tempfile.TemporaryDirectory() as tmp_dir:
27
+ # Prep folder and files
28
+ path_tmp_dir = Path(tmp_dir)
29
+ subfolder = path_tmp_dir / "subfolder"
30
+ subfolder.mkdir()
31
+ (path_tmp_dir / "json_file.json").touch()
32
+ (path_tmp_dir / "correct_2.txt").touch()
33
+ (path_tmp_dir / "uncorrect.txt").touch()
34
+ (path_tmp_dir / "uncorrect.jpeg").touch()
35
+ (subfolder / "correct.txt").touch()
36
+ (subfolder / "uncorrect_sub.txt").touch()
37
+
38
+ def _glob_path() -> list[str]:
39
+ return [
40
+ str(file.relative_to(path_tmp_dir))
41
+ for file in path_tmp_dir.glob("**/*")
42
+ if file.is_file()
43
+ ]
44
+
45
+ # Patch list_repo_files called by fn
46
+ with patch(
47
+ "vllm.transformers_utils.config.list_repo_files",
48
+ MagicMock(return_value=_glob_path()),
49
+ ) as mock_list_repo_files:
50
+ out_files = sorted(
51
+ list_filtered_repo_files(
52
+ tmp_dir, allow_patterns, "revision", "model", "token"
53
+ )
54
+ )
55
+ assert out_files == sorted(expected_relative_files)
56
+ assert mock_list_repo_files.call_count == 1
57
+ assert mock_list_repo_files.call_args_list[0] == call(
58
+ repo_id=tmp_dir,
59
+ revision="revision",
60
+ repo_type="model",
61
+ token="token",
62
+ )
tests/transformers_utils/test_utils.py
+5
-1
@@ -2,7 +2,11 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5
-from vllm.transformers_utils.utils import is_cloud_storage, is_gcs, is_s3
5
+from vllm.transformers_utils.utils import (
6
+ is_cloud_storage,
7
+ is_gcs,
8
+ is_s3,
9
+)
10
11
12
def test_is_gcs():
tests/v1/entrypoints/llm/test_struct_output_generate.py
+11
-3
@@ -46,11 +46,15 @@ EAGLE_SPEC_CONFIG = {
46
47
PARAMS_MODELS_BACKENDS_TOKENIZER_MODE = [
48
("mistralai/Ministral-8B-Instruct-2410", "xgrammar", "auto", None),
49
- ("mistralai/Ministral-8B-Instruct-2410", "guidance", "auto", None),
49
+ # FIXME: Since "auto" will use Mistral tokenizer and these backends do not support
50
+ # it, we skip these tests for now.
51
+ # ("mistralai/Ministral-8B-Instruct-2410", "guidance", "auto", None),
52
+ # ("mistralai/Ministral-8B-Instruct-2410", "lm-format-enforcer", "auto", None),
53
+ ("mistralai/Ministral-8B-Instruct-2410", "guidance", "hf", None),
54
pytest.param(
55
"mistralai/Ministral-8B-Instruct-2410",
56
"lm-format-enforcer",
53
- "auto",
57
+ "hf",
58
None,
59
marks=pytest.mark.skip(
60
reason=(
@@ -80,7 +84,7 @@ PARAMS_MODELS_BACKENDS_TOKENIZER_MODE = [
84
# ("mistralai/Ministral-8B-Instruct-2410", "outlines", "mistral", None),
85
# ("Qwen/Qwen2.5-1.5B-Instruct", "guidance", "auto"),
86
("mistralai/Ministral-8B-Instruct-2410", "outlines", "auto", NGRAM_SPEC_CONFIG),
83
- ("mistralai/Ministral-8B-Instruct-2410", "guidance", "auto", NGRAM_SPEC_CONFIG),
87
+ ("mistralai/Ministral-8B-Instruct-2410", "guidance", "hf", NGRAM_SPEC_CONFIG),
88
("Qwen/Qwen2.5-1.5B-Instruct", "xgrammar", "auto", NGRAM_SPEC_CONFIG),
89
("meta-llama/Meta-Llama-3.1-8B-Instruct", "xgrammar", "auto", EAGLE_SPEC_CONFIG),
90
]
@@ -151,6 +155,8 @@ def test_structured_output(
155
),
156
seed=120,
157
tokenizer_mode=tokenizer_mode,
158
+ load_format="auto" if not model_name.startswith("mistralai/") else "hf",
159
+ config_format="auto" if not model_name.startswith("mistralai/") else "hf",
160
speculative_config=speculative_config,
161
)
162
@@ -720,6 +726,8 @@ def test_structured_output_auto_mode(
726
max_model_len=1024,
727
structured_outputs_config=dict(backend="auto"),
728
tokenizer_mode=tokenizer_mode,
729
+ load_format="auto",
730
+ config_format="auto",
731
)
732
733
sampling_params = SamplingParams(
vllm/config/model.py
+5
-4
@@ -81,7 +81,7 @@ TaskOption = Literal[
81
"transcription",
82
"draft",
83
]
84
-TokenizerMode = Literal["auto", "slow", "mistral", "custom"]
84
+TokenizerMode = Literal["auto", "hf", "slow", "mistral", "custom"]
85
ModelDType = Literal["auto", "half", "float16", "bfloat16", "float", "float32"]
86
LogprobsMode = Literal[
87
"raw_logits", "raw_logprobs", "processed_logits", "processed_logprobs"
@@ -130,7 +130,8 @@ class ModelConfig:
130
name or path will be used."""
131
tokenizer_mode: TokenizerMode = "auto"
132
"""Tokenizer mode:\n
133
- - "auto" will use the fast tokenizer if available.\n
133
+ - "auto" will use "hf" tokenizer if Mistral's tokenizer is not available.\n
134
+ - "hf" will use the fast tokenizer if available.\n
135
- "slow" will always use the slow tokenizer.\n
136
- "mistral" will always use the tokenizer from `mistral_common`.\n
137
- "custom" will use --tokenizer to select the preregistered tokenizer."""
@@ -241,8 +242,8 @@ class ModelConfig:
242
first one."""
243
config_format: str | ConfigFormat = "auto"
244
"""The format of the model config to load:\n
244
- - "auto" will try to load the config in hf format if available else it
245
- will try to load in mistral format.\n
245
+ - "auto" will try to load the config in hf format if available after trying
246
+ to load in mistral format.\n
247
- "hf" will load the config in hf format.\n
248
- "mistral" will load the config in mistral format."""
249
hf_token: bool | str | None = None
vllm/model_executor/model_loader/__init__.py
+2
@@ -30,6 +30,7 @@ logger = init_logger(__name__)
30
# if a new load format is added here
31
LoadFormats = Literal[
32
"auto",
33
+ "hf",
34
"bitsandbytes",
35
"dummy",
36
"fastsafetensors",
@@ -45,6 +46,7 @@ LoadFormats = Literal[
46
]
47
_LOAD_FORMAT_TO_MODEL_LOADER: dict[str, type[BaseModelLoader]] = {
48
"auto": DefaultModelLoader,
49
+ "hf": DefaultModelLoader,
50
"bitsandbytes": BitsAndBytesModelLoader,
51
"dummy": DummyModelLoader,
52
"fastsafetensors": DefaultModelLoader,
vllm/model_executor/model_loader/default_loader.py
+19
-1
@@ -31,6 +31,7 @@ from vllm.model_executor.model_loader.weight_utils import (
31
safetensors_weights_iterator,
32
)
33
from vllm.platforms import current_platform
34
+from vllm.transformers_utils.config import list_filtered_repo_files
35
36
logger = init_logger(__name__)
37
@@ -96,8 +97,25 @@ class DefaultModelLoader(BaseModelLoader):
97
load_format = self.load_config.load_format
98
use_safetensors = False
99
index_file = SAFE_WEIGHTS_INDEX_NAME
99
- # Some quantized models use .pt files for storing the weights.
100
+
101
+ # First check for 'auto' format that mistral files format are present.
102
+ # This is to load mistral models with official format by default.
103
if load_format == "auto":
104
+ load_format = (
105
+ "mistral"
106
+ if len(
107
+ list_filtered_repo_files(
108
+ model_name_or_path=model_name_or_path,
109
+ allow_patterns=["consolidated*.safetensors"],
110
+ revision=revision,
111
+ )
112
+ )
113
+ > 0
114
+ else "hf"
115
+ )
116
+
117
+ # Some quantized models use .pt files for storing the weights.
118
+ if load_format == "hf":
119
allow_patterns = ["*.safetensors", "*.bin"]
120
elif load_format == "safetensors" or load_format == "fastsafetensors":
121
use_safetensors = True
vllm/transformers_utils/config.py
+43
-3
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+import fnmatch
5
import json
6
import os
7
import time
@@ -355,6 +356,41 @@ def list_repo_files(
356
return with_retry(lookup_files, "Error retrieving file list")
357
358
359
+def list_filtered_repo_files(
360
+ model_name_or_path: str,
361
+ allow_patterns: list[str],
362
+ revision: str | None = None,
363
+ repo_type: str | None = None,
364
+ token: str | bool | None = None,
365
+) -> list[str]:
366
+ try:
367
+ all_files = list_repo_files(
368
+ repo_id=model_name_or_path,
369
+ revision=revision,
370
+ token=token,
371
+ repo_type=repo_type,
372
+ )
373
+ except Exception:
374
+ logger.error(
375
+ "Error retrieving file list. Please ensure your `model_name_or_path`"
376
+ "`repo_type`, `token` and `revision` arguments are correctly set. "
377
+ "Returning an empty list."
378
+ )
379
+ return []
380
+
381
+ file_list = []
382
+ # Filter patterns on filenames
383
+ for pattern in allow_patterns:
384
+ file_list.extend(
385
+ [
386
+ file
387
+ for file in all_files
388
+ if fnmatch.fnmatch(os.path.basename(file), pattern)
389
+ ]
390
+ )
391
+ return file_list
392
+
393
+
394
def file_exists(
395
repo_id: str,
396
file_name: str,
@@ -619,10 +655,14 @@ def get_config(
655
656
if config_format == "auto":
657
try:
622
- if is_gguf or file_or_path_exists(model, HF_CONFIG_NAME, revision=revision):
623
- config_format = "hf"
624
- elif file_or_path_exists(model, MISTRAL_CONFIG_NAME, revision=revision):
658
+ # First check for Mistral to avoid defaulting to
659
+ # Transformers implementation.
660
+ if file_or_path_exists(model, MISTRAL_CONFIG_NAME, revision=revision):
661
config_format = "mistral"
662
+ elif is_gguf or file_or_path_exists(
663
+ model, HF_CONFIG_NAME, revision=revision
664
+ ):
665
+ config_format = "hf"
666
else:
667
raise ValueError(
668
"Could not detect config format for no config file found. "
vllm/transformers_utils/configs/mistral.py
+1
-1
@@ -118,7 +118,7 @@ def _remap_general_mistral_args(config: dict) -> dict:
118
"model_type": ("model_type", "transformer"),
119
"hidden_act": ("activation", "silu"),
120
"tie_word_embeddings": ("tied_embeddings", False),
121
- "max_seq_len": ("max_seq_len", 128_000),
121
+ "max_seq_len": ("max_seq_len", config.get("max_position_embeddings", 128_000)),
122
"max_position_embeddings": ("max_position_embeddings", 128_000),
123
}
124
vllm/transformers_utils/tokenizer.py
+19
-11
@@ -3,8 +3,8 @@
3
4
import contextlib
5
import copy
6
+import importlib.util
7
import os
7
-import warnings
8
from functools import lru_cache
9
from pathlib import Path
10
from typing import TYPE_CHECKING, Any, TypeAlias
@@ -15,7 +15,10 @@ from typing_extensions import assert_never
15
16
from vllm import envs
17
from vllm.logger import init_logger
18
-from vllm.transformers_utils.config import get_sentence_transformer_tokenizer_config
18
+from vllm.transformers_utils.config import (
19
+ get_sentence_transformer_tokenizer_config,
20
+ list_filtered_repo_files,
21
+)
22
from vllm.transformers_utils.tokenizers import MistralTokenizer
23
from vllm.transformers_utils.utils import check_gguf_file
24
@@ -182,25 +185,29 @@ def get_tokenizer(
185
kwargs["gguf_file"] = Path(tokenizer_name).name
186
tokenizer_name = Path(tokenizer_name).parent
187
185
- # if tokenizer is from official mistral org
186
- is_from_mistral_org = str(tokenizer_name).split("/")[0] == "mistralai"
187
- if is_from_mistral_org and tokenizer_mode != "mistral":
188
- warnings.warn(
189
- "It is strongly recommended to run mistral models with "
190
- '`--tokenizer-mode "mistral"` to ensure correct '
191
- "encoding and decoding.",
192
- FutureWarning,
193
- stacklevel=2,
188
+ # if `tokenizer_mode` == "auto", check if tokenizer can be loaded via Mistral format
189
+ # first to use official Mistral tokenizer if possible.
190
+ mistral_common_installed = importlib.util.find_spec("mistral_common") is not None
191
+ if tokenizer_mode == "auto" and mistral_common_installed:
192
+ allow_patterns = ["tekken.json", "tokenizer.model.v*"]
193
+ files_list = list_filtered_repo_files(
194
+ model_name_or_path=str(tokenizer_name),
195
+ allow_patterns=allow_patterns,
196
+ revision=revision,
197
)
198
+ if len(files_list) > 0:
199
+ tokenizer_mode = "mistral"
200
201
tokenizer: AnyTokenizer
202
if tokenizer_mode == "mistral":
203
+ logger.debug_once(f"Loading MistralTokenizer from {tokenizer_name}")
204
tokenizer = MistralTokenizer.from_pretrained(
205
str(tokenizer_name), revision=revision
206
)
207
elif tokenizer_mode == "custom":
208
from vllm.transformers_utils.tokenizer_base import TokenizerRegistry
209
210
+ logger.debug_once(f"Loading CustomTokenizer from {tokenizer_name}")
211
tokenizer = TokenizerRegistry.get_tokenizer(
212
str(tokenizer_name),
213
*args,
@@ -210,6 +217,7 @@ def get_tokenizer(
217
)
218
else:
219
try:
220
+ logger.debug_once(f"Loading AutoTokenizer from {tokenizer_name}")
221
tokenizer = AutoTokenizer.from_pretrained(
222
tokenizer_name,
223
*args,
vllm/v1/engine/processor.py
+22
-3
@@ -20,6 +20,7 @@ from vllm.multimodal.utils import argsort_mm_positions
20
from vllm.pooling_params import PoolingParams
21
from vllm.sampling_params import SamplingParams
22
from vllm.transformers_utils.tokenizer import AnyTokenizer
23
+from vllm.transformers_utils.tokenizers.mistral import MistralTokenizer
24
from vllm.utils import length_from_prompt_token_ids_or_embeds
25
from vllm.v1.engine import EngineCoreRequest
26
from vllm.v1.metrics.stats import MultiModalCacheStats
@@ -300,12 +301,24 @@ class Processor:
301
# allows <|special_token|> and similar, see
302
# https://github.com/guidance-ai/llguidance/blob/main/docs/syntax.md#special-tokens
303
# Without tokenizer these are disallowed in grammars.
304
+ if isinstance(self.tokenizer, MistralTokenizer):
305
+ raise ValueError(
306
+ "Mistral tokenizer is not supported for the 'guidance' "
307
+ "structured output backend. Please use ['xgrammar', 'outlines'] "
308
+ "backends or tokenizer_mode='hf' instead."
309
+ )
310
validate_guidance_grammar(params, tokenizer=None)
311
elif backend == "outlines":
312
# outlines backend
313
validate_structured_output_request_outlines(params)
314
elif backend == "lm-format-enforcer":
315
# lm format enforcer backend
316
+ if isinstance(self.tokenizer, MistralTokenizer):
317
+ raise ValueError(
318
+ "Mistral tokenizer is not supported for the 'lm-format-enforcer' "
319
+ "structured output backend. Please use ['xgrammar', 'outlines'] "
320
+ "backends or tokenizer_mode='hf' instead."
321
+ )
322
validate_structured_output_request_lm_format_enforcer(params)
323
else:
324
# NOTE: backend must be "auto" here, because we have
@@ -320,9 +333,15 @@ class Processor:
333
except ValueError:
334
# The request either failed validation
335
# or includes some jsonschema feature(s) that
323
- # are not supported in xgrammar. Fall back to guidance.
324
- validate_guidance_grammar(params, tokenizer=None)
325
- params.structured_outputs._backend = "guidance"
336
+ # are not supported in xgrammar.
337
+ if isinstance(self.tokenizer, MistralTokenizer):
338
+ # Fall back to outlines if the tokenizer is Mistral
339
+ validate_structured_output_request_outlines(params)
340
+ params.structured_outputs._backend = "outlines"
341
+ else:
342
+ # Fall back to guidance by default.
343
+ validate_guidance_grammar(params, tokenizer=None)
344
+ params.structured_outputs._backend = "guidance"
345
# Remember that this backend was set automatically
346
params.structured_outputs._backend_was_auto = True
347