cohere: add long-form chunking - #123
Draft
rafarrw wants to merge 1 commit into
Draft
Conversation
Cohere Transcribe trains on clips of at most 35 seconds, but the runtime gated only against the encoder positional table (~400 s). Past the trained span the decoder's cross-attention loses alignment, emits EOS early, and drops the rest of the audio, returning TRANSCRIBE_OK with no WARN and no transcribe_was_truncated(). Window longer audio to the trained span with 1 s of overlap, preferring low-energy boundaries, and stitch the windows in token space by matching the tail of the accumulated hypothesis against the head of the next one. Decode once at the end so the tokenizer's own spacing is preserved. The seam search is ported from canary_token_seam(), including its asymmetric search widths, so both families stitch the same way. Report input capacity as unbounded (max_audio_ms = 0) and move the family to the chunked bucket in docs/input-limits.md. n_ctx still bounds the per-window output budget. Batches carrying long inputs fall back to the serial path, since the batched graph assumes one encoder pass per utterance. Two assertions in cohere_e2e_smoke.cpp expected a rejected transcribe_run to clear the previous result; src/transcribe.cpp documents preserving it as intentional, so they now check the result survives.
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
boundaries preferring low-energy points
hypothesis against the head of the next window, then decode once at the end
max_audio_ms = 0) and move thefamily to the chunked bucket in
docs/input-limits.md, keepingn_ctxas theper-window output budget
graph assumes one encoder pass per utterance
Motivation
Cohere Transcribe trains on clips of at most 35 seconds. The value lives in
max_audio_clip_sin the HF feature-extractor config and appears in the portingintake, but the converter never writes to the GGUF. So the runtime gates only
against the encoder positional table, roughly 400 seconds, and accepts anything
in between as a single pass.
Past the trained span the decoder's cross-attention loses alignment. The decoder
emits EOS early and drops the rest of the audio. The run returns
TRANSCRIBE_OKwith noWARNand notranscribe_was_truncated(), whichdocs/input-limits.mdstates never happens.On a 66.3 second recording (
transcribe-cli, CPU, Q5_K_M) the single-pass pathreturned 107 words against 146 for the windowed path. The single-pass output
dropped a whole paragraph from the middle and spliced the surrounding text
together. The decoder's 512 max-new-tokens cap does not explain this. Lowering
--n-ctxto 300 produced byte-identical output. Lowering to 100 truncated withthe expected
WARNand a non-OK status, so detection works and its silence atthe default carries meaning.
This surfaced through Handy. Dictated paragraphs came back fluent, correctly
punctuated, and missing their middle.
Correctness
Windows overlap, so repeated speech is the adversarial case. A genuinely repeated
sentence must survive instead of matching the overlap and getting trimmed. A
first prototype used symmetric seam search and dropped one of four identical
repetitions.
The seam search now comes from
canary_token_seam()in #112, including itsacceptance rules and asymmetric search widths derived from the model's frame
duration. Canary's other merge path assigns overlap words by timestamp midpoint.
Cohere advertises
max_timestamp_kind = NONE, so only the token-space halfapplies here. If #112 changes in review, this should follow rather than diverge.
samples/jfk.wavx4Known limitation. When no seam matches, the code appends the window whole and
logs a
DEBUGline. Repeating a few words stays recoverable. Dropping speechdoes not. The fallback does trigger. A capitalised word at a sentence start
tokenises differently from its lower-case form mid-sentence, so the 44 second
case leaves one duplicated word at one seam. Normalised text reconciliation would
remove the duplicate, but the approach diverges from the token-space strategy in
#112, so I would rather ask first.
Performance
Windowing runs faster than the single pass, not slower. Conformer attention
scales quadratically with sequence length, so three short windows cost less than
one long one.
On the 66.3 second clip, total time dropped from 20.1 s to 17.9 s, about 11%.
Encode alone dropped from 19.1 s to 16.7 s. Measured with
transcribe-benchonthe identical binary, CPU backend, 1 warmup and 4 iterations each. The
per-iteration ranges never overlap (18.8 to 19.5 s against 16.3 to 17.0 s), so
this is not run-to-run noise.
Scope
src/arch/cohere/model.cpp,src/arch/cohere/cohere.h. Windowing, seamsearch, batch routing, capability change.
examples/cli/main.cpp. The limits printer treatedmax_audio_ms == 0with anon-zero context as "context too small for audio + prompt". A family both
windowed and context-capped breaks the assumption.
tests/cohere_smoke.cpp,tests/cohere_e2e_smoke.cpp.docs/input-limits.md,docs/models/cohere-transcribe-03-2026.md,include/transcribe.h.Out of scope: streaming, word timestamps, cross-window decoder context.
AI Assistance
AI assisted with the implementation, tests, and this description. I understand
and own the diff.
Validation
Environment: Windows 11 Pro 26200, MSVC 19.44, CPU backend,
cohere-transcribe-03-2026-Q5_K_M.gguf(HF revisiondfa4adeb), on top of223c9b0.transcribe_cohere_smokeadds 4 cases for boundary selection, 5 for the seamsearch, and 1 for batch cancellation. The long-form e2e case requires all four
occurrences of a distinctive phrase and bounds total output length to 90 to 120%
of the expected transcript. So the test catches both missing repetitions and
runaway duplication.
Two assertions in
cohere_e2e_smoke.cppexpected a rejectedtranscribe_runtoclear the previous result.
src/transcribe.cppdocuments the opposite asintentional: "Caller-param rejections [...] preserve the previous snapshot". The
assertions were stale, not the library. They now check the previous result
survives. Bisection confirmed those failures predate this change.
Gates not applicable. Intake signoff, Preflight A, and Preflight B cover porting
a new family. This changes an existing one and touches no converter, GGUF,
manifest, or tolerance file.
Gate still open:
This is why the PR is a draft. Running the command with authenticated access
would close the remaining Cohere-specific validation gate. The Windows Sortformer
build failure noted above is separate and predates this change.
Also not covered. Unspaced scripts stay untested. Decoding once at the end
instead of concatenating strings should reproduce the tokenizer's own spacing for
Chinese and Japanese, but I have no audio in those languages to confirm.