Conversation
Member
|
This is a great idea! |
Contributor
Author
Thanks, see the performances notes 😀. |
Member
Wow, that is huge and a big win especially for apps that want to show the streaming output. |
Contributor
|
Edit: Apologies for the original version of this comment. It was posted by an agent without my consent or review. Let me rewrite it in my own words. |
aleroot
force-pushed
the
alessio/tokenizer-streaming-decoder
branch
from
September 15, 2026 15:45
ae6f3b0 to
39c89c4
Compare
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.
Proposed changes
Generation currently constructs
NaiveStreamingDetokenizerdirectly, preventing tokenizer adapters from supplying an incremental implementation. This can require repeated decoding of a growing text segment, especially for long responses without newlines.Add
Tokenizer.makeStreamingDetokenizer()with a default implementation returning the existing naive decoder. Tokenizer adapters can opt into their own decoding implementation without adding a tokenizer dependency to MLX or changing the existing swift-transformers adapter.This is an extension point: existing adapters retain the default decoder. Performance improvements require an adapter that supplies an optimized implementation.
Example: swift-tokenizers
An application using aleroot/swift-tokenizers can implement the factory inside its existing
MLXLMCommon.Tokenizerbridge, whereupstreamis its wrappedTokenizers.Tokenizer:The following adapter connects that library's independent decoding session to MLX's append/next/finish lifecycle:
Special tokens remain enabled because MLX's tool and reasoning parsers consume them. Each factory call creates independent state.
finish()returns any unread output and the decoder's final text. Appending afterfinish()is a programmer error under this lifecycle.Note
In my own benchmark, coupled with
swift-tokenizers, incremental decoding substantially reduced tokenizer CPU time compared with repeatedly decoding the full generated prefix. For 4,096 tokens of newline-free text, decoding took approximately 0.19 ms instead of 55.6 ms for Qwen, and 1.64 ms instead of 1.16 seconds for Llama 2.These are decoder-only measurements, not end-to-end generation speedups. MLX’s default detokenizer resets its decoding segment at newlines, so its actual improvement will depend on the output and tokenizer. Long uninterrupted responses, JSON, and code are likely to benefit most.
Checklist
Put an
xin the boxes that apply.pre-commit run --all-filesto format my code / installed pre-commit prior to committing changesAI usage
accurately describes the code changes.