Skip to content

perf: cache whisper context between requests + releaseModel()#3

Open
andreayres wants to merge 2 commits into
BasedHardware:mainfrom
andreayres:fix/cache-whisper-context
Open

perf: cache whisper context between requests + releaseModel()#3
andreayres wants to merge 2 commits into
BasedHardware:mainfrom
andreayres:fix/cache-whisper-context

Conversation

@andreayres

Copy link
Copy Markdown

Summary

  • Cache the native whisper context process-globally (keyed by model path, mutex-guarded) instead of calling whisper_init_from_file() + whisper_free() on every transcribe request.
  • Applied identically to Android (src/main.cpp), iOS, and macOS native copies.
  • Also fixes pre-existing context leaks on early WAV-error return paths that skipped whisper_free().
  • Expose Whisper.releaseModel() so apps can free the cached context on demand (plain ReleaseModelRequest, no freezed codegen).

Why

On-device streaming STT (e.g. Omi composite + on-device Whisper) flushes short buffers every few seconds. Reloading ggml-small from disk each time dominated wall time — e.g. 9.7s audio in ~19.5s (~2× real-time) was mostly model load, not inference. With a warm cache, small models can keep up with near-real-time use.

Notes for reviewers

  • whisper_full mutates the context; the mutex serializes concurrent isolate requests.
  • Callers should not call releaseModel() on every short-lived provider dispose if they reconnect often (e.g. BLE flaps); release when leaving on-device STT entirely.
  • Not device-tested in this PR environment (no Flutter/NDK here) — please run a mobile build + a short streaming transcription before merge.

Test plan

  • Build plugin for Android (and iOS if available)
  • Transcribe several short clips back-to-back; confirm first load is slow, subsequent are much faster
  • Call releaseModel() then transcribe again; confirm reload happens once
  • Force concurrent/isolate requests if your app does that; no crashes / no double-free
  • Error path: invalid/short WAV should not leak (process memory stable across many failures)

andreayres and others added 2 commits July 2, 2026 20:20
whisper_init_from_file() was called on every transcribe request and the
context freed at the end, so each request paid a full model load from
disk - seconds on mobile, dwarfing inference for short audio chunks and
making near-real-time streaming transcription impossible with anything
above the tiny model. Error paths before whisper_free() also leaked the
context.

Cache the context in a process-global keyed by model path, guarded by a
mutex (whisper_full mutates the context, and Dart callers issue requests
from short-lived isolates). A new "releaseModel" request type frees the
cached context so callers can reclaim memory when done transcribing.

Applied identically to src/ (Android), ios/Classes/ and macos/Classes/,
which are copies of the same file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Companion to the native context cache: Whisper.releaseModel() sends the
new "releaseModel" request so apps can drop the cached model from memory
once transcription is finished. ReleaseModelRequest is a plain
WhisperRequestDto implementation (no freezed codegen required), and
_request() gains an ensureModel flag so releasing never triggers a model
download.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andreayres

Copy link
Copy Markdown
Author

Review ping

Looking for a review on this perf fix — highest impact of the two related PRs.

Why it matters

On-device streaming STT was reloading the ggml model from disk on every short buffer flush. Measured path: ~9.7s audio → ~19.5s wall (~2× real-time), mostly load not inference. Caching the native whisper context (mutex + path key) should make 2nd+ buffers much closer to real-time.

Companion app PR

After merge

App still pins whisper_flutter_new at 684fb43… in app/pubspec.yaml — I'll open a tiny follow-up bump PR once this lands.

Happy to adjust API / naming if maintainers prefer a different release surface than Whisper.releaseModel().

@andreayres

Copy link
Copy Markdown
Author

cc @beastoin @xuegao-tzx @guyluz11 — would love a maintainer eye on this when you have bandwidth (esp. the native cache + mutex approach).

mdmohsin7 added a commit to BasedHardware/omi that referenced this pull request Jul 11, 2026
… teardown (#9438)

## Summary

`CompositeTranscriptionSocket.stop()` / `disconnect()` tore down child
sockets while `_status` was still `connected`, so each child's
`onClosed` re-entered `_onSocketClosed` as an **unexpected** close:

- Misleading log: `Primary socket closed (code: null)`
- Double-disconnect of both children
- False `onClosed` to `CaptureProvider` → reconnect snackbar +
keep-alive churn on every intentional teardown (including every BLE
reconnect)

## Fix

Set `_status = disconnected` **before** stopping/disconnecting children
so `_onSocketClosed`'s guard treats those callbacks as expected.

## Context

Seen with custom STT + on-device Whisper composite mode: device BLE
flaps cause intentional composite teardown; the spurious close path made
each flap look like a transcription outage and triggered unnecessary
reconnect UI.

Related plugin work (context cache so reconnects are cheaper):
BasedHardware/whisper_flutter_new#3

## Test plan

- [ ] Enable composite / on-device Whisper STT
- [ ] Start recording, stop recording → no `Primary socket closed (code:
null)` warning for intentional stop
- [ ] Simulate BLE disconnect/reconnect while recording (or toggle
device) → no false reconnect snackbar solely from composite teardown
race
- [ ] Intentional transcription settings change still reconnects cleanly

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/9438?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
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