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 8, 12:30 UTC
[Misc] Clean up validation logic in input processor (#34144)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Feb 11, 2026 at 11:29 UTC
b5dcb372e4ba04043a012475cea7cc901412f25a
3 files changed
+72
-86
tests/v1/engine/test_process_multi_modal_uuids.py
-1
@@ -20,7 +20,6 @@ def _build_input_processor(
20
) -> InputProcessor:
21
model_config = ModelConfig(
22
model="Qwen/Qwen2.5-VL-3B-Instruct",
23
- skip_tokenizer_init=True,
23
max_model_len=128,
24
mm_processor_cache_gb=mm_cache_gb,
25
)
vllm/multimodal/encoder_budget.py
+1
@@ -62,6 +62,7 @@ class MultiModalBudget:
62
processor = mm_registry.create_processor(model_config, cache=cache)
63
64
self.cache = cache
65
+ self.processor = processor
66
mm_config = model_config.get_multimodal_config()
67
enable_mm_embeds = mm_config is not None and mm_config.enable_mm_embeds
68
vllm/v1/engine/input_processor.py
+71
-85
@@ -72,13 +72,15 @@ class InputProcessor:
72
self.mm_registry = mm_registry
73
self.mm_processor_cache = mm_registry.processor_cache_from_config(vllm_config)
74
75
- self.mm_encoder_cache_size: int | None = None
76
- if (
77
- mm_registry.supports_multimodal_inputs(model_config)
78
- and not model_config.skip_tokenizer_init
79
- ):
75
+ self.supports_mm_inputs = mm_registry.supports_multimodal_inputs(model_config)
76
+ self.mm_encoder_cache_size = 0
77
+ self.skip_prompt_length_check = False
78
+ if self.supports_mm_inputs:
79
mm_budget = MultiModalBudget(vllm_config, mm_registry)
80
self.mm_encoder_cache_size = mm_budget.encoder_cache_size
81
+ self.skip_prompt_length_check = (
82
+ mm_budget.processor.info.skip_prompt_length_check
83
+ )
84
mm_budget.reset_cache() # Not used anymore
85
86
self.input_preprocessor = InputPreprocessor(
@@ -670,76 +672,25 @@ class InputProcessor:
672
resumable=resumable,
673
)
674
673
- def _validate_model_inputs(
674
- self, encoder_inputs: SingletonInputs | None, decoder_inputs: SingletonInputs
675
- ):
676
- if encoder_inputs is not None:
677
- self._validate_model_input(encoder_inputs, prompt_type="encoder")
678
-
679
- self._validate_model_input(decoder_inputs, prompt_type="decoder")
680
-
681
- def _validate_model_input(
675
+ def _validate_prompt_len(
676
self,
683
- prompt_inputs: SingletonInputs,
684
- *,
677
+ prompt_len: int,
678
prompt_type: Literal["encoder", "decoder"],
679
):
687
- model_config = self.model_config
688
-
689
- prompt_ids = (
690
- None
691
- if prompt_inputs["type"] == "embeds"
692
- else prompt_inputs["prompt_token_ids"]
693
- )
694
- prompt_embeds = (
695
- prompt_inputs["prompt_embeds"]
696
- if prompt_inputs["type"] == "embeds"
697
- else None
698
- )
699
- prompt_len = length_from_prompt_token_ids_or_embeds(prompt_ids, prompt_embeds)
700
- if not prompt_ids:
701
- if prompt_type == "encoder" and model_config.is_multimodal_model:
702
- pass # Mllama may have empty encoder inputs for text-only data
703
- elif prompt_inputs["type"] == "embeds":
704
- pass # Prompt embeds should not have prompt_ids.
705
- else:
706
- raise ValueError(f"The {prompt_type} prompt cannot be empty")
707
-
708
- tokenizer = self.tokenizer
709
- if tokenizer is not None:
710
- max_input_id = max(prompt_ids or (), default=0)
711
-
712
- # NOTE: tokenizer.max_token_id is the tokenizer’s vocab size while
713
- # self.model_config.get_vocab_size() is the model’s vocab size.
714
- # For Qwen3 models, the language model has extra tokens that do
715
- # not exist in the tokenizer, and vice versa for multimodal
716
- # placeholder tokens in some multimodal models.
717
- # See https://github.com/QwenLM/Qwen3/issues/29#issuecomment-1933720399 # noqa: E501
718
- # and https://github.com/vllm-project/vllm/pull/22471#discussion_r2312251421 # noqa: E501
680
+ if self.skip_prompt_length_check:
681
+ return
682
720
- # Here we take the max of the two to determine if a token id is
721
- # truly out-of-vocabulary.
722
- if max_input_id > max(
723
- tokenizer.max_token_id, self.model_config.get_vocab_size() - 1
724
- ):
725
- raise ValueError(f"Token id {max_input_id} is out of vocabulary")
683
+ if prompt_len == 0 and prompt_type == "decoder":
684
+ raise ValueError(f"The {prompt_type} prompt cannot be empty")
685
727
- max_prompt_len = self.model_config.max_model_len
686
+ model_config = self.model_config
687
+ max_prompt_len = (
688
+ model_config.max_model_len
689
+ if prompt_type == "decoder"
690
+ else self.mm_encoder_cache_size
691
+ )
692
if prompt_len > max_prompt_len:
729
- if model_config.is_multimodal_model:
730
- mm_registry = self.input_preprocessor.mm_registry
731
- model_cls = mm_registry._get_model_cls(model_config)
732
- factories = model_cls._processor_factory
733
- ctx = mm_registry._create_processing_ctx(
734
- model_config,
735
- tokenizer=tokenizer,
736
- )
737
- mm_info = factories.info(ctx)
738
-
739
- if mm_info.skip_prompt_length_check:
740
- return
741
-
742
- if model_config.is_multimodal_model:
693
+ if self.supports_mm_inputs:
694
suggestion = (
695
"Make sure that `max_model_len` is no smaller than the "
696
"number of text tokens plus multimodal tokens. For image "
@@ -757,17 +708,7 @@ class InputProcessor:
708
f"longer than the maximum model length of {max_prompt_len}. "
709
f"{suggestion}"
710
)
760
-
761
- # TODO: Find out how many placeholder tokens are there so we can
762
- # check that chunked prefill does not truncate them
763
- # max_batch_len = self.scheduler_config.max_num_batched_tokens
764
-
765
- if (
766
- prompt_len == max_prompt_len
767
- and prompt_type == "decoder"
768
- and not model_config.is_multimodal_model
769
- and self.model_config.runner_type != "pooling"
770
- ):
711
+ elif prompt_len == max_prompt_len and model_config.runner_type == "generate":
712
suggestion = (
713
"Make sure that `max_model_len` is no smaller than the "
714
"number of text tokens (prompt + requested output tokens)."
@@ -778,11 +719,29 @@ class InputProcessor:
719
f"model length of {max_prompt_len}. {suggestion}"
720
)
721
781
- if (
782
- prompt_type == "decoder"
783
- and prompt_inputs["type"] == "multimodal"
784
- and self.mm_encoder_cache_size is not None
785
- ):
722
+ def _validate_model_input(
723
+ self,
724
+ prompt_inputs: SingletonInputs,
725
+ prompt_type: Literal["encoder", "decoder"],
726
+ ) -> None:
727
+ model_config = self.model_config
728
+ tokenizer = self.tokenizer
729
+
730
+ prompt_ids = (
731
+ None
732
+ if prompt_inputs["type"] == "embeds"
733
+ else prompt_inputs["prompt_token_ids"]
734
+ )
735
+ prompt_embeds = (
736
+ prompt_inputs["prompt_embeds"]
737
+ if prompt_inputs["type"] == "embeds"
738
+ else None
739
+ )
740
+
741
+ prompt_len = length_from_prompt_token_ids_or_embeds(prompt_ids, prompt_embeds)
742
+ self._validate_prompt_len(prompt_len, prompt_type)
743
+
744
+ if prompt_inputs["type"] == "multimodal":
745
decoder_mm_positions = prompt_inputs["mm_placeholders"]
746
for modality, mm_positions in decoder_mm_positions.items():
747
for mm_position in mm_positions:
@@ -797,6 +756,33 @@ class InputProcessor:
756
f"by setting --limit-mm-per-prompt at startup."
757
)
758
759
+ if prompt_ids and tokenizer is not None:
760
+ max_input_id = max(prompt_ids, default=0)
761
+
762
+ # NOTE: tokenizer.max_token_id is the tokenizer’s vocab size while
763
+ # self.model_config.get_vocab_size() is the model’s vocab size.
764
+ # For Qwen3 models, the language model has extra tokens that do
765
+ # not exist in the tokenizer, and vice versa for multimodal
766
+ # placeholder tokens in some multimodal models.
767
+ # See https://github.com/QwenLM/Qwen3/issues/29#issuecomment-1933720399 # noqa: E501
768
+ # and https://github.com/vllm-project/vllm/pull/22471#discussion_r2312251421 # noqa: E501
769
+
770
+ # Here we take the max of the two to determine if a token id is
771
+ # truly out-of-vocabulary.
772
+ model_vocab_size = model_config.get_vocab_size()
773
+ if max_input_id > max(tokenizer.max_token_id, model_vocab_size - 1):
774
+ raise ValueError(f"Token id {max_input_id} is out of vocabulary")
775
+
776
+ def _validate_model_inputs(
777
+ self,
778
+ encoder_inputs: SingletonInputs | None,
779
+ decoder_inputs: SingletonInputs,
780
+ ):
781
+ if encoder_inputs is not None:
782
+ self._validate_model_input(encoder_inputs, prompt_type="encoder")
783
+
784
+ self._validate_model_input(decoder_inputs, prompt_type="decoder")
785
+
786
def stat_mm_cache(self) -> MultiModalCacheStats | None:
787
return self.input_preprocessor.stat_mm_cache()
788