Report the matched stop sequence on GenerationBatch.Response - #1869
Open
mloiterman wants to merge 1 commit into
Open
mloiterman wants to merge 1 commit into
mloiterman wants to merge 1 commit into
Conversation
StopSequences keeps the matched token sequence on the trie node (__match__) but Matcher.advance() reduces it to a boolean, so a BatchGenerator consumer that receives finish_reason == "stop" cannot tell which stop sequence fired or how many already-emitted tokens it spans. TextStateMachine.step uses the same information (match[0]) to locate the start of a text match; the token-level path discards it. Add Matcher.matched, the sequence completed at the current position, and an optional match_sequence field on Response populated from it when a stop fires and None otherwise. Existing consumers are unaffected: the field defaults to None and nothing else changes.
mloiterman
force-pushed
the
fix/batch-response-match-sequence
branch
from
September 9, 2026 18:34
5d07f9f to
ab7d994
Compare
mloiterman
marked this pull request as ready for review
September 9, 2026 20:03
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.
Summary
StopSequenceMatcherbuilds an Aho-Corasick trie whose terminal nodesstore the sequence that matched (
_build_trie:node["__match__"] = (tuple(seq), idx)), butmatch()reduces that toa boolean and
GenerationBatch.nextreports onlyfinish_reason = "stop":A multi-token stop sequence matches only on its final token. The earlier
tokens were already yielded on previous steps as ordinary, non-terminal
responses.
A
BatchGeneratorconsumer that streams therefore cannot tell how manyof its already-emitted tokens belong to the stop, or which stop sequence fired.
The existing upstream text-level path keeps this information for itself —
TextStateMachine.stepuseslen(match[0])to find where a matchstarts in its buffer. However, the token-level path discards it before any
caller can see it. There is no reason the two paths should not expose the
same information.
Response.match_sequenceexisted before #1501 (on the oldSequenceStateMachine) and was dropped along with that class.Impact (downstream)
This was encountered in a production server built directly on
BatchGenerator(notmlx_lm.server) that works on tokens. Our serverholds back a fixed number of the most recent tokens and trims the
client's stop string at the stop terminal.
mlx_lm.serverdoes not seethis because it works only on decoded text. It routes every chunk
through
TextStateMachinewith the stop words as transitions. The statemachine holds back anything that might be the start of a stop word, and
the server calls
discardon stop.A token-level consumer has no exact trim length without the field. One
alternative is to re-decode the held-back text and search it for each
stop string, which is only a guess when the same string tokenizes
differently.
The other is to run its own copy of the trie in lockstep with this one,
which duplicates library internals in application code and remains
exact only while it stays a faithful duplicate of upstream's code.
Diff
Additive.
Matcher.matchedexposes the sequence completed at thecurrent position;
Response.match_sequencedefaults toNone, so noexisting constructor call or consumer changes, and
advance()'ssignature is untouched.
__match__[0]is already a tuple, so it is passed through as is. A"length" finish and every non-terminal response carry
None.Test
test_batch_generate_stop_match_sequence, next totest_batch_generate_with_stop_sequencesand using the same logit-biasdevice: three sequences forced to emit token 0, with stops
[0],[0, 0]and[1]. Step 1:(0,)on the single-token stop,Noneonthe in-progress and non-matching slots. Step 2:
(0, 0)on thetwo-token stop,
Noneon the slot still running. Fails onmainwithAttributeError: 'Response' object has no attribute 'match_sequence';passes with the fix, as does the existing stop-matcher test.