Summary
removeDuplicateTokenSequence() in StreamingAsrManager's per-chunk dedup path matches purely on token-ID equality, with no check that the matched span corresponds to the same region of audio. This causes false-positive "duplicate" detection whenever two different, unrelated words happen to share a short common subword prefix within the search window — corrupting the transcript by silently dropping the shared-prefix tokens from the second word, including whatever token carries the word-start (leading-space) marker.
This is a token-loss bug, not just a formatting/cosmetic issue: the removed tokens are gone from accumulatedTokens, so both the live streaming updates and finish()'s final reconstruction are corrupted.
Where
Sources/FluidAudio/ASR/AsrTranscription.swift, removeDuplicateTokenSequence(previous:current:maxOverlap:) (~line 446), specifically the "extended search" block (~line 484-509).
- Called from
transcribeStreamingChunk() (~line 257-268) with previousTokens = StreamingAsrManager's accumulatedTokens.
Repro (Russian streaming ASR)
Streaming a real conversation through StreamingAsrManager (parakeet-tdt-0.6b-v3-coreml, chunkSeconds: 11, leftContextSeconds: 2, rightContextSeconds: 2), two unrelated words spoken ~10+ seconds apart both start with "тран-":
трансформацию (earlier in the session)
транскрибируется (a later, different chunk)
Expected: ...трансформацию... транскрибируется... (two distinct words).
Actual: ...трансформациюскрибируется... — the leading "тран" of the second word is gone and it's glued directly onto the first, with no space.
Vocab-level evidence
Checked parakeet_v3_vocab.json:
6841 → " тра" (leading space — word-start marker)
3726 → "тра" (no leading space — mid-word continuation)
2394 → "ран"
Both words tokenize starting with the same [" тра", "ран"] (= " тран") sequence — 2 tokens, well within the default maxOverlap: 12 and boundarySearchFrames: 20. removeDuplicateTokenSequence finds this as a "duplicate" against the tail of previousTokens (from the unrelated earlier word) and strips it from the new chunk's tokens — including token 6841 (" тра"), which is what carries the space-before-word marker. Downstream detokenization then concatenates the remainder ("скрибируется") directly onto the previous word.
Why this is a general/structural issue, not a one-off
The matcher has no temporal-proximity check even though timestamps are already available at the call site (hypothesis.timestamps / adjustedTimestamps). Any two words separated in time that happen to share a ≥2-token common prefix within the search window are at risk — plausible fairly often in morphologically rich languages (Russian prefixes like транс-, при-, раз-, про-, etc.) combined with the model's fine-grained subword vocabulary (many 1-3 character Cyrillic pieces).
Suggested direction
Require the matched overlap to also be validated against timestamp/frame adjacency — i.e., only treat a token match as a genuine chunk-boundary duplicate if both occurrences fall within the actual audio overlap region (left/right context seconds), not just anywhere within an arbitrary token-count window. A coincidental prefix match between words spoken at very different times should never qualify.
Environment
- FluidAudio: 0.12.6
- Model: parakeet-tdt-0.6b-v3-coreml
- Swift 6.3.2, macOS 26.5.1
StreamingAsrConfig(chunkSeconds: 11, hypothesisChunkSeconds: 1, leftContextSeconds: 2, rightContextSeconds: 2, minContextForConfirmation: 10, confirmationThreshold: 0.5)
Happy to provide more repro pairs from logged sessions if useful — raw audio isn't shareable (real user conversations), but I have several more token-boundary examples from production logs.
Summary
removeDuplicateTokenSequence()inStreamingAsrManager's per-chunk dedup path matches purely on token-ID equality, with no check that the matched span corresponds to the same region of audio. This causes false-positive "duplicate" detection whenever two different, unrelated words happen to share a short common subword prefix within the search window — corrupting the transcript by silently dropping the shared-prefix tokens from the second word, including whatever token carries the word-start (leading-space) marker.This is a token-loss bug, not just a formatting/cosmetic issue: the removed tokens are gone from
accumulatedTokens, so both the live streaming updates andfinish()'s final reconstruction are corrupted.Where
Sources/FluidAudio/ASR/AsrTranscription.swift,removeDuplicateTokenSequence(previous:current:maxOverlap:)(~line 446), specifically the "extended search" block (~line 484-509).transcribeStreamingChunk()(~line 257-268) withpreviousTokens=StreamingAsrManager'saccumulatedTokens.Repro (Russian streaming ASR)
Streaming a real conversation through
StreamingAsrManager(parakeet-tdt-0.6b-v3-coreml,chunkSeconds: 11,leftContextSeconds: 2,rightContextSeconds: 2), two unrelated words spoken ~10+ seconds apart both start with "тран-":трансформацию(earlier in the session)транскрибируется(a later, different chunk)Expected:
...трансформацию... транскрибируется...(two distinct words).Actual:
...трансформациюскрибируется...— the leading "тран" of the second word is gone and it's glued directly onto the first, with no space.Vocab-level evidence
Checked
parakeet_v3_vocab.json:Both words tokenize starting with the same
[" тра", "ран"](= " тран") sequence — 2 tokens, well within the defaultmaxOverlap: 12andboundarySearchFrames: 20.removeDuplicateTokenSequencefinds this as a "duplicate" against the tail ofpreviousTokens(from the unrelated earlier word) and strips it from the new chunk's tokens — including token6841(" тра"), which is what carries the space-before-word marker. Downstream detokenization then concatenates the remainder ("скрибируется") directly onto the previous word.Why this is a general/structural issue, not a one-off
The matcher has no temporal-proximity check even though timestamps are already available at the call site (
hypothesis.timestamps/adjustedTimestamps). Any two words separated in time that happen to share a ≥2-token common prefix within the search window are at risk — plausible fairly often in morphologically rich languages (Russian prefixes likeтранс-,при-,раз-,про-, etc.) combined with the model's fine-grained subword vocabulary (many 1-3 character Cyrillic pieces).Suggested direction
Require the matched overlap to also be validated against timestamp/frame adjacency — i.e., only treat a token match as a genuine chunk-boundary duplicate if both occurrences fall within the actual audio overlap region (left/right context seconds), not just anywhere within an arbitrary token-count window. A coincidental prefix match between words spoken at very different times should never qualify.
Environment
StreamingAsrConfig(chunkSeconds: 11, hypothesisChunkSeconds: 1, leftContextSeconds: 2, rightContextSeconds: 2, minContextForConfirmation: 10, confirmationThreshold: 0.5)Happy to provide more repro pairs from logged sessions if useful — raw audio isn't shareable (real user conversations), but I have several more token-boundary examples from production logs.