Skip to content

Forced aligner: --words-out + fixed chunking throws "timestamp outside the chunk span" at ~the 10th chunk of a process; the same audio passes in a fresh process #249

Description

@bjhengen

Summary

--words-out (Qwen3 forced aligner) with fixed-mode chunking throws

audiocpp_cli failed: Audio chunker word merge received a timestamp outside the chunk span

at roughly the 9th–10th chunk of a single process, aborting the whole run so no timestamps are
produced for any chunk.

The important part: the same audio passes in a fresh process. The failure needs both
triggering content and enough accumulated per-process state — neither alone is sufficient. That
points at per-call state in the aligner rather than at any particular audio or timestamp value.

This is long-standing, not a regressionrelease-0.4 fails identically (see below). Please
don't search a recent commit window for it.

Environment

Version release-0.5 (3178daf), built from source
Also fails on release-0.4 (archived binary, clean 0.4 worktree + 0.4 model_specs/), 3/3
Still present on main @ f08e61dsrc/framework/audio/chunking.cpp:602 is unchanged
Backend CUDA, RTX 5090 (sm_120), CUDA 13.3
Models Qwen3-ASR-1.7B-hf + Qwen3-ForcedAligner-0.6B
Input 16 kHz mono WAV (ffmpeg-normalised)

Reproduce

# FAILS at ~chunk 10
./build/bin/audiocpp_cli --task asr --family qwen3_asr \
  --model models/Qwen3-ASR-1.7B-hf --backend cuda \
  --audio repro-A-150s-fails.wav \
  --text-out /tmp/t.txt --words-out /tmp/w.json \
  --session-option qwen3_asr.forced_aligner_model_path=models/Qwen3-ForcedAligner-0.6B \
  --audio-chunk-mode fixed --audio-chunk-seconds 15

# PASSES -- same length, same chunk count, different content
#   ... --audio repro-B-150s-PASSES-negative-control.wav ...

# PASSES -- same content as A, fewer chunks
#   ... --audio control-60s-passes.wav ...

What isolates it to per-process state, not the audio

The same 15 s of audio (135–150 s of repro-A):

Presented as Result
chunk 1 of 1 (isolated) pass
chunk 2 of 2 (120–150 s window) pass
chunk 10 of 10 (full 150 s) FAIL

The chunk-count threshold is sharp — tested at 75 / 90 / 105 / 120 / 135 / 150 s, 9 chunks pass,
10 fails
. repro-B is the negative control: identical duration and chunk count, different
content, passes.

Consistent with that reading, --audio-chunk-seconds 30 on the same 150 s passes — fewer aligner
invocations per process, not better audio. That is our downstream workaround.

Throw site

src/framework/audio/chunking.cpp:602, in the word-merge path:

const int64_t local_start = std::max<int64_t>(word.span.start_sample, 0);
const int64_t local_end   = std::min<int64_t>(word.span.end_sample, source_samples);
if (local_start >= local_end) {
    throw std::runtime_error("Audio chunker word merge received a timestamp outside the chunk span");
}

Two observations, offered as leads rather than conclusions:

  1. A single out-of-span word aborts the entire run. Even once the underlying cause is found, it
    may be worth clamping or dropping the offending word (with a warning) instead of throwing — a
    40-minute transcription currently loses every timestamp because of one bad span. The adjacent
    speech-metadata merge at :637 has the same all-or-nothing shape.
  2. This looks related in family to VibeVoice-ASR: chunked (VAD/fixed) timestamps are in the input sample rate, not the 24 kHz single-pass domain — inconsistent #167 (chunked timestamps in the wrong sample-rate domain),
    which was fixed for vibevoice_asr in 0.5 by rate-aware chunking. The qwen3_asr + forced
    aligner words path still reaches this throw on 16 kHz input. It may be a surviving corner of the
    same domain mismatch — though the sharp chunk-count threshold above suggests state accumulation
    is at least as important as any rate conversion.

Ruled out

Two candidate commits were eliminated by testing directly at each one's parent — both still fail 3/3:

Commit Tested at parent Verdict
622cadd "Align ASR chunk timestamp domains" 788253b not the cause
7ce769e "Add Qwen3 ASR streaming path" 80627af not the cause

We originally filed this internally as a 0.5 regression on the strength of a 3-minute gap between
our last green run and the 0.5 deploy. That was coincidence — the earlier runs used recordings that
do not trigger it.

Coverage note

No regression test appears to cover multi-chunk fixed-mode alignment with --words-out in any
released version — a test that runs ≥10 chunks through one process would catch this.

Fixtures

I have the three 16 kHz WAVs used above (repro-A-150s-fails, repro-B-150s-PASSES-negative-control,
control-60s-passes) and can attach them or provide the failing --words-out JSON, chunk-boundary
dump, or any instrumented run that would help. Happy to test a patch on sm_120.

Related: #198 (a different all-or-nothing abort in the same aligner path, from punctuation-only
chunks).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions