voice_audio_multi/docs/models/earshot_vad.md
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

9.4 KiB

Earshot VAD

Native C++ port of Earshot 1.2.2, a small streaming voice activity detector by pyke.io (MIT OR Apache-2.0). It scores 16 ms frames of 16 kHz audio with a 40 KB network: a mel front end, three depthwise-separable convolutions and two int16 minGRU layers.

The weights ship in the repository, so there is nothing to download. The port is plain C++ and does not use a ggml graph or a GPU. It runs on the CPU whatever backend the session asks for, with the widest kernel set the CPU supports: AVX-512 (F, BW and VNNI), AVX2, or the SSE2 / NEON baseline. Every kernel set, compiler, thread count and the streaming path give the same scores, bit for bit.

Quick Start

audiocpp_cli --task vad --model assets/framework/models/earshot_vad \
  --audio speech.wav --segments-out segments.json

Streaming emits speech-start and speech-end events as frames arrive. It accepts 16 kHz chunks of any size:

audiocpp_cli --task vad --mode streaming --model assets/framework/models/earshot_vad \
  --audio speech_16k.wav --segments-out segments.json
Field Value
Family earshot_vad (alias earshot)
Model directory assets/framework/models/earshot_vad
Task vad
Modes offline, streaming
Input Offline: any sample rate and channel count, converted to 16 kHz mono and mapped back to input samples. Streaming: 16 kHz mono, any chunk size.
Output Speech segments (--segments-out), with the mean frame score as confidence
Threads --threads parallelises the offline front end, convolutions and the minGRU matrix products; only the elementwise recurrence runs in order
Build Part of the default full model set; with AUDIOCPP_MODEL_SET=custom, add earshot_vad to AUDIOCPP_MODELS

On the server, configure a "task": "vad" model entry and call POST /v1/tasks/run. The response lists segments.

Options

Pass options with --request-option name=value, or as session options with an earshot_vad. prefix. Durations are rounded up to whole 16 ms frames. The defaults reproduce upstream SegmenterOptions::default().

Option Default Meaning
threshold 0.6 Smoothed score needed to start a segment (upstream attack_threshold).
neg_threshold 0.5 Smoothed score below which a segment starts to end (decay_threshold).
smoothing_factor 0.4 Exponential smoothing of raw frame scores, [0, 1).
start_delay_ms 32 Consecutive voiced time needed to open a segment.
min_silence_duration_ms 64 Consecutive silence needed to close a segment (end_delay).
speech_pad_ms 80 Upstream preroll_frames. A segment starts preroll - 2 * start_delay + 1 frames before its first voiced frame (2 frames, 32 ms, with the defaults).
min_speech_duration_ms 96 Shorter segments are dropped (min_duration).
keep_trailing_segment true Keep a segment that is still open at the end of the audio.

Other Silero option names, such as max_speech_duration_s, are accepted and ignored.

Kernel set

The detector has CPU kernels for SSE2 or NEON (baseline), AVX2 and AVX-512 (F, BW and VNNI). Each session picks one through earshot_vad.exec_plan; the default, auto, uses the widest one the build and CPU support. All kernel sets give the same scores, bit for bit.

--session-option 'earshot_vad.exec_plan={"detector":{"impl":"native","backend":"cpu","params":{"isa":"avx2"}}}'

isa takes auto, baseline, avx2 or avx512. Asking for a kernel set the build or CPU cannot run is an error. With --log or --log-file, each run writes a timing line earshot_vad.kernel <name> naming the kernel set it used.

VAD chunking for ASR

gigaam_asr loads its chunking VAD through the model registry, so it can use Earshot directly:

audiocpp_cli --task asr --family gigaam_asr --model <gigaam.gguf>   --audio long.wav --request-option audio_chunk_mode=vad   --session-option gigaam_asr.vad_model_path=assets/framework/models/earshot_vad

The other ASR families that support audio_chunk_mode=vad load Silero VAD directly for now.

Differences from upstream

Per-frame scores match the Rust crate to within 1e-4. The last float bits differ because the FFT is a different implementation, ln and exp are Cephes polynomials (relative error below 2e-7) instead of the C library's, and the mel sums use eight interleaved partial sums. Those choices are what make the scores identical on every platform. The integer minGRU layers are exact. Segments match upstream segments() except for two deliberate fixes:

  • Trailing speech is kept. Upstream drops a segment that is still open when the audio ends. That would lose the last words of an ASR input, so audio.cpp closes it at the end of the audio. Set keep_trailing_segment=false for the upstream behaviour.
  • No timestamp drift. Upstream's segment iterator skips its frame counter on every frame that returns a segment. Each later segment is therefore reported one frame (16 ms) earlier per earlier segment, so the error grows over a long file. audio.cpp counts every frame.

A segment start is also clamped so it never lies after the current frame. That only matters when speech_pad_ms is close to 0, where upstream's formula would place the start in the future (or underflow).

Validation

tests/earshot_vad/reference_rs is a small Cargo program pinned to upstream. It dumps per-frame scores and segments. tests/earshot_vad/check_parity.py compares it with earshot_vad_probe and the CLI. It checks offline and streaming, and allows for upstream's frame-counter lag:

cargo build --release --manifest-path tests/earshot_vad/reference_rs/Cargo.toml
python tests/earshot_vad/check_parity.py \
  --probe build/<preset>/bin/earshot_vad_probe \
  --reference tests/earshot_vad/reference_rs/target/release/earshot_reference \
  --cli build/<preset>/bin/audiocpp_cli --bench 5 <16 kHz mono wavs...>

earshot_vad_test (ctest) checks the committed upstream golden scores. It also checks:

  • the frame-by-frame detector and 4-thread scoring equal 1-thread scoring bit for bit;
  • inputs longer than one 4096-frame block, scored with 1, 3, 4 and 8 threads, equal the detector bit for bit, on every kernel set the CPU supports;
  • every supported kernel set reproduces the golden scores, and the scores of one clip hash to the same committed value on every kernel set and platform;
  • earshot_vad.exec_plan parsing, errors for unsupported kernel sets, and that offline and streaming sessions run the kernel set they asked for;
  • streaming equals offline for any chunk size;
  • silence and noise produce no segments;
  • 48 kHz stereo input maps back to input samples;
  • every streamed segment is announced by a SpeechStart event;
  • the options, loader detection, and VAD chunk planning through the registry.

earshot_vad_probe --isa auto|baseline|avx2|avx512 benchmarks one kernel set.

Speed

The table shows wall time to score 10 minutes of 16 kHz audio (37,583 frames) with earshot_vad_probe --bench, best of five runs, on a Ryzen 9 9950X3D. The upstream Rust crate takes 147 ms for the same file on Windows and 133 ms on Linux (frame loop, one thread; 127 ms with -C target-cpu=native).

Build Kernels Offline, 1 thread Offline, 8 threads Streaming
Windows 11, MSVC 19.44 AVX-512 25 ms 5 ms 29 ms
Windows 11, MSVC 19.44 AVX2 38 ms 6 ms 39 ms
Ubuntu 24.04 (WSL), gcc 13 AVX-512 20 ms 3.1 ms 25 ms
Ubuntu 24.04 (WSL), gcc 13 AVX2 28 ms 4.5 ms 31 ms
Ubuntu 24.04 (WSL), clang 18 AVX-512 17 ms 2.6 ms 25 ms

The second table covers the 30 clips of the TEN VAD test set, repeated to 43.7 min. It was run on Google Colab with a 12-vCPU Xeon @ 2.20 GHz (AVX-512 with VNNI), gcc 13 and a Release build. Each figure is session.wall_ms from audiocpp_cli --task vad, best of three.

Model Threads ms x real time
earshot_vad 1 345 7,603
earshot_vad 4 99 26,557
Upstream Rust crate, frame loop 1 2,200 1,192
marblenet_vad 4 4,518 581
pulsevad 4 5,405 485
firered_vad 4 6,862 382
silero_vad 4 19,942 132

On the same machine, earshot_vad_probe --bench gives these times for the Earshot model alone on that 43.7 min file:

Kernels Offline, 1 thread Offline, 4 threads Offline, 12 threads Streaming
AVX-512 351 ms 97 ms 60 ms 443 ms
AVX2 486 ms 134 ms 82 ms 511 ms
Baseline (SSE2) 892 ms 335 ms 298 ms 980 ms

Accuracy

This is frame accuracy on the TEN VAD test set (30 labelled clips, 262 s), at 10 ms resolution, with every model at its default settings through audiocpp_cli.

Model Precision Recall F1
silero_vad 0.900 0.974 0.935
earshot_vad 0.893 0.962 0.926
marblenet_vad 0.892 0.948 0.919
firered_vad 0.863 0.978 0.917
pulsevad 0.922 0.905 0.914

Earshot's raw frame scores reach a PR-AUC of 0.977 on the same set.

Weights

assets/framework/models/earshot_vad/earshot_vad.safetensors contains upstream src/weights.bin and the src/filters.rs Hann window and mel tables, unchanged. Regenerate it with:

python tests/earshot_vad/convert_earshot.py \
  --output assets/framework/models/earshot_vad/earshot_vad.safetensors