Repository navigation
Conversation
_validate_input_ids_in_vocab existed but had zero callers, so user-supplied input_ids (native /generate and OpenAI token-id prompts) were never checked; and the method itself only guarded id >= vocab_size, missing id < 0. An out-of-range id indexes the model's embedding out of bounds (on CUDA a device-side assert that takes down the whole server); a negative id silently wraps to the end of the embedding table. Wire the validator into _validate_one_request (only for user-provided ids; tokenizer output is always valid) and reject both out-of-range and negative ids. Adds an empty-input guard.
|
cc @hnyls2002 @merrymercy (managers owners). Part of #31597 — this wires up the existing-but-dead |
There was a problem hiding this comment.
Code Review
This pull request adds validation for user-supplied input_ids to prevent out-of-bounds token IDs (both negative and exceeding vocabulary size) from causing CUDA device-side asserts or silent corruption, along with a new unit test suite. The reviewer suggests extending this validation to embedding requests (EmbeddingReqInput) as well, rather than limiting it to generation requests, to prevent similar server crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # Validate generation-specific fields | ||
| if isinstance(obj, GenerateReqInput): | ||
| # Only user-supplied token ids can be out of range; ids produced by | ||
| # the tokenizer are always valid, so skip the scan for text requests. | ||
| if obj.input_ids is not None: | ||
| self._validate_input_ids_in_vocab( | ||
| input_ids, self.model_config.vocab_size | ||
| ) |
There was a problem hiding this comment.
The vocabulary validation for user-supplied input_ids is currently restricted to GenerateReqInput requests. However, EmbeddingReqInput requests can also accept user-supplied input_ids (e.g., via the embedding endpoints). If an out-of-bounds token ID is supplied to an embedding request, it will also trigger a CUDA device-side assert or index out-of-bounds error, crashing the server or causing silent corruption.
Moving this validation outside of the isinstance(obj, GenerateReqInput) block ensures that both generation and embedding requests are properly validated.
| # Validate generation-specific fields | |
| if isinstance(obj, GenerateReqInput): | |
| # Only user-supplied token ids can be out of range; ids produced by | |
| # the tokenizer are always valid, so skip the scan for text requests. | |
| if obj.input_ids is not None: | |
| self._validate_input_ids_in_vocab( | |
| input_ids, self.model_config.vocab_size | |
| ) | |
| # Only user-supplied token ids can be out of range; ids produced by | |
| # the tokenizer are always valid, so skip the scan for text requests. | |
| if obj.input_ids is not None: | |
| self._validate_input_ids_in_vocab( | |
| input_ids, self.model_config.vocab_size | |
| ) | |
| # Validate generation-specific fields | |
| if isinstance(obj, GenerateReqInput): |
|
Independent confirmation of this one, in case it helps it move — we hit the same crash in Reproduced. An in-vocab prompt was sent first and served 200 with the process alive, so the result is Server side, the assert this PR prevents: This fix shape closes it. We applied a functionally equivalent patch — wire A regression lane, proven both ways on one host in one job, patch as the only difference:
The probes include a negative id and a batched prompt with one bad id, both of which pass One question, not a review blocker. The call sits inside Happy to help with anything that would get this landed. |
|
Follow-up with a concrete downstream data point, in case it helps prioritize this. We now ship this same fix in NVIDIA's sglang fork (our 26.08 release branch and When this PR merges we would like to drop our fork patch entirely and converge on upstream. The one difference currently preventing that is the placement question from my earlier comment: with the call inside Happy to send that as a suggestion on the diff if useful, or share our verification setup. Not pushing on scope otherwise — the PR as it stands already closes the crash we hit. |
Part of #31597.
Motivation
_validate_input_ids_in_vocabexists but has zero callers, so user-suppliedinput_ids(native/generate, and OpenAI token-id prompts) are never validated. And the method itself only checksid >= vocab_size— it missesid < 0. Consequences:>= vocab_sizeindexes the model's embedding (nn.Embedding(vocab, hidden)) out of bounds. On CUDA that is a device-side assert that poisons the context and takes down the whole server from a single request (e.g.POST /generate {"input_ids":[1000000000],"sampling_params":{"max_new_tokens":1}}).This is the same untrusted-input class as
logit_bias/token_ids_logprob/stop_token_ids, which are already validated.Modifications
Wire
_validate_input_ids_in_vocabinto_validate_one_request, guarded onobj.input_ids is not Noneso it only scans user-provided token ids (tokenizer output is always valid, so text requests pay nothing). Reject both out-of-range and negative ids, and add an empty-input guard.Tests
test_tokenizer_manager_input_ids_validation.py: ids>= vocab_size,== vocab_size, and negative all raise (single and batch); in-range and empty pass. The negative case used to pass under the old>= vocab_size-only check.Checklist
CI States
Latest PR Test (Base): ❌ Run #29813122122
Latest PR Test (Extra): ❌ Run #29813121808