Долгое время меня мучал вопрос - возможно ли запустить ИИшку у себя на телефоне, и если да, то какую. Я уверен, что об этом думали многие, но не понимали смысла, зачем тратить время на такого рода занятия. Чтож, я не выдержал и сделал мини-приложение, которое запускает Qwen / Gemma модель и общается с вами без доступа в интернет.
Задачи минимум:
1. Развернуть модель ИИ у себя на iPhone
2. Навайбкодить приложение, где можно общаться ИИ без доступа в интернет
3. Замерить потребление ресурсов моего iPhone во время работы с приложением
Что получилось в итоге
iPhoneLLM — приложение-чат, где вместо облачного ИИ отвечает модель Gemma 2B, работающая прямо на iPhone.
Характеристики:
Полный офлайн — Network = 0 KB/s (проверено в Xcode)
Скорость — 8-25 токенов/сек в зависимости от iPhone
Память — ~356 MB RAM
Модель — Gemma 2B Q4_K_M (1.5 ГБ)
Весь код проекта и инструкция для запуска: https://github.com/Chashchin-Dmitry/iPhoneLLM
Часть 1: Подготовка
Что понадобится
Компонент |
Требования |
|---|---|
Mac |
macOS 13 (Ventura) или новее |
Xcode |
Версия 15+ (бесплатно в App Store, ~25 ГБ) |
iPhone |
iOS 16+, минимум 4 ГБ RAM (iPhone 12 и новее) |
Apple ID |
Обычный бесплатный аккаунт |
Установка Xcode
Если у вас ещё нет Xcode:
Открываем App Store на Mac
Ищем "Xcode"
Нажимаем "Получить" → "Установить"
Ждём загрузки (~25 ГБ, может занять 30-60 минут)
После установки запускаем Xcode и принимаем лицензионное соглашение

Часть 2: Создание проекта
Новый проект в Xcode
File → New → Project (или Cmd+Shift+N)
Выбираем iOS → App
Нажимаем Next
Настройки проекта
Product Name:
LocalChatTeam: Ваш Apple ID (Personal Team)
Organization Identifier: любой (например
com.yourname)Interface:
SwiftUILanguage:
Swift



Часть 3: Добавление библиотеки LLM.swift
Для работы с языковыми моделями используем библиотеку LLM.swift — это Swift-обёртка над llama.cpp.
Добавляем пакет
В Xcode: File → Add Package Dependencies...
В поле поиска вставляем URL:
https://github.com/eastriverlee/LLM.swift
Нажимаем Enter и ждём загрузки
Убеждаемся что в "Add to Target" выбран
LocalChatНажимаем Add Package

Инцидент #1: "No such module 'LLM'"
После добавления пакета при попытке сборки получаем ошибку:
No such module 'LLM'
Причина: Пакет добавлен в проект, но не подключен к target.
Решение:
Нажимаем на проект LocalChat (синяя иконка)
Выбираем Targets → LocalChat
Переходим на вкладку Build Phases
Раскрываем "Link Binary With Libraries"
Нажимаем +
Находим LLM (под LLM Package)
Нажимаем Add

Часть 4: Пишем код
Скорее не пишем, а клонируем мой написанный проект на GitHub. Напомню, вот ссылка - https://github.com/Chashchin-Dmitry/iPhoneLLM
Структура файлов
LocalChat/
├── LocalChatApp.swift # Точка входа (не меняем)
├── ContentView.swift # Обновляем
├── Message.swift # Создаём
├── ChatViewModel.swift # Создаём
└── ChatView.swift # Создаём
Message.swift — модель сообщения
import Foundation
struct Message: Identifiable, Equatable {
let id = UUID()
let content: String
let isUser: Bool
let timestamp: Date
init(content: String, isUser: Bool) {
self.content = content
self.isUser = isUser
self.timestamp = Date()
}
}
ChatViewModel.swift — логика работы с LLM
import Foundation
import SwiftUI
import LLM
@MainActor
class ChatViewModel: ObservableObject {
@Published var messages: [Message] = []
@Published var inputText: String = ""
@Published var isLoading: Bool = false
@Published var isModelLoaded: Bool = false
@Published var loadingStatus: String = "Загрузка модели..."
@Published var currentResponse: String = ""
private var bot: LLM?
init() {
Task {
await loadModel()
}
}
private func loadModel() async {
loadingStatus = "Загружаю Gemma 2B..."
guard let modelURL = Bundle.main.url(
forResource: "gemma-2-2b-it-Q4_K_M",
withExtension: "gguf"
) else {
loadingStatus = "Модель не найдена в Bundle!"
return
}
bot = LLM(from: modelURL, template: .gemma)
isModelLoaded = true
loadingStatus = "Готов к общению!"
let welcome = Message(
content: "Привет! Я локальная нейросеть Gemma 2B. Работаю прямо на твоём iPhone, без интернета.",
isUser: false
)
messages.append(welcome)
}
func sendMessage() async {
let text = inputText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty, let bot = bot, !isLoading else { return }
let userMessage = Message(content: text, isUser: true)
messages.append(userMessage)
inputText = ""
isLoading = true
currentResponse = ""
let botMessage = Message(content: "", isUser: false)
messages.append(botMessage)
let responseIndex = messages.count - 1
bot.update = { [weak self] _ in
Task { @MainActor in
guard let self = self else { return }
self.currentResponse = bot.output
self.messages[responseIndex] = Message(
content: self.currentResponse,
isUser: false
)
}
}
await bot.respond(to: text)
messages[responseIndex] = Message(content: bot.output, isUser: false)
isLoading = false
}
func clearChat() {
messages.removeAll()
bot?.stop()
if isModelLoaded {
let welcome = Message(content: "Чат очищен. Начнём сначала!", isUser: false)
messages.append(welcome)
}
}
}
ChatView.swift — интерфейс чата
Полный код интерфейса (около 300 строк) доступен в репозитории. Основные компоненты:
LoadingView — экран загрузки модели
MessageListView — список сообщений со скроллом
MessageBubble — пузырь сообщения (разные стили для пользователя и бота)
TypingIndicator — анимация "печатает..."
InputBarView — поле ввода с кнопкой отправки
ContentView.swift — точка входа
import SwiftUI
struct ContentView: View {
var body: some View {
ChatView()
}
}
Часть 5: Добавление модели
Скачивание модели
Модель Gemma 2B в формате GGUF (~1.5 ГБ):
Ссылка: gemma-2-2b-it-Q4_K_M.gguf
Добавление в проект
Скачиваем файл
.ggufПеретаскиваем в Xcode в папку LocalChat
-
В диалоге ставим галочки:
✅ Copy items if needed
✅ Add to target: LocalChat
Нажимаем Finish

Инцидент #2: "Модель не найдена в Bundle"
Если приложение показывает "Модель не найдена":
Выбираем файл
.ggufв левой панели XcodeВ правой панели (File Inspector)
Проверяем Target Membership → галочка на LocalChat
Часть 6: Настройка подписи
Добавляем Apple ID в Xcode
Xcode → Settings (или Cmd+,)
Вкладка Accounts
Нажимаем + → Apple ID
Входим своим обычным Apple ID
Настраиваем подпись приложения
В левой панели нажимаем на LocalChat (синяя иконка проекта)
Выбираем Targets → LocalChat
Вкладка Signing & Capabilities
Ставим галочку "Automatically manage signing"
В поле Team выбираем свой Apple ID
Инцидент #3: "Signing requires a development team"
Если появляется эта ошибка — значит не выбран Team. Решение выше.
Часть 7: Подготовка iPhone
Подключение
Подключаем iPhone к Mac кабелем USB/USB-C
Разблокируем iPhone
При запросе "Доверять этому компьютеру?" — нажимаем Доверять
Инцидент #4: "Waiting to reconnect to iPhone"
Если Xcode не видит iPhone:
Отключаем и подключаем кабель
Разблокируем iPhone
Пробуем другой USB-порт
Перезапускаем Xcode
Включаем Developer Mode
Важно! Начиная с iOS 16, Apple требует включения режима разработчика.
На iPhone:
Настройки → Конфиденциальность и безопасность
Прокручиваем в самый низ
Находим "Режим разработчика" (Developer Mode)
Включаем переключатель
Нажимаем "Перезагрузить"
После перезагрузки нажимаем "Включить"
Вводим пароль iPhone
Инцидент #5: "Developer Mode disabled"
Ошибка при запуске:
Developer Mode disabled
To use iPhone for development, enable Developer Mode in Settings → Privacy & Security.
Решение: Включить режим разработчика (инструкция выше).
Часть 8: Запуск
Выбираем устройство
В верхней панели Xcode, рядом с кнопкой ▶️:
Нажимаем на выпадающий список
Выбираем свой iPhone (не симулятор!)
Запускаем
Нажимаем ▶️ Run (или Cmd+R)
Ждём компиляции (первый раз 1-2 минуты)
Приложение устанавливается на iPhone

Инцидент #6: "Untrusted Developer"
При первом запуске iOS показывает "Ненадёжный разработчик".
На iPhone:
Настройки → Основные
VPN и управление устройством
Находим свой Apple ID / email
Нажимаем "Доверять"
После этого возвращаемся в Xcode и нажимаем Run снова.
Часть 9: Результаты
Время запустить приложение и посмотреть его работоспособность. Вот как оно выглядит на iPhone.

Заходим и делаем первые запросы:
Первый запрос в наш локальный ИИ на iPhone:
Замеры производительности
Использовал Debug Navigator в Xcode (Cmd+7) для мониторинга:
Во время генерации ответа:
Метрика |
Значение |
|---|---|
CPU |
65% |
Memory |
355.9 MB |
Energy Impact |
Very High |
Disk |
400 KB/s |
Network |
0 KB/s |

В состоянии покоя:
Метрика |
Значение |
|---|---|
CPU |
0% |
Memory |
355.2 MB |
Energy Impact |
High |
Disk |
0 KB/s |
Network |
0 KB/s |

Ключевые выводы
Network = 0 — подтверждает полный офлайн, никаких данных не отправляется
CPU 0% в покое — не разряжает батарею когда не используется
~356 MB RAM — стабильное потребление памяти
После закрытия приложения — вся память освобождается
Скорость генерации
iPhone |
Токенов/сек |
|---|---|
iPhone 12 |
~8 |
iPhone 13 |
~10 |
iPhone 14 |
~15 |
iPhone 15 Pro |
~25 |
Часть 10: Доработки
Скрытие клавиатуры по тапу
Добавил .onTapGesture для скрытия клавиатуры при нажатии на чат.
Парсинг Markdown
Добавил поддержку базового Markdown в ответах через AttributedString:
private func parseMarkdown(_ text: String) -> AttributedString {
do {
return try AttributedString(
markdown: text,
options: AttributedString.MarkdownParsingOptions(
interpretedSyntax: .inlineOnlyPreservingWhitespace
)
)
} catch {
return AttributedString(text)
}
}
Альтернативные модели
Можно использовать другие GGUF модели:
Модель |
Размер |
RAM |
Качество |
Скорость |
|---|---|---|---|---|
Llama 3.2 1B |
0.7 ГБ |
2 ГБ |
Базовое |
Быстрая |
Gemma 2B Q4_K_S |
1.3 ГБ |
3 ГБ |
Хорошее |
Средняя |
Gemma 2B Q4_K_M |
1.5 ГБ |
4 ГБ |
Хорошее |
Средняя |
Phi-3 Mini |
2.2 ГБ |
5 ГБ |
Отличное |
Медленнее |
Скачать можно на HuggingFace.
Возможные улучшения
Я бы докрутил в приложении следующий функционал в будущем:
Добавить голосовой ввод
Сохранение истории чатов
Настройка системного промпта (чтобы модель не писала бред или в меньшем количестве)
Заключение
Ну что, мы добились цели. Теперь у нас есть карманный ИИ чат, которого можно мучать в самолете, в поезде и где вам только еще придет в голову его включить.
Моя основная цель была посмотреть, возможно ли вообще запустить LLM у себе на смартфоне и если да, то какое потребление ресурсов будет у iPhone при развертывании любой модели ИИ.
Надеюсь, я закрыл гештальт многим, кто думал, но не решался запустить ИИшку у себя на телефоне.
Буду рад за лайк и подписку на канал :) https://t.me/notes_from_cto