mudler / mudler/vllm.cpp

fix(TOOLS-STRUCTURED-CORE): the grammar matcher rejects every token after termination, so a multi-token batch kills its own request

Open
#2,691 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
423
Forks
53
Avg merge
20h 26m
Merged PRs (30d)
310

Description

Row: TOOLS-STRUCTURED-CORE (.agents/engine-matrix.md:160); the parity claim that must move with it is owned by TOOLS-XGRAMMAR (.agents/engine-matrix.md:161).

Found by wave PORTQ-5 (#2679) re-deriving PORT-NOW entry [197], upstream 12f64b39d2 vllm#52805. Nothing was executed — this is a static reading of source.

What upstream did

XgrammarGrammar.accept_tokens returns True (was False) when already terminated and breaks out of the loop the moment the matcher terminates mid-batch, instead of only discovering it after the whole batch; validate_tokens returns [] when terminated and breaks at the terminating token; reset() clears _is_terminated. The motivating case is MTP / spec-decode batches.

What this tree does

NativeGrammar::accept_tokens (src/vllm/v1/structured_output/backend_native.cpp:1139-1147) advances over every token and returns false on the first failure, with no post-termination early-out in either direction:

for (const int32_t token : tokens) {
  Snapshot next = history_.back();
  if (!advance_snapshot(next, token)) return false;
  history_.push_back(std::move(next));
}

Once EOS is consumed, cur.done is set, and the comment at :1210-1212 states the consequence outright: accept_tokens returns false in the done state for any token.

The multi-token caller is unguarded (src/vllm/v1/core/sched/scheduler.cpp:1286):

if (!struct_output_request->grammar->accept_tokens(req_id, new_token_ids)) {
  request->status = RequestStatus::kFinishedError;
  ...
}

So a batch whose grammar terminates before its last token fails the request with FINISHED_ERROR — a worse form of the bug upstream fixed. StructuredOutputManager guards its own per-token calls (manager.cpp:113), which is why only the scheduler path is exposed.

This lands in the native matcher, not in backend_xgrammar.cpp: XgrammarStructuredOutputBackend::compile_grammar (src/vllm/v1/structured_output/backend_xgrammar.cpp:27-58) delegates to inner_, so both backends share one matcher.

Two of the three sub-changes are already satisfied

NativeGrammar::reset() (:1183-1190) rebuilds history_ from InitState, so there is no stale flag to clear. validate_tokens (:1149-1158) has no production caller — the only references outside structured_output/ are the two deferral comments at scheduler.cpp:1540,1559.

This tree is at the pin

git show 5559679229:vllm/v1/structured_output/backend_xgrammar.py carries _is_terminated, the return False guard, the unguarded validate_tokens and a reset() that does not clear the flag. Not a pre-pin hole.

Size and a caution for the implementer

~15 lines of product code plus a red-first test driving a terminating batch through Scheduler::update_from_output.

Do not key the early-out on is_terminated(). That predicate also returns true for cur.awaiting — a lazy grammar that has not yet seen its trigger (backend_native.cpp:1167-1177). An unconditional "terminated => accept everything and stop" would silently disable constraint enforcement for every lazy / structural-tag grammar. Key it on cur.done / IsFullyMatched.

Record edit owed in the same change

.agents/specs/xgrammar-backend.md:132 asserts the matcher is "already 1:1 with backend_types.py" on accept/validate/reset — a claim of parity with the pre-commit behaviour, which this commit moves.

Not established

That a spec-decode batch reaches accept_tokens with a post-EOS token in a shipped configuration. Nothing was run.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with NativeGrammar::accept_tokens, validate_tokens, and reset() in src/vllm/v1/structured_output/backend_native.cpp, then trace the scheduler call at src/vllm/v1/core/sched/scheduler.cpp:1286 and update_from_output. Add a red-first test for a terminating multi-token batch while preserving lazy grammar enforcement, and update .agents/specs/xgrammar-backend.md:132 when the behavior and parity claim are correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend, documentation
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.