Skip to content

serve: logprobs with "parallel" (batch slots) for one-token decisions; 400 for longer replies - #1

Merged
gt50 merged 1 commit into
gt50:logprobsfrom
karls0r:logprobs-batched
Oct 19, 2026
Merged

gt50 merged 1 commit into
gt50:logprobsfrom
karls0r:logprobs-batched

Conversation

@karls0r

@karls0r karls0r commented Oct 16, 2026

Copy link
Copy Markdown

🤖 Contributed by an AI agent (Ren, running Claude) on behalf of @karls0r.

As discussed in Niko1221#1916 (comment): logprobs with "parallel" (batch slots) for one-token decisions, and a 400 for longer replies. One commit on top of logprobs (c8ee496), server only, engine unchanged.

Problem: with "parallel", generate() goes to generate_batched(). A one-token request there is an admission (BGEN, read by the engine as GEN 1 with logprobs=K), so the engine writes the LP line, but _control() had no LP branch and dropped it → "logprobs": null on every reply.

Change (serve/server.py +57/−8):

  • _control(): with logprobs asked, a T line is held until the next line; its LP line goes to lp_lines before the token is passed on. Any other line, or 0.5 s of silence, passes the held token on first, so an engine without LP lines only delays a token by one line.
  • lp_lines per thread with batch slots, like last (concurrent requests must not share it).
  • logprobs + "parallel" + max_tokens != 1 → 400 (fail closed, like logit_bias with batch), because BT lines carry no LP lines. LP for the BT slot phase is left for later.

Tests: serve/test_logprobs_parallel.py (fake engine of test_parallel.py with LP lines behind the real StrataEngine/Service): one decision, four at once (each gets its own entries), not asked (unchanged), longer replies refused, engine without LP lines.

Checked:

Not tested: HIP/SYCL builds (change is Python only), Responses and Anthropic APIs.

…; 400 for longer replies

With "parallel" set, generate() goes to generate_batched(), whose control-line reader (_control) had no LP
handling: the engine wrote the LP line (BGEN is read as GEN 1, logprobs=K included) and the server dropped it,
so every reply came back with "logprobs": null.

- _control(): with logprobs asked, a T line is held until the next line; its LP line goes to lp_lines before the
  token is passed on (no second read, nothing skipped: any other line, or 0.5 s of silence, passes it on first)
- lp_lines per thread with batch slots, as last is (several requests run at once)
- a batch slot's BT lines carry no LP lines, so logprobs with parallel and max_tokens != 1 is a 400 (fail closed,
  as logit_bias does) instead of partial or misplaced entries
- serve/test_logprobs_parallel.py: the fake engine of test_parallel with LP lines; one decision, four at once
  (each gets its own), not asked, longer replies refused, an engine without LP lines

Contributed by an AI agent (Ren, Claude) on behalf of karls0r; measured on 2x RTX 3090, layer split.

@gt50 gt50 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you, this is a clean fix. Reviewed and merging.

Checked:

  • _control(): both callers read it to the end, so a one-token admission (T, LP, DONE) passes the token with its LP line and leaves DONE in the queue. An engine without LP lines passes the token on at the next line or after 0.5 s, as described.
  • lp_lines per thread with batch slots, like last. batch = 0 is a class attribute, so the property is safe before __init__ sets it.
  • Tests: serve.test_logprobs_parallel, serve.test_logprobs, serve.test_parallel and serve.test_server pass on this branch (332 tests, Windows, Python only; I did not run it against a live engine).

One thing for a follow-up, not blocking: with replicas, ReplicaEngine.batch counts each engine as at least 1 (serve/replicas.py: max(1, ...)). So getattr(svc.engine, "batch", 0) is non-zero even when no replica has batch slots, and logprobs with max_tokens above 1 get the 400 telling the user to "run without parallel" although "parallel" is off. Checking for slots on the replicas' own engines (svc.engine.engines when it has them) would keep the 400 to real batch slots.

@gt50
gt50 merged commit 6ca853b into gt50:logprobs Oct 19, 2026
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