1 <p align="center">
2 <img src="https://raw.githubusercontent.com/ondeinference/onde/main/assets/onde-inference-logo.svg" alt="Onde Inference" width="96">
3 </p>
4
5 <h1 align="center">Onde Inference</h1>
6
7 <p align="center">
8 <strong>Run LLMs on-device from Flutter and Dart. Metal on iOS and macOS, CPU everywhere else.</strong>
9 </p>
10
11 <p align="center">
12 <a href="https://pub.dev/packages/onde_inference"><img src="https://img.shields.io/pub/v/onde_inference?style=flat-square&color=235843&labelColor=17211D&label=pub.dev" alt="pub.dev"></a>
13 <a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
14 <a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
15 </p>
16
17 <p align="center">
18 <a href="https://github.com/ondeinference/onde">Rust SDK</a> · <a href="https://swiftpackageindex.com/ondeinference/onde-swift">Swift SDK</a> · <a href="https://central.sonatype.com/artifact/com.ondeinference/onde-inference">Kotlin Multiplatform SDK</a> · <a href="https://www.npmjs.com/package/@ondeinference/react-native">React Native SDK</a> · <a href="https://ondeinference.com">Website</a>
19 </p>
20
21 ---
22
23 Run Qwen 2.5 models directly inside your Flutter app. The model downloads from Hugging Face on first launch, then everything runs locally. No server, no API key, and no user data leaving the device. On an iPhone 15 Pro, Metal gets you around 15 tok/s. Android and desktop run on CPU, so they are slower, but they work well for local inference.
24
25 You get multi-turn chat, streaming, one-shot generation, and configurable sampling in a single package.
26
27 ## Platform support
28
29 | Platform | Backend | Default model | Notes |
30 |----------|---------|---------------|-------|
31 | iOS 13+ | Metal | Qwen 2.5 1.5B (~941 MB) | Simulator uses `aarch64-apple-ios-sim` |
32 | macOS 10.15+ | Metal | Qwen 2.5 3B (~1.93 GB) | Apple silicon and Intel |
33 | Android API 21+ | CPU | Qwen 2.5 1.5B (~941 MB) | arm64-v8a, armeabi-v7a, x86_64, x86 |
34 | Linux x86_64 | CPU | Qwen 2.5 3B (~1.93 GB) | CUDA possible, see docs |
35 | Windows x86_64 | CPU | Qwen 2.5 3B (~1.93 GB) | CUDA possible, see docs |
36
37 Web is not supported. On-device inference needs native system access that browsers don't expose.
38
39 ---
40
41 ## Quick start
42
43 ```yaml
44 dependencies:
45 onde_inference: ^0.1.0
46 ```
47
48 The inference engine is written in Rust and wired into Dart through [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge). You will need a working [Rust toolchain](https://rustup.rs). The first build is usually slow, around 5 to 10 minutes, because it has to compile the full dependency tree. After that, incremental builds are much faster.
49
50 ### Initialize
51
52 Call this once at startup, before anything else:
53
54 ```dart
55 import 'package:onde_inference/onde_inference.dart';
56
57 void main() async {
58 WidgetsFlutterBinding.ensureInitialized();
59 await OndeInference.init();
60 runApp(const MyApp());
61 }
62 ```
63
64 ### Load a model
65
66 ```dart
67 final engine = await OndeChatEngine.create();
68
69 // Picks the default model for the device:
70 // iOS / Android → Qwen 2.5 1.5B (~941 MB)
71 // macOS / Linux / Windows → Qwen 2.5 3B (~1.93 GB)
72 final elapsed = await engine.loadDefaultModel(
73 systemPrompt: 'You are a helpful assistant.',
74 );
75 print('Model loaded in ${elapsed.toStringAsFixed(1)} s');
76 ```
77
78 ### Chat
79
80 ```dart
81 final result = await engine.sendMessage('What is Rust's ownership model?');
82 print(result.text);
83 print('Generated in ${result.durationDisplay}');
84 ```
85
86 ### Stream
87
88 ```dart
89 final buffer = StringBuffer();
90
91 await for (final chunk in engine.streamMessage('Tell me a short story.')) {
92 buffer.write(chunk.delta);
93 setState(() => _displayText = buffer.toString());
94 if (chunk.done) break;
95 }
96 ```
97
98 ### Engine status
99
100 ```dart
101 final info = await engine.info();
102
103 print(info.status); // EngineStatus.ready
104 print(info.modelName); // "Qwen 2.5 3B"
105 print(info.approxMemory); // "~1.93 GB"
106 print(info.historyLength); // number of turns so far
107 ```
108
109 ### History
110
111 ```dart
112 final history = await engine.history();
113 for (final msg in history) {
114 print('${msg.role}: ${msg.content}');
115 }
116
117 // Clear history but keep the model loaded.
118 final removed = await engine.clearHistory();
119 print('Cleared $removed messages.');
120
121 // Seed from a saved session — no inference runs.
122 await engine.pushHistory(ChatMessage.user('Hello from last session!'));
123 await engine.pushHistory(ChatMessage.assistant('Hi! How can I help today?'));
124 ```
125
126 ### One-shot generation
127
128 This runs inference without changing conversation history. It is useful for things like prompt enhancement, classification, or formatting.
129
130 ```dart
131 final result = await engine.generate(
132 [
133 ChatMessage.system('You are a JSON formatter. Output only valid JSON.'),
134 ChatMessage.user('Name: Alice, Age: 30, City: Stockholm'),
135 ],
136 sampling: SamplingConfig.deterministic(),
137 );
138 print(result.text);
139 ```
140
141 ### Unload
142
143 ```dart
144 await engine.unloadModel();
145 ```
146
147 ---
148
149 ## Model selection
150
151 ```dart
152 // Platform-aware default (recommended).
153 final config = OndeInference.defaultModelConfig();
154
155 // Force a specific model.
156 final small = OndeInference.qwen251_5bConfig(); // ~941 MB
157 final medium = OndeInference.qwen253bConfig(); // ~1.93 GB
158 final coder = OndeInference.qwen25Coder3bConfig(); // ~1.93 GB, code-tuned
159
160 await engine.loadGgufModel(
161 medium,
162 systemPrompt: 'You are an expert software engineer.',
163 );
164 ```
165
166 | Model | Size | Good for |
167 |-------|------|----------|
168 | Qwen 2.5 1.5B Instruct Q4_K_M | ~941 MB | iOS, tvOS, Android |
169 | Qwen 2.5 3B Instruct Q4_K_M | ~1.93 GB | macOS, Linux, Windows |
170 | Qwen 2.5 Coder 1.5B Instruct Q4_K_M | ~941 MB | Code on mobile |
171 | Qwen 2.5 Coder 3B Instruct Q4_K_M | ~1.93 GB | Code on desktop |
172
173 ---
174
175 ## Sampling
176
177 All fields are optional. `null` means "use the engine default".
178
179 ```dart
180 final sampling = SamplingConfig(
181 temperature: 0.7,
182 topP: 0.95,
183 topK: 40,
184 maxTokens: 256,
185 );
186
187 await engine.setSampling(sampling);
188 ```
189
190 Presets:
191
192 ```dart
193 SamplingConfig.defaultConfig() // temp=0.7, max 512 tokens
194 SamplingConfig.deterministic() // greedy, temp=0.0
195 SamplingConfig.mobile() // temp=0.7, max 128 tokens
196 ```
197
198 ---
199
200 ## Error handling
201
202 All engine methods throw `OndeException` on failure:
203
204 ```dart
205 try {
206 await engine.loadDefaultModel();
207 } on OndeException catch (e) {
208 debugPrint('Inference error: ${e.message}');
209 }
210 ```
211
212 Common causes include calling `sendMessage` before loading a model, having no internet connection on first run while the model still needs to download, or running out of memory. The 3B model needs about 2 GB free, so on tighter devices you will usually want the 1.5B model instead.
213
214 ---
215
216 ## Sandboxed app setup (iOS / macOS)
217
218 On iOS and sandboxed macOS, the default Hugging Face cache path sits outside the app container. Call `setupCacheDir()` once at startup so Onde uses a location your app can actually access:
219
220 ```dart
221 import 'package:onde_inference/onde_inference.dart';
222 import 'package:path_provider/path_provider.dart';
223
224 void main() async {
225 WidgetsFlutterBinding.ensureInitialized();
226 await OndeInference.init();
227
228 String? fallback;
229 if (Platform.isIOS || Platform.isAndroid) {
230 final dir = await getApplicationSupportDirectory();
231 fallback = dir.path;
232 }
233 await OndeInference.setupCacheDir(fallbackDir: fallback);
234
235 runApp(const MyApp());
236 }
237 ```
238
239 Onde first tries the App Group shared container (`group.com.ondeinference.apps`) so Onde-powered apps can share downloaded models. If that App Group is not configured, it falls back to the app's private directory.
240
241 ---
242
243 ## Contributing
244
245 The source lives at [github.com/ondeinference/onde](https://github.com/ondeinference/onde):
246
247 - Rust core: `src/`
248 - Dart bridge crate: `sdk/dart/rust/`
249 - Dart library: `sdk/dart/lib/`
250 - Example app: `sdk/dart/example/`
251
252 Open an issue before sending large PRs.
253
254 ## License
255
256 Onde is dual-licensed under **MIT** and **Apache 2.0**. You can use either one.
257
258 - [MIT License](https://github.com/ondeinference/onde/blob/main/LICENSE-MIT)
259 - [Apache License 2.0](https://github.com/ondeinference/onde/blob/main/LICENSE-APACHE)
260
261 © 2026 [Splitfire AB](https://splitfire.se)
262
263 ---
264
265 ## Copyright
266
267 © 2026 [Onde Inference](https://ondeinference.com) (Splitfire AB).