openslr/librispeech_asr
Viewer • Updated • 585k • 75k • 249
Voice activity detection for a realtime voice agent. For every 10ms of microphone audio, say whether a human is speaking. Must ignore background noise, coughs, laughter, door slams, keyboard, crying babies and other non-speech human sounds. Runs on-device next to the agent, streaming, in well under 1ms per frame.
A tiny, single-purpose System 1 model built with BYOJL: a JEPA-style
encoder trained only for this task, with a calibrated decision policy, shipped as a 0.10M-parameter ONNX
bundle that runs on any CPU with just numpy + onnxruntime.
| task | audio / frame (one decision every 10 ms) |
| labels | non_speech, speech |
| parameters | 101,458 |
| backbone | tiny-crnn · embedding dim 96 |
| objective | f1, primary suite ava |
| decision policy | hysteresis, threshold 0.325 (calibrated on ava's calib split) |
| CPU, streaming | 0.173 ms per 30 ms chunk, 1 thread (PyTorch; the ONNX bundle is faster) |
non_speech: silence, noise, music, and non-verbal human sounds (coughing, laughing, breathing, crying)speech: a frame inside an utterance of human speech, including pauses up to ~300ms inside an utteranceEvery model below, this one and the baselines, was scored by the same code on the same frozen test splits, with its decision policy calibrated on the same calib splits. Bold = this model.
| model | params | builtin f1 | ava f1 (primary) | builtin ROC-AUC | ava ROC-AUC |
|---|---|---|---|---|---|
| v2 (this model) | 0.10M | 0.8794 | 0.8763 | 0.976 | 0.939 |
| silero (baseline) | 0.55M | 0.8850 | 0.8555 | 0.981 | 0.923 |
| v1 (BYOJL experiment) | 0.10M | 0.8941 | 0.8485 | 0.982 | 0.900 |
| v0 (BYOJL experiment) | 0.10M | 0.8906 | 0.8451 | 0.983 | 0.900 |
| energy (baseline) | – | 0.7281 | 0.7275 | 0.828 | 0.694 |
| suite | dataset | test items | f1 | precision | recall | ROC-AUC | avg precision | decision threshold |
|---|---|---|---|---|---|---|---|---|
| builtin | vad-builtin | 600 | 0.8794 | 0.8517 | 0.9089 | 0.9760 | 0.9684 | 0.750 |
| ava | nccratliri/vad-human-ava-speech | 20 | 0.8763 | 0.8456 | 0.9094 | 0.9391 | 0.9494 | 0.325 |
pip install huggingface_hub numpy onnxruntime soundfile
from huggingface_hub import snapshot_download
import sys
path = snapshot_download("bsye/byojl-vad")
sys.path.insert(0, path)
from byojl_rt import load
m = load(path)
import soundfile as sf
wav, sr = sf.read("clip.wav", dtype="float32") # 16 kHz mono
print(m.predict(wav)["segments"])
emb = m.encode(...) # the learned representation (JEPA-style), for probes / retrieval / other heads
s = m.stream()
for chunk in mic_chunks(): # float32 mono, any size
out = s.step(chunk) # {"probs": [...per 10ms...], "speaking": bool, "events": [{"type": "start"|"end", "t_s": ...}]}
python byojl_rt.py serve . --port 8800
# GET /v1/models · POST /v1/vad/predict · POST /v1/vad/encode
spec.yaml (included in this repo).openslr/librispeech_asr: role positiveashraq/esc50: role negativenccratliri/vad-human-ava-speech: role labeled, partition trainbyojl improve from the previous board):real-data-ava (from v1): ava: objective 0.848 vs silero 0.855, 0.046 below builtin. The model has never seen this domain; train on the source's train partition (hash-disjoint from the suite's calib/test files).utterance-labels (from v1): ava: calibration had to move the decision threshold to 0.20 (P=0.824, R=0.875) -> the suite's label convention differs from training's. Training labels only voiced frames (gaps > 60ms are negative); segment suites usually label whole utterances. Fill gaps up to 300ms in synthetic labels.git clone https://github.com/bsye/BYOJL && cd BYOJL && pip install -e .
# put spec.yaml from this repo at specialists/vad/spec.yaml, then:
byojl build vad && byojl board vad && byojl export vad