Уявіть: 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-асистента.







