feat(enhancement): LocalVQE echo cancellation + noise suppression (beta) - #930
Alex-Wengg wants to merge 9 commits into
Conversation
Adds LocalVqeManager / LocalVqeStream, a Core ML port of LocalVQE (localai-org/LocalVQE, Apache-2.0): joint acoustic echo cancellation, noise suppression and dereverberation for 16 kHz speech, requested in #49 for hands-free calls where the mic picks up the loudspeaker. The models are fp32 streaming exports with explicit recurrent state (33 in_*/out_* tensors). LocalVqeStream discovers the state tensors from the model description, passes each call's outputs back as the next call's inputs, buffers arbitrary input sizes into whole calls, drops the leading hop (the t<0 region) and flushes one hop of zeros at the end so whole-clip output is sample-aligned and length-preserving. Two chunk exports per checkpoint: 256 ms (files, 36x RTFx for v1.3 on M5 Pro CPU) and 16 ms (live capture, 1.2 ms per call). Verified against the upstream PyTorch reference (74 dB, 16-bit WAV limited) and the upstream GGML CLI (80 dB) on the upstream double-talk demo clip; 100/256/1000/4096-sample streaming buffers and whole-clip processing agree to 1e-5. CPU is the default compute unit: the graph is too small for ANE dispatch to pay off and fp16 was rejected for parity (102 -> 5 dB). Upstream's GGUF-only v1.4-AEC / GTCRN line depends on a C++ adaptive-filter front-end and is not ported. CLI: fluidaudiocli enhance mic.wav --reference speaker.wav --output clean.wav (--streaming reports per-call latency; --model-dir loads local bundles). Model tests skip in CI and when the bundle is absent locally (FLUIDAUDIO_LOCALVQE_MODEL_DIR overrides the cache location). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
PocketTTS Smoke Test ✅
Runtime: 0m7s Note: PocketTTS uses CoreML MLState (macOS 15) KV cache + Mimi streaming state. CI VM lacks physical GPU — audio quality and performance may differ from Apple Silicon. |
Parakeet EOU Benchmark Results ✅Status: Benchmark passed Performance Metrics
Streaming Metrics
Test runtime: 0m57s • 09/18/2026, 06:49 PM EST RTFx = Real-Time Factor (higher is better) • Processing includes: Model inference, audio preprocessing, state management, and file I/O |
Offline VBx Pipeline ResultsSpeaker Diarization Performance (VBx Batch Mode)Optimal clustering with Hungarian algorithm for maximum accuracy
Offline VBx Pipeline Timing BreakdownTime spent in each stage of batch diarization
Speaker Diarization Research ComparisonOffline VBx achieves competitive accuracy with batch processing
Pipeline Details:
🎯 Offline VBx Test • AMI Corpus ES2004a • 1049.0s meeting audio • 113.5s processing • Test runtime: 1m 57s • 09/18/2026, 06:55 PM EST |
Sortformer High-Latency Benchmark ResultsES2004a Performance (30.4s latency config)
Sortformer High-Latency • ES2004a • Runtime: 2m 15s • 2026-09-18T23:03:00.728Z |
… for LocalVQE Scores an echo canceller with the in-repo Parakeet TDT v3 ASR on the Microsoft AEC-Challenge synthetic set (mic + loopback + clean near-end triples, 200-example subset at FluidInference/aec-challenge-synthetic-mini, auto-downloaded). The clean-near-end transcript is the reference, the loopback transcript gives the far-end words; reports recall (1 - (D+S)/N), WER, and far-end word leakage per condition and per SER bucket, plus enhancement RTFx. --no-reference adds a silent-far-end condition to show what the model does without the loopback. Results (200 files, SER -10..+10 dB, M5 Pro, 256 ms chunk, CPU): unprocessed recall 39.5% / leakage 33.8%; v1.3 87.5% / 1.8% (36x RTFx); v1.2 86.3% / 1.9% (62x); v1.3 with silent reference 45.3% / 24.4%. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
VAD Benchmark ResultsPerformance Comparison
Dataset Details
✅: Average F1-Score above 70% |
ASR Benchmark Results ✅Status: All benchmarks passed Parakeet v3 (multilingual)
Parakeet v2 (English-optimized)
Streaming (v3)
Streaming (v2)
Streaming tests use 5 files with 0.5s chunks to simulate real-time audio streaming 25 files per dataset • Test runtime: 7m40s • 09/18/2026, 06:57 PM EST RTFx = Real-Time Factor (higher is better) • Calculated as: Total audio duration ÷ Total processing time Expected RTFx Performance on Physical M1 Hardware:• M1 Mac: ~28x (clean), ~25x (other) Testing methodology follows HuggingFace Open ASR Leaderboard |
Supertonic3 Smoke Test ✅
Runtime: 0m30s Note: CI VMs lack a physical Neural Engine; the ANE-bucketed VectorEstimator falls back to CPU here. This validates download + variant resolution + synthesis, not ANE residency/perf. |
Speaker Diarization Benchmark ResultsSpeaker Diarization PerformanceEvaluating "who spoke when" detection accuracy
Diarization Pipeline Timing BreakdownTime spent in each stage of speaker diarization
Speaker Diarization Research ComparisonResearch baselines typically achieve 18-30% DER on standard datasets
Note: RTFx shown above is from GitHub Actions runner. On Apple Silicon with ANE:
🎯 Speaker Diarization Test • AMI Corpus ES2004a • 1049.0s meeting audio • 51.6s diarization time • Test runtime: 2m 30s • 09/18/2026, 06:50 PM EST |
…GML fidelity result AECMOS / ERLE / DNSMOS over the 800-clip ICASSP 2022 blind set for the Swift port (v1.3, v1.2, unprocessed), and the aligned same-clip control against the upstream GGML engine (identical per-scenario means, per-clip echo delta mean -0.0001). Notes the two places the upstream README table cannot be reproduced from the published weights. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
…-help, doc wording - LocalVqeStream: use MLModel's async prediction API. Apple documents the synchronous API as not thread-safe (WWDC23 10049); streams created from one manager share its MLModel and could previously call it concurrently. enhance()/flush() become async. - enhance / enhance-benchmark: print usage via stdout so --help shows in release builds (the logger is silent there). - Docs: numerical equivalence rather than bit-match; single-talk agreement with the 38.2 vs 40.6 dB exception; double-talk mismatch stated as an unresolved upstream protocol discrepancy; note concurrent streams. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
…ct enhance-benchmark recall editDistance(hyp, ref) labelled a reference word missing from the hypothesis as an insertion and an extra hypothesis word as a deletion. WER was unaffected (it sums all three) but the breakdown was wrong everywhere it is read: enhance-benchmark's recall (N - D - S)/N awarded 100% to an empty transcript, and canary-transcribe / tts-asr-verify printed S/D/I swapped. Corrected recall on the AEC-Challenge synthetic subset (167 scored, the 33 examples with an empty clean-near-end transcript are now excluded and reported): unprocessed 44.1% -> v1.3 77.6% / v1.2 73.0%; previously published as 39.5% -> 87.5% / 86.3%. Leakage 34.0% -> 1.1%. Docs relabel the subset as exploratory (first 200 of a training shard, machine-transcript references). Adds WERCalculatorTests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
… 'not from published weights' claim The HF model-card protocol (legacy AECMOS model, first 20 s; rated-segment DNSMOS; technical-report gated ERLE) reproduces its unprocessed baseline exactly and every doubletalk / near-end cell within 0.02. Reference table stays on the challenge protocol; v1.2 far-end echo rows remain unexplained and are reported as such. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
v1.2 far-end differs on echo, gated ERLE and OVRL, not only echo MOS; v1.3 numbers stated with their actual bounds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud
Closes the LocalVQE request in #49 (#49 (comment)).
Core ML port of LocalVQE (Apache-2.0): joint acoustic echo cancellation + noise suppression + dereverberation for 16 kHz speech. Models on HF at FluidInference/localvqe-coreml; conversion in mobius (FluidInference/mobius PR: feat/localvqe-coreml).
LocalVqeManager(whole-clip / file) andLocalVqeStream(push any buffer size, 16 ms algorithmic latency, sample-aligned + length-preserving afterflush()); state tensors discovered from the model description and fed back call to callfluidaudiocli enhance mic.wav --reference speaker.wav --output clean.wav(--streamingreports per-call latency,--model-dirloads local bundles)Documentation/Enhancement/LocalVQE.md, README / Models.md / CLI.md entries; tests skip in CI and when the bundle is absentQuality verification — port fidelity validated; published benchmark substantially reproduced, with unresolved v1.2 far-end differences. (ICASSP 2022 AEC-Challenge blind set, 800 real clips, AECMOS + ERLE + DNSMOS): the Swift port scores identically to the upstream GGML engine on identical aligned samples (per-scenario means equal to two decimals, per-clip echo-MOS delta mean +0.0002, p95 0.017). Under the HF model card's own protocol (legacy AECMOS model over the first 20 s) the port reproduces the card's unprocessed baseline exactly and every doubletalk / near-end cell within 0.02 (v1.3 doubletalk 4.73 / 2.62); v1.3 far-end within 0.15 echo from aligned output (0.04 from raw GGML output), 0.8 dB gated ERLE, 0.01 OVRL; the v1.2 far-end rows: rendering at the pre-v1.2 delay window (dmax 32) reproduces the card's ERLE and deg, while its echo MOS cells are not reproduced by any local hypothesis and are reported as unexplained. Both protocols tabulated in Documentation/Enhancement/LocalVQE.md.
Recall benchmark (
fluidaudiocli enhance-benchmark, AEC-Challenge synthetic 200-file subset, ASR-scored, M5 Pro): exploratory, 167 scored examples): near-end word recall 44.1% unprocessed → 77.6% (v1.3) / 73.0% (v1.2); far-end word leakage 34.0% → 1.1%; with a silent reference v1.3 only reaches 42.3% / 23.9%, so the loopback is required for AEC. Earlier figures (87.5%/86.3%) were inflated by swapped insertion/deletion labels in the shared WERCalculator, fixed in this PR with tests. Dataset atFluidInference/aec-challenge-synthetic-mini.Parity on the upstream double-talk demo (M5 Pro, release): Swift vs upstream GGML CLI 80 dB SNR; v1.3 256 ms chunk 7.1 ms/call (36x RTFx) on CPU, 16 ms chunk 1.2 ms/call.
Reviewer notes:
🤖 Generated with Claude Code
https://claude.ai/code/session_015S4u7dmbe4skFjUNMpodud