com.harryaskham.talos · Kotlin · C++
Talos
An OpenAI-compatible model server that runs on an Android phone. It downloads models from Hugging Face, loads each one when a request first names it, keeps it while it is in use and unloads it after an idle timeout. With nothing loaded, it costs one foreground service with a listening socket: no inference process, timer, wake lock or GPU context.
It serves chat and completions, embeddings and reranking, speech recognition and synthesis, spoken replies, music and images, on port 8484 by default.
Processes and engines
| Engine | Models | Runs on |
|---|---|---|
| llama.cpp (a pinned fork) | GGUF language models, including 1- and 2-bit ternary packs | CPU, Vulkan, OpenCL with Adreno kernels, Hexagon NPU |
| LiteRT-LM 0.17.1 | .litertlm bundles | GPU, CPU, NPU |
| audio.cpp 0.9.1 | speech, spoken replies, music | CPU only |
| stable-diffusion.cpp | small image models | CPU, Adreno OpenCL |
Measured on an Adreno 830
| Model | Backend | Prefill | Decode |
|---|---|---|---|
| Gemma 4 E2B Q4_0 | OpenCL | 143.5 tok/s | 12.3 tok/s |
| Gemma 4 E2B Q4_0 | Vulkan | 5.9 tok/s | 6.0 tok/s |
| Bonsai 4B PQ2_0 | Vulkan | 5.0 tok/s | 12.1 tok/s |
| Bonsai 4B PQ2_0 | OpenCL | 0.4 tok/s | 0.3 tok/s |
| Bonsai 2 27B PTQ1_0 | Vulkan | 0.8 tok/s | 2.8 tok/s |
These were measured on a Snapdragon 8 Elite phone over SSH through a proot linker, so they are lower bounds rather than app benchmarks. They decide Talos's automatic placement: OpenCL for standard quantisations, Vulkan for ternary packs, with Vulkan's fused kernels turned off because Adreno's driver crashes in them.
They also say what a phone is good for. An agent's system prompt and tool schemas are often over 10,000 tokens, and at these prefill rates a ternary model on Vulkan takes many minutes to read them, and the 27B model hours. For agents, a LiteRT-LM bundle on the GPU or a small standard GGUF on OpenCL is the practical choice, with the client's context window set to the model's real one.
For speech, Qwen3-ASR 0.6B transcribed 11 seconds of speech in 8.1 seconds with its encoder on the GPU. Qwen3-TTS 1.7B produced 1.8 to 3.0 frames a second (12.5 frames make one second of audio) with its audio stages on the CPU, against 0.04 with everything on OpenCL, so speech models keep those stages on the CPU by default.
Beyond chat
- Speech: transcription, synthesis with saved voices for cloning (importable from a Git repository, Hugging Face or a folder), and native spoken replies.
- Music: short stereo clips from a prompt, which can continue earlier audio.
- Images: generation and masked edits with small models.
- Typed decisions:
/v1/systemoneanswers yes/no, choice and score questions about a piece of text with probabilities. These are next-token scores, not calibrated confidences, and LiteRT-LM is not supported because it does not expose token probabilities. - Logs: two minutes of CPU, GPU and memory per Talos process, and request metadata without prompts, outputs or audio. Sampling only runs while the tab is on screen, and a value Android will not reveal shows as "—" rather than an estimate.

# after downloading the Gemma 4 E2B preset in the Models tab
curl http://127.0.0.1:8484/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model": "gemma-4-e2b-gpu", "stream": true,
"messages": [{"role": "user", "content": "hi"}]}'
The listener can bind to 127.0.0.1, all interfaces or one interface. API keys are optional and separate from the address; the admin routes need an admin key from other devices unless explicitly opened.
Limits
- Large models are slow to read long prompts on a phone; see the measurements above.
- A GGUF file is a container, not a promise: model families without an engine adapter are labelled unsupported.
- Rerankers run on the CPU unless chosen otherwise, because Adreno's OpenCL and Vulkan failed on ranking heads.
- GPU load and memory are only shown where Android lets an app read them.