feat(breeze_tts): sub-chunk streaming via resumable AR stepper - #473
Open
Th-Underscore wants to merge 2 commits into
Open
feat(breeze_tts): sub-chunk streaming via resumable AR stepper#473Th-Underscore wants to merge 2 commits into
Th-Underscore wants to merge 2 commits into
Conversation
Refactor Impl::generate into begin/step/end_stream (offline path delegates, bit-identical output). Session emits multiple SSE deltas per text chunk via prefix decode with lookahead margin. Opt-in via stream_subchunk/stream_frames_per_event/stream_lookahead_margin; default path unchanged. 40/40 ctest green.
Growing prefix decodes in sub-chunk streaming change chunk_frames every event. Building the replacement first peaks at old+new VRAM and fragments the allocator until cudaMalloc fails mid-stream even at ~90% usage. Reset first; strictly reduces peak with no behavior change.
Th-Underscore
force-pushed
the
realtime-breeze-stream
branch
from
September 6, 2026 08:11
2beda85 to
1000c2a
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.
Summary
BreezeTTS streaming currently emits at most one audio event per text chunk, so first audio only becomes available once the chunk's full autoregressive run plus decode has finished. This adds a sub-chunk streaming path: a resumable autoregressive stepper advances the backbone by a configurable number of frames per event (reusing the existing streaming primitives) and emits the decoded prefix audio every event, withholding a configurable lookahead margin.
This directly addresses the streaming time-to-first-audio concern raised in issue #467, where chunk-granular streaming buffers an entire text chunk before emitting any audio.
breeze_tts.stream_subchunk(bool, defaultfalse— existing chunk-granular streaming is unchanged),breeze_tts.stream_frames_per_event(default 32),breeze_tts.stream_lookahead_margin(default 12; must stay below frames-per-event or the emit watermark stalls into the 256-iteration guard).generate()delegates to the same stepper as a single event; output is bit-identical to the pre-stepper path (verified).Known limitation: per-event prefix re-decode is O(N²)
Each event decodes the entire accumulated code prefix (the Mimi decoder has no incremental state), so per-event cost is
c_AR·E + c_decode·prefixand total decode work grows quadratically in frames within a chunk. Measured on a 988-codepoint single-run corpus (below), per-event cost climbs monotonically through the run. Two facts bound the practical impact:text_chunk_sizealready bounds both cost and drift.StreamingConv1d.previous/StreamingConvTranspose1d.partialring buffers) across ~30 decoder layers, with a mandatory byte-identical parity gate. That is a separate, large change and is out of scope here; it is the planned follow-up.Spec note
For a released GGUF the server prefers the GGUF-embedded contract spec, so the new options are unknown to already-published artifacts and are rejected (
unknown BreezeTTS request option: stream_lookahead_margin) until the server is launched with--model-spec-overridepointing at a spec directory containing the updatedmodel_specs/breeze_tts.json. This is a stale-artifact issue, not a code bug.Validation
Tested on 2×V100-16GB, server on GPU 2, released
breeze-tts-2-q8_0GGUF +--model-spec-override, 24/24 unit-gate green on the current base (upstream moved the extended tests out of the unit gate after this PR's base PR #423 merged; 24/24 is the full gate).The 16/8 arm costs +25.3% wall on the long corpus (vs ~+5% on short clips) — the O(N²) penalty growing as a fraction of wall with length, exactly as the model above predicts. 16/8 is a clean win for short clips (lower TTFT, ~2× event cadence); for long-form within one chunk 32/12 is the cheaper trade until the incremental-decode follow-up lands.
Test artifacts (gists)
python3 stream_client.py --port 5025 --server http://127.0.0.1:5023 --voice-dir <dir-with-clone-wavs>fpe_measure.py <frames> <margin>(short-clip A/B) andfpe_long.py <frames> <margin>(long-corpus A/B; forces one AR run viatext_chunk_size=2048)/tmp/long_test.txtbefore runningfpe_long.pyVoice reference for streaming tests
Smoke tests used a VCTK speaker p233 clone: 15.75s at 48 kHz, a deterministic same-text concat of utterances 016+025+030 with 0.3s silence gaps, reproducibly rebuildable from the source dataset (https://huggingface.co/datasets/badayvedat/VCTK — the source copy is declared apache-2.0; note VCTK is Cambridge corpus data). Transcripts sidecar: https://gist.github.com/Th-Underscore/bbfd2a7d65953e44025a739cc9cdfd26. The WAV itself cannot be published (gists do not accept binary files); a maintainer can rebuild it from the utterance list above if desired.
Limitations
Complete and validated as described; the incremental per-frame decoder state (Mimi
previous/partial) is the planned follow-up.