Долгое время меня мучал вопрос - возможно ли запустить ИИшку у себя на телефоне, и если да, то какую. Я уверен, что об этом думали многие, но не понимали смысла, зачем тратить время на такого рода занятия. Чтож, я не выдержал и сделал мини-приложение, которое запускает 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
|
Компонент |
Требования |
|---|---|
|
Mac |
macOS 13 (Ventura) или новее |
|
Xcode |
Версия 15+ (бесплатно в App Store, ~25 ГБ) |
|
iPhone |
iOS 16+, минимум 4 ГБ RAM (iPhone 12 и новее) |
|
Apple ID |
Обычный бесплатный аккаунт |
Если у вас ещё нет Xcode:
Открываем App Store на Mac
Ищем "Xcode"
Нажимаем "Получить" → "Установить"
Ждём загрузки (~25 ГБ, может занять 30-60 минут)
После установки запускаем 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
Для работы с языковыми моделями используем библиотеку LLM.swift — это Swift-обёртка над llama.cpp.
В Xcode: File → Add Package Dependencies...
В поле поиска вставляем URL:
https://github.com/eastriverlee/LLM.swift
Нажимаем Enter и ждём загрузки
Убеждаемся что в "Add to Target" выбран LocalChat
Нажимаем Add Package
После добавления пакета при попытке сборки получаем ошибку:
No such module 'LLM'
Причина: Пакет добавлен в проект, но не подключен к target.
Решение:
Нажимаем на проект LocalChat (синяя иконка)
Выбираем Targets → LocalChat
Переходим на вкладку Build Phases
Раскрываем "Link Binary With Libraries"
Нажимаем +
Находим LLM (под LLM Package)
Нажимаем Add
Скорее не пишем, а клонируем мой написанный проект на GitHub. Напомню, вот ссылка - https://github.com/Chashchin-Dmitry/iPhoneLLM
LocalChat/ ├── LocalChatApp.swift # Точка входа (не меняем) ├── ContentView.swift # Обновляем ├── Message.swift # Создаём ├── ChatViewModel.swift # Создаём └── ChatView.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() } }
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) } } }
Полный код интерфейса (около 300 строк) доступен в репозитории. Основные компоненты:
LoadingView — экран загрузки модели
MessageListView — список сообщений со скроллом
MessageBubble — пузырь сообщения (разные стили для пользователя и бота)
TypingIndicator — анимация "печатает..."
InputBarView — поле ввода с кнопкой отправки
import SwiftUI struct ContentView: View { var body: some View { ChatView() } }
Модель 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
Если приложение показывает "Модель не найдена":
Выбираем файл .gguf в левой панели Xcode
В правой панели (File Inspector)
Проверяем Target Membership → галочка на LocalChat
Xcode → Settings (или Cmd+,)
Вкладка Accounts
Нажимаем + → Apple ID
Входим своим обычным Apple ID
В левой панели нажимаем на LocalChat (синяя иконка проекта)
Выбираем Targets → LocalChat
Вкладка Signing & Capabilities
Ставим галочку "Automatically manage signing"
В поле Team выбираем свой Apple ID
Если появляется эта ошибка — значит не выбран Team. Решение выше.
Подключаем iPhone к Mac кабелем USB/USB-C
Разблокируем iPhone
При запросе "Доверять этому компьютеру?" — нажимаем Доверять
Если Xcode не видит iPhone:
Отключаем и подключаем кабель
Разблокируем iPhone
Пробуем другой USB-порт
Перезапускаем Xcode
Важно! Начиная с iOS 16, Apple требует включения режима разработчика.
На iPhone:
Настройки → Конфиденциальность и безопасность
Прокручиваем в самый низ
Находим "Режим разработчика" (Developer Mode)
Включаем переключатель
Нажимаем "Перезагрузить"
После перезагрузки нажимаем "Включить"
Вводим пароль iPhone
Ошибка при запуске:
Developer Mode disabled To use iPhone for development, enable Developer Mode in Settings → Privacy & Security.
Решение: Включить режим разработчика (инструкция выше).
В верхней панели Xcode, рядом с кнопкой ▶️:
Нажимаем на выпадающий список
Выбираем свой iPhone (не симулятор!)
Нажимаем ▶️ Run (или Cmd+R)
Ждём компиляции (первый раз 1-2 минуты)
Приложение устанавливается на iPhone
При первом запуске iOS показывает "Ненадёжный разработчик".
На iPhone:
Настройки → Основные
VPN и управление устройством
Находим свой Apple ID / email
Нажимаем "Доверять"
После этого возвращаемся в Xcode и нажимаем Run снова.
Время запустить приложение и посмотреть его работоспособность. Вот как оно выглядит на 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 |
Добавил .onTapGesture для скрытия клавиатуры при нажатии на чат.
Добавил поддержку базового 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
Источник


