Repository navigation
serve: logprobs with "parallel" (batch slots) for one-token decisions; 400 for longer replies - #1
Merged
Merged
Conversation
…; 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
approved these changes
Oct 19, 2026
gt50
left a comment
Owner
There was a problem hiding this comment.
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 leavesDONEin the queue. An engine without LP lines passes the token on at the next line or after 0.5 s, as described.lp_linesper thread with batch slots, likelast.batch = 0is a class attribute, so the property is safe before__init__sets it.- Tests:
serve.test_logprobs_parallel,serve.test_logprobs,serve.test_parallelandserve.test_serverpass 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 oflogprobs(c8ee496), server only, engine unchanged.Problem: with
"parallel",generate()goes togenerate_batched(). A one-token request there is an admission (BGEN, read by the engine asGEN 1withlogprobs=K), so the engine writes the LP line, but_control()had no LP branch and dropped it →"logprobs": nullon every reply.Change (
serve/server.py+57/−8):_control(): with logprobs asked, aTline is held until the next line; itsLPline goes tolp_linesbefore 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_linesper thread with batch slots, likelast(concurrent requests must not share it).logprobs+"parallel"+max_tokens != 1→ 400 (fail closed, likelogit_biaswith batch), becauseBTlines carry no LP lines. LP for theBTslot phase is left for later.Tests:
serve/test_logprobs_parallel.py(fake engine oftest_parallel.pywith LP lines behind the realStrataEngine/Service): one decision, four at once (each gets its own entries), not asked (unchanged), longer replies refused, engine without LP lines.Checked:
servetests on this branch: 698 passed, 11 skipped on the build host; on a second Linux box 1 env-only failure intest_keepawake, identical on c8ee496."parallel": 2,--batch-groups 2: 109 cases serially and two at a time, every reply has logprobs; |Δp| to the run without"parallel"mean 0.007 (max 0.08). The same patch has run in our production since 2026-10-11 (rebased onto v0.1.43.1, where the only conflict is the batch loop now intry/exceptfrom Batch slots return HTTP 400 "BGEN: no such free slot" instead of queueing; the affected conversation stays broken Niko1221/Strata#1994).Not tested: HIP/SYCL builds (change is Python only), Responses and Anthropic APIs.