Repository navigation
[Bugfix][Router] Orchestrated P/D: prefill request generates one token (min_tokens, /v1/responses) - #1126
[Bugfix][Router] Orchestrated P/D: prefill request generates one token (min_tokens, /v1/responses)#1126ibrahimnd2000 wants to merge 3 commits into
Conversation
…n (min_tokens, /v1/responses) The prefill request is sent with max_tokens=1, but: - min_tokens from the client was copied into it; vLLM rejects min_tokens > max_tokens with a 400, so any request with min_tokens > 1 failed. Drop it from the prefill request (the decode request keeps it), as send_request_to_prefiller already does. - /v1/responses ignores max_tokens and caps output with max_output_tokens (default: the rest of the context), so the prefill generated a whole answer and returned kv_transfer_params for prompt + output; decode built on KV that did not match its prompt and returned garbage. Set max_output_tokens=1 on the prefill request for /v1/responses. Signed-off-by: Muhammad Ibrahim <ibrahimnd2000@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request addresses issues in the orchestrated prefill/decode flow where prefill requests could fail or generate more than one token. It removes min_tokens from the prefill request to prevent HTTP 400 errors and sets max_output_tokens to 1 for the Responses API. A new test suite is also added to verify these changes. The reviewer suggested also popping min_completion_tokens from the prefill request to avoid similar validation failures when clients use that parameter.
| # Avoid min_tokens > max_tokens=1 conflict in vLLM SamplingParams (400); | ||
| # the decode request keeps the client's min_tokens. | ||
| prefill_request_json.pop("min_tokens", None) |
There was a problem hiding this comment.
In addition to min_tokens, the OpenAI API and vLLM also support min_completion_tokens. If a client sends min_completion_tokens with a value greater than 1, it will cause the same BadRequestError (HTTP 400) during the prefill request because max_tokens is set to 1. We should also pop min_completion_tokens from the prefill request to prevent this failure.
| # Avoid min_tokens > max_tokens=1 conflict in vLLM SamplingParams (400); | |
| # the decode request keeps the client's min_tokens. | |
| prefill_request_json.pop("min_tokens", None) | |
| # Avoid min_tokens / min_completion_tokens > max_tokens=1 conflict in vLLM SamplingParams (400); | |
| # the decode request keeps the client's min_tokens. | |
| prefill_request_json.pop("min_tokens", None) | |
| prefill_request_json.pop("min_completion_tokens", None) |
There was a problem hiding this comment.
Thanks. I checked this against vLLM 0.31: min_completion_tokens isn't a vLLM request field (no reference under vllm/entrypoints; the chat request has min_tokens only), and it isn't in the OpenAI API either. A prefill-style request with max_tokens: 1, min_completion_tokens: 200 returns 200 with 1 completion token, since the unknown field is ignored. So it can't cause the 400, and I've left it out to keep the prefill body otherwise unchanged. Happy to add it if a backend that honours it shows up.
ptimizeroracle
left a comment
There was a problem hiding this comment.
ran this locally at 3d9aef8, not a maintainer, checking the claim
src/tests/test_disaggregated_orchestrated_prefill_limits.py: 5 pass with the change- with
request.pyput back to the merge base, 4 of them fail: bothtest_min_tokens_only_on_the_decode_requestcases and bothtest_responses_prefill_generates_one_tokencases send_request_to_prefillerdoes already popmin_tokens(the comment there says the same thing aboutmax_tokens=1), so this brings the orchestrated path in line with the other p/d mode- full
src/testsrun on the branch: 268 passed, 1 failed, the sametest_prefixaware_routertest that fails on main on my machine - merges clean with #1132 on the same route and the combined tests pass
what i could not check: the vllm side of the description (the 400 on min_tokens > max_tokens, and /v1/responses ignoring max_tokens). vllm does not install on my machine, so that part is taken from the description
the router change looks correct to me
|
Thanks for verifying! For the vLLM side, from our deployment (vLLM 0.31.0):
The |
Signed-off-by: Muhammad Ibrahim <ibrahimnd2000@gmail.com>
Signed-off-by: Muhammad Ibrahim <ibrahimnd2000@gmail.com>
9f8e8f5 to
39a1eef
Compare
route_orchestrated_disaggregated_requestsends the prefill request withmax_tokens=1and the client's original body to decode. Two cases break the "one token on prefill" assumption.1.
min_tokens> 1 fails the whole request (HTTP 400). The client'smin_tokensis copied into the prefill request, and vLLM rejectsmin_tokens > max_tokens:This happens with or without a client
max_tokens.send_request_to_prefiller(the other P/D mode) already dropsmin_tokens; this does the same in the orchestrated path. The decode request keeps it.2.
/v1/responsesreturns garbage. vLLM's Responses API ignoresmax_tokens: it readsmax_output_tokens, and when that is unset the limit is the rest of the context. So the prefill generates a whole answer and returnskv_transfer_paramsfor prompt + output, and decode builds on KV that does not match its own prompt.Measured on vLLM 0.31 with the NIXL connector, one 162-token prompt, posting the router's prefill body directly to a prefill pod:
remote_num_tokens/v1/chat/completions/v1/responses(today)/v1/responses+max_output_tokens: 1End to end through the router,
/v1/responsesreturned unreadable text from the first token. With this change it returns the expected reasoning and tool call, the same as chat completions. The prefill request now also getsmax_output_tokens=1on/v1/responses, or whenever the body already hasmax_output_tokens. The decode request keeps the client's value.Tests:
src/tests/test_disaggregated_orchestrated_prefill_limits.pyruns the orchestrated flow with a fake client and checks three things:min_tokens: 200, with and withoutmax_tokens, the prefill request has nomin_tokensandmax_tokens1, while decode keeps both client values;/v1/responses, withmax_output_tokens300 or unset, the prefill request getsmax_output_tokens1, while decode keeps the client's value;max_output_tokens.4 of the 5 cases fail without the fix. The rest of
src/testspasses (265).test_multipart_proxy.pycouldn't be collected locally becausehttpxisn't installed. pre-commit (black, isort, ruff, codespell) passes.We run both fixes in production on a GLM-5.3 P/D deployment (vLLM 0.31, NIXL over EFA):
min_tokensrequests and/v1/responsesboth return correct output.Related orchestrated-P/D fixes: #1117, #1122, #1123.