Native Kotlin Multiplatform re-implementation of the Synaptics FunctionGemma
function-calling demo, built on SKaiNET + SKaiNET-transformers, targeting the
Synaptics Astra Machina SL2610 (rdk, aarch64, Torq NPU).
Goal: the same on-device voice/text → tool-call pipeline — mic → VAD →
Moonshine ASR (Torq NPU) → FunctionGemma LLM (CPU) → action — as a single
cross-compiled aarch64 binary, at ≥ the Python app's speed with Kotlin/KMP comfort.
Heavy models go DSL → StableHLO → IREE (Torq for NPU, IREE llvm-cpu +NEON for CPU).
See the full plan in ../../.claude/plans/.
This is a Kotlin port of the upstream Python demo:
The original demonstrates fully on-device, cloud-free voice/text control of hardware on a
Synaptics Coralboard. It uses FunctionGemma 270M (fine-tuned for tool routing) for the
LLM and Moonshine for ASR on the Torq NPU, with a Microphone → Silero VAD → Moonshine
ASR → FunctionGemma → dispatcher → hardware pipeline. Its key trick is functional
tokens: each tool is a single special token, so no JSON schema is injected into the prompt,
which keeps inference fast (~1.3–2.0 s/turn). It exposes six tools — set_lights,
play_buzzer, set_alarm, cancel_alarm, get_system_status, respond — and ships as
Python (PyQt6 UI + CLI REPL) driving RGB LEDs, a piezo buzzer, and an optional Neopixel ring.
What this port keeps: the same six tools, the same compact tool-call codec
(<tool_N>(args)<end> Octopus-v2 named-arg style), and the same model lineup.
What changes: the whole pipeline is rewritten in Kotlin/Native and runs the heavy models
through the SKaiNET DSL → StableHLO → IREE path instead of the vendor Python runtime —
shipping as one cross-compiled aarch64 binary rather than a Python venv. The default action
handlers are log-only (no Coral HAT assumed); register your own to drive real hardware.
- Phase 0 ✅ — KMP scaffold (
jvm+linuxArm64), ActionRouter ported, dev loop proven: hostjvmRunand a cross-compiled aarch64 binary that deploys + runs on the board. - Phase 1 ✅ — FunctionGemma-270M reimplemented via SKaiNET-transformers (gemma3 DSL → StableHLO → IREE f16 vmfb); numerically matches llama.cpp and runs on the board.
- Phases 2–4 — Moonshine-on-NPU from Kotlin, full live-mic pipeline, parity/latency gate.
See docs/STATUS.md for the detailed, up-to-date status, know-how, and handoff notes.
jvm()— fast host dev + A/B reference harness.linuxArm64()— the shipped board binary (cross-compiled from an x64 host; Kotlin/Native cannot cross-compile from ARM).
The FunctionGemma-270M GGUF (~248 MB) is not committed (gitignored). Fetch it once
from Hugging Face (BrinqAI/functiongemma-270m-physical-ai)
into models/:
./gradlew downloadModel # set HF_TOKEN=… only if the repo becomes gated./gradlew :jvmRun # run on host
./gradlew :jvmTest # common + jvm tests
./gradlew :linkReleaseExecutableLinuxArm64 # cross-compile aarch64 binary
BOARD=root@<board-ip> ./gradlew deployBoard # build + push + run on the board
# or directly:
BOARD=root@<board-ip> sh scripts/deploy.sh --runThe board IP changes per boot — set BOARD accordingly. Deploy streams the binary over ssh
(cat; the BusyBox board has no rsync/sftp) and adds a libcrypt.so.1 compat symlink
(Kotlin/Native links glibc libcrypt.so.1; the board ships libxcrypt libcrypt.so.2),
running with LD_LIBRARY_PATH so nothing in the system tree is touched.
Open the Gradle project (no Android target — jvm + linuxArm64 only). The KMP plugin
surfaces jvmRun and link…LinuxArm64. Run configs live in .run/.
src/commonMain/voicecc/ actions/ (ActionRouter, the 6 tools) + App.kt
src/jvmMain/ host main + readSystemStatus actual + export/ (DAG→StableHLO bridge)
src/linuxArm64Main/ board main + Pipeline (ASR→LLM→codec→action)
App-local KMP modules:
:asr Moonshine ASR on the Torq NPU
:vad Silero speech segmenter
The Gemma LLM runtime was extracted into SKaiNET-transformers and is consumed
via composite build (see settings.gradle.kts):
sk.ainet.transformers:skainet-transformers-runtime-gemma-iree
GemmaDecoder (decode loop) + IreeRuntime (vmfb driver) + CompactCodec/ToolCall
scripts/deploy.sh host→board deploy (+ libcrypt compat)
docs/STATUS.md detailed status / know-how / handoff
Apache-2.0 (consistent with SKaiNET).