fix: make Cohere Transcribe usable and stop buffered streaming from crawling - #54
Merged
Mr-Sunglasses merged 1 commit intoSep 6, 2026
Conversation
…rawling Cohere Transcribe never transcribed anything from a default-configured client. Its decoder refuses to run without an explicit language -- sherpa-onnx answers "Invalid language: 'auto'" and returns an empty result -- but every client defaults that field to auto: the session schema, the test-transcription query parameter, and the test panel's first <option>. So the model failed with `language_unsupported` unless the operator happened to change the dropdown first. The catalog now carries `requires_explicit_language`, the mirror of `detects_language_automatically`, and /health reports it so mobile clients can drop "Detect language" too. The web test panel reads it and hides that option, selecting the first supported language instead; it also greys out languages outside a model's coverage when the model does not detect its own, which heads off the same class of failure. The engine-side guard stays as the backstop. Parakeet Unified English INT8 Streaming 560 ms was ~20x slower than the non-streaming export of the same weights, not faster. Its encoder ONNX declares buffered_streaming=1 with a 560-frame left context, a 16-frame chunk and 40 frames of right context, and takes no cache tensors: it re-encodes a 6.2-second window every 160 ms step. 560 ms is the latency before a word appears, not throughput. Measured on a 10-core M-series at 8 threads it decodes 7.4 s of audio in 7.4 s (RTF ~1.0) against 373 ms for the batch export -- so it only just keeps up with live audio, and costs ~20x for a finished file where there are no partials to deliver early. Nothing can make that export fast, so whole-file work now goes to the batch export when the user has it. Models name a `batch_twin_id`; SherpaOnnxEngine holds one recognizer per export, transcribe() uses the batch one and create_stream() keeps the streaming one. Measured through the gateway: 7398 ms -> 212 ms, identical transcript. The twin is resolved on each use rather than snapshotted in __init__, because nothing on the download or delete paths rebuilds the engine -- only select-model, engine config, and deleting the *active* model do. Snapshotting meant a twin downloaded afterwards was never used, and one deleted afterwards left the engine pointing at a missing directory, raising an unwrapped RuntimeError as a 500 while /health still reported ready. A twin that cannot load now falls back to the selection as well, so an opportunistic speed-up can never fail a request the selection could have served. Also relabels the streaming model from "Fast · streaming" to "Low latency · high CPU" and rewrites both Unified descriptions -- the non-streaming entry's had been copy-pasted and described the wrong model.
✅ Deploy Preview for voca-gateway canceled.
|
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
Mr-Sunglasses
deleted the
fix/cohere-language-and-buffered-streaming-throughput
branch
September 6, 2026 03:37
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.
Two reported problems, both reproduced against the real models on disk.
1. Cohere Transcribe never transcribed anything
Not a download or model problem — the model works fine once told a language:
Its decoder refuses to run without an explicit language, and passing
autothroughyields an empty transcript rather than a fallback:
But every client defaults that field to
auto—CreateSessionRequest.language,the
test-transcriptionquery parameter, and the test panel's first<option>. Sothe model failed with
language_unsupportedon any default-configured client.Fix.
CatalogModel.requires_explicit_language, the mirror ofdetects_language_automatically, is set on Cohere and reported by/healthso mobileclients can drop "Detect language" too.
syncTestLanguages()in the web UI reads it,hides that option and selects the first supported language instead. It also greys out
languages outside a model's coverage when the model does not self-detect, which heads
off the same class of failure (picking Hindi on Cohere). An empty
languagesliststill means "unknown", so nothing is restricted there. The engine-side guard stays as
the backstop.
2. "Streaming 560 ms" was ~20× slower than the batch export, not faster
Same audio, same machine, same 8 threads:
parakeet-unified-en-0.6b-int8(non-streaming)parakeet-unified-en-0.6b-560ms-int8(streaming)The encoder's ONNX metadata explains it:
No cache state: this is a sliding-window export, not a cache-aware streaming
Conformer. Every step re-encodes 560+16+40 = 616 feature frames (~6.2 s of audio) to
emit 16 new frames (160 ms). Measured: 47 decode steps for 7.4 s of audio
(7.435 / 0.160 ≈ 46.5 ✔), ~152 ms each — about 290 s of audio pushed through the
encoder to transcribe 7.4 s. Thread scaling confirms it is compute, not overhead:
So "560 ms" is the latency before a word appears, not throughput. The model barely
keeps up with live audio and costs ~20× for a finished file, where there are no
partials to deliver early. There is no config knob for this.
Fix. Whole-file work goes to the batch export of the same weights when the user
has it. Models name a
batch_twin_id;SherpaOnnxEngineholds one_ResidentRecognizerper export,transcribe()uses the batch one andcreate_stream()keeps the streaming one. When the twin is not installed both pointat the same holder, so behaviour is unchanged. Measured through the gateway:
7398 ms → 212 ms, identical transcript,
streaming_supportedstill true.Two bugs found reviewing that fix, and fixed
Resolving the twin once in
__init__was wrong, because nothing on the download ordelete paths rebuilds the engine — only
select_model, engineconfigure, anddeleting the active model do.
the streaming model is selected and transcription stays at RTF 1.005. Since the
model description now tells users to install it, this would have silently done
nothing.
forget_if_active(twin_id)returnsfalse for a non-active model, so the engine kept pointing at a removed directory.
After the next unload it raised an unwrapped
RuntimeError— an HTTP 500 — while/healthstill reported ready:The twin is now resolved on each use, and a twin that cannot load falls back to the
selection, so an opportunistic speed-up can never fail a request the selection could
have served. Verified live, with no engine rebuild between steps:
unload()andmodel_is_residentstill reach a twin that has since been uninstalled,so idle offload can reclaim its weights.
Also
Relabelled the streaming model from
"Fast · streaming"to"Low latency · high CPU"and rewrote both Unified descriptions — the non-streaming entry's had been copy-pasted
and described the streaming variant.
Checks
592 tests pass (28 new), plus
ruff check,ruff format --check,flake8 --select=WPS,E999, andmypy— the full CI quality job. New tests cover thetwin wiring through
EngineManager.select_model, a twin appearing and disappearingwithout a rebuild, load-failure fallback, residency/unload across both exports, the
/healthflag, and an assertion that Cohere is the only model flagged.One judgement call worth flagging:
warmup()now loads both exports when a twin isin play (~1.3 GB instead of 663 MB), since an engine that reports itself warm and still
pays a cold load on the first dictation has warmed the wrong one. Easy to change if
you would rather warm only the batch export.
🤖 Generated with Claude Code