executorch_flutter 0.7.1
executorch_flutter: ^0.7.1 copied to clipboard
ExecuTorch on-device ML inference for Flutter using dart:ffi — vision models plus experimental streaming LLM. Android, iOS, macOS, Linux, Windows, Web.
ExecuTorch Flutter #
A Flutter plugin for on-device ML inference using PyTorch ExecuTorch, supporting Android, iOS, macOS, Windows, Linux, and Web.
pub.dev | Live Demo | Example App
Table of Contents #
- Overview
- Features
- Installation
- Quick Start
- Platform Support
- API Reference
- On-device LLM (Gemma 4)
- Build Configuration
- Advanced Usage
- Web Platform
- Example Application
- Model Export
- Troubleshooting
- Vulkan Backend (Experimental)
- Contributing
- License
Overview #
ExecuTorch Flutter provides a simple Dart API for loading and running ExecuTorch models (.pte files) in your Flutter applications. The package handles all native platform integration, providing you with a straightforward interface for on-device machine learning inference.
Writing a Dart server or command-line tool instead — no Flutter SDK involved?
Use executorch_dart directly;
this package is a thin wrapper over it that adds Flutter asset-bundle loading
and Web support.
Features #
- Cross-Platform: Android (API 23+), iOS (13.0+), macOS (11.0+), Windows, Linux, and Web
- Type-Safe API: dart:ffi bindings with type-safe Dart wrapper classes
- Async Operations: Non-blocking model loading and inference
- Multiple Models: Support for concurrent model instances
- Error Handling: Structured exception handling with clear error messages
- Backend Support: XNNPACK (all platforms), CoreML (Apple), Metal + MLX (macOS), Vulkan (opt-in)
- 13 Tensor Dtypes: float32/64, float16, bfloat16, int8/16/32/64, uint8/16/32/64, bool
- On-device LLM (experimental): streaming text generation with Gemma 4 (XNNPACK CPU + MLX Apple-GPU) — see docs/LLM.md
- Live Camera: Real-time inference with camera stream support
Library Size by Backend #
📊 Download Release Size Comparison (SVG) | Download Debug Size Comparison (SVG) | JSON Report
Installation #
Requirements: Flutter 3.38+ (first version with native assets hooks)
dependencies:
executorch_flutter: ^0.7.0
Quick Start #
1. Load a Model #
import 'package:executorch_flutter/executorch_flutter.dart';
// Load from Flutter assets (recommended - works on all platforms)
final model = await loadModelFromAsset('assets/models/model.pte');
2. Run Inference #
final inputTensor = TensorData(
shape: [1, 3, 224, 224],
dataType: TensorType.float32,
data: yourImageBytes,
);
final outputs = await model.forward([inputTensor]);
for (var output in outputs) {
print('Shape: ${output.shape}, Type: ${output.dataType}');
}
3. Clean Up #
await model.dispose();
Model Loading Options #
| Method | Platforms | Use Case |
|---|---|---|
loadModelFromAsset(path) |
All (including web) | Bundled assets |
ExecuTorchModel.loadFromBytes(bytes) |
All (including web) | Downloaded/cached models |
ExecuTorchModel.load(filePath) |
Native only | External file paths |
loadModelFromAsset is a top-level function, not a static method on
ExecuTorchModel — this is what changed in 0.6.1 (see
CHANGELOG.md).
// From bytes
final byteData = await rootBundle.load('assets/models/model.pte');
final model = await ExecuTorchModel.loadFromBytes(byteData.buffer.asUint8List());
// From file path (native platforms only)
final model = await ExecuTorchModel.load('/path/to/model.pte');
Platform Support #
| Platform | Min Version | Architectures | Backends |
|---|---|---|---|
| Android | API 23 | arm64-v8a, armeabi-v7a, x86_64, x86 | XNNPACK, Vulkan* |
| iOS | 13.0+ | arm64, x86_64+arm64 (sim) | XNNPACK, CoreML, Vulkan* |
| macOS | 11.0+ | arm64, x86_64 | XNNPACK, CoreML, Metal, MLX*, Vulkan* |
| Windows | 10+ | x64 | XNNPACK, Vulkan* |
| Linux | Ubuntu 20.04+ | x64, arm64 | XNNPACK, Vulkan* |
| Web | Modern browsers | WebAssembly | XNNPACK (Wasm SIMD) |
*Opt-in. Vulkan is experimental — see Vulkan Backend. MLX is the Apple-Silicon GPU runtime used by the LLM path (macOS 14+, arm64) — see docs/LLM.md.
Platform Configuration #
If you encounter deployment target errors, update your project settings:
iOS Deployment Target (iOS 13.0+)
- Open
ios/Runner.xcworkspacein Xcode - Select Runner target → Build Settings
- Search "iOS Deployment Target" → Set to 13.0
macOS Deployment Target (macOS 11.0+)
- Open
macos/Runner.xcworkspacein Xcode - Select Runner target → Build Settings
- Search "macOS Deployment Target" → Set to 11.0
After updating, run:
flutter clean && flutter pub get && flutter build <platform>
API Reference #
ExecuTorchModel #
// Top-level loader — Flutter asset bundle, all platforms including web
Future<ExecuTorchModel> loadModelFromAsset(String assetPath)
// ExecuTorchModel static factories
static Future<ExecuTorchModel> loadFromBytes(Uint8List modelBytes)
static Future<ExecuTorchModel> load(String filePath) // Native only
// Inference
Future<List<TensorData>> forward(List<TensorData> inputs)
// Lifecycle
Future<void> dispose()
bool get isDisposed
String get modelId
ExecutorchManager.instance also has loadModelFromAssets(assetPath) — an
extension method that does the same thing but caches the model in the
manager, like its loadModel/loadModelFromBytes counterparts.
ExecuTorchLLM (experimental) #
On-device generative text — Google Gemma 4 E2B — with token-by-token streaming, separate from the tensor API. Loaded from file paths (weights are 1+ GB) and driven by a stateful decode loop + tokenizer + KV cache. Backends: XNNPACK (CPU, all platforms) and MLX (Apple-Silicon GPU, macOS arm64).
// Load (file paths; mlxMetallibPath is MLX-only)
static Future<ExecuTorchLLM> load({
required String modelPath,
required String tokenizerPath,
String? dataPath,
String? mlxMetallibPath,
})
// Stream tokens as they decode
Stream<String> generate(String prompt, {GenConfig config})
// Control / lifecycle
void stop(); // cooperative cancel mid-generation
void reset(); // clear KV cache / start a new conversation
Future<void> dispose(); // release native resources
// GenConfig — temperature-only sampling (no top-p/top-k)
const GenConfig({int maxNewTokens, int seqLen, double temperature, bool echo, bool ignoreEos});
final llm = await ExecuTorchLLM.load(
modelPath: '/path/gemma-4-E2B-it_xnnpack.pte',
tokenizerPath: '/path/gemma-4-E2B-it_tokenizer.json',
);
// Gemma 4 needs its turn markers around the message:
final prompt = '<bos><|turn>user\nExplain Flutter in one line.<turn|>\n<|turn>model\n';
await for (final piece in llm.generate(prompt,
config: const GenConfig(maxNewTokens: 512, temperature: 0))) {
stdout.write(piece);
}
await llm.dispose();
Enable it in pubspec.yaml (hooks.user_defines.executorch_dart):
llm: true
backends: [xnnpack, mlx] # mlx is auto-dropped off macOS-arm64
📖 Full guide: docs/LLM.md — model export (the Gemma 4 scripts), the chat template, the MLX
mlx.metallibshipping step, stopping, platform support, and troubleshooting. A complete streaming chat screen is inexample/lib/screens/llm_chat_screen.dart.
TensorData #
final tensor = TensorData(
shape: [1, 3, 224, 224], // Dimensions
dataType: TensorType.float32, // See dtype table below
data: Uint8List(...), // Raw bytes, little-endian
name: 'input_0', // Optional
);
Supported dtypes — all 13 map 1:1 to ExecuTorch's native types:
| Dtype | Bytes | Dtype | Bytes | Dtype | Bytes |
|---|---|---|---|---|---|
float32 |
4 | int8 |
1 | uint32 |
4 |
float64 |
8 | int16 |
2 | uint64 |
8 |
float16 |
2 | int32 |
4 | bool_ |
1 |
bfloat16 |
2 | int64 |
8 | ||
uint8 |
1 | uint16 |
2 |
Building data by hand is error-prone, so use ExecutorchManager to encode
numeric lists — it handles float16/bfloat16 conversion (round-to-nearest-even)
and endianness:
final tensor = ExecutorchManager.instance.createTensorData(
shape: [1, 4],
dataType: TensorType.float16,
data: [1.0, 2.5, -3.25, 0.5],
);
BackendQuery #
Query available backends at runtime:
// Check specific backend
if (BackendQuery.isAvailable(Backend.coreml)) {
model = await loadModelFromAsset('assets/model_coreml.pte');
} else {
model = await loadModelFromAsset('assets/model_xnnpack.pte');
}
// List all available backends
final backends = BackendQuery.available;
print('Available: ${backends.map((b) => b.displayName).join(", ")}');
| Backend | Display Name | Platforms |
|---|---|---|
Backend.xnnpack |
XNNPACK | All |
Backend.coreml |
CoreML | iOS, macOS |
Backend.metal |
Metal | macOS |
Backend.vulkan |
Vulkan | Android, iOS, macOS, Windows, Linux |
Backend.qnn |
Qualcomm QNN | Android |
Backend.mps |
(deprecated — use metal) |
macOS |
Tokenizer #
Text to token ids and back, independent of any model. Encoder models —
embeddings, classification, retrieval — take token ids as an input tensor and
produce a vector in a single forward(), so they have no generation loop to
borrow a tokenizer from.
final tokenizer = await Tokenizer.load('/path/to/tokenizer.json');
final ids = tokenizer.encode('some text');
final input = TensorData(
shape: [1, ids.length],
dataType: TensorType.int64,
data: Int64List.fromList(ids).buffer.asUint8List(),
);
final outputs = await model.forward([input]);
print(tokenizer.decode(ids));
tokenizer.dispose();
Supported: HuggingFace tokenizer.json built on BPE (GPT-2, Llama,
Gemma, Qwen, Mistral), SentencePiece .model, TikToken, llama2.c.
Not supported: WordPiece / BERT-family — that rules out BERT, DistilBERT, MiniLM and most sentence-transformers models. The error names the specific reason rather than reporting a generic parse failure.
Native platforms only. It is dart:ffi throughout with no WebAssembly
equivalent, so Web code referencing Tokenizer fails to compile rather than
failing at runtime. For generative models use ExecuTorchLLM, which owns its
tokenizer internally.
Exception Hierarchy #
ExecuTorchException (base)
├── ExecuTorchModelException // Model loading/lifecycle
├── ExecuTorchInferenceException // Inference execution
├── ExecuTorchValidationException // Tensor validation
├── ExecuTorchMemoryException // Memory/resources
├── ExecuTorchIOException // File I/O
└── ExecuTorchPlatformException // Platform communication
Build Configuration #
Configure the native build in your app's pubspec.yaml:
hooks:
user_defines:
executorch_dart:
debug: false # Enable debug logging
build_mode: "prebuilt" # "prebuilt", "local", or "source"
# prebuilt_version: "1.4.0.6" # Optional: pin specific native version
# For source mode: build from local ExecuTorch checkout
# build_mode: "source"
# executorch_source: "/path/to/executorch"
# For local mode: point at pre-compiled libraries
# local_lib_dir: "/path/to/compiled/libs"
backends:
- xnnpack
- coreml
- metal
The key under user_defines: is the package that owns the native build —
executorch_dart, even though you depend on executorch_flutter. This
package used to own the build directly and read executorch_flutter: here;
see the 0.6.1 entry in CHANGELOG.md if you're migrating.
Options #
| Option | Default | Description |
|---|---|---|
debug |
false |
Debug logging + debug binaries |
build_mode |
"prebuilt" |
"prebuilt" (fast), "local" (pre-compiled), or "source" (from source) |
prebuilt_version |
Current | Prebuilt release version |
executorch_source |
- | Path to local ExecuTorch checkout (source mode) |
local_lib_dir |
- | Path to pre-compiled libraries (local mode) |
backends |
Platform-specific | Backends to enable |
Default Backends by Platform #
| Platform | Defaults |
|---|---|
| Android | xnnpack |
| iOS | xnnpack, coreml |
| macOS | xnnpack, coreml, metal |
| Windows/Linux | xnnpack |
Listing backends: replaces the defaults entirely — include every backend you
want. vulkan, mlx, and qnn are never on by default. A legacy mps entry
is accepted and treated as metal on macOS.
Environment Variables #
| Variable | Description |
|---|---|
EXECUTORCH_BUILD_MODE |
Override build mode (prebuilt, local, source) |
EXECUTORCH_SOURCE_DIR |
Path to local ExecuTorch checkout (source mode) |
EXECUTORCH_INSTALL_DIR |
Path to pre-compiled libraries (local mode) |
EXECUTORCH_CACHE_DIR |
Custom cache directory for source builds |
EXECUTORCH_DISABLE_DOWNLOAD |
Skip prebuilt download |
Advanced Usage #
Preprocessing Strategies #
The example app demonstrates three preprocessing approaches:
| Strategy | Performance | Platforms | Dependencies |
|---|---|---|---|
| GPU Shader | ~75ms (web), comparable to OpenCV (native) | All | None |
| OpenCV | Very fast | Native only | opencv_dart |
| CPU (image lib) | ~560ms (web), slower | All | image |
GPU Preprocessing Tutorial - Step-by-step guide with GLSL shader examples.
Web Platform #
Web runs via WebAssembly with XNNPACK backend.
Performance #
| Metric | Native | Web (Wasm) |
|---|---|---|
| YOLO11n Inference | ~50-100ms | ~622ms |
| Total E2E | ~150-200ms | ~855ms |
When to use Web:
- Demos and prototyping
- Interactive inference (sub-second)
- No app install required
Not recommended for:
- Real-time camera inference
- High-throughput batch processing
Setup #
-
Run setup script:
dart run executorch_flutter:setup_web -
Add to
web/index.html:<head> <script src="js/executorch_wrapper.js"></script> </head> -
Use XNNPACK models (same as native).
Serving models on web #
Host your models on an origin you control, or bundle them as Flutter assets.
On native you can download a .pte from anywhere. On web the browser enforces
CORS, and a cross-origin fetch only succeeds if the server sends
Access-Control-Allow-Origin. GitHub release assets do not send it, so
fetching a model straight from a GitHub release fails in every browser:
Access to fetch at 'https://github.com/.../releases/download/...pte'
blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
Three approaches that work:
- Bundle the model as an asset and load it with
loadModelFromAsset— the simplest option, and the right one when the model ships with the app. - Serve it from your own origin, same host as the app, so CORS never applies.
- Use a CDN or object store that sends
Access-Control-Allow-Originif the model must be fetched cross-origin.
Remember that label files and any other side-car assets go through the same check — it is easy to fix the model download and still fail on labels.
This repo's own web demo takes the second approach: its deploy workflow copies the models into the published site so they are served from the app's origin.
Example Application #
The example/ directory includes:
- Unified Model Playground - Multiple model types in one interface
- MobileNet V3 - Image classification (1000 ImageNet classes)
- YOLO - Object detection (v5, v8, v11)
- Camera Mode - Real-time inference
- Settings - Thresholds, preprocessing, performance overlay
cd example
flutter run -d macos # or ios, android, windows, linux, chrome
Converting PyTorch Models to ExecuTorch #
Convert your PyTorch models to .pte format:
Official ExecuTorch Export Guide
Example app models are hosted at executorch_flutter_models and downloaded automatically.
To export manually:
cd models/python
python3 main.py
LLM (Gemma 4) models are exported with dedicated scripts (they need a tokenizer + quantization recipe, not the tensor export path):
python models/python/export_gemma4_xnnpack.py # CPU model (all platforms)
python models/python/export_gemma4_mlx.py # Apple-GPU model (macOS)
See docs/LLM.md for the full export recipe, the required
tokenizer.json / mlx.metallib, and how to load them with ExecuTorchLLM.
Troubleshooting #
Model loading fails
- Verify asset is listed in
pubspec.yaml - Check model bytes:
modelBytes.lengthInBytes > 0 - Re-export with correct ExecuTorch version
Inference returns error
- Double-check the
shape:/dataType:you pass match what the model was exported with —ExecuTorchModeldoesn't expose shape introspection, so this has to come from the export script or the model's documentation - Ensure shapes match exactly (including batch dimension)
- Verify the dtype matches what the model was exported with — a
data size mismatcherror meansdata.length!=elementCount * dataType.sizeInBytes - Prefer
ExecutorchManager.instance.createTensorData(...)over packing bytes by hand, especially forfloat16/bfloat16
Edits to native C++ code have no effect
The default prebuilt build mode downloads an already-compiled library, so
local changes under native/ are ignored. Use build_mode: "source" with
executorch_source: pointing at a local ExecuTorch checkout. See
CONTRIBUTING.md.
Memory issues
- Always call
dispose()when done - Don't load too many models simultaneously
Experimental: Vulkan Backend #
Warning: Vulkan is experimental and opt-in.
Status #
| Platform | Status |
|---|---|
| Android | Works on most devices; see #26 for PowerVR GPU status |
| Windows/Linux | Generally functional |
| macOS/iOS | Works via MoltenVK (Vulkan-to-Metal translation) |
Enable Vulkan #
hooks:
user_defines:
executorch_dart:
backends:
- xnnpack
- vulkan
Vulkan Troubleshooting #
"uniform data allocation exceeded" on Android
This can occur when Vulkan tensor metadata exceeds the per-tensor uniform buffer limit. Fix submitted upstream: pytorch/executorch#17294.
Vulkan on PowerVR GPUs
Some PowerVR devices may produce incorrect Vulkan results due to texture dimension limits. Being tracked upstream: pytorch/executorch#17299. XNNPACK is recommended as a fallback.
Recommendations #
- Production: Use XNNPACK (stable everywhere)
- Apple platforms: Use CoreML (iOS/macOS) or Metal (macOS) instead of Vulkan
- Testing: Report issues with device info and logs
Contributing #
Contributions welcome! See CONTRIBUTING.md for guidelines.
Acknowledgments #
- opencv_dart - Referenced for understanding Flutter native assets build patterns and cross-platform FFI packaging
License #
MIT License - see LICENSE.
Support #
Built with love for the Flutter and PyTorch communities.