Skip to content

serve stage 2: response_format → per-request grammars → grammar-forced drafts in the multiplexed server (stacked on #148)#192

Open
fabio-rovai wants to merge 3 commits into
JustVugg:devfrom
fabio-rovai:feat/serve-grammar
Open

serve stage 2: response_format → per-request grammars → grammar-forced drafts in the multiplexed server (stacked on #148)#192
fabio-rovai wants to merge 3 commits into
JustVugg:devfrom
fabio-rovai:feat/serve-grammar

Conversation

@fabio-rovai

Copy link
Copy Markdown
Contributor

Completes the seam proposed in #146 and started in #148 (stacked on it — the first two commits are that PR + its ws-tolerance follow-up; review the top two commits here): an OpenAI client sends response_format, and the batched server turns it into grammar-forced draft speedup, per request, with no GBNF hand-writing anywhere.

What lands

  • Gateway (openai_server.py): response_format {"type":"json_object"} (ships a generic whitespace-tolerant JSON grammar), {"type":"json_schema"} (schema forwarded verbatim; the engine compiles it via schema_gbnf.h), plus a raw-GBNF {"type":"gbnf"} extension. Draft-source semantics end to end: an uncompilable schema costs the speedup, never the request, never the output.
  • Protocol: optional 7th SUBMIT field gbytes; grammar text rides the payload after the prompt. 6-field headers unchanged.
  • Engine: per-slot GrDraft (a mechanical de-globalization commit precedes this), lifecycle owned by slot reuse, walkers fed on every emitted token. Grammar drafting now runs inside run_serve_mux for greedy requests: a drafting slot steps out of the shared batch for one forward and uses the proven single-sequence verify path (kv_bind + step_all — the exact primitives mux_submit already uses for prefill), then rejoins. No changes to step_decode_batch. Rejected drafts' KV entries are overwritten by the next forward, same as the existing prefix-truncation path. Sampling requests never draft (rejection-resampling under sampling is out of scope and documented as such).

Verified

Honest caveats

  1. Drafted and undrafted runs of the same request can differ in near-tie tokens: the verify forward is S=1+k where the plain path is S=1, and batch-size reduction-order sensitivity is the documented Greedy decoding is not reproducible: MTP (3/5 prompts) and the CUDA expert tier (2/5) both change the output #100 behavior (same as MTP). Verification guarantees each emitted stream is a valid greedy stream of its own forward shapes.
  2. Acceptance counters aren't yet surfaced per-request in mux mode (they aggregate in the slot's GrDraft) — a STAT-line extension would make the speedup observable per request; happy to add in this PR or a follow-up, your call.

🤖 Generated with Claude Code

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

Thanks for the contribution! This is a substantial feature that adds protocol changes, a JSON Schema → GBNF compiler, grammar handling in the OpenAI server, engine integration, and a large new test suite.

Before this can be approved, I think a few things should be clarified:

  1. Backward compatibility

    • Please document the protocol change introducing the optional gbytes field in the SUBMIT header and confirm compatibility with older clients/servers.
  2. Documentation

    • The new response_format modes (json_object, json_schema, and gbnf) should be documented for users, including examples and any limitations.
  3. Testing

    • The new tests cover many success cases, but it would be good to add more negative and edge-case tests (very large schemas, malformed GBNF, invalid grammar payloads, nested depth limits, etc.).
  4. Performance

    • Since grammars are compiled/generated per request, it would be helpful to include some discussion or benchmarks on the overhead for large schemas.

Overall the direction looks promising, but given the size of the change I'd prefer these points to be addressed before approval.

fabio-rovai added a commit to fabio-rovai/colibri that referenced this pull request Jul 14, 2026
…ed compile overhead

Addresses the JustVugg#192 review: server usage documented in docs/grammar-draft.md
(incl. back-compat statement for the additive SUBMIT field and the JustVugg#100-class
near-tie caveat); gateway pre-checks grammar payloads at 1 MiB (matching the
engine's gbytes bound); negative tests for non-dict response_format, empty and
oversized grammars, plus an explicit test that malformed GBNF passes the
gateway by design (engine fail-soft, draft-source semantics). Measured compile
overhead: 7.8 us/request typical schema, 17.9 us at the 32-level nesting cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fabio-rovai

Copy link
Copy Markdown
Contributor Author

@rajpratham1 all four points addressed in the two pushes just now (plus a rebase of the whole stack onto current main after the merge wave — and since #176 merged underneath, this branch now sits cleanly on top of it):

  1. Backward compatibility — the gbytes field is additive in both directions: old client → new server (6-field headers parse unchanged, gbytes=0; covered by the parse tests), new client → old server never occurs through this gateway (it only emits the 7th field when a grammar exists). Now stated explicitly in docs/grammar-draft.md and the decode_batch.h comment.
  2. Documentationdocs/grammar-draft.md (the canonical page from docs: grammar-forced drafts reference page (requested in #146) #170) gained a "Server usage: response_format" section: all three modes with examples, the draft-source-not-constraint semantics, the greedy-only drafting rule, the 1 MiB cap, and the Greedy decoding is not reproducible: MTP (3/5 prompts) and the CUDA expert tier (2/5) both change the output #100-class near-tie caveat.
  3. Testing — added: non-dict response_format, empty GBNF, oversized grammar (>1 MiB, now pre-checked at the gateway to match the engine's gbytes bound), and an explicit test documenting that malformed GBNF passes the gateway by design — the engine fail-softs it, because a bad grammar must cost the speedup, never the request. Depth limits are enforced and tested engine-side (SGB_MAX_DEPTH in the SCHEMA=<file.json>: JSON-Schema → GBNF compiler for grammar-forced drafts (follow-up to #70, seam discussed in #146) #148 suite).
  4. Performance — measured (M3 Max, 10k iterations): schema→GBNF compile + gr_parse = 7.8 µs/request for a typical 4-field schema, 17.9 µs at the 32-level nesting cap. Against generation at ~1 token/s on this hardware class, per-request compilation is 5–6 orders of magnitude below one token; caching compiled grammars would be complexity without a payoff.

🤖 Generated with Claude Code

@JustVugg
JustVugg changed the base branch from main to dev July 14, 2026 16:19
@JustVugg

Copy link
Copy Markdown
Owner

#111 (GPU resident pipeline) just landed on dev and overlaps this on decode_batch.h/glm.c — one rebase onto current dev and I'll merge straight away (everything else in today's wave is in, so nothing further will move under you). The stage-2 design itself looks right on review: fail-soft grammar setup, per-request state in one struct, no unsafe constructs. Sorry for the churn — your stack landed 3-for-3 today (#148, #170, #176).

@fabio-rovai

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (e21318e, post-#111/#195/#201/#202) — the stack is now the three stage-2 commits only (the #148 base deduped away after your merge). One conflict resolved in run_serve_mux init: kept the #195 Windows _setmode binary-mode block alongside the per-slot GrDraft array. make glm clean, full make test-c + python suite green on Apple Silicon. Ready when you are — and thanks for the fast reviews today.

fabio-rovai added a commit to fabio-rovai/colibri that referenced this pull request Jul 14, 2026
…ed compile overhead

Addresses the JustVugg#192 review: server usage documented in docs/grammar-draft.md
(incl. back-compat statement for the additive SUBMIT field and the JustVugg#100-class
near-tie caveat); gateway pre-checks grammar payloads at 1 MiB (matching the
engine's gbytes bound); negative tests for non-dict response_format, empty and
oversized grammars, plus an explicit test that malformed GBNF passes the
gateway by design (engine fail-soft, draft-source semantics). Measured compile
overhead: 7.8 us/request typical schema, 17.9 us at the 32-level nesting cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fabio-rovai and others added 3 commits July 16, 2026 20:22
…avior change) — groundwork for per-request grammars in serve_mux

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…UBMIT -> grammar-forced drafts in the multiplexed server

The OpenAI gateway previously 400'd every response_format; the mux engine path
ran with speculation disabled entirely. Now:

- openai_server.py: response_format {"type":"json_object"} (generic ws-tolerant
  JSON grammar), {"type":"json_schema"} (schema forwarded as-is, compiled
  engine-side by schema_gbnf.h), and a raw-GBNF {"type":"gbnf"} extension.
  Draft-source semantics throughout: a schema the engine cannot compile costs
  the speedup, never the request and never the output.
- SUBMIT protocol: optional 7th field gbytes; grammar text appended to the
  payload after the prompt. 6-field headers unchanged (back-compatible).
- Engine: per-slot GrDraft (grammar_setup_text/grammar_teardown split out of
  the env-driven setup); walkers fed on every emitted token. Grammar-forced
  drafting in run_serve_mux for greedy requests: a drafting slot leaves the
  shared batch for one forward and runs the proven single-sequence verify path
  (kv_bind + step_all) — the same primitives prefill already uses per
  submission — then rejoins; rejected drafts' KV entries are overwritten by the
  next forward exactly like the existing prefix-truncation path. Sampling
  requests never draft (verification under sampling needs rejection resampling;
  out of scope).

Tests: 7-field SUBMIT parse cases; response_format->grammar plumbing incl.
fail cases; test doubles updated; generic JSON grammar parse+walk validated
against grammar.h. make test-c green; python suite green except the known
environmental memory_available failure (JustVugg#150 fixes it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed compile overhead

Addresses the JustVugg#192 review: server usage documented in docs/grammar-draft.md
(incl. back-compat statement for the additive SUBMIT field and the JustVugg#100-class
near-tie caveat); gateway pre-checks grammar payloads at 1 MiB (matching the
engine's gbytes bound); negative tests for non-dict response_format, empty and
oversized grammars, plus an explicit test that malformed GBNF passes the
gateway by design (engine fail-soft, draft-source semantics). Measured compile
overhead: 7.8 us/request typical schema, 17.9 us at the 32-level nesting cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

3 participants