From b8271e55ec733647d2ad82e11b2cc57b7b950df7 Mon Sep 17 00:00:00 2001 From: Piotr Duda Date: Sun, 25 Oct 2026 01:33:10 +0200 Subject: [PATCH] List the Neural Engine as npu on Apple silicon Macs The device's accelerators only ever added GPUs to cpu, so an Apple silicon Mac reported ["cpu", "mps"] and never its Neural Engine. The Swift SDK lists npu on Apple devices. Platforms get npu_accelerators(), empty by default, and macOS returns npu on arm64. gpu_accelerators() is unchanged, so GPU offload detection is not affected. --- CHANGELOG.md | 6 ++++++ tests/test_platform_adapters.py | 22 ++++++++++++++++++++++ wildedge/accelerators.py | 1 + wildedge/platforms/base.py | 5 ++++- wildedge/platforms/macos.py | 4 ++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7b234..73c5416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ under Unreleased and move into a version section at release time. ## Unreleased +### Changed + +- Apple silicon Macs list the Neural Engine as `npu` in the device's + accelerators, next to `cpu` and `mps`. It was missing, so a model that runs + there had no matching entry. The Swift SDK already lists it. + ## 0.3.0 - 2026-10-24 ### Added diff --git a/tests/test_platform_adapters.py b/tests/test_platform_adapters.py index a9b3d85..ab00a11 100644 --- a/tests/test_platform_adapters.py +++ b/tests/test_platform_adapters.py @@ -52,6 +52,20 @@ def test_macos_gpu_accelerators_for_arm64(monkeypatch): assert gpu_name is None +def test_macos_lists_the_neural_engine_on_arm64_only(monkeypatch): + platform = MacOSPlatform() + monkeypatch.setattr("platform.machine", lambda: "arm64") + assert platform.npu_accelerators() == ["npu"] + monkeypatch.setattr("platform.machine", lambda: "x86_64") + assert platform.npu_accelerators() == [] + + +def test_other_platforms_list_no_npu(): + assert LinuxPlatform().npu_accelerators() == [] + assert WindowsPlatform().npu_accelerators() == [] + assert UnknownPlatform().npu_accelerators() == [] + + def test_unknown_platform_disk_bytes_failure_returns_none(monkeypatch): platform = UnknownPlatform() @@ -223,3 +237,11 @@ def test_platform_adapters_expose_state_and_cache_paths(): ): assert adapter.state_base() assert adapter.cache_base() + + +def test_apple_silicon_device_lists_cpu_gpu_and_neural_engine(monkeypatch): + platform = MacOSPlatform() + monkeypatch.setattr("platform.machine", lambda: "arm64") + monkeypatch.setattr(platform, "load_or_create_device_uuid", lambda: "uuid") + info = platform.detect_device(api_key="k", app_version="1.0") + assert info.accelerators == ["cpu", "mps", "npu"] diff --git a/wildedge/accelerators.py b/wildedge/accelerators.py index 617e395..c984ec4 100644 --- a/wildedge/accelerators.py +++ b/wildedge/accelerators.py @@ -9,5 +9,6 @@ ROCM = "rocm" # AMD GPU MPS = "mps" # Apple GPU (Metal) COREML = "coreml" # Apple Core ML, which may use the GPU or Neural Engine +NPU = "npu" # Neural processing unit, such as Apple's Neural Engine DIRECTML = "directml" # Windows DirectML OPENVINO = "openvino" # Intel OpenVINO diff --git a/wildedge/platforms/base.py b/wildedge/platforms/base.py index 7ebefd5..073f39d 100644 --- a/wildedge/platforms/base.py +++ b/wildedge/platforms/base.py @@ -46,6 +46,9 @@ def gpu_accelerator_for_offload(self) -> str: ... # Optional hardware introspection — subclasses override what they support. + def npu_accelerators(self) -> list[str]: + return [] + def meminfo(self) -> tuple[int | None, int | None]: return None, None @@ -113,7 +116,7 @@ def detect_device( cpu_cores=os.cpu_count(), ram_total_bytes=self.ram_bytes(), disk_total_bytes=self.disk_bytes(), - accelerators=[accelerators.CPU, *gpu_accs], + accelerators=[accelerators.CPU, *gpu_accs, *self.npu_accelerators()], gpu_name=gpu_name, ) if overrides: diff --git a/wildedge/platforms/macos.py b/wildedge/platforms/macos.py index 2321356..4fc1330 100644 --- a/wildedge/platforms/macos.py +++ b/wildedge/platforms/macos.py @@ -118,6 +118,10 @@ def gpu_accelerators(self) -> tuple[list[str], str | None]: def gpu_accelerator_for_offload(self) -> str: return accelerators.MPS if platform.machine() == "arm64" else accelerators.CPU + def npu_accelerators(self) -> list[str]: + # Every Apple silicon Mac has a Neural Engine. + return [accelerators.NPU] if platform.machine() == "arm64" else [] + def cpu_freq(self) -> tuple[int | None, int | None]: """Read CPU frequency via sysctl.