Введение
Долгое время меня мучал вопрос – возможно ли запустить ИИшку у себя на телефоне, и если да, то какую. Я уверен, что об этом думали многие, но не понимали смысла, зачем тратить время на такого рода занятия. Чтож, я не выдержал и сделал мини-приложение, которое запускает 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:
LocalChat -
Team: Ваш Apple ID (Personal Team)
-
Organization Identifier: любой (например
com.yourname) -
Interface:
SwiftUI -
Language:
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
Автор: Dmitrii-Chashchin


