MagicWX: an Android 17 prototype that keeps LLM chat and SD1.5 image generation on the device
Android 17 local LLM prototype with Jetpack Compose and ONNX Runtime for offline AI inference experiments.
At a glance
- What is it?
- MagicWX is a Kotlin and Jetpack Compose app that runs text models through ONNX Runtime, MediaPipe or LiteRT and generates images through a native MNN backend on localhost. Its own README treats most of its model catalog as a research queue rather than a support list, and that distinction is the most useful thing about it.
- Who is it for?
- Adopt MagicWX if you want a working reference for runtime adapter dispatch on Android and you accept that the visible model list is short by design: the built-in experience engine, RWKV-7 World 0.4B, Qwen3 0.6B, Qwen2.5 0.5B, SmolLM2 360M and TinyLlama 1.1B Chat LiteRT.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap MagicWX is trying to fill on Android 17
Most Android AI demos stop at a single model and a single modality. MagicWX is built around the opposite assumption: that an app should hold several runtimes behind one interface and decide at load time which one a given model needs. The README frames the project as fully offline text chat combined with on-device Stable Diffusion image generation, and the target is API 37, which is Android 17. That target is unusual enough to be a constraint in itself, since it puts the project ahead of the platform most devices run today.
The intended audience is narrow. This is for engineers who want to see how a Kotlin app wires ONNX Runtime, MediaPipe/LiteRT and a native image backend into one Compose UI, and who are willing to read the repository's own validation rules before trusting a model name. It is not aimed at someone who wants to download an APK and chat with a large model.
Adapter dispatch, load gates and the localhost:18081 image process
The repository layout shows the mechanism. `ModelRuntimeAdapter.kt` holds runtime adapter dispatch and load gates, and `MediaPipeLlmAdapter.kt` handles the MediaPipe/LiteRT `.task` text runtime. The README names four adapters: `BUILTIN_TEXT`, `ONNX_TEXT_GENERATION`, `LITERT_LM` (MediaPipe), and an isolated native image backend. Text generation goes through a shared `TextGenerationEngine.kt` interface, with `RWKVModel.kt` as the ONNX Runtime inference wrapper and separate tokenizer implementations (`RWKVTokenizer.kt` for the RWKV vocabulary, `HFTokenizer.kt` described as an experimental `tokenizer.json` reader).
The image path is architecturally separate. According to the README, the native backend lives in `libmagicwx_image_backend.so` and runs as a foreground-service-managed process on localhost:18081 with SSE streaming. That choice keeps a large native diffusion pipeline out of the app process, which matters on Android where a long CPU-bound job can otherwise take the UI down with it. The trade-off is a second process to start, supervise and shut down, plus a local socket that exists for as long as the service does.
Downloads follow the same pattern of putting work outside the UI thread. `ModelDownloadService.kt` is a foreground service with a persistent progress notification, and `ModelDownloadEvents.kt` carries in-process progress events to `MainViewModel.kt`, which holds the MVVM state. The README states that background downloads are user-started, not automatic.
What the registered model table actually promises
The README is explicit that the app exposes only models that passed device validation, and it separates registered candidates from verified support. The visible list is short: the built-in experience model, RWKV-7 World 0.4B, Qwen3 0.6B, Qwen2.5 0.5B, SmolLM2 360M, and TinyLlama 1.1B Chat LiteRT. The built-in entry is described as a deterministic local experience engine, not a bundled large model weight, so first-run chat works without a download but is not evidence that a real model runs.
Everything else is hidden, and the README gives reasons rather than silence. Qwen2 0.5B is hidden for garbled output. SmolLM2 135M is hidden for abnormal output. SmolLM2 135M MHA is hidden for an ONNX Runtime shape mismatch. MobileLLM 125M produced webpage fragments. Gemma 3 270M produced repeated fragments. That level of disclosure is rare in a prototype README and it is the most credible part of the document.
The validation bar itself is stated: a complete package manifest, required assets, successful load, a fixed-input dry-run, and device verification evidence. The README tells readers not to describe a model as supported until all of that exists. Treat the candidate pool table as a queue, because that is how the project labels it.
Getting it running: builds, downloads and the image workspace
The material does not include a build command, a Gradle task, or an install snippet, so there is no honest way to write a step-by-step setup here. What can be traced is the shape of the workflow. The package is `com.qihao.open.rwkv`, the UI is Jetpack Compose with Material3 (`ui/theme/Theme.kt`), and the app is Kotlin. Model files arrive through the foreground download service rather than being bundled, apart from the built-in experience engine.
For image generation, the README describes a complete workspace rather than a single button: txt2img, img2img and inpaint, mask painting, cropping, album import, a parameter panel, an automatic result page, and history persisted in Room. The documented CPU pipeline is SD1.5 with a UNet in fp16, built with `-O3`, and the README reports roughly 10 to 14 seconds per 512x512 step on a Samsung SM-A566E. That figure is the project's own device measurement, not an independent one.
If you clone this to evaluate it, the first thing to check is whether the native backend starts and binds on localhost:18081 on your hardware, because every image feature depends on that process being alive.
Where MagicWX is the wrong tool
The README answers this more directly than most projects would. The NPU and QNN pipelines (SD1.5 NPU, SDXL, Anima, upscaler) are registered but will only open after QNN runtime integration, which means the fast path is not available yet and everything runs on CPU. The multimodal scope, ASR, VAD, TTS, image understanding and VLM, is listed as separate roadmap items, so there is no speech or vision input today.
The target API level is the second problem. Building against Android 17 narrows the install base considerably, and a project that tracks a platform release this closely will keep absorbing churn from that platform. The candidate table also shows how fragile the ONNX text path is in practice: several small models failed on shape mismatches or degenerate output, which suggests that adding a new ONNX model is not a configuration change but a debugging session.
Finally, the built-in experience model can mislead a first-time evaluator. It answers without a download, so a quick trial says nothing about whether RWKV, Qwen or SmolLM2 will load on the same device.
How this differs from llama.cpp and GGUF-based Android apps
The obvious alternative for offline text on Android is a llama.cpp build running GGUF weights, and the README itself lists Llama 3.2 1B GGUF and a Llama 3.2 1B Uncensored GGUF as candidates that would need a native llama.cpp adapter. The difference in approach is not cosmetic. llama.cpp owns the whole stack: quantization format, tokenizer, sampler and inference loop live in one native library, so a new model is usually a new file. MagicWX instead dispatches across runtimes, which means ONNX models need a matching tokenizer and a graph that survives the fixed-input dry-run, while MediaPipe models need a `.task` bundle.
That dispatch layer is the reason MagicWX can mix an RWKV vocabulary tokenizer with a Hugging Face `tokenizer.json` reader and a LiteRT runtime in one app, and it is also the reason its failure modes are more varied. If your goal is to run one well-supported GGUF model reliably, llama.cpp is the shorter path. If your goal is to understand how several Android runtimes can sit behind one adapter interface, MagicWX is the more instructive codebase.
Licence, maintenance and what the release cadence implies
The project is MIT licensed, which permits commercial use and modification provided the copyright notice and permission notice are retained. That is a permissive arrangement, but it says nothing about the licences of the model weights the app downloads. The README notes that the Llama 3.2 1B ONNX candidate requires licence and runtime validation, and that a non-gated public source was checked for one uncensored GGUF candidate. Each model you enable carries its own terms, and the MIT licence on the app code does not transfer to them. This is not legal advice; check the terms of any weight you ship.
Maintenance cost is visible in the release history. v1.1.1, v1.1.3 and v1.2.0 landed within roughly five weeks of each other, with v1.2.0 introducing the NPU image work and v1.1.3 described as an adapter-gated prototype. The README's latest-release line says v1.1.5 while the release list stops at v1.2.0, a small inconsistency worth noting if you pin a version. The repository also splits documentation: human-facing product and design documents under `doc/`, and AI-facing change plans under `openspec/`. That second directory tells you the project expects contributors to work with generated change plans, which is a maintenance convention you either accept or do not.
Editorial conclusion
Adopt MagicWX if you want a working reference for runtime adapter dispatch on Android and you accept that the visible model list is short by design: the built-in experience engine, RWKV-7 World 0.4B, Qwen3 0.6B, Qwen2.5 0.5B, SmolLM2 360M and TinyLlama 1.1B Chat LiteRT. Do not adopt it if you need a stable chat product, because the README describes the multimodal scope (ASR, VAD, TTS, image understanding, VLM) as separate roadmap items and the NPU and QNN image pipelines as registered but not yet open. Before building on it, verify on your own hardware that the MNN image backend starts on localhost:18081 and that a 512x512 generation completes, then check the package manifest for whichever text model you intend to ship.
Community notes