Always-on 14-way keyword spotter on the VoxRT custom on-device inference runtime. ~636K-parameter streaming Conformer-Medium, 16 kHz mono in, per-class sigmoid posteriors + threshold-crossing detection events. Detects the phrases "yes / no / cancel / play / pause / next / previous / up / down / back / on / off / voxrt / hey_vox".
- Status: production-ready for x86_64 + aarch64 Linux. Runtime numeric parity vs PyTorch reference validated bit-identically across AVX2 (x86_64) and NEON (aarch64) on the deployed model — see the Performance section for measured numbers.
- Current version:
v0.1.0 - Target platforms:
x86_64Linux (servers, dev boxes, containers — Haswell 2013+ / Excavator 2015+ with AVX2) andaarch64Linux (Raspberry Pi 3 / 4 / 5 / Zero 2, NVIDIA Jetson, AWS Graviton, Rock Pi / Orange Pi / other aarch64 SBCs) - glibc baseline: 2.17 (manylinux2014_x86_64 + manylinux2014_aarch64 — Pi OS Bullseye/Bookworm, Jetson L4T 32.x/35.x/36.x, Ubuntu 18.04+, Debian 10+, RHEL/CentOS 7+, Amazon Linux 2, AWS Graviton)
- Languages shipped: Python (PyPI wheel × 2 arches), Node.js (npm, both arches in one package), Go (
go get, both arches bundled), C / C++ (2 tarballs — one per arch — plus CMake + pkg-config), Rust (git dependency) - License: Apache-2.0 (wrapper sources) · proprietary (compiled runtime, redistribution allowed via this SDK)
- Keyword-model weights: proprietary in-house (synthetic + open training data; no upstream license obligations)
What is VoxRT?
VoxRT is a from-scratch inference runtime for on-device speech models. No ONNX Runtime, no PyTorch Mobile, no LiteRT — a custom Rust core sized and tuned for streaming voice workloads on phone-class and edge-Linux hardware.
voxrt-kws is the keyword-spotting product on that runtime, alongside voxrt-wake-word-linux (always-on wake phrase) and voxrt-asr-linux (streaming ASR). Same runtime crate, same NEON / AVX2 kernel set, same .vxrt model format — pick the SDK that matches your task. The wake-word SDK triggers a single phrase; the KWS SDK classifies utterances across a fixed vocabulary; the ASR SDK produces free-form transcripts.
Custom keyword sets (your own command vocabulary, brand-specific commands, additional languages) are part of the commercial VoxRT SDK tier. Contact [email protected].
Model quality
Held-out speaker-disjoint test split, per-class threshold-swept macro-F1 on the 14-way vocabulary:
- F1 macro: 0.9671 at the default operating point (threshold 0.9, consecutive-3 firing, cooldown 25 emits).
- Preserves the noise-robustness win of the M10 augmentation recipe while matching the M9 clean-set headroom (data + capacity co-tuning — see the internal
docs/kws/M13-STATUS.md).
Each keyword's per-class ROC AUC ≥ 0.99 on the held-out set. Default operating point tuned for false-fire suppression in an always-on setting; lower the threshold at runtime if your application can tolerate more triggers in exchange for higher recall.
Performance
x86_64 release builds detect AVX2 + FMA at runtime and dispatch every hot kernel (mel frontend, Conformer MHA, depthwise conv module, FFN, LayerNorm, softmax, sigmoid) into 8-wide AVX2 + FMA. aarch64 release builds use NEON 4-wide FMA in the same kernels. All ARMv8-A hardware mandates NEON, so no runtime detection is needed.
Native bench = 60 s of silence pushed in 512-sample (32 ms) chunks after 100-chunk warmup, single-thread. RTF = wall-time / audio-time (lower is better):
| Device | Backend | RTF | Throughput | ms / chunk (mean / p99) |
|---|---|---|---|---|
| WSL x86_64 (Ryzen dev laptop) | AVX2 + FMA | 1.03 % | 97.2× realtime | 0.33 / 0.55 |
| Raspberry Pi Zero 2 W | NEON A53 @ 1 GHz | 32.2 % | 3.1× realtime | 10.3 / 13.0 |
| Raspberry Pi 3 A+ / B / B+ (extrapolated) | NEON A53 @ 1.2–1.4 GHz | ~24–28 % | ~3.5–4× | scaled |
| Raspberry Pi 4 B / 400 (extrapolated) | NEON A72 @ 1.5–1.8 GHz | ~13–16 % | ~6–8× | scaled |
| Raspberry Pi 5 (extrapolated) | NEON A76 @ 2.4 GHz | ~6–8 % | ~12–17× | scaled |
| NVIDIA Jetson Nano / Orin Nano (extrapolated) | NEON A57 / A78AE | ≲ Pi 4–5 | ≳ 6–17× | scaled |
| AWS Graviton / Ampere Altra (extrapolated) | NEON Neoverse N1 / V1 | ≪ 1 % | server-class | scaled |
| x86_64 servers (AVX2, Haswell 2013+) | AVX2 + FMA | ≲ 1 % | ≳ 100× | scaled |
The Pi Zero 2 W row is a direct measurement (RTF 32.2 % sustained across 1875 consecutive 32 ms chunks; p99 chunk = 41 % of the 32 ms real-time budget → 59 % headroom even in the worst chunk). The WSL x86_64 row is similarly a direct measurement. All other rows scale from these two baselines by clock + µarch — conservative estimates, same binary, no per-device tuning.
Cross-architecture numeric parity (5-sample PyTorch-reference diff, WSL AVX2 vs Pi NEON, same model):
| Metric | WSL AVX2 | Pi NEON | ||
|---|---|---|---|---|
| posterior max \ | Δ\ | across 5 samples | 7.2 × 10⁻⁴ | 7.2 × 10⁻⁴ (identical) |
| firing score max \ | Δ\ | 1.6 × 10⁻⁴ | 1.6 × 10⁻⁴ (identical) | |
| firing emit \ | Δ\ | max | 0 | 0 |
| firing counts / class match | 5 / 5 | 5 / 5 |
Both backends produce bit-identical output vs the PyTorch reference on real audio inputs — the AVX2 and NEON kernels sum in the same order, and 4-wide vs 8-wide accumulation happens to converge at these tensor shapes. Post-diff sits at 7 × the abs-tolerance floor used for the golden CI (5 × 10⁻³).
Binary footprint
- C tarball (x86_64) (
voxrt-kws-0.1.0-x86_64-linux-gnu.tar.gz): ~350 KB compressed. Containslibvoxrt_kws.so(~600 KB stripped, glibc-2.17 baseline),voxrt_kws.h, pkg-config + CMake configs, examples/, LICENSE × 2, README. - C tarball (aarch64) (
voxrt-kws-0.1.0-aarch64-linux-gnu.tar.gz): ~300 KB compressed. Same layout, ~520 KB.so. - PyPI wheel (
voxrt_kws-...-cp39-abi3-manylinux_2_17_{x86_64,aarch64}.whl): two per version, one per arch. ~350 KB / ~300 KB compressed. Singleabi3wheel covers Python 3.9 / 3.10 / 3.11 / 3.12 / 3.13 via the pyo3 stable-ABI bridge. - npm package (
voxrt-kws-0.1.0.tgz, imports as@voxrt/kws): ~650 KB compressed. One tgz carries bothvoxrt-kws.linux-x64-gnu.node+voxrt-kws.linux-arm64-gnu.node— install once, runs on both arches. - Go module (in-repo at
go/): both.sos + Go source. Fetched bygo get github.com/VoxRT/voxrt-kws-linux/go. - Keyword model
voxrt_kws.vxrt(downloaded separately from voxrt-kws-models): ~1.3 MB fp16 encrypted (M13 Conformer-Medium,d_model=96, 4 blocks, 636 K params).
Net effect on a shipped app: ~1.9 MB once the runtime .so + .vxrt + language wrapper are bundled.
Install
Python
pip install voxrt-kws
One abi3 wheel per arch covers Python 3.9 / 3.10 / 3.11 / 3.12 / 3.13. pip picks the wheel that matches your interpreter's platform automatically.
Node.js
npm install @voxrt/kws
TypeScript definitions ship with the package. Both linux-x64-gnu and linux-arm64-gnu native addons are in the same tarball — require('@voxrt/kws') resolves the right one at load time.
Go
go get github.com/VoxRT/voxrt-kws-linux/[email protected]
The go/ subdirectory of this repository IS the Go module — no separate publish step. cgo picks up the bundled .so via ${SRCDIR}-relative rpath at build time; no LD_LIBRARY_PATH or system install required.
C / C++
Pick the tarball that matches your host arch:
# x86_64 host
curl -L https://github.com/VoxRT/voxrt-kws-linux/releases/download/v0.1.0/voxrt-kws-0.1.0-x86_64-linux-gnu.tar.gz \
| sudo tar -xz -C /usr/local --strip-components=1
# aarch64 host
curl -L https://github.com/VoxRT/voxrt-kws-linux/releases/download/v0.1.0/voxrt-kws-0.1.0-aarch64-linux-gnu.tar.gz \
| sudo tar -xz -C /usr/local --strip-components=1
sudo ldconfig
After extract:
gcc main.c $(pkg-config --cflags --libs voxrt-kws) -o my_app
Or in CMake:
find_package(VoxRTKws 0.1.0 REQUIRED)
target_link_libraries(my_app PRIVATE VoxRT::Kws)
Rust
Pin the crate via git until the crates.io decision is finalised:
[dependencies]
voxrt-kws = { git = "https://github.com/VoxRT/voxrt-kws-linux", tag = "v0.1.0" }
Get the keyword model
The voxrt_kws.vxrt model file (AES-256-GCM encrypted, ~1.3 MB) is distributed separately from voxrt-kws-models so the SDK ships without weights and the two version independently. All v0.1.x SDK releases use the same model v0.1.0.
- Download once at deploy time:
``sh curl -LO https://github.com/VoxRT/voxrt-kws-models/releases/download/v0.1.0/voxrt_kws.vxrt `` Bundle alongside your application binary.
- Download on first run + SHA-256 verify — recommended if you want to swap models without a redeploy.
- Embed as a build-time resource — for Python:
importlib.resources. For Node.js:require.resolve+fs.readFileSync. For Go://go:embed. For C: any resource-embedder. Feed the bytes tofrom_bytes(...)on your language'sKwsEngineconstructor.
Quick start
Python — the smallest possible end-to-end pipeline:
from voxrt_kws import KwsEngine
engine = KwsEngine.from_path("voxrt_kws.vxrt")
engine.threshold = 0.9
for chunk in mic_iter(): # int16 mono @ 16 kHz, any chunk size
for d in engine.push_pcm_i16(chunk):
print(f"detect! {d.class_name} t={d.timestamp_sec:.3f}s score={d.score:.4f}")
Every language ships an equivalent end-to-end example under examples/. All quickstarts use only stdlib WAV parsing — no numpy / sounddevice / go-audio dependency at the entry level.
Examples
Two flavours are shipped in every language channel — a live-microphone example (aimed at edge deployments — Pi, Jetson, kiosk boxes, industrial SBCs) and a WebSocket server example (aimed at server / browser-facing deployments — a WS endpoint accepting audio frames from a browser mic and streaming detection events back). The example set:
| Language | Example | What it does |
|---|---|---|
| C | examples/c/alsa-mic-quickstart | Live ALSA microphone → keyword detection events on stdout. Build with make, run against plughw:0. |
| C++ | examples/c/alsa-mic-quickstart-cpp | Same live-mic demo in idiomatic C++17: RAII wrappers over voxrt_kws_t* + snd_pcm_t*, std::atomic Ctrl-C, exception-based errors. |
| C | examples/c/ws-detect-server | libwebsockets-based WebSocket endpoint that streams keyword detections as JSON frames. Skeleton for a browser mic → server backend. |
| C++ | examples/c/ws-detect-server-cpp | C++17 variant of the WebSocket server. Same wire protocol. |
| C++ / CMake | examples/c/cmake-consumer | Skeleton CMake project consuming the SDK via find_package(VoxRTKws ...). Copy-paste into a real project. |
| Python | examples/python/quickstart | Walk a 16 kHz WAV file in 32 ms chunks, print detections + RTF. Pure stdlib (wave + struct). |
| Python | examples/python/alsa-mic-quickstart | Live mic via sounddevice. |
| Python | examples/python/ws-detect-server | websockets-based server, same JSON protocol as the C variant. |
| Node.js | examples/nodejs/quickstart | Same shape, JavaScript. Manual WAV header parse via fs + Buffer, no npm deps. |
| Node.js | examples/nodejs/alsa-mic-quickstart | Live mic via the mic npm package. |
| Node.js | examples/nodejs/ws-detect-server | WebSocket server via ws. |
| Go | examples/go/quickstart | Same shape, Go. encoding/binary for the WAV header. cgo picks up the bundled .so. |
| Go | examples/go/alsa-mic-quickstart | Live mic via github.com/gordonklaus/portaudio. |
| Go | examples/go/ws-detect-server | WebSocket server via github.com/gorilla/websocket. |
Tuning
Threshold. Sigmoid-space [0, 1], shared across every keyword class. Default 0.9. Lower increases recall (more detections, more false positives); raise increases precision.
Consecutive frames. Number of consecutive emits a class posterior must stay ≥ threshold before firing. Default 3 (120 ms at 25 fps). Lower for tighter reaction; raise if you're getting brief spurious triggers.
Cooldown. Per-class post-detection suppression in emits. Default 25 (1.0 s at 25 fps). Prevents stuttering when a keyword lingers above threshold across many emits.
CPU affinity. Not exposed as a public knob — the SDK does not pin threads on Linux. If you're on a big.LITTLE Pi (Pi 4/5) and want the KWS thread to stick to the perf cluster, wrap push_pcm_i16 in a thread you affine yourself via sched_setaffinity (POSIX C), os.sched_setaffinity (Python), or runtime.LockOSThread + unix.SchedSetaffinity (Go).
All three knobs are mutable at any time — change them between chunks safely.
License
- Wrapper sources (Python / Node / Go / C headers / examples / docs): Apache-2.0.
- Compiled binaries (
.so,.node,.whl, prebuilt tarball artefact): proprietary, see LICENSE-BINARY. - Keyword model weights (
voxrt_kws.vxrt): proprietary in-house, same terms as LICENSE-BINARY.
Commercial licensing for custom keyword vocabularies, additional languages, or bulk-device deployments: [email protected].
Links
- Brand + docs: <https://voxrt.com>
- Wake-word SDK (Linux): <https://github.com/VoxRT/voxrt-wake-word-linux>
- ASR SDK (Linux): <https://github.com/VoxRT/voxrt-asr-linux>
- Browser SDK (WASM): <https://github.com/VoxRT/voxrt-kws-browser>
- Keyword-model releases: <https://github.com/VoxRT/voxrt-kws-models>
- Issues: <https://github.com/VoxRT/voxrt-kws-linux/issues>