[Bugfix][Frontend] Validate stop_token_ids against vocab size (#54196)
Signed-off-by: QwertyJack <7554089+QwertyJack@users.noreply.github.com> Co-authored-by: QwertyJack <7554089+QwertyJack@users.noreply.github.com>
jack committed
Aug 30, 2026 at 00:25 UTC
5b0e5b69ac1a3884a6479c9537789c95263cc804
1 file changed
+46
-15
vllm/sampling_params.py
+46
-15
@@ -789,8 +789,9 @@ class SamplingParams(
789
self._validate_logprobs(model_config)
790
self._validate_logit_bias(model_config)
791
self._validate_trace_replay(model_config, speculative_config)
792
+ self._validate_stop_token_ids(model_config)
793
self._validate_logits_processors(model_config)
793
- self._validate_allowed_token_ids(tokenizer)
794
+ self._validate_allowed_token_ids(model_config)
795
self._validate_spec_decode(speculative_config)
796
self._validate_diffusion(model_config)
797
self._validate_structured_outputs(
@@ -859,6 +860,31 @@ class SamplingParams(
860
value=num_prompt_logprobs,
861
)
862
863
+ def _validate_stop_token_ids(self, model_config: ModelConfig) -> None:
864
+ """Validate stop_token_ids are within vocabulary range."""
865
+ if not self.stop_token_ids:
866
+ return
867
+
868
+ # stop_token_ids are used as column indices into the logits tensor,
869
+ # whose width is the model's vocab size (LogitsProcessor is built from
870
+ # config.vocab_size, InputBatch.vocab_size comes from
871
+ # model_config.get_vocab_size()), so use the same bound here — like
872
+ # _validate_logit_bias, which indexes the same tensor.
873
+ vocab_size = model_config.get_vocab_size()
874
+ invalid_token_ids = [
875
+ token_id
876
+ for token_id in self.stop_token_ids
877
+ if token_id < 0 or token_id >= vocab_size
878
+ ]
879
+
880
+ if invalid_token_ids:
881
+ raise VLLMValidationError(
882
+ f"token_id(s) {invalid_token_ids} in stop_token_ids contain "
883
+ f"out-of-vocab token ids. Vocabulary size: {vocab_size}",
884
+ parameter="stop_token_ids",
885
+ value=invalid_token_ids,
886
+ )
887
+
888
def _validate_logit_bias(self, model_config: ModelConfig) -> None:
889
"""Validate logit_bias token IDs are within vocabulary range."""
890
if not self.logit_bias:
@@ -941,7 +967,7 @@ class SamplingParams(
967
968
validate_logits_processors_parameters(model_config.logits_processors, self)
969
944
- def _validate_allowed_token_ids(self, tokenizer: TokenizerLike | None) -> None:
970
+ def _validate_allowed_token_ids(self, model_config: ModelConfig) -> None:
971
allowed_token_ids = self.allowed_token_ids
972
if allowed_token_ids is None:
973
return
@@ -953,19 +979,24 @@ class SamplingParams(
979
value=allowed_token_ids,
980
)
981
956
- if tokenizer is not None:
957
- vocab_size = len(tokenizer)
958
- invalid_token_ids = [
959
- token_id
960
- for token_id in allowed_token_ids
961
- if token_id < 0 or token_id >= vocab_size
962
- ]
963
- if invalid_token_ids:
964
- raise VLLMValidationError(
965
- "allowed_token_ids contains out-of-vocab token id!",
966
- parameter="allowed_token_ids",
967
- value=invalid_token_ids,
968
- )
982
+ # allowed_token_ids are client-supplied ids used as column indices
983
+ # into the logits tensor (the mask in InputBatch is sized by
984
+ # model_config.get_vocab_size(), and the ids are written as
985
+ # mask[req_index][allowed_token_ids]), so use the same bound here —
986
+ # like _validate_stop_token_ids and _validate_logit_bias, which
987
+ # index the same tensor.
988
+ vocab_size = model_config.get_vocab_size()
989
+ invalid_token_ids = [
990
+ token_id
991
+ for token_id in allowed_token_ids
992
+ if token_id < 0 or token_id >= vocab_size
993
+ ]
994
+ if invalid_token_ids:
995
+ raise VLLMValidationError(
996
+ "allowed_token_ids contains out-of-vocab token id!",
997
+ parameter="allowed_token_ids",
998
+ value=invalid_token_ids,
999
+ )
1000
1001
def _validate_spec_decode(
1002
self,