Skip to content

fix(stt): start each transcript word at the end of the token before it - #950

Merged
EtienneLescot merged 1 commit into
mainfrom
claude/word-timing-phase1
Sep 30, 2026
Merged

EtienneLescot merged 1 commit into
mainfrom
claude/word-timing-phase1

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Transcript word boundaries move from ~105 ms late to ~30 ms, with no download. This is Phase 1 of #948.

Root cause. whisper.cpp's t_dtw marks where a token ends. The helper used a word's first-token t_dtw as its start, so every word started one token late.

Changes

  • whisper-stt/src/main.cpp: a word starts at the t_dtw of the token before it and ends at its own last token's. The previous token carries across segments. It also emits anchor, the first-token time, which always lies inside the word.
  • electron/stt/snapWordBoundaries.ts: the RMS lookback snap is gone; kept on top of the fix, it drags correct boundaries 120 ms early. Phrase edges are anchored on the VAD in both directions, and a stretch owns a word by its anchor. MAX_ANCHOR_SEC, the tail rule and punctuation collapsing are unchanged. anchor stays off the IPC contract.
  • tools/stt-eval/word-timing/: the harness from Transcript word timings: measure them, then make them precise enough for text-based cuts #948, dependency-free, with a README.
  • Doc: § Word-level alignment now gives the root cause, the rules and the numbers.

+15 ms offset (config F): not kept. It gains 4 points of inner boundaries within 50 ms but drops noisy phrase deletes to 82%, under the 84% bar.

Results (harness, Vulkan, ggml-small-q8_0, times in ms)

Corpus Pipeline Inner start: median / P90 / within 50 ms Phrase-initial within 50 ms 1-word delete: clean / audible residue / clipping Phrase delete: clean WER
clean before 107 / 275 / 33% 84% 6% / 100 / 103 85% 9%
clean after 31 / 125 / 64% 90% 19% / 40 / 44 89% 9%
noisy before 105 / 280 / 31% 82% 5% / 101 / 104 82% 9%
noisy after 31 / 125 / 63% 88% 19% / 41 / 45 87% 9%
  • Phase 1 acceptance holds on both. The targets were: median ≤ 35, P90 ≤ 130, within 50 ms ≥ 60%, phrase-initial ≥ 85%, clean cuts ≥ 18%, phrase delete ≥ 84%, WER unchanged.
  • Real French take (real-check.mjs). The first-token time sits a median 200 ms after the new start. After the post-pass, all 9 phrase starts land on the VAD onset; the raw starts were at −8.8 s to +0.58 s.
  • Helper invariants on all 96 corpus responses. No word ends before it starts. Starts are monotonic, every start lies within 0.15 s of a speech stretch, and every anchor lies inside its word.

Related issue

Part of #948 (Phase 1 of 4).

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

None: no UI change.

Testing

  • Harness: run-helper.mjs then evaluate.mjs on the 96-clip corpus, with a local Windows Vulkan build of the helper. The baseline is reproduced exactly by evaluate.mjs vk-stock --snap <main's snapWordBoundaries.ts>.
  • Unit: vitest --run electron/stt (87 pass). tsc --noEmit and -p tsconfig.test.json report no new errors. Biome is clean.
  • Native CI: build-whisper-stt.yml builds the helper on macOS, Linux and Windows.
  • Not run: the full unit suite. CI runs it.

🤖 Generated with Claude Code

whisper.cpp's t_dtw marks where a token ends, so taking a word's first-token
t_dtw as its start put every word one token late (+175 ms median). The helper
now starts a word at the previous token's t_dtw, ends it at its own last token's,
and sends the first-token time as `anchor` to tell which stretch of speech owns
it. The RMS lookback snap goes; phrase edges are anchored on the VAD in both
directions. Adds the word-timing harness used to measure it (issue #948).
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 5f41ce98-927c-46d3-b1cb-640cc56412f9

📥 Commits

Reviewing files that changed from the base of the PR and between ab7d9dd and 019e198.

📒 Files selected for processing (18)
  • .gitignore
  • electron/native/whisper-stt/src/main.cpp
  • electron/stt/snapWordBoundaries.test.ts
  • electron/stt/snapWordBoundaries.ts
  • electron/stt/whisperServer.test.ts
  • electron/stt/whisperServer.ts
  • scripts/test-whisper-stt.mjs
  • technical-documentation/architecture/transcription-and-captions.md
  • tools/stt-eval/word-timing/README.md
  • tools/stt-eval/word-timing/corpus-texts.mjs
  • tools/stt-eval/word-timing/evaluate.mjs
  • tools/stt-eval/word-timing/lib.mjs
  • tools/stt-eval/word-timing/make-corpus.mjs
  • tools/stt-eval/word-timing/real-check.mjs
  • tools/stt-eval/word-timing/run-helper.mjs
  • tools/stt-eval/word-timing/summarize.mjs
  • tools/stt-eval/word-timing/tts.ps1
  • tools/stt-eval/word-timing/validate-ref.mjs
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EtienneLescot
EtienneLescot merged commit c9c5642 into main Sep 30, 2026
22 checks passed
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