Skip to content

perf(desktop-gpui): caption long recordings in minute-long Parakeet chunks (up to 44% faster, no 6m40s limit) - #2539

Merged
richiemcilroy merged 3 commits into
CapSoftware:mainfrom
unhingedpanda:perf/parakeet-chunked-captions
Oct 19, 2026
Merged

richiemcilroy merged 3 commits into
CapSoftware:mainfrom
unhingedpanda:perf/parakeet-chunked-captions

Conversation

@unhingedpanda

@unhingedpanda unhingedpanda commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Generating captions with the default "Recommended" model (Parakeet TDT 0.6b v3 int8) fails on any recording longer than 6m40s. Below that limit it gets slower per minute as the recording grows, and on real speech it drops whole sentences. The GPUI app passed the whole recording to Parakeet in one encoder pass. That pass fails with an ONNX broadcast error past 400 s (5,000 encoder frames). Up to that point its time and memory grow faster than the audio's length.

This PR feeds Parakeet one minute of audio at a time. Each cut lands on the quietest 100 ms in the last 5 s before the minute mark, so cuts usually fall between words. Word timestamps are shifted back onto the recording's timeline. The model configuration, including its 4-thread default, is unchanged. Whisper models are untouched.

Results

Machine: Apple M5 Pro (15 cores), 24 GB, release build. Each number is the median of 3 runs, and before and after runs alternate. The 60-minute "after" figure is a single run. Time includes model load. Peak memory comes from /usr/bin/time -l.

Recording Before After
1 min (one chunk) 2.24 s, 2.2 GB 2.16 s, 2.2 GB
3 min 6.45 s, 4.0 GB 5.12 s, 2.3 GB (-21%)
5 min 12.61 s, 4.5 GB 8.40 s, 2.3 GB (-33%)
6 min 17.27 s, 6.8 GB 9.62 s, 2.3 GB (-44%)
6m22s real screen recording 17.59 s, 6.4 GB 9.88 s, 2.2 GB (-44%)
10 min fails after 2.0 s 15.15 s, 2.3 GB
30 min fails after 79 s, peak 15.1 GB 43.22 s, 2.4 GB
60 min not run 87.43 s, 2.8 GB

After the change, time grows linearly at about 1.45 s per recorded minute. Peak memory stays flat at about 2.3 GB, most of which is the loaded model (1.8 GB right after load).

Correctness

Real speech. I used a 6m22s screen recording of someone talking through a demo. It is short enough that the old code still runs. The old single pass returned 534 words and the chunked pass returned 634. Most of the difference is whole sentences that the single pass left out, at 35 s, 68 s, 86 s, 101 s and 257 s. I transcribed a 12 s clip around each of four of those spots on its own, and every one contains the sentence. So the single pass was dropping real speech. Within 3 s of the six cut points, three short spans differ, all near the first cut. One is a small miss ("compose" became "compost"). The other five cuts show no differences.

Scripted speech. This audio is macOS say reading plain prose sentences from Cap's docs, which gives a known script to score against. It is written as 16 kHz mono s16le, the same format extract_project_audio writes. Text-to-speech pauses cleanly at every sentence, so it is easier to cut than real speech. Word error rate against the script:

Recording Before After
1 min 1.08% 1.08%
3 min 1.79% 1.39%
5 min 2.28% 1.56%
6 min 2.08% 1.58%
10 min fails 1.48%
30 min fails 1.75%
60 min fails 1.89%

Word counts differ by a few words between builds (for example 1,002 against 998 at 6 min). The WER table above covers those differences.

Other checks

  • Words and timestamps go through the same normalize_caption_words and caption_word_chunks as before.
  • The WAV is checked to be 16 kHz mono, which is what extract_project_audio writes. Empty audio returns "No speech detected" instead of reaching parakeet-rs, which indexes the first sample.
  • A short tail chunk is safe. Inputs of 50 ms and 200 ms return no words and no error.
  • The new unit tests check four things. Cuts land in a silent gap. Chunks tile the input with no gaps or overlaps. No chunk exceeds 60 s. A recording of exactly 60 s stays one chunk.

Reproduce

The ignored benchmark_parakeet_transcription test times the real process_with_parakeet call. For the baseline, I ran the same test on unchanged main (3d19c5cfa).

cd apps/desktop-gpui
CAP_PARAKEET_BENCH_MODEL=~/Library/Application\ Support/so.cap.desktop/transcription_models/parakeet-best \
CAP_PARAKEET_BENCH_WAV=/path/to/16k-mono.wav \
cargo test --release benchmark_parakeet_transcription -- --ignored --nocapture

Set CAP_PARAKEET_BENCH_WORDS=/path/out.tsv to dump the words with their timestamps.

Notes

  • Raising Parakeet's intra-op threads from 4 to the core count cuts another 20 to 30% in my runs. It also doubles CPU time and leaves ORT spin-waiting on, while camera blur deliberately turns spinning off. I left threads out of this PR so its gain stays clear. Threads can be a separate change.
  • fix(captions): chunk audio for Parakeet so recordings over ~6m40s caption #2249 tried chunking for the Tauri app, using 300 s windows with overlap and dedup. Its author closed it before a maintainer reviewed it. This change targets the GPUI app. Cutting at quiet points avoids the overlap merge.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issue was found.

Summary

Parakeet now reads recordings in chunks of at most one minute, choosing quiet cut points and shifting word times onto the recording’s timeline.

  • Parakeet captions long GPUI recordings in quiet-cut minute chunks.

Reviews (4) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@unhingedpanda is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread apps/desktop-gpui/src/transcription.rs Outdated
std::fs::write(path, dump).expect("write words");
}
}
Err(error) => println!("parakeet_seconds={seconds:.2} error={error}"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed benchmark looks successful

benchmark_parakeet_transcription passes even when process_with_parakeet fails. A missing WAV, model error, or failed long recording prints an error but still gives cargo test a successful exit. Keep the timing output, then fail the test so developers can tell a broken run from a successful one.

Suggested change
Err(error) => println!("parakeet_seconds={seconds:.2} error={error}"),
Err(error) => panic!("parakeet_seconds={seconds:.2} error={error}"),
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/transcription.rs
Line: 2243

Comment:
**Failed benchmark looks successful**

`benchmark_parakeet_transcription` passes even when `process_with_parakeet` fails. A missing WAV, model error, or failed long recording prints an error but still gives `cargo test` a successful exit. Keep the timing output, then fail the test so developers can tell a broken run from a successful one.

```suggestion
            Err(error) => panic!("parakeet_seconds={seconds:.2} error={error}"),
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@unhingedpanda

Copy link
Copy Markdown
Contributor Author

@greptileai review

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks for this! It now conflicts with main after today's merges. Could you merge or rebase onto the latest main? Once it's up to date I'll run the full engine verify and real-capture suites on it and merge if they pass.

…-captions

# Conflicts:
#	apps/desktop-gpui/src/transcription.rs
@unhingedpanda

Copy link
Copy Markdown
Contributor Author

Merged the latest main (2b2bf84). The only conflict was in process_with_parakeet: the chunk loop now passes its offset tokens to main's new caption_segments_from_tokens, so the verify oracles cover the same post-processing path. cargo test --manifest-path apps/desktop-gpui/Cargo.toml --locked --bin cap-gpui passes locally on aarch64 macOS (1199 passed, 0 failed).

@richiemcilroy

Copy link
Copy Markdown
Member

@greptileai review

@richiemcilroy richiemcilroy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced on an M4 Max with Parakeet TDT 0.6b v3 int8: a 6.5 min scripted recording goes from 17.2 s / 6.8 GB to 9.3 s / 2.4 GB, and a 7m53s one that fails on main transcribes in 11.1 s. WER against the script is within noise (6.9% vs 7.5%, differences spread across the recording, not at the cuts). Thanks!

@richiemcilroy
richiemcilroy merged commit 74f5acd into CapSoftware:main Oct 19, 2026
37 of 39 checks passed
@unhingedpanda

Copy link
Copy Markdown
Contributor Author

@richiemcilroy Thanks for building such a great piece of software and making it free. Glad to finally contribute, I've been an avid Cap user for a long time. Would both #2536 and #2539 be eligible for sponsorship?

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.

2 participants