Instructions to use mlx-community/silero-vad-v6 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use mlx-community/silero-vad-v6 with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir silero-vad-v6 mlx-community/silero-vad-v6
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
| """ | |
| Minimal 256ms unified inference example for Silero VAD v6 (MLX, 16 kHz). | |
| Same weights as 32ms streaming mode (model.safetensors), but processes 8× | |
| 512-sample chunks per outer call sharing LSTM state, then aggregates the 8 | |
| internal probabilities via noisy-OR: 1 - prod(1 - p_i). | |
| Designed for offline / batch use cases like ASR preprocessing where 256ms | |
| decision granularity is acceptable. Roughly 1.5-2× faster wall time than the | |
| 32ms streaming inference on the same audio (fewer mx.eval barriers). | |
| Usage: | |
| uv pip install mlx safetensors numpy huggingface_hub | |
| uv run python example_256ms.py path/to/audio_16k_mono.wav | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| import wave | |
| from pathlib import Path | |
| import mlx.core as mx | |
| import numpy as np | |
| from huggingface_hub import hf_hub_download | |
| from safetensors.numpy import load_file | |
| CHUNK_SAMPLES = 512 | |
| BLOCK_TOTAL = 4096 | |
| CONTEXT_SAMPLES = 64 | |
| STFT_HOP = 128 | |
| STFT_PAD_RIGHT = 64 | |
| LSTM_HIDDEN = 128 | |
| SAMPLE_RATE = 16_000 | |
| KEYS = ( | |
| "vad_16k.stft_conv.weight", | |
| "vad_16k.conv1.weight", "vad_16k.conv1.bias", | |
| "vad_16k.conv2.weight", "vad_16k.conv2.bias", | |
| "vad_16k.conv3.weight", "vad_16k.conv3.bias", | |
| "vad_16k.conv4.weight", "vad_16k.conv4.bias", | |
| "vad_16k.lstm.Wx", "vad_16k.lstm.Wh", "vad_16k.lstm.bias", | |
| "vad_16k.final_conv.weight", "vad_16k.final_conv.bias", | |
| ) | |
| def load_weights(path: str) -> dict[str, mx.array]: | |
| raw = load_file(path) | |
| return {k: mx.array(raw[k]) for k in KEYS} | |
| def reflect_pad_right_576(samples_576: np.ndarray) -> np.ndarray: | |
| """PyTorch ReflectionPad1d(right=64): out[L+i] = in[L-2-i] for i in 0..63.""" | |
| L = samples_576.shape[-1] | |
| pad = np.empty(STFT_PAD_RIGHT, dtype=samples_576.dtype) | |
| for i in range(STFT_PAD_RIGHT): | |
| pad[i] = samples_576[L - 2 - i] | |
| return np.concatenate([samples_576, pad], axis=-1) | |
| def predict_chunk(audio_640: mx.array, h: mx.array, c: mx.array, w: dict | |
| ) -> tuple[mx.array, mx.array, mx.array]: | |
| z = mx.conv1d(audio_640, w["vad_16k.stft_conv.weight"], stride=STFT_HOP, padding=0) | |
| real, imag = z[:, :, :129], z[:, :, 129:] | |
| x = mx.sqrt(real * real + imag * imag + 1e-12) | |
| cfgs = [(1, 1), (2, 1), (2, 1), (1, 1)] | |
| for i, (stride, padding) in enumerate(cfgs, start=1): | |
| x = mx.conv1d(x, w[f"vad_16k.conv{i}.weight"], stride=stride, padding=padding) | |
| x = x + w[f"vad_16k.conv{i}.bias"] | |
| x = mx.maximum(x, 0) | |
| feat = x[:, 0, :] | |
| gates = feat @ w["vad_16k.lstm.Wx"].T + h @ w["vad_16k.lstm.Wh"].T + w["vad_16k.lstm.bias"] | |
| i_g, f_g, g_g, o_g = mx.split(gates, 4, axis=-1) | |
| c_new = mx.sigmoid(f_g) * c + mx.sigmoid(i_g) * mx.tanh(g_g) | |
| h_new = mx.sigmoid(o_g) * mx.tanh(c_new) | |
| dec = mx.conv1d( | |
| mx.maximum(h_new, 0)[:, None, :], | |
| w["vad_16k.final_conv.weight"], stride=1, padding=0, | |
| ) | |
| dec = dec + w["vad_16k.final_conv.bias"] | |
| return mx.sigmoid(dec), h_new, c_new | |
| def predict_block_256ms(block_audio_4096: np.ndarray, | |
| initial_ctx: np.ndarray, | |
| h: mx.array, c: mx.array, | |
| w: dict | |
| ) -> tuple[float, np.ndarray, mx.array, mx.array]: | |
| """Process one 256ms block (8× 32ms internal chunks) with LSTM state shared | |
| across the chunks, return aggregated probability via noisy-OR.""" | |
| rolling_ctx = initial_ctx | |
| chunk_probs: list[mx.array] = [] | |
| for ci in range(8): | |
| src = block_audio_4096[ci * CHUNK_SAMPLES : (ci + 1) * CHUNK_SAMPLES] | |
| merged = np.concatenate([rolling_ctx, src]) | |
| padded = reflect_pad_right_576(merged) | |
| audio = mx.array(padded[None, :, None]) | |
| prob, h, c = predict_chunk(audio, h, c, w) | |
| chunk_probs.append(prob) | |
| rolling_ctx = src[-CONTEXT_SAMPLES:].copy() | |
| product = (1.0 - chunk_probs[0]) | |
| for ci in range(1, 8): | |
| product = product * (1.0 - chunk_probs[ci]) | |
| agg = 1.0 - product | |
| mx.eval(agg, h, c) | |
| new_ctx = block_audio_4096[-CONTEXT_SAMPLES:].copy() | |
| return float(agg[0, 0, 0].item()), new_ctx, h, c | |
| def load_wav_16k_mono(path: str) -> np.ndarray: | |
| with wave.open(path, "rb") as w: | |
| if w.getframerate() != SAMPLE_RATE: | |
| raise SystemExit(f"expected 16 kHz, got {w.getframerate()}") | |
| if w.getnchannels() != 1: | |
| raise SystemExit(f"expected mono, got {w.getnchannels()} channels") | |
| if w.getsampwidth() != 2: | |
| raise SystemExit(f"expected 16-bit PCM, got {w.getsampwidth()*8}-bit") | |
| n = w.getnframes() | |
| raw = w.readframes(n) | |
| return (np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0).copy() | |
| def main() -> None: | |
| if len(sys.argv) != 2: | |
| print(__doc__, file=sys.stderr) | |
| sys.exit(1) | |
| weights_path = hf_hub_download(repo_id="mlx-community/silero-vad-v6", | |
| filename="model.safetensors") | |
| w = load_weights(weights_path) | |
| samples = load_wav_16k_mono(sys.argv[1]) | |
| leftover = samples.shape[0] % BLOCK_TOTAL | |
| if leftover: | |
| samples = np.concatenate([samples, np.zeros(BLOCK_TOTAL - leftover, dtype=np.float32)]) | |
| n_blocks = samples.shape[0] // BLOCK_TOTAL | |
| h = mx.zeros((1, LSTM_HIDDEN)) | |
| c = mx.zeros((1, LSTM_HIDDEN)) | |
| initial_ctx = np.zeros(CONTEXT_SAMPLES, dtype=np.float32) | |
| threshold = 0.5 | |
| n_speech_blocks = 0 | |
| print(f"blocks={n_blocks}, duration={samples.shape[0]/SAMPLE_RATE:.2f}s, block_dur=256.0ms") | |
| print(f"{'idx':>5} {'time(s)':>8} {'p(speech)':>10}") | |
| for bi in range(n_blocks): | |
| block_audio = samples[bi * BLOCK_TOTAL : (bi + 1) * BLOCK_TOTAL] | |
| prob, initial_ctx, h, c = predict_block_256ms(block_audio, initial_ctx, h, c, w) | |
| if prob >= threshold: | |
| n_speech_blocks += 1 | |
| if bi < 25: | |
| print(f"{bi:>5d} {bi*BLOCK_TOTAL/SAMPLE_RATE:>8.3f} {prob:>10.4f}") | |
| print(f"\nspeech blocks (threshold {threshold}): {n_speech_blocks}/{n_blocks} " | |
| f"({n_speech_blocks/n_blocks*100:.1f}%)") | |
| if __name__ == "__main__": | |
| main() | |