fix(qwen3): preserve explicit stop-token causes - #978
Open
RicardoMin wants to merge 2 commits into
Open
Conversation
Signed-off-by: RicardoMin <17879681016@163.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a4d324ec8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Signed-off-by: RicardoMin <17879681016@163.com>
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.
Related to #865
Qwen3: preserve explicit stop-token causes in the stepped contract
Why this is a separate PR
This is the scoped follow-up requested by the maintainers during review of
#865. They asked
that the broad stop-contract change be split so that the shared boundary,
frontend bridges, and one model can be reviewed and validated independently.
This PR therefore extracts the Qwen3 migration from that work; Qwen3.5 and the
other model schedulers remain on their existing contract for now.
Summary
The previous vLLM dependency update fixed several frontend compatibility issues,
but the stepped Qwen3 path still collapsed two independent controls into the
single legacy
ignore_eosflag. That made an explicitstop_token_idsrequestindistinguishable from a model-EOS request and forced the bridge to guess a
synthetic stop token after the scheduler had already discarded the real one.
This PR gives Qwen3 a typed stop contract. It preserves the sampled trigger
token and its logprob, carries the concrete
StopCausethrough the schedulerand stepped bridge, and keeps EOS handling independent from request-provided
stop IDs.
What was wrong
The old contract exposed only
FinishReason::StoporFinishReason::Length.When a request stopped, the bridge could not tell whether the model emitted EOS
or an explicit request stop token. It therefore reconstructed a sentinel (EOS
first, otherwise the first configured stop ID). That reconstruction can report
the wrong token, loses the token's logprob, and is incorrect for a speculative
span where the first terminal token is followed by additional accepted tokens.
The old boolean also could not express the valid combination "ignore model EOS,
but still stop on these explicit request token IDs".
Contract change
FinishReason::Stop; trigger may be suppressed or reconstructedFinishReason::Stop+StopCause::Eos(id); token is retained internally; wirestop_reasonis absentstop_token_idsmatchFinishReason::Stop+StopCause::Token(id); actual ID is reported as wirestop_reasonignore_eos=trueFinishReason::LengthFinishReason::Length+ no stop cause; final sampled token is retainedEOS has precedence when the same ID is both the active EOS token and an explicit
request stop. Completion-token accounting is incremented once, including the
trigger token.
Scope and compatibility
Following the maintainer's scope request on #865, this PR intentionally
migrates Qwen3 only. The shared request/step types
accept an optional typed cause, while the existing legacy event path remains
available for models that have not been audited. The legacy bridge keeps its
synthetic-sentinel fallback only when an old producer supplies no typed cause.
Therefore Qwen3.5 and other model schedulers are not changed in this PR and do
not need to adopt the new resolver contract yet. If the maintainers agree with
the semantics, the remaining model lines can be migrated one at a time with
their own lifecycle tests.
Implementation
StopPolicy,EosPolicy, andStopCauseat the frontend engineboundary.
boolean for the stepped path.
ordinary decode, and speculative verification.
StopCause::Token(id)to the vLLM-compatible wirestop_reason.shared default field; no legacy model runtime behavior is changed.
Automated verification
cargo test --release -p pegainfer-frontend --libcargo test --release -p pegainfer-qwen3 --libcargo test --release -p pegainfer-sim --tests -- --test-threads=1cargo check --release -p pegainfer-qwen35 --features qwen35cargo build --release -p pegainfer-server --bin pegainfercargo fmt --all -- --checkgit diff --checkHTTP A/B verification
The comparison used two already-running OpenAI-compatible endpoints on the
same validation host. The explicit stop set covered the complete vocabulary,
so the first generated token was guaranteed to exercise the request-stop path.
This is a deterministic contract probe, not a generation-quality benchmark.
Results are shown as
finish_reason / stop_reason / completion_tokens:ignore_eos=true)length / null / 8stop / 12095 / 1stop / 12095 / 1length / null / 8length / null / 8length / null / 8The Qwen3.5 rows are an intentional legacy comparison: ordinary generation
still works, but its un-migrated scheduler does not yet satisfy the new typed
explicit-stop contract. They are not a claim that every legacy model fails in
all workloads.
Reproduction
Build and start each server independently. Qwen3.5 requires its feature-gated
Triton build environment; it does not support or require a
--gpu-memory-utilizationCLI argument.Then run the attached script (Python standard library only):
The script prints a compact comparison table and writes machine-readable JSON.
Use
--stop-token-id IDto replace the full-vocabulary deterministic set witha single known token when reproducing on a different prompt/model pair.
Follow-up
As requested during review of #865, this PR deliberately stops at the Qwen3
migration boundary. After the
maintainers confirm that the independent EOS/request-stop semantics are wanted,
the same policy propagation and resolver audit can be applied to Qwen3.5 and the
other legacy model lines in separate, model-scoped changes.
pr865_qwen3_stop_contract_ab.py