FD-VAD
FD-VAD is a compact, ASR-free and streaming semantic endpoint detector for full-duplex spoken interaction. It feeds short sliding windows of audio directly to a small language model and emits a per-chunk Continue / Stop decision, deciding while the user is still speaking whether the turn is semantically complete β using meaning, not just silence.
Model
- Audio encoder: frozen
WavLM-base-plus(16 kHz, 50 Hz frame rate). - Modality adapter: temporal downsampling by 4 (concatenate consecutive frames) + 2-layer MLP into the LLM embedding space.
- LLM backbone:
Qwen2.5-0.5B-Instructadapted with LoRA (rank 16). Encoder and LLM base weights are frozen; only the adapter and LoRA parameters are trained (~10 M params, ~1.7% of the total). - Streaming: sliding window of 2.56 s (256 frames), stride 320 ms; each window is encoded independently (causal β no future audio leaks into a decision), so per-window compute is fixed regardless of utterance length (~46 ms/window on an A100).
- Objective: last-chunk cross-entropy over a
Continue (0)/Stop (1)target.
Training data
English subset of pipecat-ai/smart-turn-data-v3.1
(BSD-2), a 16 kHz conversational endpointing corpus with native complete / incomplete labels.
One epoch, AdamW, effective batch 64, bf16.
Results (English smart-turn-v3.1 test)
| Metric | Value |
|---|---|
| Chunk-level F1 (Stop=positive) | 0.83 |
| Chunk false-interruption rate | 0.6% |
| Per-utterance endpoint accuracy | 0.965 (complete 0.968 / incomplete 0.962) |
Statistically indistinguishable from a strong non-streaming turn classifier on the endpoint decision, while deciding incrementally. Generalizes from synthetic to unseen human speech with a ~1.1% chunk-accuracy drop, and transfers zero-shot to external end-of-turn benchmarks.
Files (self-contained, fully offline β ~1.5 GB)
fd_vad.ptβ trained adapter + LoRA weights (119 MB).base/wavlm-base-plus/β the frozen WavLM encoder weights (MIT), bundled.base/qwen2.5-0.5b/β the frozen Qwen2.5-0.5B-Instruct backbone + tokenizer (Apache-2.0), bundled (bf16).config.yamlβ architecture / window configuration (points at the bundledbase/weights).fd_vad/β the inference code (model, encoder wrapper, adapter, windowing).infer.pyβ ready-to-run end-of-turn inference on an audio file.requirements.txtβ dependencies.
This repo is fully self-contained: everything needed to run end-of-turn inference β encoder,
LLM backbone, and the trained adapter/LoRA β is included. No weights are downloaded at runtime
(infer.py sets HF_HUB_OFFLINE=1 when the bundled base/ weights are present). The bundled base
models are redistributed under their original permissive licenses (WavLM: MIT; Qwen2.5: Apache-2.0).
Usage
pip install -r requirements.txt
python infer.py path/to/audio.wav # prints the committed EOT time, or "incomplete"
python infer.py audio.wav --tau 0.9 --k 1 # more conservative endpoint
python infer.py audio.wav --show-stream # per-320ms P(Stop) trace
Or in Python:
import torch, sys; sys.path.insert(0, ".")
from fd_vad.config import load_config
from fd_vad.models.fd_vad import FDVAD
cfg = load_config("config.yaml"); cfg.encoder.kind = "wavlm"
model = FDVAD(cfg).eval() # pulls WavLM + Qwen from the Hub
model.load_trainable_state_dict(torch.load("fd_vad.pt", map_location="cpu")["model"])
# slide a 2.56 s window (stride 320 ms) over 16 kHz mono audio;
# out = model(window[B,T], lengths[B]); out.probs2[:, 1] is P(Stop) per chunk. See infer.py.
Intended use & limitations
Intended as a portable turn-taking signal for full-duplex voice agents. English only (WavLM-base-plus + English training data). It is a semantic endpoint detector, not a general VAD; for continuous multi-turn audio, pair it with a lightweight acoustic VAD gate that decides when an endpoint is plausible while FD-VAD decides whether the preceding speech is complete.
License
MIT.
- Downloads last month
- 10