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 with <a href="https://ondeinference.com/">Onde Inference</a>. 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 leaves the device. On an iPhone 15 Pro, Metal reaches around 15 tok/s. Android, Linux, and Windows run on CPU, so they are slower but still useful for fully local inference.
24
25 You get multi-turn chat, streaming, one-shot generation, configurable sampling, and structured tool call metadata in one package.
26
27 ## Platform support
28
29 | Platform | Backend | Default model | Notes |
30 |----------|---------|---------------|-------|
31 | iOS 13+ | Metal | Qwen 2.5 Coder 1.5B (~941 MB) | CocoaPods and Swift Package Manager plugin manifests are included |
32 | macOS 10.15+ | Metal | Qwen 2.5 Coder 3B (~1.93 GB) | CocoaPods and Swift Package Manager plugin manifests are included |
33 | Android API 21+ | CPU | Qwen 2.5 Coder 1.5B (~941 MB) | arm64-v8a, armeabi-v7a, x86_64, x86 |
34 | Linux x86_64 | CPU | Qwen 2.5 Coder 3B (~1.93 GB) | CUDA possible, see docs |
35 | Windows x86_64 | CPU | Qwen 2.5 Coder 3B (~1.93 GB) | CUDA possible, see docs |
36
37 Web is not supported. On-device inference needs native system access that browsers do not expose.
38
39 ---
40
41 ## Quick start
42
43 ```yaml
44 dependencies:
45 onde_inference: ^1.0.2
46 ```
47
48 The inference engine is written in Rust and connected to Dart through [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge). You need a working [Rust toolchain](https://rustup.rs). The first build is usually slow because it compiles the full native dependency tree.
49
50 ### Initialize
51
52 Call this once at startup before creating any `OndeChatEngine`:
53
54 ```dart
55 import 'package:flutter/widgets.dart';
56 import 'package:onde_inference/onde_inference.dart';
57
58 Future<void> main() async {
59 WidgetsFlutterBinding.ensureInitialized();
60 await OndeInference.init();
61 runApp(const MyApp());
62 }
63 ```
64
65 ### Load a model
66
67 ```dart
68 final engine = OndeChatEngine();
69
70 final elapsed = await engine.loadDefaultModel(
71 systemPrompt: 'You are a helpful assistant.',
72 );
73
74 print('Model loaded in ${elapsed.toStringAsFixed(1)} s');
75 ```
76
77 For production, you can load the model assigned to your Onde app from the dashboard:
78
79 ```dart
80 final assignedElapsed = await engine.loadAssignedModel(
81 appId: 'your-app-id',
82 appSecret: 'your-app-secret',
83 systemPrompt: 'You are a helpful assistant.',
84 );
85
86 print('Assigned model loaded in ${assignedElapsed.toStringAsFixed(1)} s');
87 ```
88
89 ### Chat
90
91 ```dart
92 final result = await engine.sendMessage(
93 message: 'What is Rust ownership?',
94 );
95
96 print(result.text);
97 print(result.durationDisplay);
98 print(result.toolCalls);
99 ```
100
101 ### Stream
102
103 ```dart
104 final buffer = StringBuffer();
105
106 await for (final chunk in engine.streamMessage(message: 'Tell me a short story.')) {
107 buffer.write(chunk.delta);
108 if (chunk.done) break;
109 }
110
111 print(buffer.toString());
112 ```
113
114 ### Status and history
115
116 ```dart
117 final info = await engine.info();
118 print(info.status);
119 print(info.modelName);
120 print(info.approxMemory);
121 print(info.historyLength);
122
123 final history = await engine.history();
124 for (final msg in history) {
125 print('${msg.role}: ${msg.content}');
126 }
127
128 final removed = await engine.clearHistoryCount();
129 print('Cleared $removed messages.');
130 ```
131
132 ### One-shot generation
133
134 This runs inference without modifying conversation history.
135
136 ```dart
137 final result = await engine.generate(
138 messages: [
139 ChatMessage(role: ChatRole.system, content: 'Output valid JSON only.'),
140 ChatMessage(role: ChatRole.user, content: 'Name: Alice, Age: 30'),
141 ],
142 sampling: OndeInference.deterministicSamplingConfig(),
143 );
144
145 print(result.text);
146 ```
147
148 ### Unload
149
150 ```dart
151 await engine.unloadModel();
152 ```
153
154 ---
155
156 ## Model selection
157
158 ```dart
159 final config = OndeInference.defaultModelConfig();
160 final small = OndeInference.qwen2515bConfig();
161 final medium = OndeInference.qwen253bConfig();
162 final coder = OndeInference.qwen25Coder3bConfig();
163
164 await engine.loadGgufModel(
165 config: coder,
166 systemPrompt: 'You are an expert software engineer.',
167 );
168 ```
169
170 | Model | Size | Good for |
171 |-------|------|----------|
172 | Qwen 2.5 1.5B Instruct Q4_K_M | ~941 MB | iOS, tvOS, Android |
173 | Qwen 2.5 3B Instruct Q4_K_M | ~1.93 GB | macOS, Linux, Windows |
174 | Qwen 2.5 Coder 1.5B Instruct Q4_K_M | ~941 MB | Code on mobile |
175 | Qwen 2.5 Coder 3B Instruct Q4_K_M | ~1.93 GB | Code on desktop |
176
177 ---
178
179 ## Sampling
180
181 All sampling fields are optional. `null` means "use the engine default".
182
183 ```dart
184 final sampling = SamplingConfig(
185 temperature: 0.7,
186 topP: 0.95,
187 topK: BigInt.from(40),
188 maxTokens: BigInt.from(256),
189 );
190
191 await engine.setSampling(sampling: sampling);
192 ```
193
194 Presets:
195
196 ```dart
197 OndeInference.defaultSamplingConfig();
198 OndeInference.deterministicSamplingConfig();
199 OndeInference.mobileSamplingConfig();
200 ```
201
202 ---
203
204 ## Error handling
205
206 The generated bridge throws `OndeError` values directly:
207
208 ```dart
209 try {
210 await engine.loadDefaultModel();
211 } on OndeError catch (e) {
212 debugPrint('Inference error: $e');
213 }
214 ```
215
216 Common causes include calling `sendMessage` before loading a model, having no internet on first run while the model still needs to download, or running out of memory.
217
218 ---
219
220 ## Sandboxed app setup (iOS / macOS / Android)
221
222 On iOS, macOS, and Android, configure the Hugging Face cache directory before loading a model. On Apple platforms, Onde first tries the shared App Group container (`group.com.ondeinference.apps`) and falls back to your provided directory.
223
224 ```dart
225 import 'dart:io' show Platform;
226
227 import 'package:flutter/widgets.dart';
228 import 'package:onde_inference/onde_inference.dart';
229 import 'package:path_provider/path_provider.dart';
230
231 Future<void> main() async {
232 WidgetsFlutterBinding.ensureInitialized();
233 await OndeInference.init();
234
235 String? fallbackDir;
236 if (Platform.isIOS || Platform.isAndroid) {
237 final dir = await getApplicationSupportDirectory();
238 fallbackDir = dir.path;
239 }
240
241 await OndeInference.setupCacheDir(fallbackDir: fallbackDir);
242 runApp(const MyApp());
243 }
244 ```
245
246 ---
247
248 ## Example app
249
250 A full Flutter example lives in `example/`. It demonstrates:
251
252 - `OndeChatEngine()` lifecycle
253 - assigned-model loading with dashboard credentials
254 - streaming chat UI
255 - sampling preset switching
256 - cache directory setup for sandboxed platforms
257
258 Run it locally from `sdk/dart/example/`.
259
260 ## Contributing
261
262 The source lives at [github.com/ondeinference/onde](https://github.com/ondeinference/onde):
263
264 - Rust core: `src/`
265 - Dart bridge crate: `sdk/dart/rust/`
266 - Dart library: `sdk/dart/lib/`
267 - Example app: `sdk/dart/example/`
268
269 Open an issue before sending large PRs.
270
271 ## License
272
273 Onde is dual-licensed under **MIT** and **Apache 2.0**. You can use either one.
274
275 - [MIT License](https://github.com/ondeinference/onde/blob/main/LICENSE-MIT)
276 - [Apache License 2.0](https://github.com/ondeinference/onde/blob/main/LICENSE-APACHE)
277
278 © 2026 [Splitfire AB](https://splitfire.se)
279
280 ---
281
282 ## Copyright
283
284 © 2026 [Onde Inference](https://ondeinference.com) (Splitfire AB).