Локальный запуск LLM на смартфоне возможен: React Native отвечает за интерфейс, llama.rn связывает JavaScript с нативным движком инференса, а GGUF-файл хранит квантованную модель. После загрузки файла приложение способно генерировать ответы на Android и iOS без обращения к облачному AI API.
Компромиссы ощутимы. Скорость зависит от чипа, свободной оперативной памяти, длины контекста и квантизации; крупная модель быстрее разрядит батарею и сильнее нагреет устройство. Для автономного чата, классификации, кратких суммаризаций и работы с приватными заметками подход практичен. Для длинных диалогов, тяжёлого кодинга и высокой скорости облачный сервис часто удобнее.
Ниже собран путь от выбора GGUF-модели до чат-интерфейса с потоковой генерацией, загрузкой файла и контролем памяти. Полезный контекст по сценариям автономных моделей есть в статье о локальных LLM на мобильных устройствах.
Запуск LLM на смартфоне: что получится собрать
Конечный результат - приложение с экраном чата, которое скачивает выбранный GGUF в локальное хранилище, создаёт контекст инференса и показывает ответ по мере генерации токенов. После полной загрузки модели интернет для диалога не нужен. Сеть остаётся нужна для первичного скачивания модели, обновлений приложения и функций, которые вы добавите отдельно.
React Native хранит историю сообщений, управляет экраном загрузки и отображает состояние генерации. Нативная часть llama.rn открывает локальный GGUF и выполняет вычисления. Модель не отправляет prompt на сервер сама по себе: это зависит от кода приложения и подключённых SDK.
Ограничения нужно закладывать в продукт сразу: файл модели занимает место в памяти устройства, контекст потребляет дополнительную RAM, длительная генерация повышает температуру корпуса. Android и iOS по-разному управляют памятью фоновых приложений, поэтому тестировать нужно на физических целевых устройствах, а не ограничиваться эмулятором.
Как выглядит поток данных в приложении
- Пользователь вводит текст в поле чата.
- Приложение добавляет сообщение с ролью
userв историю интерфейса. - Код собирает prompt по chat template конкретной модели и обрезает старую историю до допустимого контекста.
llama.rnпередаёт prompt нативному движку и начинает генерацию.- Движок возвращает токены или текстовые фрагменты.
- Интерфейс дописывает их в последнее сообщение
assistant, прокручивает список при необходимости и показывает скорость.
Самая частая архитектурная ошибка - пытаться передать URL репозитория прямо в контекст модели. Движку нужен путь к уже скачанному локальному файлу. Загрузка, проверка места и обработка сетевого сбоя живут отдельно от инференса.
Что понадобится до начала работы
- React Native-проект с возможностью собирать нативные Android- и iOS-версии.
- Android SDK, JDK и настроенный Gradle для Android.
- Xcode, CocoaPods и macOS для сборки iOS.
- Пакет
llama.rn, подключённый как нативный модуль. - GGUF-файл, совместимый с используемым движком.
- Физический смартфон или планшет для проверки памяти, нагрева и скорости.
- Менеджер файловой системы и загрузчик с поддержкой временного файла, отмены и прогресса.
Точные команды установки и параметры сборки сверяйте с документацией версии React Native и llama.rn, закреплённой в вашем проекте. Нативный модуль требует новой сборки приложения после установки. Expo Go для такого сценария обычно не подходит: нужен development build или bare-проект с подключённым нативным кодом.
Выбор модели для смартфона: размер, память и квантизация
Количество параметров - лишь первая оценка. Для смартфона важны размер GGUF-файла, рабочая память при загрузке, размер KV-кэша для контекста, число генерируемых токенов и скорость конкретного чипа. Модель, которая открылась после старта, может завершиться при длинном диалоге: контекст и история продолжают занимать память.
Практический порядок выбора такой: сначала зафиксируйте сценарий, затем проверьте ресурсы целевых устройств, возьмите компактную модель, сравните несколько квантований одной модели и измерьте результат. Не выбирайте вариант только по названию Q4 или размеру файла.
Какие характеристики смартфона важны
| Параметр | На что влияет | Что проверить |
|---|---|---|
| Оперативная память | Загрузку весов, KV-кэш, стабильность при сворачивании приложения | Свободную RAM во время работы, а не общий объём в спецификации |
| Свободное хранилище | Скачивание GGUF, временный файл, хранение нескольких моделей | Размер файла с запасом на загрузку и распаковку, если её использует ваш загрузчик |
| Архитектура и нативные библиотеки | Совместимость и скорость вычислений | ABI Android, конфигурацию iOS и поддержку в сборке модуля |
| Тепловой режим | Снижение скорости после нескольких минут нагрузки | Повторные запросы, длинную генерацию, зарядку устройства |
| Батарея | Время автономной работы и ограничения энергосбережения | Расход при серии запросов и поведение в фоне |
Размер скачанного GGUF не равен полному потреблению памяти. К весам модели добавляются контекст, служебные буферы, память JavaScript-интерфейса и ресурсы самого приложения. Длинный контекст особенно заметен: KV-кэш растёт с числом токенов истории.
Q4, K и I: как читать обозначения квантизации
Квантование уменьшает точность хранения весов ради меньшего файла и меньшего потребления памяти. Обозначение Q4 обычно указывает на низкобитный вариант около 4 бит на вес, но само имя не описывает все детали схемы. Суффиксы K и I обозначают семейства квантования или методы представления весов.
Сравнивайте Q4, K- и I-варианты только внутри одной исходной модели, с одинаковым prompt, длиной контекста и параметрами генерации. Компактный файл может запускаться легче, но хуже держать инструкции, чаще ошибаться в структурированном выводе или давать менее связный текст. Более тяжёлый вариант способен улучшить качество, но перестать помещаться в доступную RAM.
Имя файла полезно читать как набор признаков: семейство модели, число параметров, instruction-вариант, контекстные особенности и квантизация. Перед скачиванием проверьте, что выбран именно GGUF, а не файл для другого рантайма.
Как подобрать модель под конкретную задачу
- Для классификации, извлечения полей и коротких суммаризаций часто достаточно компактной instruct-модели и короткого prompt.
- Для локального чата важнее устойчивое следование инструкциям и корректный chat template.
- Для офлайн-помощника по личным заметкам заранее ограничьте длину истории и продумайте поиск по данным, иначе контекст быстро разрастётся.
- Для генерации кода проверьте модель на типичных для приложения задачах, а не на одном красивом примере.
- Для моделей с рассуждениями задайте лимит токенов выше обычного и предусмотрите отдельное отображение промежуточного текста.
Варианты на базе DeepSeek-R1 и похожие reasoning-модели могут тратить заметную часть вывода на рассуждения. Это увеличивает время ответа, расход батареи и объём отображаемого текста. Лицензию модели и условия распространения нужно проверить до публикации приложения, особенно если GGUF доступен для загрузки пользователям прямо из интерфейса.
Подключаем llama.rn к React Native
Интеграция состоит из четырёх частей: добавить пакет, собрать нативные проекты, получить локальный путь к GGUF и создать контекст. API пакета меняется между версиями, поэтому перед переносом примера в production сверьте имена методов и доступные параметры с документацией закреплённой зависимости.
Установка и настройка нативных платформ
Добавьте llama.rn в зависимости проекта выбранным менеджером пакетов. Для iOS после установки зависимостей обычно требуется обновить Pods и пересобрать приложение через Xcode или CLI. Android-проект нужно пересобрать через Gradle: JavaScript fast refresh не добавит нативную библиотеку в уже установленную сборку.
Проверьте минимальные версии SDK, ABI и настройки сборки, которые требует версия пакета. Если проект использует Expo, подготовьте development build с нативным модулем. Отдельно проверьте release-сборку: проблемы с упаковкой нативных библиотек иногда проявляются только в ней.
Загрузка GGUF-файла и создание контекста
Ниже показана структура кода. Названия initLlama, completion и release характерны для распространённого API библиотеки, но их сигнатуры нужно сопоставить с вашей версией.
import { initLlama } from 'llama.rn';
let context: Awaited<ReturnType<typeof initLlama>> | null = null;
export async function loadModel(modelPath: string) {
if (!modelPath) {
throw new Error('Не указан путь к GGUF-файлу');
}
if (context) {
await context.release();
context = null;
}
try {
context = await initLlama({
model: modelPath,
n_ctx: 2048,
n_threads: 4
});
return context;
} catch (error) {
context = null;
throw new Error(`Не удалось загрузить модель: ${String(error)}`);
}
}
export async function unloadModel() {
if (context) {
await context.release();
context = null;
}
}Перед вызовом initLlama проверьте существование файла и убедитесь, что загрузчик пометил его готовым. Путь должен вести в локальное хранилище приложения. Не храните контекст в состоянии React-компонента: его удобнее держать в сервисе или useRef, чтобы лишний рендер не менял ссылку на нативный объект.
Жизненный цикл модели в мобильном приложении
Инициализируйте модель один раз после выбора и повторно используйте контекст в диалоге. Создание контекста на каждое сообщение увеличивает задержку и риск нехватки памяти. При смене модели сначала остановите генерацию, освободите текущий контекст и только потом загрузите новый файл.
При уходе с экрана чата отменяйте активный запрос и освобождайте нативные ресурсы, если модель не нужна на других экранах. Если вы храните контекст глобально ради быстрого возврата в чат, добавьте понятные правила очистки при нехватке памяти, выходе пользователя или смене профиля.
Собираем мобильный чат-бот без интернета
Минимальный экран содержит список сообщений, поле ввода, кнопку отправки, индикатор подготовки модели, состояние ошибки и кнопку отмены. История интерфейса и prompt модели - разные сущности. В интерфейсе удобно хранить структурированные сообщения, а перед генерацией преобразовывать их в формат конкретной модели.
Состояние чата и подготовка prompt
type Role = 'system' | 'user' | 'assistant';
type Message = {
id: string;
role: Role;
text: string;
};
function buildPrompt(messages: Message[]) {
return messages
.map((message) => {
if (message.role === 'system') return `<|system|>\n${message.text}`;
if (message.role === 'user') return `<|user|>\n${message.text}`;
return `<|assistant|>\n${message.text}`;
})
.join('\n') + '\n<|assistant|>\n';
}Этот шаблон приведён как форма примера, а не как универсальный prompt. Каждая модель может использовать собственные служебные токены, роли и правила окончания сообщения. Неподходящий chat template часто выглядит как «глупая» модель: она повторяет роли, продолжает вопрос пользователя или игнорирует системную инструкцию.
Перед запуском генерации обрежьте пробелы во вводе. Заблокируйте повторную отправку, пока идёт ответ. При ошибке оставьте вопрос пользователя в истории и покажите, что можно повторить запрос после освобождения памяти или смены настройки.
Генерация по токенам вместо ожидания целого ответа
Потоковая генерация даёт пользователю ранний сигнал, что модель работает. Создайте пустое сообщение assistant, затем дописывайте фрагменты в его текст. Обновления стоит объединять в короткие пакеты, иначе частые рендеры списка сообщений начнут конкурировать с инференсом.
let cancelled = false;
async function sendMessage(prompt: string, appendToken: (text: string) => void) {
cancelled = false;
const startedAt = Date.now();
const result = await context?.completion({
prompt,
n_predict: 256,
onToken: (token: string) => {
if (!cancelled) appendToken(token);
}
});
return {
result,
elapsedMs: Date.now() - startedAt
};
}
function stopGeneration() {
cancelled = true;
// Вызов отмены зависит от API версии llama.rn.
}Отмена должна прерывать нативную генерацию, а не только переставать обновлять текст на экране. Используйте сигнал отмены или метод остановки, который предоставляет ваша версия библиотеки. После размонтирования компонента запретите обновление React state: активный callback может прийти уже после перехода пользователя на другой экран.
Автопрокрутка и обработка длинных ответов
Автопрокрутка нужна, пока пользователь читает конец чата. Если он прокрутил список вверх, новые токены не должны возвращать его вниз принудительно. Храните флаг близости к нижней границе списка и показывайте компактную кнопку перехода к последнему сообщению.
Для длинных ответов применяйте виртуализированный список и стабильные идентификаторы сообщений. Не пересоздавайте весь массив объектов на каждый токен, если библиотека списка начинает терять позицию. Ограничивайте историю, передаваемую модели, отдельно от истории, которую пользователь видит в интерфейсе.
Загружаем модели с Hugging Face и показываем прогресс
GGUF-файлы часто весят гигабайты, поэтому модель лучше доставлять отдельной загрузкой после установки приложения. Такой подход уменьшает размер первоначальной установки и позволяет предложить несколько вариантов квантизации. Он требует надёжного менеджера загрузок.
Как определить, какой файл скачивать
В каталоге модели сопоставьте имя файла с выбранным семейством, instruction-вариантом и квантизацией. До старта загрузки покажите пользователю имя файла, ожидаемый размер и объём свободного места. Если модель состоит из нескольких частей, загрузчик должен скачать полный набор и не пытаться открыть только первую часть.
Проверяйте совместимость GGUF с используемой версией движка, источник файла и лицензию. Названия репозиториев и конкретных файлов меняются, поэтому их нужно перепроверить непосредственно перед выпуском приложения.
Прогресс, ошибки и неполная загрузка
- Скачивайте файл во временный путь, например с суффиксом
.part. - Показывайте полученный объём, полный размер и процент, когда сервер сообщает длину файла.
- Дайте пользователю отменить загрузку.
- При ошибке удаляйте временный файл или явно помечайте его как непригодный для запуска.
- После успешной загрузки переименовывайте файл в постоянный путь.
- Проверяйте наличие готового файла перед созданием контекста.
Не считайте файл рабочим только потому, что он существует. Неполная загрузка может оставить файл с правильным именем и привести к ошибке инициализации. Для повторного скачивания предусмотрите удаление повреждённой копии и освобождение места.
Оптимизация под слабое железо
Рабочий прототип на флагманском смартфоне ещё не гарантирует приемлемый опыт на целевой аудитории. Снижайте нагрузку измеримо: меняйте один параметр, фиксируйте метрики и сравнивайте одинаковые запросы. Связь между размером модели, контекстом и скоростью нелинейна.
Как измерять скорость инференса
- Время до первого токена: задержка между отправкой и первым видимым фрагментом ответа.
- Скорость генерации: число токенов в секунду после старта вывода.
- Полное время ответа: от нажатия кнопки до завершения.
- Длина входного prompt и объём сгенерированного текста.
- Потребление памяти и поведение после нескольких диалогов подряд.
- Температура устройства и признаки троттлинга при длительной нагрузке.
Фиксируйте модель, квантизацию, настройки контекста, лимит генерации и устройство. Без этой записи сравнение превращается в впечатление, которое трудно повторить. Базовый счётчик можно считать как число полученных токенов, делённое на время между первым и последним токеном.
Память, контекст и температура
Первый рычаг для слабого устройства - уменьшить модель или выбрать более компактную квантизацию. Второй - ограничить n_ctx и число новых токенов. Третий - удалять из prompt старые сообщения, которые уже не влияют на текущий вопрос.
Не держите одновременно несколько открытых контекстов, если продукту не нужны параллельные модели. Освобождайте предыдущую модель до загрузки новой. Во время долгого ответа покажите состояние генерации и кнопку остановки: пользователь не должен перезапускать приложение из-за ощущения зависания.
Троттлинг часто заметен после серии запросов, зарядки и работы в тёплой среде. Тестируйте такие условия отдельно. Приложение должно корректно переживать замедление, а не обещать фиксированную скорость на всех смартфонах.
Отображение мыслей моделей вроде DeepSeek-R1
Некоторые модели выводят рассуждение, специальные токены или отдельную финальную часть. Формат зависит от модели, её chat template и того, как движок возвращает текст. Нельзя заранее считать, что каждая reasoning-модель размечает вывод одинаково.
Сначала сохраните сырой поток токенов в диагностическом режиме и проверьте реальный формат на выбранной модели. После этого можно отделять распознанные секции в сворачиваемый блок «Ход рассуждений» и показывать финальный ответ отдельно. Если разметка нестабильна, выводите единый текст: неверный парсер хуже отсутствия декоративного блока.
Типичные ошибки и проверка перед публикацией приложения
Большинство проблем локального чата связаны не с React Native-разметкой, а с файлом модели, памятью, prompt и жизненным циклом нативного контекста. Логи нативного слоя нужны с первого дня разработки: по JavaScript-ошибке редко можно понять, почему движок не открыл GGUF.
Диагностика проблем с моделью
| Симптом | Вероятная причина | Проверка |
|---|---|---|
| Контекст не создаётся | Повреждённый файл, неподдерживаемый GGUF, неверный путь | Размер файла, статус загрузки, логи нативного слоя, совместимость движка |
| Приложение закрывается при запросе | Нехватка памяти или слишком большой контекст | Уменьшить модель, n_ctx и лимит новых токенов |
| Ответ повторяет prompt | Неподходящий chat template | Сверить роли, служебные токены и стоп-последовательности модели |
| Интерфейс зависает | Тяжёлые обновления списка на каждый токен | Батчить обновления, использовать виртуализацию, профилировать рендер |
| Смена модели ломает чат | Старый контекст не освобождён | Остановить генерацию, вызвать освобождение ресурсов, затем создать новый контекст |
В каждом отчёте об ошибке записывайте модель, квантование, размер файла, устройство, версию ОС, параметры контекста и длину prompt. Эти данные позволяют отличить дефект приложения от аппаратного лимита.
Что учесть при распространении приложения
Модель увеличивает размер дистрибутива или создаёт долгую первую загрузку. Оба варианта требуют понятного UX: укажите размер файла, место хранения, возможность удалить модель и понятный статус готовности. Не храните историю переписки бессрочно без явного решения пользователя.
Локальная обработка снижает объём данных, покидающих устройство, но не отменяет защиту пользовательской информации. Ограничьте доступ к файлам приложения, продумайте удаление истории и моделей, проверьте разрешения на хранение данных и правила магазинов приложений. Лицензия GGUF или исходной модели может ограничивать коммерческое распространение.
Для понимания базового движка и причин популярности формата GGUF пригодится разбор экосистемы llama.cpp и запуска моделей на CPU. Компактные модели для агентных сценариев на устройстве разобраны в материале о LFM2.5-2.6B на смартфонах.
Итоги: когда локальная LLM на смартфоне оправдана
React Native вместе с llama.rn позволяет собрать автономный чат с GGUF-моделью для Android и iOS. Путь предсказуем: определить задачу и целевые устройства, выбрать модель и квантизацию, скачать файл в локальное хранилище, создать контекст, организовать потоковый вывод и измерить поведение под нагрузкой.
Подход оправдан, когда нужны приватность, автономность, предсказуемые расходы без AI API и работа без сети. Он плохо подходит для самых крупных моделей, длинного контекста и сценариев, где критична высокая скорость ответа. Размер модели, лимит контекста и качество интерфейса здесь важнее эффектной демонстрации первого запуска.
Минимальный чек-лист рабочего прототипа
- Android- и iOS-сборки запускаются на физических устройствах.
- GGUF скачивается в локальное хранилище с индикатором прогресса.
- Неполный файл не передаётся в инициализацию модели.
- Контекст создаётся по локальному пути и освобождается при смене модели.
- Один запрос даёт потоковый ответ без подключения к AI API.
- Повторная отправка блокируется во время генерации.
- Пользователь может отменить активный ответ.
- История prompt ограничена, а длинный чат не ломает прокрутку.
- Ошибки памяти и загрузки видны пользователю и записываются в диагностику.
- После скачивания модели чат работает в авиарежиме.