cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

Description

@TGiles

Summary

cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
directly to the closing clause. The run returns success, nothing is logged, and
transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
max_audio_ms advertises 400 s.

Found while using Handy for dictation. Handy is not implicated: calling the library directly
through the Python binding reproduces it exactly.

Environment

  • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
    Handy 0.9.0 (Windows x86_64)
  • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
  • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
  • arch=cohere_asr, variant=cohere-transcribe-03-2026
  • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
  • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

What the library reports about itself

max_audio_ms=400000 (400.0 s)
effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
max_timestamp_kind=none
supports_streaming=False
transcribe_was_truncated() == false on the 134 s run that loses 73% of the content

was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
not distinguish the two cases at all.

Steps to reproduce

importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

Expected

Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

Actual

75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
covers the opening then jumps to the passage's final clause:

Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
cloak race, and the component moved to Mozglue afterward. Most mornings start and about
preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
mangle any of them.

The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

Length sweep

Same recording truncated to increasing prefixes, model loaded once, only input duration
varying. Expected counts use the measured reading rate of 2.08 words per second.

inputwords returnedexpectedratio
20.0 s4242101%
30.0 s596294%
35.0 s687393%
40.0 s748389%
50.0 s9710493%
60.0 s9412575%
90.0 s8118743%
134.0 s7527927%

Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
then decreases, which is what puts the loss in the middle rather than the tail.

No chunking happens on this path

len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
it:

cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)

1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
from that arithmetic rather than read from the model file. So the encoder consumes everything
and the decoder fails to traverse it.

The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
degenerates:

...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
characters)

A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
attending over far more encoder context than it was trained on. That suggests an input-length
policy problem in the cohere_asr family implementation rather than any one compute kernel.

For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
carries four visible seams where segments were joined. The hosted path chunks. This one does
not. The weights are fine.

The decisive control: chunk the input and the same library succeeds

Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
80.95 s, and 123.14 s.

runwordsvs reference
Reference passage279100%
Hosted api.cohere.com298107%
Local, 4 chunks under 45 s294105%
Local, one 134 s pass7527%

A caller that chunks gets a complete transcript from the library that fails in one pass.

Suggested fix, in preference order

  1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
    CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
    feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
    demonstrated on a 55 minute input.
  2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
    input is rejected with InputTooLong rather than silently answered.
  3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
    false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
    whisper.

Other controls

  • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
    silencedetect at -40 dB finds no gap of 1.5 s or longer.
  • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
    gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
    down into each gap rather than cutting mid-waveform.
  • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
    the same passage end to end.
  • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
    here, reporting cohere: cuda backend requested but not available then failing model load
    with BackendError status 8, because the archive ships no CUDA runtime and this machine has
    the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
    to add anything.

Related

#89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
false, content missing. Different trigger, since that one needed an initial_prompt and short
audio while this needs no prompt and long audio. Whether they share a decode-loop termination
path is for you to say.

Audio

The 134 s WAV used for every number above is attached in a comment below.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

      Description

      @TGiles

      Summary

      cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
      recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
      directly to the closing clause. The run returns success, nothing is logged, and
      transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
      max_audio_ms advertises 400 s.

      Found while using Handy for dictation. Handy is not implicated: calling the library directly
      through the Python binding reproduces it exactly.

      Environment

      • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
        Handy 0.9.0 (Windows x86_64)
      • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
      • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
      • arch=cohere_asr, variant=cohere-transcribe-03-2026
      • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
      • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

      What the library reports about itself

      max_audio_ms=400000 (400.0 s)
      effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
      max_timestamp_kind=none
      supports_streaming=False
      transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
      

      was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
      not distinguish the two cases at all.

      Steps to reproduce

      importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
      print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

      Expected

      Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
      defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

      Actual

      75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
      covers the opening then jumps to the passage's final clause:

      Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
      flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
      cloak race, and the component moved to Mozglue afterward. Most mornings start and about
      preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
      pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
      mangle any of them.

      The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

      Length sweep

      Same recording truncated to increasing prefixes, model loaded once, only input duration
      varying. Expected counts use the measured reading rate of 2.08 words per second.

      inputwords returnedexpectedratio
      20.0 s4242101%
      30.0 s596294%
      35.0 s687393%
      40.0 s748389%
      50.0 s9710493%
      60.0 s9412575%
      90.0 s8118743%
      134.0 s7527927%

      Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
      advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
      then decreases, which is what puts the loss in the middle rather than the tail.

      No chunking happens on this path

      len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
      the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
      it:

      cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
      

      1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
      from that arithmetic rather than read from the model file. So the encoder consumes everything
      and the decoder fails to traverse it.

      The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
      degenerates:

      ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
      characters)

      A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
      attending over far more encoder context than it was trained on. That suggests an input-length
      policy problem in the cohere_asr family implementation rather than any one compute kernel.

      For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
      model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
      carries four visible seams where segments were joined. The hosted path chunks. This one does
      not. The weights are fine.

      The decisive control: chunk the input and the same library succeeds

      Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
      and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
      Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
      80.95 s, and 123.14 s.

      runwordsvs reference
      Reference passage279100%
      Hosted api.cohere.com298107%
      Local, 4 chunks under 45 s294105%
      Local, one 134 s pass7527%

      A caller that chunks gets a complete transcript from the library that fails in one pass.

      Suggested fix, in preference order

      1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
        CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
        feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
        demonstrated on a 55 minute input.
      2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
        input is rejected with InputTooLong rather than silently answered.
      3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
        false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
        whisper.

      Other controls

      • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
        silencedetect at -40 dB finds no gap of 1.5 s or longer.
      • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
        gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
        down into each gap rather than cutting mid-waveform.
      • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
        the same passage end to end.
      • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
        here, reporting cohere: cuda backend requested but not available then failing model load
        with BackendError status 8, because the archive ships no CUDA runtime and this machine has
        the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
        to add anything.

      Related

      #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
      pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
      false, content missing. Different trigger, since that one needed an initial_prompt and short
      audio while this needs no prompt and long audio. Whether they share a decode-loop termination
      path is for you to say.

      Audio

      The 134 s WAV used for every number above is attached in a comment below.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

          Description

          @TGiles

          Summary

          cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
          recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
          directly to the closing clause. The run returns success, nothing is logged, and
          transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
          max_audio_ms advertises 400 s.

          Found while using Handy for dictation. Handy is not implicated: calling the library directly
          through the Python binding reproduces it exactly.

          Environment

          • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
            Handy 0.9.0 (Windows x86_64)
          • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
          • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
          • arch=cohere_asr, variant=cohere-transcribe-03-2026
          • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
          • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

          What the library reports about itself

          max_audio_ms=400000 (400.0 s)
          effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
          max_timestamp_kind=none
          supports_streaming=False
          transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
          

          was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
          not distinguish the two cases at all.

          Steps to reproduce

          importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
          print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

          Expected

          Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
          defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

          Actual

          75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
          covers the opening then jumps to the passage's final clause:

          Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
          flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
          cloak race, and the component moved to Mozglue afterward. Most mornings start and about
          preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
          pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
          mangle any of them.

          The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

          Length sweep

          Same recording truncated to increasing prefixes, model loaded once, only input duration
          varying. Expected counts use the measured reading rate of 2.08 words per second.

          inputwords returnedexpectedratio
          20.0 s4242101%
          30.0 s596294%
          35.0 s687393%
          40.0 s748389%
          50.0 s9710493%
          60.0 s9412575%
          90.0 s8118743%
          134.0 s7527927%

          Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
          advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
          then decreases, which is what puts the loss in the middle rather than the tail.

          No chunking happens on this path

          len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
          the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
          it:

          cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
          

          1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
          from that arithmetic rather than read from the model file. So the encoder consumes everything
          and the decoder fails to traverse it.

          The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
          degenerates:

          ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
          characters)

          A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
          attending over far more encoder context than it was trained on. That suggests an input-length
          policy problem in the cohere_asr family implementation rather than any one compute kernel.

          For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
          model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
          carries four visible seams where segments were joined. The hosted path chunks. This one does
          not. The weights are fine.

          The decisive control: chunk the input and the same library succeeds

          Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
          and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
          Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
          80.95 s, and 123.14 s.

          runwordsvs reference
          Reference passage279100%
          Hosted api.cohere.com298107%
          Local, 4 chunks under 45 s294105%
          Local, one 134 s pass7527%

          A caller that chunks gets a complete transcript from the library that fails in one pass.

          Suggested fix, in preference order

          1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
            CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
            feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
            demonstrated on a 55 minute input.
          2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
            input is rejected with InputTooLong rather than silently answered.
          3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
            false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
            whisper.

          Other controls

          • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
            silencedetect at -40 dB finds no gap of 1.5 s or longer.
          • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
            gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
            down into each gap rather than cutting mid-waveform.
          • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
            the same passage end to end.
          • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
            here, reporting cohere: cuda backend requested but not available then failing model load
            with BackendError status 8, because the archive ships no CUDA runtime and this machine has
            the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
            to add anything.

          Related

          #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
          pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
          false, content missing. Different trigger, since that one needed an initial_prompt and short
          audio while this needs no prompt and long audio. Whether they share a decode-loop termination
          path is for you to say.

          Audio

          The 134 s WAV used for every number above is attached in a comment below.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

              Description

              @TGiles

              Summary

              cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
              recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
              directly to the closing clause. The run returns success, nothing is logged, and
              transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
              max_audio_ms advertises 400 s.

              Found while using Handy for dictation. Handy is not implicated: calling the library directly
              through the Python binding reproduces it exactly.

              Environment

              • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
                Handy 0.9.0 (Windows x86_64)
              • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
              • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
              • arch=cohere_asr, variant=cohere-transcribe-03-2026
              • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
              • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

              What the library reports about itself

              max_audio_ms=400000 (400.0 s)
              effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
              max_timestamp_kind=none
              supports_streaming=False
              transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
              

              was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
              not distinguish the two cases at all.

              Steps to reproduce

              importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
              print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

              Expected

              Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
              defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

              Actual

              75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
              covers the opening then jumps to the passage's final clause:

              Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
              flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
              cloak race, and the component moved to Mozglue afterward. Most mornings start and about
              preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
              pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
              mangle any of them.

              The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

              Length sweep

              Same recording truncated to increasing prefixes, model loaded once, only input duration
              varying. Expected counts use the measured reading rate of 2.08 words per second.

              inputwords returnedexpectedratio
              20.0 s4242101%
              30.0 s596294%
              35.0 s687393%
              40.0 s748389%
              50.0 s9710493%
              60.0 s9412575%
              90.0 s8118743%
              134.0 s7527927%

              Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
              advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
              then decreases, which is what puts the loss in the middle rather than the tail.

              No chunking happens on this path

              len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
              the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
              it:

              cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
              

              1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
              from that arithmetic rather than read from the model file. So the encoder consumes everything
              and the decoder fails to traverse it.

              The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
              degenerates:

              ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
              characters)

              A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
              attending over far more encoder context than it was trained on. That suggests an input-length
              policy problem in the cohere_asr family implementation rather than any one compute kernel.

              For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
              model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
              carries four visible seams where segments were joined. The hosted path chunks. This one does
              not. The weights are fine.

              The decisive control: chunk the input and the same library succeeds

              Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
              and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
              Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
              80.95 s, and 123.14 s.

              runwordsvs reference
              Reference passage279100%
              Hosted api.cohere.com298107%
              Local, 4 chunks under 45 s294105%
              Local, one 134 s pass7527%

              A caller that chunks gets a complete transcript from the library that fails in one pass.

              Suggested fix, in preference order

              1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
                CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
                feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
                demonstrated on a 55 minute input.
              2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
                input is rejected with InputTooLong rather than silently answered.
              3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
                false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
                whisper.

              Other controls

              • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
                silencedetect at -40 dB finds no gap of 1.5 s or longer.
              • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
                gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
                down into each gap rather than cutting mid-waveform.
              • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
                the same passage end to end.
              • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
                here, reporting cohere: cuda backend requested but not available then failing model load
                with BackendError status 8, because the archive ships no CUDA runtime and this machine has
                the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
                to add anything.

              Related

              #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
              pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
              false, content missing. Different trigger, since that one needed an initial_prompt and short
              audio while this needs no prompt and long audio. Whether they share a decode-loop termination
              path is for you to say.

              Audio

              The 134 s WAV used for every number above is attached in a comment below.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

                  Description

                  @TGiles

                  Summary

                  cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
                  recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
                  directly to the closing clause. The run returns success, nothing is logged, and
                  transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
                  max_audio_ms advertises 400 s.

                  Found while using Handy for dictation. Handy is not implicated: calling the library directly
                  through the Python binding reproduces it exactly.

                  Environment

                  • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
                    Handy 0.9.0 (Windows x86_64)
                  • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
                  • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
                  • arch=cohere_asr, variant=cohere-transcribe-03-2026
                  • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
                  • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

                  What the library reports about itself

                  max_audio_ms=400000 (400.0 s)
                  effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
                  max_timestamp_kind=none
                  supports_streaming=False
                  transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
                  

                  was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
                  not distinguish the two cases at all.

                  Steps to reproduce

                  importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
                  print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

                  Expected

                  Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
                  defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

                  Actual

                  75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
                  covers the opening then jumps to the passage's final clause:

                  Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
                  flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
                  cloak race, and the component moved to Mozglue afterward. Most mornings start and about
                  preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
                  pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
                  mangle any of them.

                  The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

                  Length sweep

                  Same recording truncated to increasing prefixes, model loaded once, only input duration
                  varying. Expected counts use the measured reading rate of 2.08 words per second.

                  inputwords returnedexpectedratio
                  20.0 s4242101%
                  30.0 s596294%
                  35.0 s687393%
                  40.0 s748389%
                  50.0 s9710493%
                  60.0 s9412575%
                  90.0 s8118743%
                  134.0 s7527927%

                  Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
                  advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
                  then decreases, which is what puts the loss in the middle rather than the tail.

                  No chunking happens on this path

                  len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
                  the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
                  it:

                  cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
                  

                  1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
                  from that arithmetic rather than read from the model file. So the encoder consumes everything
                  and the decoder fails to traverse it.

                  The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
                  degenerates:

                  ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
                  characters)

                  A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
                  attending over far more encoder context than it was trained on. That suggests an input-length
                  policy problem in the cohere_asr family implementation rather than any one compute kernel.

                  For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
                  model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
                  carries four visible seams where segments were joined. The hosted path chunks. This one does
                  not. The weights are fine.

                  The decisive control: chunk the input and the same library succeeds

                  Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
                  and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
                  Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
                  80.95 s, and 123.14 s.

                  runwordsvs reference
                  Reference passage279100%
                  Hosted api.cohere.com298107%
                  Local, 4 chunks under 45 s294105%
                  Local, one 134 s pass7527%

                  A caller that chunks gets a complete transcript from the library that fails in one pass.

                  Suggested fix, in preference order

                  1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
                    CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
                    feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
                    demonstrated on a 55 minute input.
                  2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
                    input is rejected with InputTooLong rather than silently answered.
                  3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
                    false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
                    whisper.

                  Other controls

                  • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
                    silencedetect at -40 dB finds no gap of 1.5 s or longer.
                  • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
                    gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
                    down into each gap rather than cutting mid-waveform.
                  • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
                    the same passage end to end.
                  • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
                    here, reporting cohere: cuda backend requested but not available then failing model load
                    with BackendError status 8, because the archive ships no CUDA runtime and this machine has
                    the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
                    to add anything.

                  Related

                  #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
                  pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
                  false, content missing. Different trigger, since that one needed an initial_prompt and short
                  audio while this needs no prompt and long audio. Whether they share a decode-loop termination
                  path is for you to say.

                  Audio

                  The 134 s WAV used for every number above is attached in a comment below.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

                      Description

                      @TGiles

                      Summary

                      cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
                      recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
                      directly to the closing clause. The run returns success, nothing is logged, and
                      transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
                      max_audio_ms advertises 400 s.

                      Found while using Handy for dictation. Handy is not implicated: calling the library directly
                      through the Python binding reproduces it exactly.

                      Environment

                      • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
                        Handy 0.9.0 (Windows x86_64)
                      • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
                      • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
                      • arch=cohere_asr, variant=cohere-transcribe-03-2026
                      • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
                      • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

                      What the library reports about itself

                      max_audio_ms=400000 (400.0 s)
                      effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
                      max_timestamp_kind=none
                      supports_streaming=False
                      transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
                      

                      was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
                      not distinguish the two cases at all.

                      Steps to reproduce

                      importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
                      print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

                      Expected

                      Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
                      defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

                      Actual

                      75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
                      covers the opening then jumps to the passage's final clause:

                      Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
                      flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
                      cloak race, and the component moved to Mozglue afterward. Most mornings start and about
                      preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
                      pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
                      mangle any of them.

                      The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

                      Length sweep

                      Same recording truncated to increasing prefixes, model loaded once, only input duration
                      varying. Expected counts use the measured reading rate of 2.08 words per second.

                      inputwords returnedexpectedratio
                      20.0 s4242101%
                      30.0 s596294%
                      35.0 s687393%
                      40.0 s748389%
                      50.0 s9710493%
                      60.0 s9412575%
                      90.0 s8118743%
                      134.0 s7527927%

                      Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
                      advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
                      then decreases, which is what puts the loss in the middle rather than the tail.

                      No chunking happens on this path

                      len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
                      the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
                      it:

                      cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
                      

                      1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
                      from that arithmetic rather than read from the model file. So the encoder consumes everything
                      and the decoder fails to traverse it.

                      The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
                      degenerates:

                      ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
                      characters)

                      A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
                      attending over far more encoder context than it was trained on. That suggests an input-length
                      policy problem in the cohere_asr family implementation rather than any one compute kernel.

                      For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
                      model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
                      carries four visible seams where segments were joined. The hosted path chunks. This one does
                      not. The weights are fine.

                      The decisive control: chunk the input and the same library succeeds

                      Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
                      and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
                      Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
                      80.95 s, and 123.14 s.

                      runwordsvs reference
                      Reference passage279100%
                      Hosted api.cohere.com298107%
                      Local, 4 chunks under 45 s294105%
                      Local, one 134 s pass7527%

                      A caller that chunks gets a complete transcript from the library that fails in one pass.

                      Suggested fix, in preference order

                      1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
                        CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
                        feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
                        demonstrated on a 55 minute input.
                      2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
                        input is rejected with InputTooLong rather than silently answered.
                      3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
                        false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
                        whisper.

                      Other controls

                      • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
                        silencedetect at -40 dB finds no gap of 1.5 s or longer.
                      • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
                        gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
                        down into each gap rather than cutting mid-waveform.
                      • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
                        the same passage end to end.
                      • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
                        here, reporting cohere: cuda backend requested but not available then failing model load
                        with BackendError status 8, because the archive ships no CUDA runtime and this machine has
                        the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
                        to add anything.

                      Related

                      #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
                      pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
                      false, content missing. Different trigger, since that one needed an initial_prompt and short
                      audio while this needs no prompt and long audio. Whether they share a decode-loop termination
                      path is for you to say.

                      Audio

                      The 134 s WAV used for every number above is attached in a comment below.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

                          Description

                          @TGiles

                          Summary

                          cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
                          recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
                          directly to the closing clause. The run returns success, nothing is logged, and
                          transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
                          max_audio_ms advertises 400 s.

                          Found while using Handy for dictation. Handy is not implicated: calling the library directly
                          through the Python binding reproduces it exactly.

                          Environment

                          • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
                            Handy 0.9.0 (Windows x86_64)
                          • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
                          • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
                          • arch=cohere_asr, variant=cohere-transcribe-03-2026
                          • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
                          • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

                          What the library reports about itself

                          max_audio_ms=400000 (400.0 s)
                          effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
                          max_timestamp_kind=none
                          supports_streaming=False
                          transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
                          

                          was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
                          not distinguish the two cases at all.

                          Steps to reproduce

                          importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
                          print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

                          Expected

                          Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
                          defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

                          Actual

                          75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
                          covers the opening then jumps to the passage's final clause:

                          Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
                          flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
                          cloak race, and the component moved to Mozglue afterward. Most mornings start and about
                          preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
                          pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
                          mangle any of them.

                          The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

                          Length sweep

                          Same recording truncated to increasing prefixes, model loaded once, only input duration
                          varying. Expected counts use the measured reading rate of 2.08 words per second.

                          inputwords returnedexpectedratio
                          20.0 s4242101%
                          30.0 s596294%
                          35.0 s687393%
                          40.0 s748389%
                          50.0 s9710493%
                          60.0 s9412575%
                          90.0 s8118743%
                          134.0 s7527927%

                          Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
                          advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
                          then decreases, which is what puts the loss in the middle rather than the tail.

                          No chunking happens on this path

                          len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
                          the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
                          it:

                          cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
                          

                          1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
                          from that arithmetic rather than read from the model file. So the encoder consumes everything
                          and the decoder fails to traverse it.

                          The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
                          degenerates:

                          ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
                          characters)

                          A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
                          attending over far more encoder context than it was trained on. That suggests an input-length
                          policy problem in the cohere_asr family implementation rather than any one compute kernel.

                          For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
                          model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
                          carries four visible seams where segments were joined. The hosted path chunks. This one does
                          not. The weights are fine.

                          The decisive control: chunk the input and the same library succeeds

                          Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
                          and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
                          Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
                          80.95 s, and 123.14 s.

                          runwordsvs reference
                          Reference passage279100%
                          Hosted api.cohere.com298107%
                          Local, 4 chunks under 45 s294105%
                          Local, one 134 s pass7527%

                          A caller that chunks gets a complete transcript from the library that fails in one pass.

                          Suggested fix, in preference order

                          1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
                            CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
                            feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
                            demonstrated on a 55 minute input.
                          2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
                            input is rejected with InputTooLong rather than silently answered.
                          3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
                            false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
                            whisper.

                          Other controls

                          • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
                            silencedetect at -40 dB finds no gap of 1.5 s or longer.
                          • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
                            gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
                            down into each gap rather than cutting mid-waveform.
                          • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
                            the same passage end to end.
                          • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
                            here, reporting cohere: cuda backend requested but not available then failing model load
                            with BackendError status 8, because the archive ships no CUDA runtime and this machine has
                            the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
                            to add anything.

                          Related

                          #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
                          pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
                          false, content missing. Different trigger, since that one needed an initial_prompt and short
                          audio while this needs no prompt and long audio. Whether they share a decode-loop termination
                          path is for you to say.

                          Audio

                          The 134 s WAV used for every number above is attached in a comment below.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              cohere_asr: input past ~50s silently yields a partial transcript, was_truncated() false, while max_audio_ms advertises 400s #128

                              Description

                              @TGiles

                              Summary

                              cohere_asr silently returns a partial transcript once input passes roughly 50 s. A 134 s
                              recording yields 75 words out of about 279, so 27% of the content, with the opening spliced
                              directly to the closing clause. The run returns success, nothing is logged, and
                              transcribe_was_truncated() reads false, so a consumer cannot detect it. Meanwhile
                              max_audio_ms advertises 400 s.

                              Found while using Handy for dictation. Handy is not implicated: calling the library directly
                              through the Python binding reproduces it exactly.

                              Environment

                              • transcribe-cpp transcribe.dll reporting transcribe_version() == 0.1.3, as shipped in
                                Handy 0.9.0 (Windows x86_64)
                              • Python binding transcribe_cpp-0.1.3-py3-none-any.whl via TRANSCRIBE_LIBRARY
                              • Model: handy-computer/cohere-transcribe-03-2026-gguf, cohere-transcribe-03-2026-Q5_K_M.gguf
                              • arch=cohere_asr, variant=cohere-transcribe-03-2026
                              • Windows 11 Pro (10.0.26200), RTX 5090 (Vulkan0), Ryzen 9 9900X
                              • Input: 134.01 s, pcm_s16le, 16 kHz mono, one continuous English reading of about 279 words

                              What the library reports about itself

                              max_audio_ms=400000 (400.0 s)
                              effective_max_audio_ms=400000 per session, so n_ctx does not narrow it
                              max_timestamp_kind=none
                              supports_streaming=False
                              transcribe_was_truncated() == false on the 134 s run that loses 73% of the content
                              

                              was_truncated is also false on a 20 s prefix, which transcribes correctly, so the flag does
                              not distinguish the two cases at all.

                              Steps to reproduce

                              importosos.environ["TRANSCRIBE_LIBRARY"] =r"C:\...\Handy\transcribe.dll"importtranscribe_cppastc# pcm = float32 mono 16 kHz, 134 s of continuous speechres=tc.transcribe(model_gguf, pcm, backend="vulkan", gpu_device=0, language="en")
                              print(len(res.text.split()), len(res.segments)) # 75 words, 1 segment

                              Expected

                              Either a transcript covering the input, or a refusal. The library declares a 400 s ceiling and
                              defines InputTooLong and OutputTruncated, so 134 s should either transcribe or report one.

                              Actual

                              75 of about 279 words, one segment, no error, no warning, was_truncated false. The transcript
                              covers the opening then jumps to the passage's final clause:

                              Alright, this is the transcription test for Cohere. Bug 2047421 tracks a dark mode white
                              flash on Windows. The repro harness pointed at the pre Zool skeleton UI and a DWM first show
                              cloak race, and the component moved to Mozglue afterward. Most mornings start and about
                              preferences. The settings redesign lives behind browser.settings-redesign.enabled, and the
                              pane mapping shim in bug.Bug 204742195 shows up often enough that a transcript should not
                              mangle any of them.

                              The bolded seam fuses a fragment of the passage's first bug number to a fragment of a later one.

                              Length sweep

                              Same recording truncated to increasing prefixes, model loaded once, only input duration
                              varying. Expected counts use the measured reading rate of 2.08 words per second.

                              inputwords returnedexpectedratio
                              20.0 s4242101%
                              30.0 s596294%
                              35.0 s687393%
                              40.0 s748389%
                              50.0 s9710493%
                              60.0 s9412575%
                              90.0 s8118743%
                              134.0 s7527927%

                              Faithful through 50 s, degrading from 60 s, so the usable ceiling is about an eighth of the
                              advertised 400 s. Past that the word count does not grow with input, it saturates near 90 and
                              then decreases, which is what puts the loss in the middle rather than the tail.

                              No chunking happens on this path

                              len(res.segments) == 1, max_timestamp_kind is none, supports_streaming is False, so
                              the whole clip goes through a single pass. The KV cache line shows the encoder does see all of
                              it:

                              cohere kv_cache: allocated 84.4 MB (f16) (self: 1024 session x 8 layers, cross: 1676 T_enc x 8 layers)
                              

                              1676 encoder frames covers 134 s at 8x subsampling of 10 ms mel frames. The 8x is inferred
                              from that arithmetic rather than read from the model file. So the encoder consumes everything
                              and the decoder fails to traverse it.

                              The CPU backend makes the decoder's role clearer. On identical input it does not stop early, it
                              degenerates:

                              ...the pane mapping shim in bug 20474214222222222222222222222222222222 (continues for 250
                              characters)

                              A repetition loop and an early jump to the ending are two symptoms of one condition, a decoder
                              attending over far more encoder context than it was trained on. That suggests an input-length
                              policy problem in the cohere_asr family implementation rather than any one compute kernel.

                              For comparison, the same file posted to https://api.cohere.com/v2/audio/transcriptions with
                              model=cohere-transcribe-03-2026 returns the complete passage, 298 words, and its output
                              carries four visible seams where segments were joined. The hosted path chunks. This one does
                              not. The weights are fine.

                              The decisive control: chunk the input and the same library succeeds

                              Splitting the identical recording into four pieces under 45 s, cutting inside natural silences,
                              and concatenating gives 294 words against the 279 word reference. All eight paragraphs present.
                              Same transcribe.dll, same GGUF, same Vulkan0 device, same process. Boundaries at 36.82 s,
                              80.95 s, and 123.14 s.

                              runwordsvs reference
                              Reference passage279100%
                              Hosted api.cohere.com298107%
                              Local, 4 chunks under 45 s294105%
                              Local, one 134 s pass7527%

                              A caller that chunks gets a complete transcript from the library that fails in one pass.

                              Suggested fix, in preference order

                              1. Chunk internally for cohere_asr, matching the reference implementation. The model card for
                                CohereLabs/cohere-transcribe-03-2026 puts this in the processor: audio exceeding the
                                feature extractor's max_audio_clip_s is split and reassembled via audio_chunk_index,
                                demonstrated on a 55 minute input.
                              2. Failing that, lower max_audio_ms for this architecture to a length that works, so long
                                input is rejected with InputTooLong rather than silently answered.
                              3. At minimum, make transcribe_was_truncated() return true here. The signal exists and reads
                                false during a real truncation, which is the gap [whisper] short-form: initial_prompt can cause silent tail truncation (early EOS at a pause) — same weights+prompt complete in whisper.cpp #89 described before fix short-form tail truncation via seek continuation #96 fixed it for
                                whisper.

                              Other controls

                              • Audio integrity. Per-second RMS shows continuous speech energy from 3 s to 133 s, and
                                silencedetect at -40 dB finds no gap of 1.5 s or longer.
                              • Microphone gating is not the cause. The file holds 17.8 s of exact digital zero across 30
                                gaps of 200 ms or more, which is a hardware noise gate zeroing pauses, since energy ramps
                                down into each gap rather than cutting mid-waveform.
                              • Different model, same length. Parakeet Unified EN 0.6B transcribed a 140.5 s reading of
                                the same passage end to end.
                              • CUDA not tested. The cuda lane of v0.1.3 loads ggml-cuda.dll but registers no device
                                here, reporting cohere: cuda backend requested but not available then failing model load
                                with BackendError status 8, because the archive ships no CUDA runtime and this machine has
                                the driver without the CUDA 12 toolkit. Vulkan and CPU already agree, so this seemed unlikely
                                to add anything.

                              Related

                              #89, silent tail truncation in the whisper short-form path via an early EOS at a prosodic
                              pause, closed by #96. Same observable shape: success status, transcribe_was_truncated()
                              false, content missing. Different trigger, since that one needed an initial_prompt and short
                              audio while this needs no prompt and long audio. Whether they share a decode-loop termination
                              path is for you to say.

                              Audio

                              The 134 s WAV used for every number above is attached in a comment below.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions