Add an On-Device Model
CUST/OS can run LLMs, embedding models, speech-to-text, and vision detection entirely on-device with no network connectivity. This guide shows how to configure each kind.
How on-device models work
For subprocess backends such as llama.cpp and Whisper, a file:// provider starts a native server on the configured port. CUST/OS connects to http://127.0.0.1:<port> for inference. LiteRT-LM loads the model in process instead. Both approaches use model files stored on the device.
Add an LLM
1. Get the model
GGUF models are the standard format. Download one from HuggingFace or your model provider:
adb push qwen2.5-7b-instruct-q4_k_m.gguf /sdcard/atak/custos/models/
2. Add a provider in custos.yaml
providers:
- name: "qwen-on-device"
task: "chat"
protocol: "openai"
runtime: "llama.cpp"
url: "file:///sdcard/atak/custos/models/qwen2.5-7b-instruct-q4_k_m.gguf"
port: 8411
model: "qwen2.5-7b"
contextSize: 8192
threads: 4
chatTemplatePath: "/sdcard/atak/custos/models/qwen3-tool-calling.jinja"
requestTimeoutMs: 120000
taskPriority: 100
tier: "handheld"
3. Save and verify
Save custos.yaml and wait for the model to load. Check the Status panel; if the provider stays offline, tap its row to read the error.
Chat templates
The on-device LLM server uses jinja templates to render chat messages and tool-call instructions. Without a tool-calling template the model cannot emit tool calls in a parseable format. Templates ship in /sdcard/atak/custos/models/:
qwen3-tool-calling.jinja-- for Qwen modelsgemma3-tool-calling.jinja-- for Gemma models
If you bring a different model family, drop in its tool-calling template and point chatTemplatePath at it.
Add an embedding model
Embeddings use the same configuration pattern with task: embedding:
- name: "local-embed"
task: "embedding"
protocol: "openai"
runtime: "llama.cpp"
url: "file:///sdcard/atak/custos/models/nomic-embed-text-v1.5.Q8_0.gguf"
port: 8414
model: "nomic-embed-text-v1.5"
contextSize: 512
threads: 4
taskPriority: 10
Embeddings power skill selection (semantic matching against the operator's message) and the vector store used by the custos.rag skill. Without an embedding provider, skill selection falls back to keyword-only matching -- still functional, just less precise.
Add a speech-to-text model (Whisper)
- name: "on-device-whisper"
task: "transcription"
protocol: "openai"
runtime: "whisper.cpp"
url: "file:///sdcard/atak/custos/models/ggml-tiny.bin"
port: 8412
model: "whisper-tiny"
taskPriority: 100
tier: "handheld"
Whisper models use GGML format (.bin). The example uses ggml-tiny.bin (77 MB). Test transcription with the speech and background noise you expect in use. Once the provider is online, it can transcribe PTT recordings on the device.
Add a vision detection model (ONNX)
- name: "yolo-detection"
task: "detection"
protocol: "vision"
runtime: "onnxruntime"
url: "file:///sdcard/atak/custos/models/yolo11m.onnx"
port: 8413
model: "yolo11m"
threads: 4
confidence: 0.15
inputSize: 640
taskPriority: 1
tier: "handheld"
Vision detection is used by skills like custos.detect_buildings. Custom labels can be specified in the properties: map.
Add an LLM (LiteRT-LM, Gemma-4-family)
LiteRT-LM is Google's on-device inference runtime. It runs in process and retains the KV cache across reasoning iterations to reduce repeated prompt processing.
Model format
LiteRT-LM requires .litertlm files. A GGUF file cannot be used with this backend. Follow the starter kit's model download instructions for a compatible file.
Setup (verified: Gemma 4 E2B-it)
adb push gemma-4-E2B-it.litertlm /sdcard/atak/custos/models/
providers:
- name: "on-device-gemma4"
task: "chat"
protocol: "litert"
url: "file:///sdcard/atak/custos/models/gemma-4-E2B-it.litertlm"
model: "gemma-4-E2B-it"
tier: "handheld"
taskPriority: 1
contextSize: 16384
threads: 4
properties:
backend: "cpu" # "cpu" (default), "gpu", or "npu"
Model size
- Gemma 4 E2B-it — tested at
contextSize: 16384on the 16 GB reference device. Check available memory on other devices. - Gemma 4 E4B-it — exceeded memory at 16K context on the reference device. Start with
4096or8192and test memory use, or use E2B-it. See Tested models.
Backend selection
cpu(default) — FP32 inference. Use this for initial setup, then test output and speed on your device.gpu— OpenCL. The custom0.11.0-custosruntime has been tested for coordinate precision on Samsung S26 Ultra (Adreno 840). Validate output accuracy and performance before using it on other hardware.npu— Hexagon / dedicated NPU path. Not verified on any device yet; treat as experimental.
Model format summary
| Format | Runtime | Extension | Source |
|---|---|---|---|
| GGUF | llama.cpp | .gguf |
HuggingFace |
| LiteRT-LM | LiteRT | .litertlm |
HuggingFace litert-community/ |
| GGML | whisper.cpp | .bin |
whisper.cpp releases |
| ONNX | onnxruntime | .onnx |
ONNX model zoo, Ultralytics |
Tuning for handheld devices
Handheld Android devices are RAM-constrained. Recommendations:
- Try quantized models -- for GGUF, Q4_K_M is a starting point for testing memory use and tool-calling accuracy.
- Tune
threads-- start with the number of performance cores on the device, then compare throughput. - Adjust
contextSize-- a smaller context can reduce memory use, but must still fit your prompts, skill definitions, and conversation history. - Pre-warm before mission -- the first inference call after startup is slow because the model is loading into RAM. Trigger it during pre-mission checks.
- Set
taskPriorityfor your deployment -- use a lower number to prefer the local model. Add remote fallback only if your deployment permits it.
Vulkan GPU offload (llama.cpp)
If your device has a Vulkan-capable GPU (Snapdragon 8 Gen 2+ or newer), you can offload layers:
providers:
- name: "qwen-vulkan"
task: "chat"
protocol: "openai"
runtime: "llama.cpp"
url: "file:///sdcard/atak/custos/models/qwen2.5-7b-instruct-q4_k_m.gguf"
port: 8411
model: "qwen2.5-7b"
contextSize: 4096
threads: 4
properties:
gpu-layers: "99"
taskPriority: 100
tier: "handheld"
CPU backend is recommended for reliability unless you have benchmarked your specific device and confirmed GPU offload provides a meaningful improvement.
Verify
Check each local provider in the Status panel after loading. If one stays offline, open its details and check the model path, format, and available memory.