Repository navigation
fix(qwen3.5): stop at max_tokens instead of one token past it - #778
Open
manueldomke wants to merge 1 commit into
Open
manueldomke wants to merge 1 commit into
manueldomke wants to merge 1 commit into
Conversation
Qwen3_5VL::generate() draws the first token before its decode loop and checked the limit only at the bottom of the loop, after the next draw. So every request that ran into max_tokens got one token more than it asked for, and paid one more forward() for it: max_tokens=1 returned "A\n" with completion_tokens 2. The loop now checks the limit first, before it draws. Its shape follows _shared_generate(): the context limit is checked inside the loop, and reaching max_tokens still wins over it, so finish_reason is unchanged. Measured on Qwen3.5-2B (NPU2), 20 independent one-token prompts, three alternating rounds against main: median wall clock 604 -> 574 ms per request, completion_tokens 2 -> 1. The first token of every answer and two 60-token generations are identical to main. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 task done
1 task done
This branch has not been deployed
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.
Motivation
Qwen3_5VL::generate()draws the first token before its decode loop andchecked
max_tokensonly at the bottom of the loop, after the next draw. Everyrequest that runs into the limit gets one token more than it asked for and pays
one more
forward()for it:max_tokens=1returns"A\n"withcompletion_tokens: 2.For a model that answers with a single token, that is the whole request: an
extra decode step, and a trailing token the caller has to strip. The Qwen3.5
fine-tunes from #776 and #777 hit it on every call:
Technical Details
The loop is now
while (true)and checks the limit first, before it draws. Ithas the same shape as
_shared_generate(): the context limit is checked insidethe loop, and reaching
max_tokensstill wins over it. Sofinish_reasonisunchanged even when both limits are hit at once.
Independent of #776 and #777; applies to
mainas is.Scope. Only
Qwen3_5VL::generate(). Two other places have the samepattern. They are left for separate changes:
Qwen3_6_MOE::generate()has the same loop, with the limit checked after thedraw. I found this by reading only; it is not changed here, because I could
not test that model.
_shared_generate()emits the token drawn in_shared_insert()withoutcounting it. There
max_tokens=Nreturns N+1 tokens whileusagereports N.Test Plan
Qwen3.5-2B (
decider-2b-NPU2) on a Ryzen AI 7 PRO 350,platform_profileperformance. The prompt set is 20 independent one-token decision prompts with
no shared prefixes,
temperature: 0. Three alternating rounds ofmainagainst this branch, one server start per variant and round, wall clock per
request. Plus two 60-token generations, compared character by character.
Test Result
Per-round medians: 604 / 605 / 602 ms against 574 / 574 / 575 ms. The first
token of all 20 answers and both 60-token generations are identical to
main.Submission Checklist
🤖 Generated with Claude Code