Интеграция MLC LLM для офлайн AI-ассистента в мобильном приложении
Представьте: пользователь в метро открывает ваше приложение и получает развёрнутый ответ от AI-ассистента без интернета. Никаких отправок данных на сервер, никаких задержек на сеть. Это реально с MLC LLM — мы внедряли этот стек в несколько коммерческих проектов. Опыт команды — 5+ лет в мобильной разработке, более 15 успешных интеграций on-device ML.
Что такое MLC LLM и зачем он нужен
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. Разница заметна. Наш опыт показывает, что MLC даёт до 50% прироста скорости на последних моделях Apple.
Как компилировать модель под iOS и Android
Компиляция — ключевой этап. Мы подготовили базу из 10+ предварительно скомпилированных моделей, но при необходимости компилируем под конкретный таргет.
# Установка 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 — это упрощает переиспользование кода между серверным и on-device вариантом.
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) — инференс не должен блокировать main thread. UI подписывается на Flow через collectAsState() в Compose или launchWhenResumed во Fragment.
Как управлять памятью и обрабатывать вытеснение
Одна модель в памяти одновременно — правило для мобиля. Выгрузка:
await engine.unload()
// Явная выгрузка освобождает Metal bufers и GPU memory
// После этого можно загрузить другую модель
На iOS Metal память — отдельный пул от system 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 ГБ). Скачиваем при первом запуске или по запросу:
// Background download через 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, а когда llama.cpp
| Критерий | MLC LLM | llama.cpp |
|---|---|---|
| Максимальная скорость на конкретном устройстве | Да, AOT-оптимизация | Нет, интерпретация |
| Поддержка нестандартных квантований | Ограничено (q4f16_1, q4f32_1 и др.) | Широкий выбор (GGUF) |
| Старые устройства (iPhone X, Android 10) | Требуется протестировать | Часто лучше |
| Кастомный семплинг | Базовый | Возможен через C++ API |
| Простота смены модели | Нужна перекомпиляция | Достаточно нового GGUF файла |
MLC LLM предпочтительнее когда: важна максимальная скорость на конкретном устройстве, целевые устройства хорошо известны (можно компилировать под конкретные архитектуры), используете официальные модели с HuggingFace (Llama, Phi, Gemma, Mistral).
llama.cpp предпочтительнее когда: нужна гибкость в выборе квантований, модель приходит в GGUF от партнёров, важна поддержка старых устройств, нужен кастомный семплинг (beam search, специфические параметры температуры).
Почему интеграция MLC от нашей команды — гарантия результата
Мы реализовали этот стек в нескольких коммерческих проектах: от AI-помощника для логистики до офлайн-переводчика. В процессе мы:
- Оптимизировали время загрузки модели с 5 секунд до 0.5 секунды за счёт кэширования.
- Настроили автоматическую перекомпиляцию моделей под разные партнёрские устройства.
- Внедрили сквозной мониторинг падений Metal и Vulkan.
Результат — стабильная работа на 97% устройств. Мы передаём заказчику всю документацию по сборке и интеграции, а также обучаем команду.
Что входит в работу
- Анализ целевых устройств и выбор модели
- Компиляция MLC LLM под iOS и Android
- Интеграция Swift Package / Kotlin SDK
- Реализация UI чата со стримингом
- Система скачивания и кэширования весов
- Обработка вытеснения из памяти и перезагрузки
- Тестирование на тепловые троттлинг и утечки памяти
- Документация и обучение команды
Процесс работы
- Аналитика: обсуждаем задачу, парк устройств, требуемую модель и функционал.
- Проектирование: определяем архитектуру, API и flow загрузки.
- Компиляция: собираем MLC библиотеки под оба таргета.
- Интеграция: встраиваем движок в приложение.
- Тестирование: проверяем производительность, стабильность и тепловыделение.
- Деплой: помогаем с публикацией в сторах.
Ориентиры по срокам
| Объём работ | Сроки |
|---|---|
| Одна платформа, одна модель, базовый чат | 3–5 недель |
| Обе платформы, несколько моделей, управление весами | 7–11 недель |
Точную оценку дадим после предварительного интервью. Пишите — обсудим ваш проект.
Хотите получить консультацию по интеграции? Свяжитесь с нами — мы поможем подобрать оптимальное решение.







