Мы измерили, где на ESP32-S3 теряется время, и переписали выполнение языковой модели под этот чип. На той же плате и тех же весах наш движок выдаёт тот же текст, что и esp32-ai, в несколько раз быстрее. А собственные модели, устроенные под этот чип, пишут лучше их модели при той же скорости.
Тексты совпадают до символа: 759 из 759. Разница только во времени: 18,8 с против 2,3 с.
Чтобы написать одно слово, модель должна прочитать все свои веса. У чипа три вида памяти с разницей в скорости почти в 10 раз, а векторный блок PIE считает в 21 раз быстрее обычного кода. После его включения процессор в основном ждёт данные.
Отсюда правило, которое определило всю работу: ускорение = меньше байт на каждое слово. Байты на слово = бит на вес × доля читаемых весов ÷ слов за одно чтение. Каждый приём ниже уменьшает один из этих множителей.
Модель затрат по операциям: чтение каждой памяти плюс вычисления. На чужих замерах, которых модель не видела при подгонке, ошибка 3–5% (всего две проверочные точки); на нашем движке она занижает время на 10–14%. Он позволил отбрасывать идеи на бумаге до написания кода.
Собственная прошивка-бенчмарк: скорость SRAM, PSRAM и Flash, ядра PIE, DMA. Выводы: PIE в 21 раз быстрее простого C (в ~13 раз быстрее ядра esp32-ai); после него узкое место — PSRAM (83 МБ/с на двух ядрах); шина PSRAM даёт ~90 МБ/с на всех, поэтому DMA не добавляет пропускной способности.
27 моделей на ноутбуке, каждый сравниваемый вариант в двух запусках (кроме одного повторного 2-битного). Справочник PLE делает модель умнее при том же объёме чтения; ReLU позволяет пропускать 79% нейронов без потерь; обучение под 4 бита почти бесплатно; 4–6 слоёв лучше 12.
C-код, общий для ПК и платы, плюс ассемблерные ядра PIE. Три версии: 70 → 98 → 110 токенов/с на нашей модели. Каждое изменение измерено отдельно; неудачные откатаны и записаны.
Их опубликованные веса без переквантования. Их прошивка и наш движок на одной плате, на их же тесте: 94,1 против 11,5 мс на токен, текст совпадает полностью.
Угадывание следующих слов на этом чипе не окупается (отрицательный результат). Пакетная обработка заранее известных слов (промпт, однозначные куски JSON) даёт ×1,5–2,8.
Модель затрат, откалиброванная на плате, предсказала скорость вариантов с точностью 1–5%. Лучший вариант (словарь 8192, справочник вдвое шире, 8,6 млн параметров) после обучения на 70 млн токенов даёт 0,63 бита на байт против 0,86 у модели esp32-ai на 29 млн; наша модель на 3,4 млн лучше её при почти той же скорости.
| Приём | Качество (val loss) | Скорость (плата или модель затрат) | Итог |
|---|---|---|---|
| Справочник PLE: таблица на слово во Flash, читается одна строка | −0,022…−0,039 | +0,5 мс, плата | работает |
| ReLU вместо SwiGLU и пропуск молчащих нейронов (79%) | −0,002 | ×1,3, плата | работает |
| Обучение сразу под 4 бита (QAT) | +0,024 | ~7× меньше байт, чем fp32 (с масштабами) | работает |
| Эксперты MoE (4 эксперта, читается 1) | −0,009 | ≈, модель затрат | слабо |
| 2 бита при том же размере (при вдвое большем числе весов и том же объёме чтения: +0,13) | +0,305 | ×1,5, модель затрат (на плате не запускали) | дорого по качеству |
| 12 слоёв вместо 6 | +0,069 | −15%, модель затрат | хуже по обоим |
Итоговая модель combo = PLE + ReLU + 4 бита: val loss 2,010 против 2,024 у обычной 4-битной (среднее двух запусков, лучше в обоих), и на 22% быстрее её на плате (98 против 80 ток/с, движок v2).
Модели с разными словарями сравниваются в битах на байт текста: сколько информации модели не хватает, чтобы предсказать каждый байт. Первые 5000 отложенных историй, каждая модель со своим токенизатором и со своими 4-битными весами, как их выполняет движок. Скорость — среднее по всему контексту 256 на нашем движке (двухступенчатый выбор слова, PSRAM 80 МГц).
| Модель | Параметров | Бит на байт | ток/с (среднее, плата) |
|---|---|---|---|
| esp32-ai TinyStories (их открытая модель) | 28,9 млн | 0,863 | ~94 |
| combo (наша: PLE + ReLU + 4 бита) | 3,4 млн | 0,732 | ~91 |
| справочник PLE вдвое шире | 4,9 млн | 0,716 | ~84 (прошлая версия движка) |
| словарь 8192 + справочник вдвое шире | 8,6 млн | 0,704 | ~81 |
| то же, обучение на 70 млн токенов вместо 20 | 8,6 млн | 0,625 | ~81 |
| словарь 16384 + справочник вдвое шире | 16,0 млн | 0,706 | — (прогноз ~74) |
Проверка честности: наша оценка их модели (2,45 нат на токен) совпадает с тем, что авторы сами публикуют (перплексия 11,4 до квантования, «~+1» после). У их модели почти все параметры — словарь на 32 тыс. токенов и справочник, а ядро всего 0,56 млн; на детских историях такой словарь почти не используется, а наш вариант со словарём 16384 показал, что справочник насыщается. Их модель мы запускаем только на нашем движке: их прошивка выполняет её со скоростью 10,6 ток/с и наши модели запустить не может.
Веса хранятся в 4 битах и распаковываются векторными командами. У esp32-ai 8 бит и обычный C: вдвое больше байт и в ~13 раз больше тактов на операцию (6,2 против 0,47).
Двухступенчатый выбор слова: 256 групп слов, сначала оценка групп, потом полный расчёт только лучших. Совпадает с полным выбором в 99,4–99,8% позиций; прочитанные строки побитово равны полному расчёту.
Каждое умножение делится пополам между ядрами, второе ядро ждёт работу в активном цикле: передача задачи стоит 83–93 такта.
Сначала буферы движка, затем самые читаемые веса в быструю внутреннюю память (~210 КБ), остальное в PSRAM, таблица PLE остаётся во Flash.
ReLU «сначала ворота»: считаем ворота, а две другие матрицы читаем только для живых нейронов. Новое ядро на аккумуляторе QACC для транспонированной матрицы.
Работа вне умножений: квантование, GELU по таблице, выбор групп. По профилю, а не наугад, сжали её с 2,2 до 1,2 мс на токен.
Прошивку esp32-ai мы собрали их способом (arduino-cli, -O3, их раздел Flash) и измерили на нашей плате. Отпечаток модели a9bdd778 совпал с их метаданными, три прогона дали одно и то же значение. Их собственная опубликованная цифра — 94,9 мс, наш замер — 94,1 мс.
| Движок | Выбор слова | PSRAM | мс/токен | ток/с | Ускорение |
|---|---|---|---|---|---|
| esp32-ai | полный, 8 бит | 80 МГц | 94,10 | 10,6 | 1× |
| наш | полный, 4 бита | 80 МГц | 25,00 | 40,0 | 3,8× |
| наш | двухступенчатый | 80 МГц | 11,52 | 86,8 | 8,2× |
| наш | полный | 120 МГц* | 18,90 | 52,9 | 5,0× |
| наш | двухступенчатый | 120 МГц* | 9,17 | 109,0 | 10,3× |
* PSRAM на 120 МГц в ESP-IDF помечена как экспериментальная: «работает при стабильной температуре». Замер при комнатной температуре, сбоев не было.
| Идея | Ожидание | Замер | Почему |
|---|---|---|---|
| Угадывать 2–4 следующих слова и проверять за одно чтение | ×1,3 по калькулятору | ×0,8–1,05 | после оптимизаций треть времени — вычисления на каждое слово; угадчик даёт лишь 1,4–1,6 слова за проход |
| DMA подгружает веса, пока ядро считает | −30% | не быстрее | шина PSRAM даёт ~90 МБ/с на всех; DMA (77 МБ/с) только делит её с ядрами, а два ядра сами читают 83 |
| Масштабы весов в 16 битах вместо 32 | −6% байт | +5% времени | перевод 8192 чисел за слово дороже сэкономленного чтения |
| Держать последние значения внимания в SRAM | −1 мс | −0,06 мс внимание | тормозили не записи, а задержки чтения PSRAM (всего вышло −0,39 мс, остаток не объяснён) |
| Веса из генератора «зёрен» (SeedLM), сжатие весов | выигрыш на GPU | проигрыш на бумаге | на чипе нет лишних тактов: распаковка дороже чтения |
| Проект | Где работает модель | Скорость на ESP32-S3 | Что есть | Чего нет |
|---|---|---|---|---|
| Этот проект | ESP32-S3, полностью автономно | 87–110 ток/с (29 млн), 110–134 (2 млн); в конце контекста ~72 | PIE-ядра, 2 ядра, двухступенчатый выбор слова, разреженный FFN, пакеты, модель затрат, побитовая проверка | прикладная модель (пока детские рассказы) |
| slvDev/esp32-ai | ESP32-S3 | 10,6 ток/с (измерено нами) | модели TinyStories и Barista, PLE | PIE, 4-битное выполнение, пропуск работы |
| esp32-tinyLLM (Rust) | ESP32-S3 | ~10,5 ток/с (их данные) | методика бит-в-бит проверок | векторный блок, большие выигрыши по байтам |
| Порты Needle (вызов команд) | ESP32-S3 | ~1,5 ток/с (их данные) | вызов инструментов по схеме | скорость |
| Deeploy (компилятор) | RISC-V MCU с NPU | — | тайлинг, DMA, малые LLM | ESP32 / Xtensa PIE |
На тех же весах и с тем же текстом: 3,8× при одинаковом расчёте и 8,2× с двухступенчатым выбором слова. Других открытых движков с PIE для LLM мы не нашли.
Сравнение устройств моделей при одинаковом объёме чтения на токен и проверка на плате. Наша модель на 3,4 млн параметров пишет лучше открытой модели esp32-ai на 29 млн при почти той же скорости на нашем движке (0,73 против 0,86 бита на байт), а лучшая – 0,63.
Двухступенчатый выбор слова, пропуск неактивных нейронов и пакетная обработка известных токенов на микроконтроллере в найденных источниках не встречались. Мы показали, какие из них работают, а какие вредят, и почему.
Модель затрат с проверкой на чужих данных, побитовое совпадение ПК и платы, проверка каждого ядра против эталона на C, повтор каждого эксперимента.
«Не нашли» означает поиск по GitHub, Hugging Face, arXiv и патентам; это не доказательство отсутствия.
ПК, плата с PIE на двух ядрах, на одном ядре и на обычном C дают одинаковые отпечатки всех токенов и всех логитов в каждом прогоне.
Ошибка модели на отложенном тексте в движке отличается от PyTorch не больше чем на 0,003. Выбор слова совпадает в 98–99% позиций; расхождения только на почти равных вариантах.
Разброс между повторами одного и того же прогона. Это не погрешность по разным текстам: от начала к концу контекста скорость падает в 1,6 раза. Все замеры сохранены в логах в репозитории.
Плата с моделью esp32-ai и нашим движком подключается к любому компьютеру по USB. Страница в Chrome или Edge подключается к плате напрямую, без установки программ. Вы пишете начало истории, плата дописывает её в реальном времени, на экране видна скорость каждого токена.
Все модели учились на детских рассказах. Для историй лучше всего наша модель со словарём 8192 (varC); модель esp32-ai тоже поддерживается; дообученная combo_qa ещё отвечает на простые вопросы по сказке (режим «Вопрос по сказке», docs/qa-finetune.md). Модель меняется прошивкой одного файла: engine/demo/flash_demo.sh.
ПК → G 120 700 40 <seed> 0 7 433 447 259 405 … плата → P 29.6 плата → T 11 10320 плата → T 612 10288 … плата → E 120 10.32
Модели маленькие (3–29 млн параметров) и обучены на детских рассказах, 20 млн токенов на модель, один LR для всех; сравнение качества с esp32-ai — на тексте нашего распределения (их оценка при этом совпала с их собственной); скорость зависит от длины контекста («110 ток/с» — позиции 13–60; сравнение с esp32-ai — на их тесте, позиции 4–203); режим PSRAM 120 МГц экспериментальный; энергия на токен не измерялась, второе ядро занято активным ожиданием; версия ядра Arduino при замере esp32-ai — 3.3.3 (у авторов 3.3.10).