Представьте: 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-ассистента.







