Chuyển đến nội dung chính

On-device AI in Flutter: what fits on a phone

The pitch is genuinely attractive: no API key, no per-token cost, no network round trip, and user data that never leaves the device. For a note-taking app, a keyboard, or anything handling health or financial records, that last point alone can decide the architecture.

Then you look at the numbers. A small quantised language model is a download of one to three gigabytes, needs most of that resident in RAM while it runs, and generates tokens at a rate that would embarrass a 2019 API. Both of those pictures are accurate. The question is which one applies to your feature.

What actually runs well on a phone

The tasks where a small local model is genuinely good are narrower than the demos suggest, and they cluster around one property: short input, short output, no world knowledge required.

TaskOn-device viabilityWhy
Text classification, intent detectionExcellentTiny models, often not LLMs at all
Embeddings for local semantic searchExcellentSmall model, runs once per document, no generation
Autocomplete, next-phrase suggestionGoodShort outputs, latency-sensitive, benefits from being local
Summarising a short noteWorkableA few hundred tokens in and out
Speech-to-textGoodMature dedicated models, well-optimised runtimes
Image classification, OCRExcellentNot language models; long-solved on device
Open-ended chatPoorUsers compare it to frontier models and it loses
Anything factualPoorA 2B-parameter model’s world knowledge is thin and confidently wrong
Long-document analysisPoorContext window and memory both run out
Code generationPoorQuality gap is largest exactly here

The dominant failure mode is not technical, it is expectation. If your UI looks like a chat assistant, users bring frontier-model expectations to a model a thousand times smaller. Frame the feature narrowly — “suggest tags”, “rewrite this sentence”, “search my notes” — and the same model reads as good.

The three numbers that decide feasibility

Before writing any code, check your feature against these:

  1. Download size. A 2B-parameter model at 4-bit quantisation lands in the low gigabytes. You cannot ship that inside the app bundle: both stores have limits well below it, and a 2GB install kills your conversion rate regardless. It must be an on-demand download, which means a UI for it, resume support, and a story for users who decline.
  2. Peak RAM. The model weights must be resident during inference, plus the KV cache, which grows with context length. On a mid-range Android device with 4GB total, this is the constraint that actually bites. Exceeding it does not degrade gracefully — the OS kills your app, and it does so on exactly the devices you tested on least.
  3. Tokens per second. Generation speed on mobile silicon is a small multiple of reading speed on a good device and below it on a bad one. Combined with a load time of several seconds for a cold model, this rules out anything that needs to feel instant unless you keep the model warm — which reintroduces the memory problem.

Measure all three on the worst device you intend to support, not on your development phone. The spread between a current flagship and a three-year-old mid-range Android is larger than any optimisation you will apply.

The runtime layer

Flutter has no built-in inference engine, so every approach routes through platform code:

  • Google AI Edge (LiteRT, and the MediaPipe LLM inference task) is the most direct path for Gemma-family models across Android and iOS.
  • The flutter_gemma package on pub.dev wraps that stack for Dart, and is the shortest route to a working prototype. Check its README for current platform support and API shape before designing around it — community plugins in this space move quickly.
  • Core ML on Apple platforms gives the best hardware utilisation on iOS, at the cost of an Apple-only code path and a model conversion step.
  • ONNX Runtime is the most portable option if you already have ONNX models and need desktop as well as mobile.
  • A method channel to llama.cpp gives you the widest model selection and the most control, and the most platform code to maintain. Worth it only if the packaged options do not support your model.

Whichever you pick, isolate it behind an interface from day one:

abstract interface class LocalInference {
  Future<bool> isAvailable();
  Future<void> load({void Function(double progress)? onProgress});
  Stream<String> generate(String prompt, {int maxTokens = 256});
  Future<void> unload();
}

Two implementations — the real one and a cloud-backed fallback — behind that interface is the design that survives. This space changes fast enough that the runtime you choose today is unlikely to be the one you ship in two years, and the interface is what keeps that from being a rewrite.

Threading: the part Flutter developers get wrong

Inference is a long-running CPU/GPU operation. Run it on the platform main thread and you freeze the UI; the jank is not subtle.

  • Native inference must run on a background thread on the native side. Dart isolates do not help here — the work is not in Dart.
  • Stream results back over the channel token by token rather than returning a completed string, for the same UX reasons streaming matters against an API.
  • Any Dart-side pre- and post-processing that is measurably expensive — tokenising, parsing, formatting a long result — belongs in an isolate.
  • Handle the app going to the background: on iOS, expect inference to be suspended, and design for a generation that stops halfway and never resumes.

Battery and thermals are a real constraint

Sustained inference is one of the heaviest things an app can do to a phone. Continuous generation warms the device noticeably and drains the battery at a rate users attribute to your app specifically — correctly.

More importantly, sustained load triggers thermal throttling, and your benchmark numbers will not hold after two minutes of use. Measure a sustained workload, not a single cold run.

Practical mitigations: never generate speculatively, cap output length hard, unload the model after a period of inactivity, and consider deferring bulk work — indexing a whole note archive, generating embeddings for every document — to when the device is charging and idle.

The hybrid design is usually the right answer

Local-only and cloud-only are both worse than the obvious middle:

  • Local for the small, frequent, private, latency-sensitive things — classification, embeddings, autocomplete, search over the user’s own data.
  • Cloud for the hard ones — long documents, real reasoning, anything requiring current world knowledge.
  • Local as the offline fallback, degraded but functional, with the UI saying plainly that it is running in offline mode.

This is the same routing decision as choosing between a small and a large API model, with two extra terms in the equation: the local path has zero marginal cost and perfect privacy, and it is available when the network is not. Weigh those against a quality gap that is much larger than the one between two cloud models.

Whatever you build, tell the user which mode produced the answer. “Generated on your device” is a feature worth advertising when it is true, and hiding the distinction turns a privacy advantage into a trust problem the first time someone notices a network request.

FAQ

Can I ship the model inside the app bundle?

Only for genuinely small models — classifiers, embedding models, speech models. Language models must be downloaded on demand.

Does a local model still need updating?

Yes, and you need a versioning and migration story for it. Plan for the download to happen more than once.

Is on-device output automatically private?

The inference is. Check that your app is not logging prompts or outputs to analytics, which quietly undoes the guarantee.

What about Flutter web and desktop?

Desktop is easier — more RAM, mains power. Web is impractical for language models today; the download alone is disqualifying.

How do I test this in CI?

You cannot meaningfully test inference quality on emulators. Test the interface with a fake implementation, and run real-model checks on a small physical device farm.


Runtime capabilities, package APIs and platform limits described here come from the documentation linked above and change quickly — verify current details, especially for community packages, before committing to a design. The task-viability table, the three feasibility numbers, the threading guidance and the hybrid recommendation are my own judgement from building on-device features in Flutter; measure against your own target devices.


Originally published on FlutterCook. Read the latest version there — that copy is the one kept up to date.

Nhận xét

Bài đăng phổ biến từ blog này

5 concepts every Flutter dev should know

  Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

Announcing Flutter 2

  Phụ lục: Flutter on the web Flutter 2 on desktops, foldables, and embedded devices The growing Flutter ecosystem Dart: The secret sauce behind Flutter Flutter 2: Available now Hôm nay, chúng tôi sẽ công bố Flutter 2: một bản nâng cấp lớn cho Flutter cho phép các nhà phát triển tạo các ứng dụng đẹp, nhanh chóng và di động cho bất kỳ nền tảng nào. Với Flutter 2, bạn có thể sử dụng cùng một cơ sở mã để gửi các ứng dụng gốc cho năm hệ điều hành: IOS, Android, Windows, macOS và Linux; cũng như trải nghiệm web nhắm mục tiêu các trình duyệt như Chrome, Firefox, Safari hoặc Edge. Flutter thậm chí có thể được nhúng vào ô tô, TV và thiết bị gia dụng thông minh, mang đến trải nghiệm di động và lan tỏa nhất cho thế giới điện toán xung quanh. Mục tiêu của chúng tôi là thay đổi cơ bản cách các nhà phát triển nghĩ về việc xây dựng ứng dụng, bắt đầu không phải với nền tảng bạn đang nhắm mục tiêu mà là với trải nghiệm bạn muốn tạo. Flutter cho phép bạn tạo ra những trải nghiệm tuyệt đẹp trong đó ...