@ondeinference / onde / commits / ac1610b

Add watchOS support: build, CI, Metal, and sysctl patch

- Add aarch64-apple-watchos and aarch64-apple-watchos-sim to build-swift-xcframework.sh - Add check-watchos and check-watchos-sim jobs to CI workflow - Enable mistralrs (Metal) and hf-hub for watchOS in Cargo.toml - Patch sysctl crate for watchOS support (pending upstream PR) - Update all platform gating and feature flags for target_os = "watchos" - Default to 1.5B model on watchOS due to memory constraints - Update LICENSE-APACHE copyright year and owner - Include watchOS in tvos_chkstk.s build.rs logic for linker workaround

Seto Elkahfi committed Apr 9, 2026 at 21:50 UTC ac1610b357488ce6ccd819fde8a15132b18b0cd5
10 files changed +86 -16
.github/scripts/build-swift-xcframework.sh
+6
@@ -25,6 +25,8 @@ cargo +nightly rustc -Z build-std --target aarch64-apple-tvos --release --lib --
25 cargo +nightly rustc -Z build-std --target aarch64-apple-tvos-sim --release --lib --crate-type staticlib
26 cargo +nightly rustc -Z build-std --target aarch64-apple-visionos --release --lib --crate-type staticlib
27 cargo +nightly rustc -Z build-std --target aarch64-apple-visionos-sim --release --lib --crate-type staticlib
28 +cargo +nightly rustc -Z build-std --target aarch64-apple-watchos --release --lib --crate-type staticlib
29 +cargo +nightly rustc -Z build-std --target aarch64-apple-watchos-sim --release --lib --crate-type staticlib
30
31 "$BINDGEN" generate "$ROOT_DIR/target/aarch64-apple-ios/release/libonde.a" --language swift --out-dir "$PACKAGE_DIR/Sources/Onde"
32
@@ -37,6 +39,8 @@ cp "$ROOT_DIR/target/aarch64-apple-tvos-sim/release/libonde.a" "$DIST_DIR/libond
39
40 cp "$ROOT_DIR/target/aarch64-apple-visionos-sim/release/libonde.a" "$DIST_DIR/libonde-visionos-sim.a"
41
42 +cp "$ROOT_DIR/target/aarch64-apple-watchos-sim/release/libonde.a" "$DIST_DIR/libonde-watchos-sim.a"
43 +
44 cp "$ROOT_DIR/target/aarch64-apple-darwin/release/libonde.a" "$DIST_DIR/libonde-macos.a"
45
46 xcodebuild -create-xcframework \
@@ -46,6 +50,8 @@ xcodebuild -create-xcframework \
50 -library "$DIST_DIR/libonde-tvos-sim.a" -headers "$HEADERS_DIR" \
51 -library "$ROOT_DIR/target/aarch64-apple-visionos/release/libonde.a" -headers "$HEADERS_DIR" \
52 -library "$DIST_DIR/libonde-visionos-sim.a" -headers "$HEADERS_DIR" \
53 + -library "$ROOT_DIR/target/aarch64-apple-watchos/release/libonde.a" -headers "$HEADERS_DIR" \
54 + -library "$DIST_DIR/libonde-watchos-sim.a" -headers "$HEADERS_DIR" \
55 -library "$DIST_DIR/libonde-macos.a" -headers "$HEADERS_DIR" \
56 -output "$FRAMEWORK_DIR"
57
.github/workflows/ci.yml
+26
@@ -126,3 +126,29 @@ jobs:
126 with:
127 key: visionos-sim
128 - run: cargo +nightly check -Z build-std --target aarch64-apple-visionos-sim
129 +
130 + check-watchos:
131 + name: Check (watchOS device)
132 + runs-on: macos-15
133 + steps:
134 + - uses: actions/checkout@v6
135 + - uses: dtolnay/rust-toolchain@nightly
136 + with:
137 + components: rust-src
138 + - uses: Swatinem/rust-cache@v2
139 + with:
140 + key: watchos
141 + - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos
142 +
143 + check-watchos-sim:
144 + name: Check (watchOS simulator)
145 + runs-on: macos-15
146 + steps:
147 + - uses: actions/checkout@v6
148 + - uses: dtolnay/rust-toolchain@nightly
149 + with:
150 + components: rust-src
151 + - uses: Swatinem/rust-cache@v2
152 + with:
153 + key: watchos-sim
154 + - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos-sim
Cargo.lock
+10 -11
@@ -3093,7 +3093,7 @@ dependencies = [
3093 [[package]]
3094 name = "mistralrs"
3095 version = "0.8.1"
3096 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3096 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3097 dependencies = [
3098 "anyhow",
3099 "candle-core",
@@ -3120,7 +3120,7 @@ dependencies = [
3120 [[package]]
3121 name = "mistralrs-audio"
3122 version = "0.8.1"
3123 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3123 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3124 dependencies = [
3125 "anyhow",
3126 "apodize",
@@ -3131,7 +3131,7 @@ dependencies = [
3131 [[package]]
3132 name = "mistralrs-core"
3133 version = "0.8.1"
3134 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3134 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3135 dependencies = [
3136 "ahash",
3137 "akin",
@@ -3228,7 +3228,7 @@ dependencies = [
3228 [[package]]
3229 name = "mistralrs-macros"
3230 version = "0.8.1"
3231 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3231 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3232 dependencies = [
3233 "darling 0.23.0",
3234 "proc-macro2",
@@ -3239,7 +3239,7 @@ dependencies = [
3239 [[package]]
3240 name = "mistralrs-mcp"
3241 version = "0.8.1"
3242 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3242 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3243 dependencies = [
3244 "anyhow",
3245 "async-trait",
@@ -3259,7 +3259,7 @@ dependencies = [
3259 [[package]]
3260 name = "mistralrs-paged-attn"
3261 version = "0.8.1"
3262 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3262 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3263 dependencies = [
3264 "anyhow",
3265 "candle-core",
@@ -3275,7 +3275,7 @@ dependencies = [
3275 [[package]]
3276 name = "mistralrs-quant"
3277 version = "0.8.1"
3278 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3278 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3279 dependencies = [
3280 "byteorder",
3281 "candle-core",
@@ -3304,7 +3304,7 @@ dependencies = [
3304 [[package]]
3305 name = "mistralrs-vision"
3306 version = "0.8.1"
3307 -source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3307 +source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3308 dependencies = [
3309 "candle-core",
3310 "image",
@@ -5486,14 +5486,13 @@ dependencies = [
5486 [[package]]
5487 name = "sysctl"
5488 version = "0.6.0"
5489 -source = "registry+https://github.com/rust-lang/crates.io-index"
5490 -checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
5489 +source = "git+https://github.com/setoelkahfi/sysctl-rs.git?branch=feature%2Fwatchos#961a771d6ab0422a34cdcb51588ecb8fd3a01250"
5490 dependencies = [
5491 "bitflags 2.11.0",
5492 "byteorder",
5493 "enum-as-inner",
5494 "libc",
5496 - "thiserror 1.0.69",
5495 + "thiserror 2.0.18",
5496 "walkdir",
5497 ]
5498
Cargo.toml
+15 -1
@@ -77,7 +77,15 @@ mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/a
77 # mistralrs-core = { version = "0.8.1", features = ["metal"] }
78 mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
79
80 -
80 +# watchOS: Metal-accelerated inference (Apple Watch Series 4+ has Apple GPU);
81 +# hf-hub + mistralrs-core needed for GLOBAL_HF_CACHE sandbox workaround.
82 +# Memory constrained — default to 1.5B model.
83 +[target.'cfg(target_os = "watchos")'.dependencies]
84 +hf-hub = { version = "0.4", features = ["tokio", "rustls-tls"], default-features = false }
85 +# mistralrs = { version = "0.8.1", features = ["metal"] }
86 +mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
87 +# mistralrs-core = { version = "0.8.1", features = ["metal"] }
88 +mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
89
90 # macOS: Metal-accelerated inference.
91 [target.'cfg(target_os = "macos")'.dependencies]
@@ -95,3 +103,9 @@ mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/a
103 [target.'cfg(target_os = "linux")'.dependencies]
104 # mistralrs = { version = "0.8.1" }
105 mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes" }
106 +
107 +# Patch sysctl with watchOS (target_os = "watchos") support until upstream merges it.
108 +# Upstream PR: https://github.com/johalun/sysctl-rs/pull/74
109 +# Fork branch: https://github.com/setoelkahfi/sysctl-rs/tree/feature/watchos
110 +[patch.crates-io]
111 +sysctl = { git = "https://github.com/setoelkahfi/sysctl-rs.git", branch = "feature/watchos" }
LICENSE-APACHE
+1 -1
@@ -187,7 +187,7 @@
187 same "printed page" as the copyright notice for easier
188 identification within third-party archives.
189
190 - Copyright [yyyy] [name of copyright owner]
190 + Copyright 2026 Onde Inference (Splitfire AB)
191
192 Licensed under the Apache License, Version 2.0 (the "License");
193 you may not use this file except in compliance with the License.
build.rs
+1 -1
@@ -17,7 +17,7 @@ fn main() {
17 // instruction — only when targeting tvOS.
18 let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
19
20 - if target_os == "tvos" || target_os == "visionos" {
20 + if target_os == "tvos" || target_os == "visionos" || target_os == "watchos" {
21 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
22 let asm_path = std::path::Path::new(&manifest_dir).join("scripts/tvos_chkstk.s");
23
src/hf_cache.rs
+4 -1
@@ -251,6 +251,7 @@ fn list_revisions(model_dir: &Path) -> Vec<String> {
251 target_os = "ios",
252 target_os = "tvos",
253 target_os = "visionos",
254 + target_os = "watchos",
255 target_os = "android"
256 ))]
257 fn make_progress(model_id: &str, downloaded: u64, total: u64, done: bool) -> ModelDownloadProgress {
@@ -888,7 +889,8 @@ pub fn list_supported_hf_models() -> SupportedHfModelsResponse {
889 target_os = "macos",
890 target_os = "ios",
891 target_os = "tvos",
891 - target_os = "visionos"
892 + target_os = "visionos",
893 + target_os = "watchos"
894 ))]
895 pub async fn download_model<F>(
896 model_id: String,
@@ -1197,6 +1199,7 @@ where
1199 target_os = "ios",
1200 target_os = "tvos",
1201 target_os = "visionos",
1202 + target_os = "watchos",
1203 target_os = "android"
1204 )))]
1205 pub async fn download_model<F>(
src/inference/engine.rs
+19 -1
@@ -61,6 +61,7 @@
61 target_os = "ios",
62 target_os = "tvos",
63 target_os = "visionos",
64 + target_os = "watchos",
65 target_os = "windows",
66 target_os = "linux",
67 target_os = "android"
@@ -72,6 +73,7 @@ use std::sync::Arc;
73 target_os = "ios",
74 target_os = "tvos",
75 target_os = "visionos",
76 + target_os = "watchos",
77 target_os = "windows",
78 target_os = "linux",
79 target_os = "android"
@@ -87,6 +89,7 @@ use super::types::*;
89 target_os = "ios",
90 target_os = "tvos",
91 target_os = "visionos",
92 + target_os = "watchos",
93 target_os = "windows",
94 target_os = "linux",
95 target_os = "android"
@@ -105,6 +108,7 @@ use mistralrs::{IsqBits, TextModelBuilder};
108 target_os = "ios",
109 target_os = "tvos",
110 target_os = "visionos",
111 + target_os = "watchos",
112 target_os = "windows",
113 target_os = "linux",
114 target_os = "android"
@@ -120,6 +124,7 @@ enum LoadedModelConfig {
124 target_os = "ios",
125 target_os = "tvos",
126 target_os = "visionos",
127 + target_os = "watchos",
128 target_os = "windows",
129 target_os = "linux",
130 target_os = "android"
@@ -148,6 +153,7 @@ impl LoadedModelConfig {
153 target_os = "ios",
154 target_os = "tvos",
155 target_os = "visionos",
156 + target_os = "watchos",
157 target_os = "windows",
158 target_os = "linux",
159 target_os = "android"
@@ -185,6 +191,7 @@ struct LoadedModel {
191 target_os = "ios",
192 target_os = "tvos",
193 target_os = "visionos",
194 + target_os = "watchos",
195 target_os = "windows",
196 target_os = "linux",
197 target_os = "android"
@@ -198,6 +205,7 @@ pub struct ChatEngine {
205 target_os = "ios",
206 target_os = "tvos",
207 target_os = "visionos",
208 + target_os = "watchos",
209 target_os = "windows",
210 target_os = "linux",
211 target_os = "android"
@@ -262,7 +270,8 @@ impl ChatEngine {
270 target_os = "android",
271 target_os = "ios",
272 target_os = "tvos",
265 - target_os = "visionos"
273 + target_os = "visionos",
274 + target_os = "watchos"
275 ))]
276 {
277 let hf_home = std::env::var("HF_HOME")
@@ -320,6 +329,7 @@ impl ChatEngine {
329 target_os = "ios",
330 target_os = "tvos",
331 target_os = "visionos",
332 + target_os = "watchos",
333 target_os = "android"
334 )) {
335 SamplingConfig::mobile()
@@ -829,6 +839,7 @@ impl ChatEngine {
839 target_os = "ios",
840 target_os = "tvos",
841 target_os = "visionos",
842 + target_os = "watchos",
843 target_os = "windows",
844 target_os = "linux",
845 target_os = "android"
@@ -849,6 +860,7 @@ impl Default for ChatEngine {
860 target_os = "ios",
861 target_os = "tvos",
862 target_os = "visionos",
863 + target_os = "watchos",
864 target_os = "windows",
865 target_os = "linux",
866 target_os = "android"
@@ -881,6 +893,7 @@ fn build_request(loaded: &LoadedModel, user_message: &str) -> RequestBuilder {
893 target_os = "ios",
894 target_os = "tvos",
895 target_os = "visionos",
896 + target_os = "watchos",
897 target_os = "windows",
898 target_os = "linux",
899 target_os = "android"
@@ -916,6 +929,7 @@ fn apply_sampling(mut req: RequestBuilder, sampling: &SamplingConfig) -> Request
929 target_os = "ios",
930 target_os = "tvos",
931 target_os = "visionos",
932 + target_os = "watchos",
933 target_os = "windows",
934 target_os = "linux",
935 target_os = "android"
@@ -950,6 +964,7 @@ fn truncate_for_log(s: &str, max_len: usize) -> String {
964 target_os = "ios",
965 target_os = "tvos",
966 target_os = "visionos",
967 + target_os = "watchos",
968 target_os = "windows",
969 target_os = "linux",
970 target_os = "android"
@@ -961,6 +976,7 @@ pub struct ChatEngine;
976 target_os = "ios",
977 target_os = "tvos",
978 target_os = "visionos",
979 + target_os = "watchos",
980 target_os = "windows",
981 target_os = "linux",
982 target_os = "android"
@@ -1048,6 +1064,7 @@ impl ChatEngine {
1064 target_os = "ios",
1065 target_os = "tvos",
1066 target_os = "visionos",
1067 + target_os = "watchos",
1068 target_os = "windows",
1069 target_os = "linux",
1070 target_os = "android"
@@ -1156,6 +1173,7 @@ impl GgufModelConfig {
1173 target_os = "ios",
1174 target_os = "tvos",
1175 target_os = "visionos",
1176 + target_os = "watchos",
1177 target_os = "android"
1178 )) {
1179 Self::qwen25_coder_1_5b()
src/inference/token.rs
+3
@@ -38,6 +38,7 @@
38 target_os = "ios",
39 target_os = "tvos",
40 target_os = "visionos",
41 + target_os = "watchos",
42 target_os = "windows",
43 target_os = "android"
44 ))]
@@ -60,6 +61,7 @@ const BUILD_TIME_HF_TOKEN: Option<&str> = option_env!("HF_TOKEN");
61 target_os = "ios",
62 target_os = "tvos",
63 target_os = "visionos",
64 + target_os = "watchos",
65 target_os = "windows",
66 target_os = "android"
67 ))]
@@ -95,6 +97,7 @@ mod tests {
97 target_os = "ios",
98 target_os = "tvos",
99 target_os = "visionos",
100 + target_os = "watchos",
101 target_os = "windows",
102 target_os = "android"
103 ))]
src/lib.rs
+1
@@ -54,6 +54,7 @@ uniffi::setup_scaffolding!();
54 target_os = "ios",
55 target_os = "tvos",
56 target_os = "visionos",
57 + target_os = "watchos",
58 target_os = "windows",
59 target_os = "linux",
60 target_os = "android"