Інтеграція локальної LLM (MLC LLM) для офлайн AI-помічника в мобільному додатку
MLC LLM (Machine Learning Compilation LLM) — проект від команди TVM, яка компілює мовні моделі безпосередньо для конкретних апаратних цілей. На відміну від llama.cpp, який працює через універсальний C++ backend, MLC генерує оптимізований Metal-код для iPhone або Vulkan для Android на час компіляції. Це забезпечує помітне прискорення — особливо на Apple Silicon.
Як MLC відрізняється від Llama.cpp
Llama.cpp інтерпретує GGUF-графи під час виконання, використовуючи Metal через загальний шлях. MLC LLM використовує AOT (Ahead-Of-Time) компіляцію: Python-скрипти генерують .metal/.vulkan шейдери, специфічні для кожної моделі та пристрою. Ціна довшої підготовки — більш ефективні шейдери.
На iPhone 14 Pro з Llama-3.2-3B Q4: llama.cpp — 10–14 t/s, MLC LLM — 16–22 t/s. Різниця помітна.
Компіляція моделей для iOS
# Встановлення mlc-llm
pip install mlc-llm
# Компіляція моделі для iPhone (Metal)
mlc_llm convert_weight \
./Llama-3.2-3B-Instruct/ \
--quantization q4f16_1 \
--output mlc-llm-weights/
mlc_llm gen_config \
./Llama-3.2-3B-Instruct/ \
--quantization q4f16_1 \
--conv-template llama-3 \
--output mlc-llm-config/
mlc_llm compile \
mlc-llm-config/mlc-chat-config.json \
--device iphone \
--output dist/libs/Llama-3.2-3B-Instruct-q4f16_1-iphone.tar
Результат — архів з .dylib та Metal шейдерами. Вбудуйте в Xcode проект.
Для Android використовуйте той же процес з --device android:
mlc_llm compile \
mlc-llm-config/mlc-chat-config.json \
--device android \
--output dist/libs/Llama-3.2-3B-Instruct-q4f16_1-android.tar
iOS SDK: Swift інтеграція
MLC LLM надає офіційний Swift Package — mlc-swift:
import MLCSwift
// Ініціалізація рушія
let engine = MLCEngine()
// Завантаження моделі (асинхронно)
try await engine.reload(
modelPath: Bundle.main.path(forResource: "Llama-3.2-3B", ofType: nil)!,
modelLib: "Llama-3.2-3B-Instruct-q4f16_1-iphone" // ім'я .dylib без розширення
)
// Потокова передача через async/await
let messages: [ChatCompletionMessage] = [
.init(role: .system, content: "You are a helpful assistant."),
.init(role: .user, content: "Пояснити, що таке RAG у машинному навчанні")
]
let request = ChatCompletionRequest(messages: messages, stream: true)
for await chunk in try await engine.chat.completions.create(request) {
if let delta = chunk.choices.first?.delta.content {
// Додайте дельту до UI в реальному часі
await MainActor.run { self.responseText += delta }
}
}
API тісно повторює OpenAI Chat Completions API — спрощує повторне використання коду між серверною та локальною реалізаціями.
Android SDK: Kotlin інтеграція
import ai.mlc.mlcllm.MLCEngine
class LLMViewModel(application: Application) : AndroidViewModel(application) {
private val engine = MLCEngine()
suspend fun loadModel(modelPath: String, modelLib: String) {
engine.reload(modelPath, modelLib)
}
fun chat(userMessage: String): Flow<String> = flow {
val messages = listOf(
ChatCompletionMessage(role = MessageRole.user, content = userMessage)
)
val request = ChatCompletionRequest(messages = messages, stream = true)
engine.chat.completions.create(request).collect { chunk ->
chunk.choices.firstOrNull()?.delta?.content?.let { delta ->
emit(delta)
}
}
}.flowOn(Dispatchers.IO)
}
flowOn(Dispatchers.IO) — інференс не повинен блокувати основний потік. UI підписується на Flow через collectAsState() в Compose або launchWhenResumed в Fragment.
Управління пам'яттю моделей
Одна модель в пам'яті одночасно — правило для мобільних пристроїв. Вивантаження:
await engine.unload()
// Явне вивантаження звільняє Metal буфери та пам'ять GPU
// Після цього можна завантажити іншу модель
На iOS пам'ять Metal — це окремий пул від системної RAM, але спільний з іншими додатками. Якщо користувач перейде до важкого додатку (гра, камера), система може примусово витіснити ресурси Metal — моделі потрібно перезавантажити.
// Обробка витіснення ресурсів Metal
NotificationCenter.default.addObserver(
forName: .MLCEngineModelUnloaded, // або власний механізм виявлення
object: nil, queue: .main
) { [weak self] _ in
Task { try await self?.engine.reload(...) }
}
Завантаження та управління моделями
Ваги моделей не вбудовуються в app bundle (обмеження App Store — 4 ГБ на весь пакет, ваги можуть бути 2–4 ГБ). Завантажуйте при першому запуску або на вимогу:
// Фонове завантаження через URLSession
func downloadModel(from url: URL, modelName: String) async throws {
let destinationURL = Self.modelsDirectory.appendingPathComponent(modelName)
guard !FileManager.default.fileExists(atPath: destinationURL.path) else { return }
let (tempURL, _) = try await URLSession.shared.download(from: url)
try FileManager.default.moveItem(at: tempURL, to: destinationURL)
}
static var modelsDirectory: URL {
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("MLCModels")
}
applicationSupportDirectory — правильне місце для великих даних додатку (не Documents, видимих користувачам в Files.app).
Коли використовувати MLC vs Llama.cpp
MLC LLM переважно коли: максимальна швидкість на конкретних пристроях важлива, цільові пристрої відомі (компіляція для конкретних архітектур), використовуються офіційні моделі HuggingFace (Llama, Phi, Gemma, Mistral).
Llama.cpp переважно коли: потрібна гнучкість у виборі квантування, моделі приходять у форматі GGUF від партнерів, потрібна підтримка старих пристроїв, потрібне користувацьке семплування (beam search, специфічні параметри температури).
Процес
Вибір моделі для задачі та діапазону пристроїв → компіляція для цілей iOS/Android → інтеграція SDK → реалізація чат-інтерфейсу з асинхронною потоковою передачею → конвеєр завантаження ваг → тестування теплових обмежень.
Орієнтири за часом
Одна платформа, одна модель, базовий чат — 3–5 тижнів. Обидві платформи, перемикання між кількома моделями, система управління вагами — 7–11 тижнів.







