You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Native memory accumulates ~50 MB per AudioStreamTranscriber recreation on the ANE path; survives full WhisperKit teardown (iPad Pro M5, iPadOS 26.5) #517
App: React Native host, but the leak is measured native-side (task_vm_info.phys_footprint) and reproduces with JS payloads bounded
Pattern
Live streaming with AudioStreamTranscriber. Because transcribeCurrentBuffer() re-submits the entire accumulated buffer every pass, we bound per-pass work by tearing the transcriber down every 120 s and creating a fresh one against the same WhisperKit instance (bank confirmed text, stopStreamTranscription(), cancel the task, release the streamer, new AudioStreamTranscriber).
Measurements (phys_footprint logged at every teardown)
~50 MB is retained per teardown/recreate cycle under continuous field audio: two 15-minute runs measured +52 and +48 MB per 120 s cycle (8 cycles each, 422 MB baseline → 840/808 MB). Under dense speech we measured 75–165 MB per cycle. Growth is monotonic; quiet audio still leaks (fewer decode passes, same cycles).
Session teardown is clean at the Swift level: in a 33-cycle run, 33 stopStreamTranscription() calls and 33 realtime-loop exits — no retained tasks or streamers.
Destroying the entire WhisperKit instance does not return the memory. Every 4th cycle we set the WhisperKit reference to nil and re-created it from scratch (full model reload): footprint dropped only 59/65/165 MB at 869/1,274/1,569 MB respectively, then resumed climbing at the same rate.
ANE-specific: under cpuAndGPU the footprint oscillates (±300 MB with full reclamations, no monotonic trend) around a much larger resident baseline (~3.5 GB, model in unified memory). Under all it accumulates like the default path.
Partially pressure-reclaimable: in a 90-minute run (45 cycles) footprint peaked at 4,069 MB, the OS reclaimed ~1.5 GB in one event, and the process stabilized at 2.6–3.0 GB. A prior run under denser load lost that race: swift_abortAllocationFailure (SIGABRT) during the Array(currentBuffer) copy in transcribeCurrentBuffer, with MALLOC at 4.9 GB in the crash report's vmSummary.
PR Fix avaudioengine deinit #508's deinit fix (applied locally, deinit hunk only): no effect on the leak. With the fixed teardown, a 12-minute run measured +58 MB/cycle (444 → 671 MB over 5 cycles) against +52/+48 in unpatched control runs on identical audio — within noise. (The fix still looks correct for its stated crash; it just is not this leak.)
An autoreleasepool-per-prediction experiment (suggested in CoreML Audio Resource Leak #393's comments) is not mechanically applicable on this codepath: all predictions go through await model.asyncPrediction(...), which a synchronous autoreleasepool cannot wrap.
What we think this is
Consistent with per-session ANE prediction-context/IOSurface memory allocated out-of-process that is not released when its owners are deallocated — same class as the accumulation reported in #265 (there on MPSGraph/cpuAndGPU; for us the GPU path is the clean one and ANE the leaking one) and adjacent to #300 and #393.
Questions
Is per-session ANE memory retention a known issue for AudioStreamTranscriber recreation? Any recommended session lifecycle for hour-scale live capture?
The WhisperAX example contains a commented-out purgeAudioSamples(keepingLast:) sketch ("TODO: Implement silence buffer purging"). Is a long-lived transcriber with in-place buffer purging the intended direction? We are implementing it (with buffer-offset timestamp accounting per ufal/whisper_streaming) and can contribute it back if useful.
AudioStreamTranscriber keeps lastBufferSize / lastConfirmedSegmentEndSeconds private, which forces a fork to implement purging externally. Would you accept a PR exposing a purge-aware API?
Full per-run device logs, footprint series, and crash reports available on request.
Environment
25c6299), SPMopenai_whisper-large-v3-v20240930_turbo_632MB(local folder load, prewarm+load)iPad17,1), iPadOS 26.5.2 — note: not present in the config.json device list, so likely outside your regression matrixcpuAndNeuralEnginepath) unless statedtask_vm_info.phys_footprint) and reproduces with JS payloads boundedPattern
Live streaming with
AudioStreamTranscriber. BecausetranscribeCurrentBuffer()re-submits the entire accumulated buffer every pass, we bound per-pass work by tearing the transcriber down every 120 s and creating a fresh one against the sameWhisperKitinstance (bank confirmed text,stopStreamTranscription(), cancel the task, release the streamer, newAudioStreamTranscriber).Measurements (phys_footprint logged at every teardown)
stopStreamTranscription()calls and 33 realtime-loop exits — no retained tasks or streamers.WhisperKitreference to nil and re-created it from scratch (full model reload): footprint dropped only 59/65/165 MB at 869/1,274/1,569 MB respectively, then resumed climbing at the same rate.cpuAndGPUthe footprint oscillates (±300 MB with full reclamations, no monotonic trend) around a much larger resident baseline (~3.5 GB, model in unified memory). Underallit accumulates like the default path.swift_abortAllocationFailure(SIGABRT) during theArray(currentBuffer)copy intranscribeCurrentBuffer, withMALLOCat 4.9 GB in the crash report's vmSummary.autoreleasepool-per-prediction experiment (suggested in CoreML Audio Resource Leak #393's comments) is not mechanically applicable on this codepath: all predictions go throughawait model.asyncPrediction(...), which a synchronousautoreleasepoolcannot wrap.What we think this is
Consistent with per-session ANE prediction-context/IOSurface memory allocated out-of-process that is not released when its owners are deallocated — same class as the accumulation reported in #265 (there on MPSGraph/
cpuAndGPU; for us the GPU path is the clean one and ANE the leaking one) and adjacent to #300 and #393.Questions
AudioStreamTranscriberrecreation? Any recommended session lifecycle for hour-scale live capture?purgeAudioSamples(keepingLast:)sketch ("TODO: Implement silence buffer purging"). Is a long-lived transcriber with in-place buffer purging the intended direction? We are implementing it (with buffer-offset timestamp accounting per ufal/whisper_streaming) and can contribute it back if useful.AudioStreamTranscriberkeepslastBufferSize/lastConfirmedSegmentEndSecondsprivate, which forces a fork to implement purging externally. Would you accept a PR exposing a purge-aware API?Full per-run device logs, footprint series, and crash reports available on request.