Skip to content

Forced word alignment (WhisperX-style CTC in the browser) - #61

Closed
wassgha wants to merge 6 commits into
mainfrom
fix-alignment-forced
Closed

wassgha wants to merge 6 commits into
mainfrom
fix-alignment-forced

Conversation

@wassgha

@wassgha wassgha commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Whisper’s word timestamps come from DTW over decoder attention — often late, drifting, weak on fricatives, and sometimes collapsing a word to a sliver. This PR adds a WhisperX-style post-pass: keep Whisper for the transcript, then measure word boundaries with a CTC acoustic model.

Pipeline

  1. Heuristic refine (lib/align.ts) — loudness envelope (incl. high band for fricatives), onset refinement, pauseless lag estimate, collapsed-word repair. Also the fallback when CTC is unavailable.
  2. CTC forced alignment (lib/forcedAlign.ts) — Viterbi over wav2vec2 emissions (Xenova/wav2vec2-base-960h, q4 ~86 MB).
  3. Edge expand — widen peaky CTC spans using the same envelope.
  4. Then disfluency placeholders → diarization (unchanged order: align before "..." recovery).

Worker integration

  • Aligner has its own weightlift ModelManager so ASR WebGPU→WASM unloadAll() does not throw away WASM aligner weights.
  • Warms in the background during transcription; progress is only claimed while waiting on the aligner (no fight with “Transcribing…”).
  • Per-batch failure keeps prior times; whole-aligner failure falls back to the heuristic path.

Tests

  • Expanded tests/align-test.ts (envelope, fricatives, collapsed words).
  • New tests/forced-align-test.ts (Viterbi, batching, unspellable words).

Follow-up

Multilingual model map + Parakeet sharing the same post-pass: #65.

@wassgha
wassgha force-pushed the fix-alignment-forced branch from bd9f064 to 69c3d3e Compare August 2, 2026 21:09
wassgha and others added 5 commits August 2, 2026 13:54
# Conflicts:
#	workers/transcription.worker.ts
The CTC aligner loaded itself with bare from_pretrained calls, so its 86 MB
download happened behind a static "Aligning words…" with no bar — indistinguishable
from a hang on a cold cache.

Register it in the existing ModelManager instead, so it shares the download
progress, cache labeling and unload-on-GPU-loss path with the ASR models. It
needs the processor, model and tokenizer separately rather than a pipeline(),
so it is a plain ModelDefinition wired to transformersProgress rather than
transformersModel().

The aligner warms in the background while Whisper runs, so its bytes must not
fight the "Transcribing…" line: speech models always own the progress line, and
the aligner only claims it once transcription is done and we are actually
waiting on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	workers/transcription.worker.ts
wav2vec2 is not in the same family as the ASR models — it is an acoustic
aligner, not a transcriber, and not something the user picks. But the deciding
factor is lifecycle, not taxonomy.

The ASR registry exists under a WebGPU→WASM fallback policy whose recovery step
is unloadAll(). The aligner never asks for a device, so transformers.js runs it
on WASM (DEFAULT_DEVICE), where a lost GPU cannot touch it. Sharing the registry
meant a GPU loss threw away 86 MB of perfectly good weights and forced a
needless session rebuild.

Splitting also removes the id special-casing the shared registry forced into the
progress subscription. The ASR handler goes back to "whatever is loading", and
the aligner's own subscription is scoped to the await in forceAlign — the
subscription's lifetime is the gate, so there is no module-level flag left to
fall out of step if that path is ever re-entered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
subscribe() only fires on the next change, so a load that had started but not
yet received its first byte left the UI on the previous message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor cursor Bot changed the title forced word alignment Forced word alignment (WhisperX-style CTC in the browser) Aug 3, 2026
Repository owner deleted a comment from cursor Bot Aug 3, 2026
@wassgha wassgha closed this Aug 6, 2026
wassgha added a commit that referenced this pull request Aug 6, 2026
CTC forced alignment for word timings (supersedes #61, #65)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant