- LlmClient интерфейс; RemoteLlmClient (OkHttp, llm.binom.pw) и LocalLlmClient (LiteRT-LM, Gemma 4 E2B) - LocalLlm: Gemma4E2B (SHA-256, GPU/CPU-бэкенды), buildLocalLlmPrompt (SYSTEM→systemInstruction, TOOL→user), GemmaModelDownloader (стриминг, прогресс, фолбэк источников), спекулятивное декодирование - LlmPrefs (персистентность выбора), PhoneApp: фабрика по llmChoice, пересоздание ассистента, скачивание/отмена - UI: GlassesInfoScreen — выбор сервер/локальная + прогресс; ChatScreen — индикатор активной модели - Тесты: +12 (LlmLocalTest) — phone 154 зелёных
21 KiB
OpenAssistant (vayun-mathur/Modern-Apps) — локальные нейронки на Android
Источник: https://github.com/vayun-mathur/Modern-Apps/tree/main/openassistant Клон: ~/WORK/modern-apps/ (depth 1). Разбор 2026-08-10.
Текстовые альтернативы E2B в формате .litertlm (проверено 2026-08-10, полка litert-community)
Вопрос: есть ли ТЕКСТОВЫЕ модели заметно умнее E2B (2B) за те же/чуть большие токены, тот же формат.
| Модель | Парам. | Контекст | Размер | Декод на 8 Gen 3 | Оценка |
|---|---|---|---|---|---|
| Gemma 4 E2B (текущая) | 2B | 32K | ~0.8 ГБ | 20-35 т/с (спекуляция вкл) | база сравнения |
| Qwen3-8B (mixed int4) | 8B | 2048 (!) | 4.66 ГБ | ~10-12 т/с (оценка, телеф. бенча нет) | заметно умнее, но контекст урезан вдрызг |
| Phi-4-mini-instruct | 3.8B | 4096 | 3.9 ГБ | 10.39 т/с GPU (S24 Ultra, 8 Gen 3, динамик int8) | умнее, в 2 раза медленнее |
| Ministral-3-3B-Reasoning | 3.3B | 4096 | ~2.2 ГБ | нет телеф. бенча | reasoning, текст, но медленный по природе |
| DeepSeek-R1-Distill-Qwen-1.5B | 1.5B | 4096 | 1.83 ГБ | 26.35 т/с CPU (S24 Ultra) | не умнее, другой класс (reasoning) |
| Gemma2-2B-IT | 2B | — | — | — | доступ ограничен (restricted) |
| Gemma3-4B-IT | 4B | — | — | — | мультимодальная, не подходит по критерию |
ВЫВОД: за "умнее + текст" платишь скоростью и контекстом. Phi-4-mini — единственный с честным замером на 8 Gen 3 (10.39 т/с — вдвое медленнее 20-25). Qwen3-8B — умнее всех, но 2048 контекст и ~10 т/с. E2B остаётся оптимумом для "незаметной" скорости (20+ т/с).
Две нейронки + токенайзер
1. Чат-LLM: Gemma 4 E2B (2B) — файл gemma-4-E2B-it.litertlm
- Формат .litertlm (LiteRT LM, Google; бывш. AI Edge / MediaPipe LLM Inference)
- Библиотека: com.google.ai.edge.litertlm:litertlm-android:0.14.0
- Запуск: Engine(EngineConfig(modelPath, backend=GPU(), visionBackend=GPU(), audioBackend=CPU(), cacheDir)) + engine.initialize()
- Speculative decoding: ExperimentalFlags.enableSpeculativeDecoding = true
- Мультимодальный ввод: Content.Text / Content.ImageFile / Content.AudioFile (голос — WavRecorder, обрабатывается на CPU)
- Источник: huggingface.co/litert-community/gemma-4-E2B-it-litert-lm
- SHA256: 181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c
- На диске: gemma4-2b.litertlm в getExternalFilesDir(null)
2. Эмбеддер SigLIP2-base (patch16, 224) — семантический поиск по фото
- Служит фото-приложению через ResultReceiver (IPC): text/image/info-запросы
- ONNX Runtime: com.microsoft.onnxruntime:onnxruntime-android:1.27.0
- Два файла: vision_model_fp16.onnx (картинки), text_model_int8.onnx (текст)
- Вектора 768-мерные (dim читается из модели), L2-нормированные, косинус
- Источник: huggingface.co/onnx-community/siglip2-base-patch16-224-ONNX
- SHA256 vision: a1959f7bd3993a607e48839f6d01e25b876fe76afda301b028b78eef68aabd95
- SHA256 text: 3a0603d3a00c05a80a6ded4743c16aaac7b1e62cdcc7e362e7ce418659b96400
- ВАЖНО: из выходов брать pooler_output (ранг-2), НЕ last_hidden_state (ранг-3), иначе вектора бессмысленны
- Препроцессинг картинки: ресайз прямо в 224x224 (без center-crop), (px-127.5)/127.5, NCHW RGB
- Сессии ONNX однопоточные (setIntraOpNumThreads(1)) — бережёт батарею
3. Токенайзер SentencePiece (Gemma tokenizer.model, ~4MB, 256k vocab)
- Файл: siglip2_tokenizer.model (SHA256 61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2)
- Нужен ТОЛЬКО текстовой башне SigLIP2 (litertlm не отдаёт token id наружу)
- Реализация без protobuf-зависимости: ручной парсинг ModelProto (pieces = field 1; piece=1 string, score=2 float, type=3 enum; индекс == token id)
- Кодирование: Viterbi Unigram сегментация + byte fallback (<0xNN>, Gemma шлёт все 256 байтовых писов)
- Препроцессинг текста: lowercase → NFKC → схлопнуть пробелы → dummy-prefix пробел → ▁ (U+2581)
- SEQ_LEN = 64 (падинг padId=0, обрезка)
- Риск: совпадение с transformers Siglip2Processor — первое, что проверять при повторе
Как это работает (архитектура)
- Foreground-сервис InferenceService, 3 очереди: standard (чат), intent (JSON-извлечение), embedding (SigLIP2 — отдельный consumer, не ждёт LLM)
- Модели качаются ТОЛЬКО с зеркала https://data.vayunmathur.com/models/ (никакого HF-фолбэка, supply-chain митигация), SHA-256 проверка, Content-Length + ETag
- Чат: история из Room-БД → ConversationConfig(systemInstruction, initialMessages, tools, automaticToolCalling=true) → sendMessageAsync стриминг
- Инструменты (AssistantToolSet): add_to_memory / get_memories / remove_memory и др., automaticToolCalling=true
- Intent-режим: system prompt "data extraction engine", стриминг, tryExtractLargestJson + валидация по JSON Schema, досрочный HALT через CancellationException("HALT"), таймаут 45с, очередь-дроп по 45с
- Версионирование: cleanupStaleSiglipModels по SiglipEmbedder.MODEL_VERSION; legacy gemma4.litertlm / gemma4-4b.litertlm удаляются при старте
- Сборка: noCompress += "onnx"; packaging pickFirsts: libLiteRtTopKOpenClSampler.so, libc++_shared.so
Ключевой вывод (что ценно для нас)
Главный урок не в архитектуре, а в скорости: автор довёл локальную нейронку на телефоне до >=20-25 ток/с — пользователь не видит задержки, ассистент отвечает "на скидку" (вживую, без заметных пауз). Это рецепт быстрого запуска нейронки на устройстве:
- Модель 2B (Gemma 4 E2B) в оптимизированном формате LiteRT LM — компактная, но живая
- GPU-бэкенд для text/vision (OpenCL), CPU только для аудио
- Speculative decoding включён (ExperimentalFlags) — дёшево ускоряет генерацию
- Одна сессия движка, переиспользуется; очереди разнесены, эмбеддинги не блокируют чат
- Токенайзер ручной (без protobuf), эмбеддер на ONNX — но это обвязка, не узкое место
Т.е. заявка на повтор: 2B-модель + LiteRT LM + GPU + speculative decoding = быстрый локальный ассистент.
Ответы на вопросы (2026-08-10)
Что за формат .litertlm и можно ли заменить модель
- .litertlm — контейнер LiteRT-LM (Google; бывш. MediaPipe LLM Inference / AI Edge): внутри квантованная модель (смесь 2/4/8 бит, text-only вес ~0.8GB), эмбеддинги (1.12GB, memory-mapped), токенайзер, плюс движок даёт KV-cache менеджмент, промпт-темплейт, function calling
- ЗАМЕНИТЬ МОЖНО, и это просто: готовые .litertlm в litert-community на HF (десятки): Qwen3 0.6B/1.7B/8B, Qwen2.5 0.5B/1.5B, TinyLlama-1.1B, Phi-4-mini, SmolLM-135M, DeepSeek-R1-Distill-Qwen-1.5B (reasoning), Gemma3-1B/4B, FunctionGemma-270M (function calling), Nanbeige-3B, Ministral-3-3B, Mage-VL (VLM), FastVLM-0.5B. Свои модели — через LiteRT Torch Generative API (ai-edge-litert, Python)
- В openassistant замена = поменять URL+SHA256 в ModelUrls и файл. SigLIP2-токенайзер от LLM не зависит
- Ограничения при замене: (а) speculative decoding вшит в сборку модели — у Gemma 4 E2B есть, у других не факт; (б) Content.ImageFile/AudioFile работают только у мультимодальных сборок; (в) tools/automaticToolCalling — у Gemma/Qwen/Phi есть, не у всех
- Для скорости на слабом железе: Qwen3-0.6B / SmolLM-135M / Qwen2.5-0.5B (быстрее, но глупее); компромисс: Qwen3-1.7B, Gemma3-1B; русский текст у Qwen обычно лучше
Мультимодальность Gemma 4 E2B
- ДА: текст + картинки + аудио в одном сообщении. В коде: Content.Text / Content.ImageFile / Content.AudioFile; text/vision на GPU, audio на CPU
- Из README: картинки/аудио класть ПЕРЕД текстом в промпте; переменное разрешение картинок через visual token budget (токенов на картинку); vision/audio части грузятся по требованию
- В openassistant intent-режим шлёт imagePaths + userText одним Messages.user — живой пример смешанного ввода
Контекст
- Полная Gemma 4 — до 256K токенов (model card). On-device litertlm-сборка: "supports up to 32k context length" — 32K токенов
- Бенчмарки litert-community: 1024 prefill + 256 decode, контекст 2048; S26 Ultra GPU: decode 52 ток/с (baseline), 66-92 ток/с со speculative decoding; prefill 3808 ток/с; TTFT 0.3с
- Приложение лимита не задаёт (ConversationConfig без maxTokens) — контекстом рулит движок (KV-cache менеджмент); история из Room идёт в initialMessages, упор в 32k движок разруливает сам
- 20-25 ток/с автора — нижняя граница "незаметно", на флагмане официально 46-92
Скорость на Snapdragon 8 Gen 3 (12 ГБ RAM)
- УСТРОЙСТВО АВТОРА (подтверждено 2026-08-10): Honor Magic V3, Snapdragon 8 Gen 3, 12 ГБ RAM. (Сначала назвал Gen 2 — ложный след, снят с протокола.)
- Официальная карточка litert-community НЕ даёт 8 Gen 3 — бенчмарки только на S26 Ultra (чип 2026, новее): GPU decode 52.1 ток/с baseline, 66.5-91.7 со speculative decoding; prefill 3808 ток/с; TTFT 0.3с; CPU: decode 46.9, prefill 557, TTFT 1.8с. Память: GPU-бэкенд 676 МБ, CPU 1733 МБ
- 8 Gen 3 (2023, Adreno 750, напр. S24 Ultra US): официальных цифр нет; сторонние замеры (MindStudio): E2B 20-35 ток/с, E4B 12-20 ток/с. Прим.: у MindStudio Pixel 9 приписан к 8 Gen 3 ошибочно (там Tensor G4); настоящий 8 Gen 3 — S24 Ultra (US) и другие
- Память 12 ГБ — с большим запасом: модель на GPU ~676-800 МБ RSS (text-only), эмбеддинги 1.12 ГБ memory-mapped (не в RSS), vision/audio по требованию. Ограничение не в RAM, а в скорости GPU/пропускной способности
- Вывод: заявленные автором 20-25 ток/с — реалистичная нижняя/средняя граница вилки для 8 Gen 3; со speculative decoding можно ждать заметно больше (порядка 50+ на флагмане, по аналогии с S26 Ultra)
Спекулятивное декодирование (MTP) на Snapdragon 8 Gen 3
- Что это: LLM обычно выдаёт 1 токен за проход сети. Спекулятивное декодирование: маленький черновик-предсказатель набрасывает сразу N следующих токенов, большая модель проверяет их ОДНИМ проходом — если угадал, получил N токенов ценой одного прохода. Ускорение до ~3x без потери качества
- У Gemma 4 это НЕ внешняя надстройка, а встроенная архитектура — Multi-Token Prediction (MTP): черновик-головы сидят прямо в чекпоинте модели (ai.google.dev/gemma/docs/mtp). Спекуляция у Gemma 4 работает автоматически при поддержке рантайма; флаг лишь включает рантайм-путь
- В openassistant флаг ПОДТВЕРЖДЁН в коде: InferenceService.kt:486 — ExperimentalFlags.enableSpeculativeDecoding = true перед engine.initialize()
- ВЫВОД: 20-25 ток/с автора НЕ означают, что спекуляция выключена. Наоборот — она включена. У автора Honor Magic V3 = Snapdragon 8 Gen 3 (Adreno 750): вилка со спекуляцией ~20-35 ток/с (MindStudio), без неё ~10-15 (вдвое меньше). Его 20-25 — ровно середина ожидаемого С включённой спекуляцией, ближе к нижней границе (зависит от термальных лимитов складного корпуса)
- Как проверить на живом телефоне: поставить enableSpeculativeDecoding = false, замерить — падение вдвое = она работала
- 8 Gen 3 (2023, Adreno 750): сторонние замеры E2B 20-35 ток/с; на 8 Gen 2 (2022, Adreno 740) было бы ниже на ~15-20%
Как повторить (чеклист)
- Скачать 4 файла с HF (см. SHA256 выше), разложить под зеркало /models/
- Android: litertlm-android 0.14.0 + onnxruntime-android 1.27.0 (+ kotlinx-coroutines 1.11.0 — иначе close$default на SendChannel)
- Backend: GPU для text/vision, CPU для audio
- Из SigLIP2 ONNX читать pooler_output, не last_hidden_state
Бонус: что ещё в монорепо Modern-Apps
findfamily (UWB-локатор), keyboard (хангыль-композитор), maps, games (Stockfish через JitPack ncnn), библиотеки: downloadservice, room, image.
Raspberry Pi 5: да, работает
Вопрос: потянет ли Gemma 4 E2B Raspberry Pi 5? Ответ: да, и это официально.
Формат и рантайм:
- LiteRT-LM теперь официально кросс-платформенный: Android, iOS, Web, Desktop, IoT (Raspberry Pi). Есть официальный CLI (uvx-установка), который гоняет .litertlm прямо на Pi — без конвертации. GitHub: google-ai-edge/LiteRT-LM.
- Google сам пишет «Try Gemma4-E4B with MTP on Linux, macOS, Windows or Raspberry Pi with the LiteRT-LM CLI».
Реальные замеры на RPi 5 (8 ГБ, 4×Cortex-A76 @ 2.4 ГГц, без GPU-акселератора):
- Ollama (GGUF): первый токен ~3-4 сек, генерация ~8-12 t/s. Модель ~1.5 ГБ.
- Стоковый llama.cpp (Q4): ~6.6 t/s.
- mkturkcan/mote (оптимизированный llama.cpp + MTP-спекуляция + A76-ядра + KleidiAI): 11.2 t/s на обычном тексте, до 16 на шаблонном, до 20.4 в turbo на повторах (логи/CSV). Устанавливается одной командой, ничего не собирается на Pi.
- Официальный LiteRT-LM CLI: цифры не подтверждены, но движок тот же — ожидаемо в районе 6-12 t/s.
Важно про контекст на Pi:
- Модель заявляет 128K контекста, но KV-кэш на 128K не влезает в 8 ГБ. На RPi 5 (8 ГБ) реально 4K-8K токенов, дальше — своп и обвал скорости. На 16 ГБ — комфортно ~32K.
- Мультимодальность (текст+картинки+видео) на Pi работает, но медленнее телефона.
Вывод: на RPi 5 E2B даёт ~8-12 t/s (сток) или ~11-20 t/s (оптимизированный mote). Это в 2-3 раза медленнее телефона (20-25 t/s), но для офлайн-ассистента, пакетной обработки и домашних экспериментов — достаточно. Воспринимается как «медленно, но читаемо», а не как «зависло».
Батарея и скорость обработки картинок (Honor Magic V3, 8 Gen 3, 12 ГБ)
Батарея
- Точных ватт на Android под GPU-нагрузкой НЕТ ни у кого: Battery Manager API Android ненадёжен под нагрузкой, iOS API не отдаёт (arxiv 2603.23640, "LLM Inference at the Edge", бенчмарк на S24 Ultra = тот же 8 Gen 3).
- Ориентиры из той статьи: S24 Ultra idle ~0.8 Вт, max ~12 Вт (система целиком). Qwen 1.5B через MLC-LLM: 9.93 ток/с sustained, префилл 25 сек (OpenCL-оверхед MLC), термальный фейл на 6-й итерации подряд: GPU freq упала до 231 МГц, 78.3°C — жёсткий флор от термогубернатора, а не плавный DVFS как у iPhone.
- Энергия на токен на edge-платформах: ~270-300 мДж/токен (Hailo-10H 1.87 Вт / 6.9 ток/с; RTX 4050 ноут 34 Вт / 131.7 ток/с).
- Honor Magic V3: батарея 5150 мАч ≈ 19.9 Вт·ч.
- Прикидка: Gemma 4 E2B на 8 Gen 3 ~22 ток/с при ~4-5 Вт системы → 0.2 Дж/токен ≈ 56 мкВт·ч/токен. Непрерывная генерация ≈ 4-4.5 часа на всю батарею (20-25%/час). Сценарий ассистента (5-10 мин генерации в час) → 1.5-4%/час — почти незаметно.
- Термальный капкан 8 Gen 3: непрерывная нагрузка без пауз → троттлинг, скорость падает до ~2 раз. Magic V3 тонкий — отвод тепла хуже, троттлинг раньше. Паузы между запросами обязательны (раз в несколько минут — ок).
- LiteRT-LM оптимизирован под Adreno лучше MLC-LLM (это гугловский движок), поэтому 20-25 ток/с у автора — уже с учётом реального троттлинга.
Картинки (видео-поток с камеры)
- Gemma 4: переменное разрешение через токен-бюджет: 70/140/280/560/1120 токенов на картинку. Google прямо советует для кадров видео НИЗКИЙ бюджет (70) ради скорости. Механика: 9 патчей на токен (3x3 среднее).
- Каждый кадр = вижн-энкодер + префилл токенов бюджета. Порядок: 0.3-1.5 сек на кадр на 8 Gen 3 (оценка, точного бенчмарка нет).
- 30 fps НЕВОЗМОЖНО: 70 токенов/кадр × 30 = 2100 ток/с префилла — префилл на телефоне ~200-500 ток/с. Реалистично: кадр раз в 2-5 сек, бюджет 70-140.
- Контекст 32K: при 140 ток/кадр ≈ 230 кадров в окне, при 70 ≈ 450. Кадр раз в 2 сек → 8-15 минут непрерывного окна → нужен скользящий буфер кадров.
- Для "где я иду/еду": модель получает кадр + вопрос ("где я?"), отвечает за 1-3 сек. Рабочий вариант — периодический анализ, а не поток. Сколько картинок за сообщение принимает on-device сборка — проверять на практике (в доке: reasoning across multiple images).
- Код автора: Content.ImageFile(path) шлёт файлы как есть, бэкенд GPU для текста+зрения (InferenceService.kt:493-494), аудио CPU. Приложение лимит картинок не задаёт.