Skip to content

feat: add opt-in vLLM sampling validation - #683

Merged
mayabar merged 13 commits into
llm-d:mainfrom
0z5a:feat/strict-request-validation
Oct 13, 2026
Merged

mayabar merged 13 commits into
llm-d:mainfrom
0z5a:feat/strict-request-validation

Conversation

@0z5a

@0z5a 0z5a commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Incremental review: Files changed against main.

What does this PR do?

Adds opt-in validation for /v1/chat/completions and /v1/completions with --strict. The vLLM engine embeds complete request schemas pinned to vLLM 0.21.0 and runs schema and semantic validation when requests arrive, before tokenization or generation. No user-supplied schema file is required.

The engine owns sampling ranges, sequence limits, streaming/logprob combinations, tool usage, structured-output constraints and repetition-detection checks. Generic request checks remain shared with lenient mode. The request types carry no validation-only state, and min_tokens has one validation site.

Why is this change needed?

The simulator otherwise leaves unused request fields unchecked. Versioning the schema and semantic rules together prevents strict mode from mixing constraints from different vLLM releases. The checked-in audit documents every request-field category and the model-dependent limits that require a real serving configuration; strict mode does not assume a vocabulary size or fixed logprob capacity.

Schema errors use the OpenAI error envelope without promising Pydantic coercion or formatting. Schema defaults affect validation only. Context-limited effective token maxima and structured-output backend compilation are outside the receipt-time validator.

How was this tested?

  • Unit tests added/updated: bundled nested schemas, exact sequence-limit messages, integer boundaries, streaming/logprob combinations, tool and structured-output constraints, repetition detection, omitted/null/explicit token limits, and strict/lenient HTTP dispatch.
  • Actual-process HTTP E2E: 35 checks across strict and lenient servers, both completion endpoints, successful JSON/SSE generation, common validation, schema errors, semantic errors and integer overflow.
  • Manual comparison against a live vLLM server for this revision.

Passed targeted tests for communication, vLLM engine, endpoint, common and API; targeted go vet; compilation of all packages; and make lint with Go 1.26.6. Regenerating the snapshot from the pinned clean vLLM checkout produced identical bytes. The installed govulncheck reported no reachable vulnerabilities.

make presubmit passed signature/DCO, module tidiness, formatting and lint checks, then failed downloading the latest vulnerability-checker tool due to a network timeout. The installed checker was run separately.

Checklist

  • Commits are signed off (git commit -s) per DCO
  • Code follows project contributing guidelines
  • Tests pass locally (make test)
  • Linters pass (make lint)
  • Documentation updated

Related Issues

Related to #676

@0z5a
0z5a marked this pull request as ready for review September 3, 2026 15:36
@0z5a

0z5a commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Hi @irar2 Would you also mind having a review on this?

@mayabar mayabar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simulator has so far parsed only the fields that affect response generation.

In strict mode, we should validate the entire request against the OpenAI API specification, along with the vLLM-specific extension fields. This means verifying that all required fields are present and that every supplied field has a valid value and is consistent with related parameters.

The implementation in this PR validates only part of the request. For example:

  • n has an upper limit configured by VLLM_MAX_N_SEQUENCES, but this limit is not currently validated.
  • When max_tokens is omitted, vLLM applies a default value. However, min_tokens is not validated against that effective maximum.

Rather than incrementally adding validation for individual fields, I suggest using an existing library that validates OpenAI-compatible requests, if one is available. Alternatively, we could generate or obtain a JSON Schema from vLLM’s OpenAPI specification:

curl http://localhost:8000/openapi.json > openapi.json

Then we can apply standard JSON Schema validation for the common request structure and add explicit validation for constraints that depend on runtime configuration or relationships between fields, such as VLLM_MAX_N_SEQUENCES and the effective max_tokens value.

@0z5a

0z5a commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I agree that strict mode should use the full request schema. I reworked this to compile the two completion request schemas from a target vLLM OpenAPI 3.1 snapshot, using the JSON Schema library already in the repository. --strict now requires --strict-openapi; schema validation covers required fields, types, nested structures, and extension fields before the sampling checks.

I also added the VLLM_MAX_N_SEQUENCES upper bound and validation of min_tokens against the effective maximum: the explicit limit or the schema's omitted-field default, capped by the remaining context window after tokenization. Explicit null and the chat-only max_completion_tokens override are handled separately.

The communication, endpoint, common, and API tests, targeted go vet, package compilation checks, and lint with Go 1.26.6 passed. The new tests cover schema failures, runtime limits, defaults, context bounds, and lenient behavior. I have not rerun the live vLLM differential benchmark for this revision. OpenAPI does not include all Python/model-dependent validators, so the docs state that limitation and do not promise identical Pydantic error text.

@mayabar mayabar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rework, and for moving to the full request schema. There are several issues in the proposed implementation that need to be resolved before this can land.

1. Strict mode should not depend on a user-supplied schema file

Requiring --strict-openapi puts the burden on the user to export and carry a snapshot, and it makes strict mode depend on an artifact the simulator cannot control. The important problem: a schema exported from a newer vLLM can be inconsistent with the validations implemented in the code here, and then strict mode silently mixes two different vLLM versions - a new field passes the schema and then hits a check written for an older release, or vice versa.

Please check a snapshot into the repository and load it from there (go:embed), so the schema version and the hand-written checks are versioned together and --strict works with no extra user setup.

2. The schema covers only a small part of the validation

Schema validation gives structure and types; most of what vLLM rejects is not expressible there. n is one example (both n < 1 and n > VLLM_MAX_N_SEQUENCES, whose default is 16384 in vllm/envs.py), but there are many more, for example:

  • stream_options set while stream is false or absent
  • top_logprobs set while logprobs is false
    ...

This needs a systematic pass over vLLM's request models and SamplingParams to cover all request fields, rather than a partial list. Please also keep the exact vLLM message text where you claim parity - for instance the n upper-bound message here drops vLLM's second sentence about setting VLLM_MAX_N_SEQUENCES.

3. Validation logic is spread across too many places

Right now a single request is validated in four different locations, with rules duplicated between them:

  • pkg/communication/strict_schema.go: schema validation, n > VLLM_MAX_N_SEQUENCES, and min_tokens against the schema's max_tokens default
  • pkg/communication/strict_validation.go: all sampling ranges, plus min_tokens against the explicit max_tokens
  • pkg/endpoint/request.go (validateTokenizedRequest): min_tokens again, against the remaining context window
  • pkg/api/request.go: strictMinTokens / strictDefaultMaxTokens fields and SetStrictTokenLimits / StrictTokenLimits, only to carry values from the first location to the third (please remove new fields from the request)

So min_tokens versus max_tokens is implemented three times with three different notions of the maximum. On top of that, pkg/endpoint/helpers.go (validateRequest) already validates n < 1 and max_tokens < 1, so those conditions now exist twice with two different messages depending on the flag. Please consolidate: one place per rule, one message per rule.

4. Align with the new engine structure

The project has been restructured recently and now has an explicit engine concept (pkg/engine, with pkg/engine/vllm supplying the vLLM-specific flags, config validation, and transport). Strict request validation should fit into that seam rather than sit beside it.

Suggested split, worth agreeing before you rework:

  • Run strict validation when the request is received, in one place, so it is not interleaved with tokenization and generation.
  • Keep generic OpenAI-level checks in the common path.
  • Put vLLM-specific fields in a vLLM validator owned by the engine, next to the existing ValidateConfig - min_tokens is a good example, it is a vLLM SamplingParams extension, not an OpenAI field.

@0z5a

0z5a commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@mayabar Thanks for the detailed review. I have addressed the requested changes in the latest commit (a76a5eab): the target vLLM schemas are now embedded in the repository, the request relationship and sampling checks have been expanded with vLLM-compatible messages, validation is centralized so each rule has a single owner, and the vLLM-specific validation now lives under pkg/engine/vllm. The updated checks are passing. Could you please take another look when you have a chance?

@mayabar mayabar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0z5a PR looks good, please see some comments below

Comment thread pkg/communication/http.go Outdated
Comment on lines +280 to +283
if err := validateStrictContentType(string(ctx.Request.Header.ContentType())); err != nil {
c.sendError(ctx, err, false)
return
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a function for all headers validation, currently it will contain only one header only

Comment thread pkg/engine/vllm/strict_schema.go Outdated
Comment on lines +52 to +57
if value, exists := os.LookupEnv("VLLM_MAX_N_SEQUENCES"); exists {
maxN, err = strconv.ParseInt(value, 10, 64)
if err != nil || maxN < 1 {
return nil, errors.New("VLLM_MAX_N_SEQUENCES must be a positive integer")
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that there are additional environment variables that are used by vLLM for request validation, e.g. VLLM_MAX_COMPLETION_PROMPTS.

@mayabar mayabar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks!
PR is ready for merge, just waiting for conflict resolution

0z5a added a commit to 0z5a/llm-d-inference-sim that referenced this pull request Sep 23, 2026
@github-actions

Copy link
Copy Markdown

Unsigned commits detected! Please sign your commits.

For instructions on how to set up GPG/SSH signing and verify your commits, please see GitHub Documentation.

@mayabar

mayabar commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

@0z5a you commit was not signed ;)

0z5a added a commit to 0z5a/llm-d-inference-sim that referenced this pull request Sep 24, 2026
@0z5a
0z5a force-pushed the feat/strict-request-validation branch from 243f218 to 9a3451c Compare September 24, 2026 04:58
@0z5a

0z5a commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for catching this. I re-signed the merge commit; GitHub now verifies the updated head, and the code tree is unchanged.

0z5a and others added 9 commits September 24, 2026 15:10
Add a strict request-validation foundation for the OpenAI chat and text completion endpoints while preserving existing lenient behavior by default.

Signed-off-by: 0z5a <[email protected]>
Signed-off-by: 0z5a <[email protected]>
Match vLLM validation for prompt logprobs, allowed token IDs, greedy sampling, and multi-error precedence.

Validated against vllm/vllm-openai:v0.21.0 on an NVIDIA L20.

Signed-off-by: 0z5a <[email protected]>
Signed-off-by: 0z5a <[email protected]>
Strict validation mirrored the engine's schema and sampling rules, but the
engine also bounds the number of prompts in a text completion request through
VLLM_MAX_COMPLETION_PROMPTS, and the header checks had no single home for
further additions.

Signed-off-by: 0z5a <[email protected]>
@0z5a
0z5a force-pushed the feat/strict-request-validation branch from 9a3451c to 10b519e Compare September 24, 2026 07:10
@mayabar

mayabar commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

@0z5a sorry for delay with review (was on vacation ;) ) please resolve conflicts and I'll be able to approve

@0z5a

0z5a commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

@0z5a sorry for delay with review (was on vacation ;) ) please resolve conflicts and I'll be able to approve

Will resolve conflicts soon, and would you mind me taking some review tasks on inference-sim when you are not available?

Keep engine-owned request validation compatible with upstream error framing and boolean flag parsing.

Signed-off-by: 0z5a <[email protected]>
@0z5a

0z5a commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Resolved the merge conflicts with main in 344b4f3, incorporating main at f33dd09.

Kept engine-owned strict validation compatible with upstream transport error framing and KV-event encoding. Registered strict validation through the shared toggle parser, added coverage for --no-strict overriding YAML, and updated the engine hook documentation and direct-handler test mocks.

Validation passed:

  • make presubmit with Go 1.26.6 and golangci-lint 2.10.1.
  • Six related package test suites with the test cache disabled, all-package/test compilation, and make build.
  • 21 HTTP checks covering strict, lenient, and YAML override modes, including sampling validation and Responses/Messages error envelopes.

The full container-dependent GPU/tokenizer integration suite was not run locally.

@mayabar

mayabar commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

@0z5a This PR had code conflicts, I merged the upstream/main to your branch.
Please pay attention that sglang engine was added and it should implement NewRequestValidator, since SGLang is experimental let's do not process any strict validation to it.

0z5a added 2 commits October 12, 2026 11:31
Implement the optional validator hook and apply strict header and schema checks only when the engine supplies a validator. Preserve ordinary request checks and cover both completion endpoints.

Signed-off-by: 0z5a <[email protected]>
Keep the current SGLang response fields and documentation while preserving the optional strict validator hook.

Signed-off-by: 0z5a <[email protected]>
@mayabar
mayabar merged commit 44a388c into llm-d:main Oct 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants