Я запустил локальную нейросеть на iPhone 3GS. 17 миллионов параметров, iOS 6 и очень много пердолинга [М Н О Г А Б У К А В]
Спойлер: она работает. Полностью офлайн. На самом iPhone 3GS Никакого API, сервера или компьютера рядом.
Второй спойлер: она довольно тупая. Но это локально посчитанная тупость, и именно поэтому весь проект, на мой взгляд, прекрасен
С чего вообще начался этот дурдом
Некоторое время назад я занимался другим крайне полезным для человечества делом: заставлял iPhone 3GS на iOS 6 нормально общаться с современным интернетом
В какой-то момент выяснилось, что если системный стек 2012 года не умеет современный HTTPS, то это вовсе не значит, что современный HTTPS не может уметь само приложение
Так появился собственный сетевой слой через mbedTLS, современные сертификаты, ручной HTTP, оптимизации под древний процессор и прочие развлечения, после которых нормальный человек обычно идёт полежать.
Я, разумеется, подумал иначе:
А нейронку туда можно?
Не API-клиент.
Не Gemini с запросами на сервер.
Не remote inference
А именно: модель лежит в памяти самого 3GS, сам 3GS считает Transformer и сам генерирует следующий токен.
Как говорится:
мозгов у меня нет, господа, но зато есть идея.
Что у нас вообще за железо
iPhone 3GS вышел в 2009 году.
У него 256 МБ RAM, ARMv7-процессор и никакого NPU, Neural Engine, CoreML и прочих вещей, которыми современные телефоны избалованы настолько, что уже забыли, как выглядит настоящее страдание.
Поэтому первый вопрос был даже не: «Какую модель обучать?»
а: «Сколько матричных умножений этот старый кусок стекла вообще способен прожевать?»
Для этого был написан отдельный ARMv7 NEON benchmark и запущен на настоящем устройстве.
Результат оказался неожиданно бодрым: в зависимости от размера GEMV телефон показывал примерно 269–416 MMAC/s. Этого оказалось достаточно, чтобы отказаться от первоначальной идеи совсем игрушечной модели на пару миллионов параметров.
После нескольких замеров мы заморозили архитектуру.
И да. Семнадцать миллионов параметров. На iPhone 3GS.
Архитектура эта с первого рабочего релиза больше не раздувалась: идея была сначала выжать максимум из 17M, а не решать любую проблему традиционным способом нейросетчиков — «добавим ещё миллиард параметров».
CoreML отсутствует по причине того, что будущее ещё не наступило
Самое очевидное препятствие: готового inference runtime для нашей ситуации практически нет.
CoreML? Нет.
PyTorch Mobile? Очень смешно.
Современные ML-фреймворки? iOS 6 желает вам удачи.
Поэтому runtime пришлось писать самим.
Приложение состоит из Objective-C оболочки и собственного C99 decoder, который реализует весь необходимый inference:
tokenizer
↓
embedding
↓
RMSNorm
↓
self-attention + RoPE
↓
KV cache
↓
SwiGLU
↓
LM head
↓
sampling
↓
следующий token
Внутри есть byte-level BPE tokenizer, memory-mapped загрузка бинарной модели, float KV cache, RMSNorm, RoPE, causal attention, SwiGLU и собственные INT8 NEON matrix-vector kernels. Сборка идёт под ARMv7, приложение таргетит iOS 6.0 и использует старое доброе manual reference counting.
То есть условный стек выглядит не так:
а скорее:
Вот поэтому этот проект вообще существует.
Почему INT8
Если хранить 17.3 миллиона параметров в FP32: 17.3M × 4 байта ≈ 69 МБ
Технически это уже не мгновенная смерть при 256 МБ RAM, но у телефона кроме весов есть операционная система, приложение, activations, KV cache и желание хотя бы иногда не умереть от нехватки памяти
INT8 уменьшает сами веса примерно до 17.5 МБ.
Но квантизация маленькой модели — опасная тема. Когда у тебя 7B или 70B параметров, небольшая потеря точности часто терпима.
Когда у тебя 17M, у модели и без того мозгов кот наплакал.
Поэтому отдельно проверялось, не превращаем ли мы маленького долбоёба в ещё меньшего долбоёба.
На финальном checkpoint:
Экспортированный model.bin совпал с simulated INT8, а настоящий C/WASI decoder на финальном parity test совпал по top-1 и top-10 на всех 18 проверенных шагах.
Это важно по одной причине.
Если модель пишет какую-то чушь, нельзя свалить вину на древний ARM: «Наверное, iPhone неправильно считает».
Нет.
iPhone считает всё правильно.
Это мы её так научили.
Теперь самая болезненная часть — обучение
Модель решено было учить на DTF (Спасибо Карасик)
Потому что если уж на старом iPhone должна жить маленькая русскоязычная нейросеть, то пусть она хотя бы говорит как человек, который слишком долго сидел в комментариях.
В качестве основы использовался датасет DTF Comments Responses Counts.
После очистки осталось 709 499 train-пар и 6 604 validation-пары.
По сути данные выглядели так:
Казалось бы — идеально. Есть вход. Есть ответ. Обучаем. Получаем маленького русскоязычного чатбота...
А фигушки
Первая модель: DTF есть, интеллекта нет
Первый вариант прошёл 6000 шагов обычного language-model training и затем response-only этап.
И она действительно научилась очень убедительно генерировать русские предложения.
Проблема заключалась лишь в одном: на вопрос она могла вообще не отвечать.
Например
Я: Скайрим играл?
3GS-LM: Ну, если в соулсах играть только в одну игру, то это вполне нормально.
Или
Я: Ты кто?
3GS-LM: А если бы еще был тот тип, который застрял и в начале второго фильма, то я бы уже не стал.
То есть модель была великолепным генератором ответа на какой-то вопрос.
Просто не обязательно на тот, который ей задали)
А потом был найден довольно тупой баг в обучении
Stage 1 специально обучался на случайных окнах из большого packed token stream. Для обычного language modeling это нормально.
Но изначальный response-only Stage 2 использовал тот же механизм.
То есть вместо полного:
модель могла получить окно, начинающееся вот так:
Или вообще попасть в середину ответа.
При этом loss считался только на assistant-токенах.
В результате мы фактически учили её: «генерируй DTF-подобный ответ»
а не: «прочитай этот конкретный вопрос и ответь именно на него».
Что модель и делала с завидной дисциплиной.
Для исправления Stage 2 был полностью переведён на aligned pairs:
Prompt и padding имеют нулевой loss, оптимизируются только токены ответа и EOS.
Стало лучше?
Да, но не настолько, как хотелось.
После aligned Stage 2 на "Скайрим вечен?", можно было уже получить "классная игра, но я не буду её покупать", или "А я не понимаю, почему все так хейтят Скайрим"
То есть хотя бы: Скайрим → Скайрим
Это уже был огромный прогресс относительно: Скайрим → какой-то мужик из второго фильма
Но быстро выяснилась следующая проблема...
DTF-комментарии — плохой instruction dataset
Человек читает ответ: «Вот именно»
и понимает, о чём речь. Потому что видит: пост, картинку, автора, предыдущие комментарии, ещё три срача в ветке и локальный мем пятилетней давности
А можешь получает только
parent_comment → child_comment
Никакого скрытого контекста
То есть огромное количество абсолютно нормальных человеческих ответов в таком датасете превращаются для модели в: prompt → случайная реплика из параллельной реальности
И маленькая модель вполне честно это учит
Тогда появился Stage 3
Идея Stage 3 была в том, чтобы добавить действительно хорошие короткие пары: вопрос → ответ именно на этот вопрос
Причём сохранить разговорный DTF-стиль, а не превратить всё в: «Конечно! Давайте подробно разберём преимущества игры Skyrim...»
Для этого решили использовать большую teacher-модель. И вот здесь проект, который должен был закончиться за вечер, неожиданно превратился в исследование качества synthetic data.
Teacher тоже оказался долбоёбом
Сначала были протестированы несколько локальных моделей
teacher пишет смысловую чушь. judge смотрит на неё. И такой: 5/5, отличный ответ.
Например вопрос: «А что за прикол не везде демку выпускать?»
Teacher отвечает примерно: «Демка — это тестовая версия игры, официальная версия выйдет позже.»
Грамматика хорошая. Тема вроде рядом. Но вопрос был: почему демку выпускают не везде?
То есть модель красиво объяснила то, чего её вообще не спрашивали.
Если таким датасетом дообучить 17M-модель, мы буквально закрепим проблему, которую пытаемся вылечить. Поэтому автоматическому judge доверять перестали.
Бюджет проекта: 0 евро
В какой-то момент логичный вариант выглядел так: использовать хороший внешний teacher через API.
Есть только одна проблема. Денег на API не было. Бюджет проекта официально составил: 0 €
Все деньги уже ушли на подписку ChatGPT
Поэтому решение было простым: отрабатывать. Всё локально.
В финале teacher'ом стала Qwen3.5-27B Q3_K_M, запущенная локально без платных API.
Финальный Stage 3
Одних 31 teacher-пар, естественно, мало.
Поэтому финальный рецепт G был смесью нескольких специально подготовленных типов короткого dialogue/alignment-текста
Документированный набор содержит 3 922 уникальные пары и 5 893 training draws; teacher-пары намеренно oversample'ились, но повторы не выдавались за новые данные
Полный Stage 3: 600 steps
learning rate: 2e-5 → 5e-6
batch size: 32
gradient accumulation: 2
checkpoint каждые 100 шагов
И тут был довольно забавный результат.
После 400 шагов validation loss продолжал улучшаться. Но сами ответы начали местами становиться хуже
Поэтому финальным стал:checkpoint 400
Не самый красивый по loss. А тот, который лучше отвечал глазами человека.
Иногда передовая методология выглядит именно так: открыть .txt и прочитать, что этот придурок написал
Sampling тоже пришлось лечить
Даже хорошая маленькая модель может начать нести херню, если слишком свободно выбирать следующий token. Было протестировано шесть вариантов sampling.
Слишком холодные режимы часто выглядели «более тематическими», потому что начинали буквально повторять промт
Например: Скайрим вечен? Скайрим вечен? А что не так?
Или вечное: А вот и нет. А вот и нет. А вот и нет. А вот и нет...
После финального обучения победил Preset A:
На 156 development prompts он дал 0% prompt copying, 0.16% повторяющихся триграмм и 99.36% нормальных EOS-завершений. На отдельном наборе из 12 финальных prompts с тремя seed — 0% копирования, 0.12% повторов и 100% EOS
И тут нужно сделать очень важную оговорку
это НЕ значит, что 99% ответов правильные.
Это значит: модель почти всегда заканчивает ответ, редко зацикливается и не тупо повторяет вопрос.
Смысловая правильность — совсем другая история
финальный результат
После релиза я поставил v0.0.5 на реальный 3GS и просто начал задавать вопросы.
И иногда она прекрасна.
Или
Эту фразу даже придумывать для статьи не надо.
Она сама написала себе слоган!
маленький DTF внутри телефона detected.
А потом...
Ага Понятно Спасибо
Другие реальные примеры тоже очень хорошо показывают предел
Тип реплики поняла. Что речь об игре — примерно поняла. Что конкретно о GTA IV — уже не факт
Компьютерная тематика где-то рядом. Но на сам вопрос мы отвечать сегодня не будем.
Здесь 17 миллионов параметров решили взять выходной
И вот это — настоящий результат проекта Не: «мы сделали ChatGPT для iPhone 3GS». Нет. Даже близко...
Мы сделали очень маленькую языковую модель, которая умеет генерировать относительно связный русский, различает некоторые типы реплик, иногда понимает тему, иногда выдаёт хороший короткий ответ…
…а иногда смотрит на слово СКАЙРИМ и начинает рассказывать про усталость после работы.
Именно поэтому в самом релизе v0.0.5 честно написано: semantic relevance улучшилась, но потолок 17M явно остаётся виден.
Что здесь тогда вообще впечатляющего?
Самое смешное заключается в том, что сложной оказалась не та часть, которую я ожидал
Я думал, проблема будет в духе "iPhone 3GS физически не потянет Transforme"
А он такой "ну ладно, 17 миллионов параметров посчитаю"
В итоге настоящим врагом оказалось: как заставить 17 миллионов параметров понимать человеческую реплику.
То есть инженерную часть мы решили значительно лучше, чем интеллектуальную
IPhone:
- загружает модель
- держит KV cache
- считает attention
- считает SwiGLU
- гоняет INT8 NEON
- выдаёт следующий token
- не требует интернета
- не требует сервера
А сама нейронка периодически: "Киберпанк это не игра, а игра."
Это восхитительно
И нет, это не облачный интерфейс
На всякий случай. Приложение действительно полностью офлайн
Можно отключить Wi-Fi, вытащить SIM-карту, положить роутер в микроволновку — генерация продолжится
В момент, когда на экране появляется очередное слово, его только что вычислил сам iPhone 3GS.
Никакой API-запрос никуда не уходит
Никакой сервер не генерирует ответ
Самый настоящий локальный inference
И вот именно поэтому даже довольно тупой ответ выглядит потрясающе
Потому что ты смотришь на телефон 2009 года и понимаешь: эта штука сейчас реально прогнала нейронку
Если захочется повторить
Весь runtime и training pipeline лежат на GitHub:
Готовый релиз:
В репозитории есть скрипты подготовки DTF dataset, tokenizer training, Stage 1, aligned Stage 2, Stage 3 experiments, fixed-prompt evaluation, sampling-тесты, INT8 export и runtime parity. Документация отдельно описывает исправленный aligned response-training и Stage 3.
Общая схема обучения выглядит так:
Сборочная цель проекта — iPhone 3GS, iOS 6.1.6, ARMv7 + NEON, Objective-C/C99 и Theos.
Самостоятельно собрать shell IPA можно через Theos:
А затем внедрить модель и tokenizer:
Raw corpus и training checkpoints отдельно в Git не положены; в документации это объясняется отсутствием заявленной лицензии у исходной dataset card.
Почему 17M, а не 30M?
Важно: 17M — не доказанный физический предел iPhone 3GS
Это архитектура, выбранная после первых аппаратных Бенчмарков и затем замороженнаа, чтобы не менять одновременно железную и обучающую часть эксперимента.
Вполне возможно, что 20M, 25M или даже больше тоже будут работать.
Другой вопрос — с какой скоростью.
И даст ли увеличение размера достаточно качества, чтобы это было оправдано.
Это уже отдельный эксперимент
Но для v0.0.5 я решил остановиться
Потому что иначе через неделю мы обнаружим, что пишем speculative decoding под ARMv7 и обсуждаем MoE на iPhone 3GS.
А мне иногда хочется ещё и спать.
И последнее: почти весь этот проект делался через AI coding agents
Это тоже довольно забавная часть истории.
Я практически не сидел и не писал всё это вручную строка за строкой. Моя роль большую часть времени выглядела примерно так: «А давай запустим локальную нейронку на 3GS.»
Агент: «Хорошо.»
Через некоторое время: ARMv7 NEON kernels, tokenizer, decoder, training pipeline, quantization, CI.
Я: «Почему она несёт хуйню?»
Агент: обнаружил неправильный response-training, переписал dataset loader, сделал eval suite.
Я: «Она всё ещё несёт хуйню.»
Агент: скачал несколько Qwen, придумал Stage 3, teacher filtering, новые loss-варианты, pilots.
Я: СТОП ПЕРДОЛИНГ. СОБЕРИ IPA.
Вот так примерно и выглядит разработка всякого в будущем 😎
Итог
В сухом остатке:
- iPhone 3GS
- 2009 год
- 256 МБ RAM
- iOS 6.1.6
- 17 308 032 параметра
- Transformer
- INT8
- ARMv7 NEON
- 256 токенов контекста
- Полностью локально
Она не умная. Она не ChatGPT. Она периодически отвечает вообще не на тот вопрос.
Но иногда выдаёт: Мозгов немного, зато целиком помещаюсь в старый айфон
И я, если честно, не смог бы закончить этот проект лучшей фразой.
Потому что это буквально его суть.
Мозгов немного. Зато они реально работают на iPhone 3GS.