From d370e43715e5f62b5e76dfd133a5043a1dd43ae6 Mon Sep 17 00:00:00 2001 From: pham-tuan-binh Date: Wed, 30 Sep 2026 15:49:16 -0500 Subject: [PATCH] Fix detection on onnxruntime 1.27.0 / arm64: disable KleidiAI kernels onnxruntime 1.27.0 on arm64 (e.g. Apple silicon) runs batch-1 convolutions through its KleidiAI kernels, which return wrong results. The speech-embedding model runs one window per call, so every score collapsed to ~0: the reference positive.wav scored 0.004 (0.98 on 1.20-1.26). Every session the package opens now sets mlas.disable_kleidiai=1 unless the caller set it; older versions accept and ignore it (checked 1.17-1.27). Adds tests/test_pretrained.py (the shipped hey_livekit model on the Rust/Swift reference clips, no torch needed) and runs it on macos-latest (arm64) in CI, since the x86 runner can't reproduce this. --- .github/workflows/ci.yml | 18 ++++++ src/livekit/wakeword/eval/evaluate.py | 5 +- src/livekit/wakeword/inference/model.py | 3 +- .../wakeword/models/feature_extractor.py | 6 +- src/livekit/wakeword/session.py | 20 +++++++ tests/fixtures/negative.wav | Bin 0 -> 64044 bytes tests/fixtures/positive.wav | Bin 0 -> 64044 bytes tests/test_pretrained.py | 54 ++++++++++++++++++ 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/livekit/wakeword/session.py create mode 100644 tests/fixtures/negative.wav create mode 100644 tests/fixtures/positive.wav create mode 100644 tests/test_pretrained.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ad4350..5a0ab84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,3 +31,21 @@ jobs: - name: Run tests run: uv run pytest --tb=short -q + + # onnxruntime's arm64 kernels differ from x86 (KleidiAI); run the pretrained-model check on Apple silicon too. + inference-arm64: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Set up Python + run: uv python install 3.12 + + - name: Install dependencies + run: uv sync + + - name: Run inference tests + run: uv run pytest tests/test_pretrained.py --noconftest --tb=short -q diff --git a/src/livekit/wakeword/eval/evaluate.py b/src/livekit/wakeword/eval/evaluate.py index dc64386..46f041f 100644 --- a/src/livekit/wakeword/eval/evaluate.py +++ b/src/livekit/wakeword/eval/evaluate.py @@ -10,6 +10,7 @@ import onnxruntime as ort from ..config import WakeWordConfig +from ..session import session_options logger = logging.getLogger(__name__) @@ -194,7 +195,9 @@ def run_eval(config: WakeWordConfig, model_path: str | Path) -> dict[str, float] if not model_path.exists(): raise FileNotFoundError(f"Model not found: {model_path}") - session = ort.InferenceSession(str(model_path), providers=["CPUExecutionProvider"]) + session = ort.InferenceSession( + str(model_path), session_options(), providers=["CPUExecutionProvider"] + ) logger.info(f"Loaded model from {model_path}") # Load validation data diff --git a/src/livekit/wakeword/inference/model.py b/src/livekit/wakeword/inference/model.py index 4f16e02..80ff712 100644 --- a/src/livekit/wakeword/inference/model.py +++ b/src/livekit/wakeword/inference/model.py @@ -10,6 +10,7 @@ from ..models.feature_extractor import MelSpectrogramFrontend, SpeechEmbedding from ..resources import get_embedding_model_path, get_mel_model_path +from ..session import session_options logger = logging.getLogger(__name__) @@ -89,7 +90,7 @@ def load_model(self, model_path: str | Path, model_name: str | None = None, sess session = ort.InferenceSession( str(model_path), providers=["CPUExecutionProvider"], - sess_options=sess_options + sess_options=session_options(sess_options), ) input_name = session.get_inputs()[0].name self._classifiers[model_name] = (session, input_name) diff --git a/src/livekit/wakeword/models/feature_extractor.py b/src/livekit/wakeword/models/feature_extractor.py index 76c8c71..51fc8b9 100644 --- a/src/livekit/wakeword/models/feature_extractor.py +++ b/src/livekit/wakeword/models/feature_extractor.py @@ -15,6 +15,8 @@ import numpy as np from onnxruntime.capi.onnxruntime_pybind11_state import SessionOptions +from ..session import session_options + logger = logging.getLogger(__name__) class MelSpectrogramFrontend: @@ -43,7 +45,7 @@ def _init_onnx(self, onnx_path: str | Path, sess_options: SessionOptions = None) self._onnx_session = ort.InferenceSession( str(onnx_path), providers=["CPUExecutionProvider"], - sess_options=sess_options, + sess_options=session_options(sess_options), ) self._input_name = self._onnx_session.get_inputs()[0].name logger.info(f"Loaded mel ONNX model from {onnx_path}") @@ -103,7 +105,7 @@ def __init__(self, onnx_path: str | Path, sess_options: SessionOptions = None): self._session = ort.InferenceSession( str(onnx_path), providers=["CPUExecutionProvider"], - sess_options=sess_options, + sess_options=session_options(sess_options), ) self._input_name = self._session.get_inputs()[0].name logger.info(f"Loaded embedding ONNX model from {onnx_path}") diff --git a/src/livekit/wakeword/session.py b/src/livekit/wakeword/session.py new file mode 100644 index 0000000..9d2ef6c --- /dev/null +++ b/src/livekit/wakeword/session.py @@ -0,0 +1,20 @@ +"""onnxruntime session options shared by every model the package runs.""" + +from __future__ import annotations + +from onnxruntime import SessionOptions + +# onnxruntime 1.27.0 on arm64 (e.g. Apple silicon) runs batch-1 convolutions through its KleidiAI +# kernels, which return wrong results. The speech-embedding model runs one window at a time, so +# every wake word scored ~0 there. Older onnxruntime versions accept this setting and ignore it. +DISABLE_KLEIDIAI = "mlas.disable_kleidiai" + + +def session_options(sess_options: SessionOptions | None = None) -> SessionOptions: + """``sess_options`` (or new defaults) with KleidiAI off, unless the caller set that entry.""" + options = sess_options if sess_options is not None else SessionOptions() + try: + options.get_session_config_entry(DISABLE_KLEIDIAI) + except RuntimeError: # not set + options.add_session_config_entry(DISABLE_KLEIDIAI, "1") + return options diff --git a/tests/fixtures/negative.wav b/tests/fixtures/negative.wav new file mode 100644 index 0000000000000000000000000000000000000000..03a51b4dfe6d63e5cc39541d415124f7997a878e GIT binary patch literal 64044 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 N)DcJ>f&Y&q@PDWv%m@Gg literal 0 HcmV?d00001 diff --git a/tests/fixtures/positive.wav b/tests/fixtures/positive.wav new file mode 100644 index 0000000000000000000000000000000000000000..1c0b94a2204b6d4dc1d89ebcdc2d4555f2f7e06f GIT binary patch literal 64044 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 G3j8m@n7+dR literal 0 HcmV?d00001 diff --git a/tests/test_pretrained.py b/tests/test_pretrained.py new file mode 100644 index 0000000..f703631 --- /dev/null +++ b/tests/test_pretrained.py @@ -0,0 +1,54 @@ +"""The shipped hey_livekit model on reference clips: inference end to end, with no torch. + +The clips are the ones the Rust and Swift ports test against. On onnxruntime 1.27.0 / arm64 the +positive clip scored 0.004 until the KleidiAI kernels were turned off (livekit.wakeword.session). +""" + +from __future__ import annotations + +import wave +from pathlib import Path + +import numpy as np +import pytest +from onnxruntime import SessionOptions + +from livekit.wakeword import WakeWordModel +from livekit.wakeword.session import DISABLE_KLEIDIAI, session_options + +MODEL = Path(__file__).resolve().parents[1] / "examples" / "resources" / "hey_livekit.onnx" +FIXTURES = Path(__file__).parent / "fixtures" +THRESHOLD = 0.5 + + +def clip(name: str) -> np.ndarray: + with wave.open(str(FIXTURES / name)) as w: + assert w.getframerate() == 16_000 and w.getnchannels() == 1 and w.getsampwidth() == 2 + return np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16) + + +@pytest.fixture(scope="module") +def model() -> WakeWordModel: + return WakeWordModel(models=[MODEL]) + + +def test_positive_clip_detected(model: WakeWordModel) -> None: + assert model.predict(clip("positive.wav"))["hey_livekit"] >= THRESHOLD + + +def test_negative_clip_rejected(model: WakeWordModel) -> None: + assert model.predict(clip("negative.wav"))["hey_livekit"] < THRESHOLD + + +def test_caller_options_are_kept() -> None: + options = SessionOptions() + options.intra_op_num_threads = 1 + model = WakeWordModel(models=[MODEL], sess_options=options) + assert options.intra_op_num_threads == 1 + assert model.predict(clip("positive.wav"))["hey_livekit"] >= THRESHOLD + + +def test_caller_can_opt_back_in() -> None: + options = SessionOptions() + options.add_session_config_entry(DISABLE_KLEIDIAI, "0") + assert session_options(options).get_session_config_entry(DISABLE_KLEIDIAI) == "0"