Skip to content

fix: force xgrammar backend in from_sampling_params#53

Open
JMPSequeira wants to merge 38 commits into
local-inference-lab:dev/eldritch-enlightenmentfrom
JMPSequeira:fix/xgrammar-backend-selection
Open

fix: force xgrammar backend in from_sampling_params#53
JMPSequeira wants to merge 38 commits into
local-inference-lab:dev/eldritch-enlightenmentfrom
JMPSequeira:fix/xgrammar-backend-selection

Conversation

@JMPSequeira

@JMPSequeira JMPSequeira commented Jun 26, 2026

Copy link
Copy Markdown

Problem

In the b12x fork, SamplingParams.validate() — which auto-selects the structured output backend via _validate_structured_outputs — may not be called before Request creation. When this happens, StructuredOutputsParams._backend stays None, and the scheduler skips grammar enforcement entirely. The structured_outputs field is accepted by the API, the grammar is validated and compiled, but the bitmask is never applied to logits during sampling. Constraints are silently ignored.

Reproduction

Send a completion request with a grammar constraint:

{
  "model": "GLM-5.2",
  "prompt": "Say: ",
  "max_tokens": 5,
  "structured_outputs": {"grammar": "root ::= \"hello\""}
}

On the unpatched b12x image, the model outputs freely (e.g. "1. Who they are") instead of being constrained to "hello".

Fix

Force the xgrammar backend in from_sampling_params() when validate() hasn't already selected one. This is a no-op when validate() has run (the backend is already set).

Verification

Tested on the eldritch-fullstack image (v0.11.2.dev279):

  1. Simple grammar: root ::= "hello" — output constrained to "hello"
  2. Full LLang GBNF grammar: 75 nonterminals, 154K-token vocab (GLM-5.2 tokenizer) — output constrained to valid LLang syntax ✓
  3. Model: Qwen3.5-9B-NVFP4 — generated valid LLang code (pattern matching, let-in, pipeline) under constraint ✓

Notes

  • This issue was diagnosed and tested using GLM-5.2 (504B) as the analysis and reasoning model.
  • The fix is 8 lines, all additive — no existing behavior is changed when validate() has already run.
  • The structured_outputs API field, grammar validation, xgrammar compilation, and model runner bitmask application all work correctly in isolation. The only gap was the backend selection not happening before Request creation.

Summary by CodeRabbit

  • Bug Fixes
    • Structured outputs now more reliably use the server’s configured backend when a request does not include one.
    • Requests that rely on automatic backend selection will now consistently choose the preferred compatible backend when available, reducing setup-related failures.

lukealonso and others added 30 commits June 23, 2026 07:30
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
lukealonso and others added 7 commits June 23, 2026 07:48
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
Make DCP sparse-indexer top-k merge use caller-owned workspace, prewarm the DCP and chunk-metadata kernels needed before capture, and avoid running profile-only sparse-indexer work inside piecewise graph capture. Keep long-context scratch reservation while bounding synthetic paged prewarm work.

Co-authored-by: Martin Vit <martin@voipmonitor.org>
Co-authored-by: Martin Vit <martin@voipmonitor.org>
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JMPSequeira, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 19 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d424e1d1-dc01-4d38-a479-e7754347ca9f

📥 Commits

Reviewing files that changed from the base of the PR and between 39032f6 and ef1d8ab.

📒 Files selected for processing (1)
  • vllm/v1/structured_output/__init__.py
📝 Walkthrough

Walkthrough

StructuredOutputManager.grammar_init now falls back to the server-configured structured output backend when the request backend is unset, and resolves auto to xgrammar before backend instantiation.

Changes

Structured output backend resolution

Layer / File(s) Summary
Backend fallback and auto resolution
vllm/v1/structured_output/__init__.py
grammar_init uses the server-configured backend when the request backend is unset, and maps auto to xgrammar before backend instantiation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main fix: selecting the xgrammar backend when structured-output params are not initialized.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/v1/structured_output/request.py`:
- Around line 45-47: The fallback in StructuredOutputsParams is overwriting the
request-level backend and breaks configured non-auto backends. Update
from_sampling_params() and the backend selection flow so it uses the configured
backend from SamplingParams instead of hard-coding "xgrammar", or move the
fallback into request-local scheduler state. Keep _backend_was_auto semantics
intact and avoid mutating params._backend before validation in request.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b067feb-9150-46c6-bfdb-f15eddb4e55b

📥 Commits

Reviewing files that changed from the base of the PR and between 2d040b9 and aa95917.

📒 Files selected for processing (1)
  • vllm/v1/structured_output/request.py

Comment thread vllm/v1/structured_output/request.py Outdated
@JMPSequeira
JMPSequeira force-pushed the fix/xgrammar-backend-selection branch 2 times, most recently from c0ed5b3 to 39032f6 Compare June 26, 2026 23:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/v1/structured_output/__init__.py`:
- Around line 139-142: `grammar_init()` is incorrectly collapsing `backend ==
"auto"` to `xgrammar`, which overrides the existing auto-resolution behavior
from `SamplingParams.validate()` and can break requests that should fall back to
`outlines` or `guidance`. Update the `GrammarInit`/backend selection path to
preserve `auto` and reuse the same resolution logic already used by
`SamplingParams.validate()`, or move backend determination earlier at request
construction so `grammar_init()` receives the resolved backend instead of
forcing `xgrammar`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4348e26e-ff22-4fcb-ad1f-0a84bed07273

📥 Commits

Reviewing files that changed from the base of the PR and between aa95917 and 39032f6.

📒 Files selected for processing (1)
  • vllm/v1/structured_output/__init__.py

Comment thread vllm/v1/structured_output/__init__.py Outdated
In the b12x fork, SamplingParams.validate() — which auto-selects the
structured output backend — may not be called before Request creation.
When this happens, StructuredOutputsParams._backend stays None, and the
StructuredOutputManager can't initialize a backend. Constraints are
silently ignored.

This fix adds a fallback in StructuredOutputManager.grammar_init:
when the request's _backend is None, use the server-configured backend
from vllm_config.structured_outputs_config.backend. When that is
'auto', try xgrammar first and fall back to guidance/outlines,
matching the resolution order in SamplingParams.validate().

This respects operator-configured backends and preserves the auto
fallback chain — no hard-coding of any specific backend.

Verified on the eldritch-fullstack image (v0.11.2.dev279) with
Qwen3.5-9B-NVFP4: constrained generation works correctly for both
simple grammars and the full LLang GBNF grammar (154K-token vocab,
75 nonterminals).

Tested with GLM-5.2 (504B) as the analysis and reasoning model for
diagnosing this issue.
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