|
Download README.md from zst50/voice-chatbot-android: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/zst50/voice-chatbot-android/resolve/main/README.md
- Command line
-
hf download hf://zst50/voice-chatbot-android/README.md
-
curl -L -o README.md https://huggingface.co/zst50/voice-chatbot-android/resolve/main/README.md
11 kB
| > The .litertlm models on this card already use the QAT that is discussed in the blog post. The most popular file, `gemma-4-E2B-it.litertlm`, uses a mixture of int2, int4 and int8 to keep it small, fast and efficient. | |
| https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/discussions/30#6a2c2e1ac539f50158631020 | |
| # Voice Chatbot (Android) β interruptible, on-device voice chat pipeline | |
| A Kotlin/Compose Android app implementing a fully local, **interruptible** | |
| voice chatbot: | |
| ``` | |
| mic ββΊ Silero VAD (sherpa-onnx) | |
| β endpointing + barge-in | |
| βΌ | |
| Whisper tiny.en Q4 (whisper.cpp) βββΊ transcript | |
| βΌ | |
| LLM (streaming): | |
| ββ Local: LiteRT-LM (`.litertlm`, e.g. Qwen3-0.6B) | |
| ββ Remote: OpenAI-compatible `/v1/chat/completions` (SSE) | |
| βΌ | |
| SentenceSplitter βββΊ per-sentence VITS TTS (sherpa-onnx + Piper voice) | |
| βΌ | |
| AudioTrack (playback; barge-in stops it instantly) | |
| ``` | |
| **Barge-in:** speak while the assistant is talking β playback and generation | |
| stop immediately and your new utterance becomes the next turn. | |
| ## Components | |
| | Piece | Runtime | Model | Size | | |
| |---|---|---|---| | |
| | STT | whisper.cpp (JNI, built via NDK/CMake) | `ggml-tiny.en-q4_0.bin` (Q4) | ~45 MB | | |
| | VAD | sherpa-onnx AAR (official prebuilt `sherpa-onnx-1.13.4.aar`) | `silero_vad.onnx` | ~2 MB | | |
| | TTS | sherpa-onnx VITS (Piper) | `vits-piper-en_US-lessac-medium` (+ `espeak-ng-data`) | ~62 MB | | |
| | LLM (local) | LiteRT-LM `com.google.ai.edge.litertlm:litertlm-android:0.15.0` | `litert-community/Qwen3-0.6B` β `Qwen3-0.6B.litertlm` (dynamic INT8) | 586 MB | | |
| | LLM (remote) | OkHttp + kotlinx.serialization (OpenAI-compatible chat completions) | any remote model, e.g. `qwen2.5-0.5b-instruct` | n/a | | |
| All models are Apache-2.0 or MIT except the Piper voice/GPL espeak-ng data | |
| (bundled in the sherpa-onnx tts-model tarball) β fine for local testing. | |
| ## Prerequisites | |
| - macOS/Linux with Android SDK (platform 35, build-tools 36, NDK `27.2.12479018`, CMake 3.22.1), | |
| JDK 17, and a connected Android device (arm64, 8 GB RAM recommended). | |
| - `adb` on PATH. | |
| ## Build | |
| Toolchain: **AGP 8.13.2 Β· Gradle 8.13 (wrapper) Β· Kotlin 2.3.21 Β· JDK 17**. | |
| whisper.cpp is vendored under `third_party/` (committed), so the project builds | |
| out-of-the-box once the SDK/NDK are present. | |
| ```bash | |
| # 0. JDK 17 (Gradle 8.13 does not support newer JDKs) | |
| # macOS/Homebrew: brew install openjdk@17 | |
| export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home | |
| # 0b. Point Gradle at the Android SDK (or set ANDROID_HOME) | |
| echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties | |
| # 1. (first time) install NDK/cmake if missing | |
| "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager \ | |
| "platforms;android-35" "ndk;27.2.12479018" "cmake;3.22.1" | |
| # 2. Build (Gradle downloads the sherpa-onnx AAR into app/libs on first run) | |
| ./gradlew :app:assembleDebug | |
| # 3. Install | |
| adb install -r app/build/outputs/apk/debug/app-debug.apk | |
| ``` | |
| Unit tests: `./gradlew :app:testDebugUnitTest` | |
| > Only if `third_party/whisper.cpp` is ever missing (e.g. a fresh export that | |
| > dropped it): `tools/fetch-deps.sh` re-clones it. | |
| ## Models | |
| **Models are bundled in the APK** (`assets/models/`) and copied to the app's | |
| private internal storage on first launch β no adb push needed. Storage used on | |
| the device: `~240 MB` under `/data/data/com.example.voicechatbot/files/models/`. | |
| > The APK is ~500 MB because of the bundled models; devices need **~1.5 GB free** | |
| > to install it. | |
| **Preinstalled:** SmolLM2 135M (LLM) + Whisper tiny.en Q4 + Silero VAD + Piper TTS. | |
| **Download in-app:** bigger LLMs (SmolLM2 360M, Qwen3 0.6B, Gemma 4 E2B/E4B/12B) | |
| and Whisper models are selected in Settings via **preset chips that | |
| auto-download the file if it isn't already saved** (progress shown on the chip). | |
| | id | Model | Size | Preinstalled | Thinking | License | | |
| |---|---|---|---|---|---| | |
| | `smollm2-135m` | SmolLM2 135M | 136 MB | β | no | Apache-2.0 | | |
| | `smollm2-360m` | SmolLM2 360M | 356 MB | download | no | Apache-2.0 | | |
| | `qwen3-0.6b` | Qwen3 0.6B int8 | 586 MB | download | yes (stripped) | Apache-2.0 | | |
| | `gemma-4-e2b` | Gemma 4 E2B (mobile 2/4/8-bit) | 2583 MB | download | yes (stripped) | Apache-2.0 | | |
| | `gemma-4-e4b` | Gemma 4 E4B (mobile 2/4/8-bit) | 3654 MB | download | yes (stripped) | Apache-2.0 | | |
| | `gemma-4-12b` | Gemma 4 12B | 6548 MB | download | yes (stripped) | Apache-2.0 | | |
| To rebuild with bundled models, the files must be present in `./models/`: | |
| ```bash | |
| tools/download-models.sh # downloads all LLM presets + whisper/VAD/TTS | |
| tools/download-models.sh smollm2-360m # or just one | |
| tools/download-models.sh list # show presets | |
| ``` | |
| (`tools/push-models.sh` is obsolete β models are in the APK now.) | |
| > **LFM-700M:** there is no `.litertlm` build of LFM2-700M on HF yet, and a | |
| > conversion requires the `litert-torch` generic-HF-export pipeline (heavy, not | |
| > guaranteed for the architecture). The SmolLM2 presets are the practical small, | |
| > non-thinking, Apache-2.0 alternatives. | |
| > **Gemma 4 E2B / E4B:** official `litert-community` LiteRT-LM builds of | |
| > `google/gemma-4-E2B-it` / `google/gemma-4-E4B-it` (Apache-2.0), using Google's | |
| > "Gemma-4 mobile" 2/4/8-bit (LUT) quantization. LiteRT-LM **memory-maps the | |
| > weights straight from the `.litertlm` file on disk** β the embedding tables are | |
| > mmap'd and never fully loaded into RAM β so the ~2.6 GB / ~3.7 GB files don't | |
| > need to fit in memory. Both support up to 32k context. Android benchmarks | |
| > (S26 Ultra, 2048 ctx): E2B ~1.7 GB CPU / ~0.7 GB GPU process RAM at | |
| > ~47β52 tok/s; E4B ~3.3 GB CPU / ~0.7 GB GPU at ~18β22 tok/s β E4B really wants | |
| > a 12 GB-class phone **and** the GPU backend. Settings β *Context window* | |
| > (LiteRT-LM `maxNumTokens`) caps the KV cache so bigger models fit in less RAM. | |
| > **Gemma 4 12B** (`gemma-4-12B-it.litertlm`, ~6.5 GB) is the largest option β | |
| > realistically it needs a 16 GB-class device with the GPU backend. | |
| > **TTS voices:** only the bundled `en_US-lessac-medium` voice is used. In-app | |
| > voice downloads were removed; Settings β *Voice (VITS)* just shows the | |
| > preinstalled voice (blank the model path for text-only chat). | |
| > **Whisper (STT):** the bundled model is `ggml-tiny.en-q4_0.bin` (~45 MB). | |
| > Better variants are available as Settings β *Whisper model* presets | |
| > (auto-downloaded on selection): unquantized tiny.en / tiny (FP16, ~75 MB), | |
| > tiny.en Q8_0 (~42 MB), or base.en Q8_0 (~78 MB). Q4_0 builds are **not** | |
| > published on Hugging Face (`tools/download-models.sh` quantizes them locally), | |
| > so Q8_0 is the closest official base.en quantized download. Larger models are | |
| > slower but more accurate, especially for non-English speech (use the | |
| > multilingual `tiny`). | |
| ## Running & testing | |
| 1. Grant the **Record audio** permission when prompted β listening starts | |
| automatically, so there is no Start button. | |
| 2. Say something. You should see `Listening β Transcribing β Thinking β | |
| Speaking`, and hear the reply sentence-by-sentence. | |
| 3. **Barge-in:** start talking mid-reply. Playback stops at once and your | |
| question is picked up as the next turn. | |
| 4. **Remote LLM:** open Settings β *Remote (OpenAI API)* β set base URL | |
| (e.g. `http://<your-server>:8000/v1`) + model name, save & restart. | |
| Works with llama.cpp server, vLLM, Ollama, etc. | |
| **Context length:** Settings β *Context length* (512β¦32768). The app sends | |
| it as `n_ctx`, which llama.cpp honours per-request. "Server default" sends | |
| nothing β the server decides (vLLM/Ollama manage context server-side). | |
| 5. **Stop** cancels TTS + generation and re-arms listening. | |
| 6. **Visualizer mode:** tap the eye icon in the top bar to hide the transcript. | |
| The screen then shows only a minimal audio-level bar visualizer and the | |
| sentence that is currently being spoken, rendered in a cursive glowing font. | |
| 7. **Mini screen + submit photo:** tap the PiP icon to enable a small draggable | |
| floating screen (grant the *Display over other apps* permission) that shows | |
| the currently-spoken sentence in the glowing cursive font, with a distinct | |
| submit-photo button (opens the system photo picker, like the chat's photo | |
| icon). While the mini screen is active a **foreground service with a | |
| persistent "listening" notification** keeps the microphone running in the | |
| background (Android 14+). Image input only works with a **Gemma 4** model on | |
| the local LiteRT-LM backend; anything else shows an error. | |
| 8. **Sampling:** Settings β *Sampling (local)* (temperature / top-p / top-k for | |
| LiteRT-LM) and *Sampling (remote)* (temperature / top-p / max tokens for the | |
| OpenAI-compatible endpoint). | |
| Tips: | |
| - Use headphones or a quiet room: without acoustic echo cancellation support | |
| the assistant may "barge in" on itself. The capture uses | |
| `VOICE_COMMUNICATION` + `AcousticEchoCanceler` when available. | |
| - TTS can be disabled by blanking the TTS model path in Settings (text-only | |
| chat) for quick LLM testing. | |
| - GPU backend: for the **local** LiteRT-LM backend this is LiteRT's GPU | |
| delegate ("ML Drift", OpenCL on Android) β roughly doubles decode speed and | |
| cuts process RAM a lot (Gemma E4B: ~3.3 GB β ~0.7 GB). For the **remote** | |
| backend the GPU is on the server (llama.cpp CUDA/Metal/OpenCL, vLLM, β¦), not | |
| in the app. Enable/disable it in Settings β *GPU backend*. | |
| ## Project layout | |
| ``` | |
| app/src/main/java/com/example/voicechatbot/ | |
| MainActivity.kt Compose UI + settings dialog | |
| config/AppConfig.kt persisted settings & model paths | |
| pipeline/VoicePipelineEngine.kt state machine, barge-in, sentence streaming | |
| audio/AudioRecorder.kt 16 kHz capture (+AEC) | |
| audio/TtsPlayer.kt AudioTrack queue with generation-based barge-in | |
| vad/VadEngine.kt Silero VAD (sherpa-onnx) | |
| stt/WhisperTranscriber.kt whisper.cpp JNI wrapper | |
| stt/WhisperNative.kt JNI declarations | |
| llm/ChatClient.kt backend interface | |
| llm/LitertChatClient.kt LiteRT-LM local inference (Flow streaming) | |
| llm/OpenAiChatClient.kt OpenAI-compatible remote client (SSE) | |
| llm/SentenceSplitter.kt token stream β sentence boundaries | |
| llm/PromptBuilder.kt system prompt | |
| tts/TtsEngine.kt VITS/Piper synthesis (sherpa-onnx) | |
| app/src/main/cpp/ whisper.cpp CMake + JNI bridge | |
| third_party/whisper.cpp vendored (committed, v1.7.5) | |
| tools/download-models.sh model fetch + Q4 quantize (local) | |
| tools/push-models.sh adb push models to device | |
| ``` | |
| ## Notes & caveats | |
| - **LiteRT-LM** requires a high-end device (Pixel 8 / S23 class). Engine init | |
| takes a few seconds and is done off the UI thread. | |
| - On interrupt, the current conversation context is dropped (clean slate for | |
| the next turn). | |
| - This is a test app: no wake word, no streaming partial transcription, no | |
| foreground service (screen must stay on), single ABI (`arm64-v8a`). | |