Skip to content

fix(zmq): enforce wire constraints on TokenSpeed direct-backend workers - #2079

Merged
slin1237 merged 1 commit into
mainfrom
zmq/tokenspeed-grammar-backend
Aug 9, 2026
Merged

slin1237 merged 1 commit into
mainfrom
zmq/tokenspeed-grammar-backend

Conversation

@slin1237

@slin1237 slin1237 commented Aug 8, 2026

Copy link
Copy Markdown
Member

Problem

TokenSpeed defaults grammar_backend to none and then drops structured-output constraints without complaint. The gateway does its part correctly — apply_tokenspeed_constraint maps all four constraint kinds onto the wire — so the request arrives intact at an engine that quietly ignores it.

The user-visible result is that tool_choice=required, tool_choice={function} and response_format=json_schema return freeform text with a 200. Output logprobs default off the same way, so logprobs=true returns null.

This is a production hole, not a test-harness one. The gRPC servicer path passes --grammar-backend xgrammar and --enable-output-logprobs explicitly, with a comment naming this exact failure mode; the smg serve ZMQ launcher never did. Anyone running TokenSpeed over ZMQ today gets silently unenforced constraints.

The e2e lane is just where it surfaced: on run 31243936303 the e2e-1gpu-chat-zmq (tokenspeed) lane logged grammar_backend='none' at startup and produced 44 failures — every one a grammar test (38 function-calling required/specific, 4 json_schema, 2 regex). tool_choice=auto passed throughout, so parsing was never the problem.

Change

TokenspeedWorkerLauncher now supplies both flags as defaults rather than launcher-owned flags, via a new WorkerLauncher._backend_arg_defaults helper. The distinction matters: _filter_backend_args strips flags SMG owns outright, whereas these are engine defaults SMG needs for the wire to behave as the API promises but an operator may legitimately override. Pass --grammar-backend llguidance and you keep yours; the other defaults stay.

The e2e harness delegates to this launcher (_build_tokenspeed_zmq_cmd), so the ZMQ lane picks the fix up with no harness change.

What this does not fix

Failing loud would be better than defaulting. The frontend cannot do that today: EngineCoreReadyResponse carries no grammar-backend field, so it cannot distinguish an unenforceable constraint from an enforceable one. That needs a handshake addition on the TokenSpeed side (#48 territory), and is worth doing — a silently-ignored constraint is a correctness bug that looks like a model quality problem.

Test Plan

  • pytest tests/test_serve.py -k Tokenspeed → 11 passed, including three new cases: defaults are applied, an operator override wins and leaves other defaults intact, and the --flag=value form is honored.
  • ruff check / ruff format --check clean.
  • The real proof is the e2e-1gpu-chat-zmq (tokenspeed) lane on this PR: those 44 failures should clear.
Checklist
  • cargo +nightly fmt passes (no Rust changes)
  • cargo clippy --all-targets --all-features -- -D warnings passes (no Rust changes)
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

TokenSpeed defaults its grammar backend to none and drops structured-output
constraints without complaint. The gateway translates all four constraint
kinds onto the wire correctly, so the request arrives intact and is then
ignored: `tool_choice=required`, `tool_choice={function}` and
`response_format=json_schema` come back as freeform text with no error.
Output logprobs default off the same way, so `logprobs=true` returns null.

The gRPC servicer path already passes both flags. The `smg serve` launcher
for the ZMQ path never did, which makes this a production hole and not a
test-harness one — the e2e lane just happens to be where it surfaced (44
failures on the TokenSpeed ZMQ lane, every one a grammar test).

Supply both as defaults rather than launcher-owned flags: an operator who
names a grammar backend keeps theirs. Failing loud instead of defaulting
would be better, but the engine does not report its grammar backend in the
handshake, so the frontend cannot tell an unenforceable constraint from an
enforceable one.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
@github-actions github-actions Bot added python-bindings Python bindings changes tests Test changes labels Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • TokenSpeed workers now automatically enable grammar constraints and output log probabilities by default.
    • Backend settings supplied by operators take precedence over these defaults, including --key=value options.
  • Bug Fixes

    • Prevented launcher defaults from overriding explicitly configured backend arguments.

Walkthrough

TokenSpeed workers now receive default xgrammar and output-logprob backend arguments. The launcher preserves explicit flag and --key=value overrides. Tests cover defaults and override behavior.

Changes

TokenSpeed backend defaults

Layer / File(s) Summary
Backend default resolution
bindings/python/src/smg/serve.py
WorkerLauncher._backend_arg_defaults adds missing backend flag-token sequences and preserves supplied flags.
TokenSpeed command integration and validation
bindings/python/src/smg/serve.py, bindings/python/tests/test_serve.py
TokenSpeed adds --grammar-backend xgrammar and --enable-output-logprobs by default. Tests cover explicit values and equals-form arguments.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • smg-project/smg#460: Adds the backend-argument forwarding that this change extends with defaults and override handling.

Suggested reviewers: catherinesue, key4ng

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the TokenSpeed direct-backend wire-constraint fix, which matches the main change.
Description check ✅ Passed The description clearly explains the TokenSpeed constraint and logprob issue, implementation, rationale, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zmq/tokenspeed-grammar-backend

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.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

👋 The PR description doesn't fully follow
PULL_REQUEST_TEMPLATE.md:

  • Missing header: ## Description
  • Missing header: ### Problem (has ## Problem instead — should be a ### subsection under ## Description)
  • Missing header: ### Solution (has ## Change instead — should be ### Solution under ## Description)
  • Missing header: ## Changes (has ## Change instead — should be ## Changes, plural)

Please update the PR description so reviewers have the context they need.

@claude claude 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.

Clean, well-motivated fix. The _backend_arg_defaults helper correctly separates overridable defaults from launcher-owned flags stripped by _filter_backend_args. The three test cases cover the key scenarios (defaults applied, operator override wins, equals-form honored). No issues found.

@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.

🧹 Nitpick comments (1)
bindings/python/tests/test_serve.py (1)

873-881: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🟡 Nit Use a non-default value for the override test.

After the default changes to llguidance, pass none here. This verifies that a different operator value is preserved and that the default is not duplicated.

🤖 Prompt for 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.

In `@bindings/python/tests/test_serve.py` around lines 873 - 881, Update
test_build_zmq_command_defaults_yield_to_the_operator to pass the non-default
grammar backend value "none" in the build_command arguments, then assert that
"none" is preserved while confirming --grammar-backend appears only once and the
other default remains.
🤖 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.

Nitpick comments:
In `@bindings/python/tests/test_serve.py`:
- Around line 873-881: Update
test_build_zmq_command_defaults_yield_to_the_operator to pass the non-default
grammar backend value "none" in the build_command arguments, then assert that
"none" is preserved while confirming --grammar-backend appears only once and the
other default remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb5576de-30f1-4021-8103-0652c25e6c7e

📥 Commits

Reviewing files that changed from the base of the PR and between c3a8287 and f3659de.

📒 Files selected for processing (2)
  • bindings/python/src/smg/serve.py
  • bindings/python/tests/test_serve.py

@slin1237
slin1237 merged commit b64f4e7 into main Aug 9, 2026
44 checks passed
@slin1237
slin1237 deleted the zmq/tokenspeed-grammar-backend branch August 9, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python-bindings Python bindings changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant