Представьте: AI-ассистент в мобильном приложении работает без интернета, все данные остаются на устройстве. Никакой передачи на сервер, никаких задержек сети. Облачные LLM требуют постоянного соединения, передают конфиденциальные данные и создают задержки. Для медицины или финансов это неприемлемо. On-device решение решает эти проблемы, но требует тщательной интеграции под платформу. Мы внедряем Llama.cpp — библиотеку инференса LLM на CPU/GPU — в iOS и Android приложения. Разберём технические детали: от выбора модели до борьбы с перегревом.
Как выбрать модель для офлайн-ассистента?
Llama.cpp работает с моделями в формате GGUF. Популярные варианты для мобиля:
| Модель | Квантование | Размер | RAM | Скорость (iPhone 14) |
|---|---|---|---|---|
| Llama-3.2-1B | Q4_K_M | 0.8 ГБ | ~1.2 ГБ | 25–35 t/s |
| Llama-3.2-3B | Q4_K_M | 2.0 ГБ | ~2.5 ГБ | 10–15 t/s |
| Phi-3-mini-4k | Q4_K_M | 2.2 ГБ | ~2.8 ГБ | 8–12 t/s |
| Gemma-2-2B | Q4_K_M | 1.6 ГБ | ~2.0 ГБ | 12–18 t/s |
| Qwen2.5-1.5B | Q4_K_M | 1.0 ГБ | ~1.4 ГБ | 20–28 t/s |
На iPhone SE 2nd gen (3 ГБ RAM) Llama-3.2-3B Q4 работает на пределе — OOM возможен при длинных контекстах. Безопасный выбор для широкого парка устройств — модели до 1.5–2 ГБ. В одном из проектов для финансового приложения мы выбрали Llama-3.2-1B Q4_K_M, что позволило уложиться в 1 ГБ памяти на iPhone SE. Скорость генерации составила 25-30 t/s, что достаточно для ответов на вопросы. Тепловой троттлинг был сведён к минимуму ограничением контекста до 1024 токенов.
Проблемы и решения при on-device LLM
| Проблема | Решение |
|---|---|
| OOM при большом контексте | Ограничить n_ctx до 1024–2048 токенов |
| Тепловой троттлинг | Мониторинг thermalState, паузы между генерациями |
| Повреждённый GGUF-файл | Верификация SHA256 после скачивания |
| Низкая скорость на старых устройствах | Использовать модели 1B с квантованием Q4 |
Как собрать llama.cpp для iOS?
# Клонируем репозиторий git clone https://github.com/ggerganov/llama.cpp cd llama.cpp # Сборка через CMake для iOS cmake -B build-ios \ -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \ -DPLATFORM=OS64 \ # arm64 only -DLLAMA_METAL=ON \ # Metal GPU ускорение -DLLAMA_STATIC=ON cmake --build build-ios --config Release Результат — libllama.a статическая библиотека. Создаём Swift Package с C-bridging header:
// llama_bridge.h #include "llama.h" // Обёртки для Swift-дружественного API void* llama_create_context(const char* model_path, int n_ctx, int n_gpu_layers); const char* llama_generate_token(void* ctx, const char* prompt); void llama_free_context(void* ctx); n_gpu_layers — количество слоёв, выгружаемых на Metal GPU. Значение -1 означает все слои на GPU. На iPhone 14 с 6 ГБ unified memory — ставьте -1. На устройствах с 3 ГБ — экспериментируйте: слишком много слоёв на GPU вызывает OOM.
Swift-обёртка для стриминга токенов
import Foundation actor LlamaSession { private var context: OpaquePointer? private var model: OpaquePointer? func load(modelPath: String, contextSize: Int32 = 2048, gpuLayers: Int32 = -1) throws { var params = llama_model_default_params() params.n_gpu_layers = gpuLayers model = llama_load_model_from_file(modelPath, params) guard model != nil else { throw LlamaError.modelLoadFailed } var ctxParams = llama_context_default_params() ctxParams.n_ctx = UInt32(contextSize) ctxParams.n_batch = 512 context = llama_new_context_with_model(model, ctxParams) } func generate(prompt: String) -> AsyncThrowingStream<String, Error> { AsyncThrowingStream { continuation in Task.detached(priority: .userInitiated) { // Токенизация var tokens = [llama_token](repeating: 0, count: 4096) let nTokens = llama_tokenize(self.model, prompt, Int32(prompt.utf8.count), &tokens, 4096, true, false) // Инференс — по одному токену for i in 0..<nTokens { llama_batch_add(&batch, tokens[Int(i)], llama_pos(i), [0], false) } while true { llama_decode(self.context, batch) let nextToken = llama_sample_token_greedy(self.context, &candidates) if nextToken == llama_token_eos(self.model) { break } // Конвертация токена в строку var buf = [Int8](repeating: 0, count: 64) llama_token_to_piece(self.model, nextToken, &buf, 64, 0, true) let piece = String(cString: buf) continuation.yield(piece) } continuation.finish() } } } } Стриминг токенов через AsyncThrowingStream — пользователь видит текст по мере генерации, не ждёт весь ответ. Это критично для UX: 10 токенов в секунду воспринимается нормально, если текст появляется постепенно.
Почему тепловые ограничения критичны?
Llama.cpp на iPhone при длительной генерации разогревает устройство. iOS throttling: при перегреве система снижает тактовую частоту, скорость генерации падает с 25 t/s до 8–10 t/s. Это не баг — поведение системы.
Практическое решение: ограничивать максимальный контекст (n_ctx) до 1024–2048 для коротких сессий. Между запросами — пауза. Мониторить ProcessInfo.processInfo.thermalState на iOS:
NotificationCenter.default.addObserver(forName: ProcessInfo.thermalStateDidChangeNotification, ...) { _ in let state = ProcessInfo.processInfo.thermalState if state == .critical || state == .serious { // Приостановить генерацию, уведомить пользователя } } Типичные ошибки при интеграции
- Слишком большой контекст — выбирайте n_ctx ≤ 2048 для мобильных устройств.
- Игнорирование тепловых throttle — мониторьте thermalState и делайте паузы.
- Неправильная версия модели — проверяйте, что GGUF-файл совместим с вашей сборкой llama.cpp.
- Отсутствие верификации хеша — повреждённые файлы приводят к крашам.
Android: llama.cpp через NDK
// CMakeLists.txt в jni/ add_library(llama_jni SHARED llama_jni.cpp) target_link_libraries(llama_jni llama ggml) // Kotlin side class LlamaEngine { init { System.loadLibrary("llama_jni") } external fun loadModel(modelPath: String, nGpuLayers: Int): Long // возвращает handle external fun generateNext(handle: Long, tokens: IntArray): String external fun freeModel(handle: Long) } На Android — Vulkan backend вместо Metal: в CMakeLists включаем LLAMA_VULKAN=ON. Поддерживается на устройствах с Vulkan 1.1+, то есть практически всё с Android 10+.
Проблема с Android: процесс не имеет ограничения памяти как целого пула — система может убить приложение (SIGKILL) при нехватке RAM без предупреждения. ComponentCallbacks2.onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL) — последний шанс освободить контекст перед убийством процесса.
Скачивание модели: прогресс и верификация
GGUF-файлы весят 1–4 ГБ. Скачиваем через URLSession (iOS) или WorkManager с DownloadManager (Android). Верификация SHA256 обязательна: после скачивания вычисляем хеш и сравниваем с ожидаемым из репозитория на HuggingFace. Повреждённый GGUF вызывает краш при парсинге заголовка или позже при инференсе — лучше поймать на верификации.
Мобильная нейросеть работает быстрее без задержек сети, что особенно важно для критичных по времени приложений. Экономия средств: полностью офлайн решение исключает затраты на серверную инфраструктуру.
Что входит в интеграцию
- Анализ парка устройств и подбор модели с оптимальным квантованием
- Сборка llama.cpp под iOS (Metal) и/или Android (Vulkan)
- Разработка Swift/Kotlin обёртки с асинхронным стримингом токенов
- Реализация скачивания моделей с прогрессом и верификацией SHA256
- UI чат-интерфейса с индикацией теплового состояния
- Стресс-тестирование на реальных устройствах и тонкая настройка контекста
- Документация по интеграции и поддержка на этапе запуска
Сроки ориентировочно
Одна платформа, базовый чат-интерфейс с выбранной моделью — от 3 недель. Обе платформы, несколько моделей на выбор, фоновое скачивание, управление контекстом — от 7 недель. Стоимость рассчитывается индивидуально.
Наш опыт — 5 лет в мобильной разработке и более 20 проектов с on-device ML. Мы гарантируем работоспособность решения на целевых устройствах после тестирования. Получите консультацию по выбору модели и оценке вашего проекта. Закажите интеграцию и убедитесь в преимуществах офлайн AI-ассистента.







