WorldTUI: 360M-модель, которая притворяется терминалом
Наберите pythoon, и Python запустится. Наберите ython, и не произойдёт ничего. Разница в одну букву выдаёт главное: перед вами не оболочка, а модель на 360 млн параметров, которая предсказывает, как должен выглядеть экран после каждого нажатия клавиши. Ни shell, ни файловой системы, ни единого настоящего файла внутри нет.
Саймон Эдвардссон, соучредитель компании V7, собрал этот проект один, на одной видеокарте RTX 4090. Он хотел проверить гипотезу, которую сформулировал ещё после выхода Genie 3: приложения будущего не пишут кодом, их учат. Вместо игрового мира он взял терминал, потому что тот состоит из символов, а не из пикселей.
WorldTUI это языковая модель, обученная предсказывать изменения экрана терминала вместо выполнения команд. Она получает нажатия клавиш как байты и отвечает, какие строки перерисовать и куда поставить курсор. Получается world model для текстового интерфейса: среда живёт только в весах модели.
Почему терминал оказался удобной мировой моделью
У Эдвардссона не было ни кластера, ни горы видеоданных, поэтому амбицию пришлось уменьшить: не игровой мир, а консоль. Терминал для такой задачи подходит почти идеально. Это сетка символов, а не пикселей, и размеченные данные можно генерировать в неограниченном объёме, просто запуская настоящие программы в контейнерах. Языковая модель к тому же уже неплохо представляет, что должна вывести команда ls или cat.
Главный вопрос, который он себе задал, звучал не «сможет ли модель имитировать вывод команды», а «сможет ли она удержать согласованный мир». Разница принципиальная: правдоподобный текст терминала генерируется легко, а вот файл, созданный пять команд назад, должен остаться тем же файлом.
Как модель видит экран
Обычная языковая модель дописывает токены в конец последовательности, и для терминала это не работает. Одно нажатие может сделать что угодно: Esc в vim переключает режим, clear стирает экран и уводит курсор в начало, вывод появляется не всегда внизу. Поэтому предсказывать нужно не поток текста, а изменения экрана.
Сессия начинается со снимка: размер экрана, его содержимое и позиция курсора. Размер входит во входные данные, а не зашит как 32 на 96, поэтому демо умеет стартовать посреди приложения. Дайте модели буфер vim или партию в snake, и она попробует продолжить с этого места, хотя лучше всего у неё получается старт с приглашения bash.
Дальше каждое нажатие передаётся как сырые байты, которые отправил бы настоящий терминал: буква это сама буква, Enter это \r, Escape это 0x1b, стрелка влево это ESC [ D. Ответ содержит, какие строки перерисовать и где окажется курсор. Ничего предварительно не очищается, новые символы просто рисуются поверх старых.
Прокрутка стала отдельной задачей. Когда курсор упирается в низ и вы жмёте Enter, каждая строка сдвигается на одну вверх. Если просить модель выводить все строки заново, одно нажатие стоит около трёх тысяч токенов. Вместо этого появилась операция COPY: три токена на строку вместо примерно 96. Тот же приём закрывает очистку экрана, вставку и удаление строк в vim, листание man-страниц.
Словарь получился крошечным: 333 токена, из них 256 байтовых, 64 для строк и несколько служебных. Декодирование ограничено грамматикой, поэтому любое предсказание заведомо является корректным обновлением экрана. Этот же маленький словарь позже позволил засунуть модель в браузер.
Данные: 336 тысяч сессий из Docker
Готового датасета с точным соответствием между нажатием и состоянием экрана не нашлось, в asciinema такой разметки нет. Пришлось генерировать самому: настоящие команды выполнялись в контейнерах Docker, а каждый шаг записывался вместе с экраном после него.
Рецепт синтетики выглядит продуманно. Имена пользователей, хосты, стили приглашения и размеры терминала рандомизируются, каталоги и файлы строятся под вымышленную роль вроде разработчика или системного администратора, затем выбирается от 6 до 24 взвешенных активностей: правка файлов, vim, Python, tmux. Состояние отслеживается, чтобы сессия в основном открывала существующие файлы. Печатается как человек, по одному нажатию, с автодополнением по Tab и долей опечаток 1,2 процента. Треть сессий начинается в середине, чтобы модель училась работать из любого состояния. Размер в основном 32 на 96, но встречаются 24 на 80 и 40 на 120, иначе модель переобучится под одну геометрию.
В итоге набралось 336 тысяч эпизодов, примерно 2 млрд токенов. Базовая модель за время своей подготовки видела около 18 трлн, то есть дообучение шло на одной девятой процента этого объёма.
Как модель учится исправлять свои ошибки
Стартовая точка, Qwen2.5-Coder-0.5B, подходила по двум причинам: она маленькая, а значит есть шанс на близкую к реальному времени работу, и она кодовая, то есть уже видела много консольных сессий. Обычное дообучение быстро освоило простые вещи: буква появляется, backspace её убирает, курсор сдвигается.
Проблема началась в длинных сессиях. Ошибка в имени файла тянулась через весь остаток сессии, и экран уезжал всё дальше от реальности. Решение Эдвардссон взял из подхода DAgger, где модель учат на состояниях, которые она посещает сама, а не только на идеальных демонстрациях.
Механика такая: берётся точка в записанной сессии, модель предсказывает от одного до четырёх следующих событий, результат сравнивается с настоящим экраном. Если расхождение небольшое, строится коррекция, которая чинит экран следующим нажатием, и обучение продолжается уже на исправленном кадре. По сути модель учится ремонтировать собственные ошибки.
Ограничение здесь важнее самой техники. Коррекция должна быть выводима из информации, которая уже появлялась на реальном экране или в наборе пользователя. Если модель переврала имя файла, которое раньше было на экране, её можно научить это исправлять. Если она придумала файл, которого никогда не существовало, учить её «исправлению» нельзя, потому что так обучение начинает поощрять галлюцинации.
Примерно треть сэмплированных сессий давала полезные примеры коррекции, и цикл повторили три раунда, каждый раз собирая ошибки свежей модели. Вместе с дополнительными данными это подняло PresetBench с 24 из 47 до 38 из 47 в режиме int4. Всё обучение и последующие итерации заняли около 75 часов на RTX 4090.
Что показали тесты
Тестов три. ShellBench содержит 106 команд в 24 написанных вручную bash-сессиях и оценивается по точному выводу. PresetBench это 47 сценарных случаев, собранных из реальных провалов в демо. Тест памяти состоит из 32 «миров»: каждый записывает два случайных значения в два файла, а потом читает их обратно в отдельных сессиях, зачёт только при точном совпадении обоих значений, всего 64 проверки. Ни один из тестов не встречается в обучающих данных.
Разница с обычным промптингом видна сразу. Qwen2.5-Coder-0.5B без дообучения набирает 52 из 106 в ShellBench и 2 из 13 в чтении файлов, Qwen3-0.6B даёт 60 и 3. WorldTUI показывает 80 из 106 и 9 из 13. Самый большой разрыв именно в памяти: 9 точных чтений против 2 и 3 у промптнутых моделей.
До надёжной файловой системы при этом далеко. Знакомые команды работают неплохо, а незнакомые комбинации ломаются быстро: после echo green colors команда cat colors часто не показывает добавленную строку. Модель выучила статистику терминала, но не правила файловой системы.
Строки считаются параллельно
Поначалу Эдвардссон вообще не хотел брать авторегрессионную модель: полная перерисовка это около трёх тысяч токенов, и генерировать их по одному для интерактивной работы казалось безнадёжным. Но альтернативы работали заметно хуже, и он сдался. На одно нажатие уходило около 38 мс, что для набора вполне терпимо, а вот всё, что перерисовывает экран целиком (vim test.txt, man, clear), занимало до 12 секунд.
Ключевое наблюдение оказалось простым: большинство строк в кадре не зависят друг от друга, и генерировать их последовательно незачем. Модель запускает по одному элементу батча на строку, все элементы получают одну и ту же историю вместе с KV-кэшем, но каждый генерирует только своё обновление. Стоимость декодирования падает с суммы всех строк, до трёх тысяч шагов, до длины самой длинной строки, примерно ста шагов. Курсор добавляется отдельным шагом в конце, когда он уже видит все обновления. Для этого пришлось сделать соответствующую маску внимания при обучении, зато p95 задержки на больших кадрах упала с 6,7 до 0,87 секунды.
Ускорение принесло и побочный эффект. Раз строки друг друга не видят, они не могут договориться: vim открывался на третьей строке вместо нулевой, snake получала несколько яблок одновременно. Лечится это спекулятивным декодированием, только в необычной роли. Обычно маленькая модель предлагает черновик, а большая его проверяет. Здесь черновик дают параллельные строки, а проверяет та же самая модель, последовательно. Верификатор оставляет токены до первого расхождения, сам генерирует исправление и предлагает остаток заново. На M4 в браузере один символ набирается примерно за 60 мс, а man test укладывается в 0,9 секунды вместо 2,5.
208 МБ в браузере
Изначально в Qwen2.5-Coder-0.5B было около 494 млн параметров, и для веб-демо это слишком много. Почти треть веса занимала таблица эмбеддингов на 151 936 токенов, 136 млн параметров, а модели нужно всего 333 токена. Выбрасывание лишнего уменьшило модель до 360 млн параметров и с 990 до 716 МБ, а квантизация в int4 сжала файл до 208 МБ.
Демо работает на собственной реализации декодера Qwen на JavaScript. Вычислительные шейдеры написаны на WGSL и выполняются через WebGPU, веса распаковываются из 4 бит прямо во время инференса, активации и кэш внимания остаются в 32-битных числах с плавающей точкой. Всё считается локально в открытой вкладке, без запросов на сервер.
Что показала дистилляция
Сжать модель по глубине тоже пробовали, и этот эксперимент оказался самым интересным. Учитель набирал 78 из 106 в ShellBench. Когда из него убрали седьмой слой без переобучения, точность предсказания следующего токена почти не изменилась: 99,3 процента превратились в 98,9. А вот точные чтения памяти обвалились с 64 из 64 до нуля. Тесты при этом подавали настоящую историю, так что дело было не в накоплении ошибок.
Первый ученик сохранил чётные слои, но взял последний слой 23 вместо 22. Он писал правдоподобный вывод терминала, но правильное значение вернул только в 31 случае из 64. Причём всегда печатал одно из двух записанных значений, то есть угадывал наудачу, и за 480 млн токенов обучения так и не восстановился.
Второй ученик на 16 слоёв, сохранивший слои 1, 7, 15 и 22, прошёл все 32 мира памяти с 64 точными чтениями из 64 и весил около 138 МБ в 4 битах. Но в ShellBench он дал 65 команд против 78 у учителя. Ученик может выглядеть нормально и тихо потерять память, а по агрегированной точности этого не увидеть. В итоге от дистилляции автор отказался.
Что работает и что ломается
Лучше всего работает именно память. Сгенерировать правдоподобный листинг каталога легко, а вот создать файл, отредактировать его, уйти в другое место и вернуться к неизменённому содержимому уже сложно. Момент, когда он открыл tmux, поправил файл в vim, сохранил и прочитал обновлённое содержимое в другом окне через cat, автор сравнил с доктором Франкенштейном.
vim переключает режимы по Esc, сохраняет, выходит и возвращается во вставку. tmux позволяет создавать панели, работать в каждой и прыгать между ними. Переменные окружения и подстановки переменных тоже подхватываются. Python REPL отвечает прилично на простых операциях, list comprehension возвращает список, правда часто с не тем содержимым. pythoon запускает Python, а ython уже нет, и это лучшая демонстрация того, что внутри нет интерпретатора.
Ломается многое. В контейнерах не было доступа в интернет, поэтому lynx бесполезен. На вложенных каталогах модель теряется и оказывается совсем в другом пути. В Firefox демо работает примерно в 20 раз медленнее, чем в Chrome, причину автор не выяснил.
Зачем это всё
Формально пользы немного, и автор говорит об этом прямо. Интерес в другом: модель поддерживает более или менее согласованный мир, не реализуя ни одного правила этого мира. Это слабая world model, и вопрос в том, насколько далеко подход уйдёт при лучшей согласованности и другом способе генерации экрана, например через диффузию вместо авторегрессии.
Родство с соседними проектами понятное. GameNGen симулировал Doom через диффузию, NeuralOS моделировал графический интерфейс рабочего стола, Genie 3 генерирует игровые сцены в реальном времени. WorldTUI ближе к старому трюку, где ChatGPT просили изобразить Linux-терминал, но обучен он именно на нажатиях и обновлениях экрана, включая полноэкранные приложения.
Что из этого реально переносимо? Синтетические данные из настоящих программ в контейнерах, с точной привязкой каждого шага к состоянию экрана. Крошечный словарь с декодированием, ограниченным грамматикой. Параллельное декодирование строк с последующей проверкой. И вывод про хрупкость памяти: узкая способность может исчезнуть при почти неизменной агрегированной точности, поэтому проверять её нужно отдельным тестом.
Следующий эксперимент автор хочет поставить на тысячах случайно сгенерированных приложений, у каждого свои элементы управления и правила. Идея в том, чтобы модель предсказывала поведение приложения, которого никогда не видела, опираясь только на контекст.
Часто задаваемые вопросы
WorldTUI это настоящий терминал?
Нет. Внутри нет оболочки, файловой системы и исполняемых файлов. Каждое нажатие клавиши превращается в предсказание модели о том, как изменится экран. Опечатка вроде pythoon запускает Python, потому что так выглядели похожие сессии в обучении, а не потому, что команда где-то выполнилась.
Сколько ресурсов нужно, чтобы обучить такую модель?
Около 75 часов на одной RTX 4090, включая три раунда самокоррекции. Данные синтетические: 336 тысяч сессий из контейнеров Docker, примерно 2 млрд токенов против 18 трлн у базовой модели. Итоговый файл в int4 весит 208 МБ и работает в браузере через WebGPU.
Почему дистилляция уничтожила память модели?
Потому что память оказалась узкой и хрупкой способностью. Удаление одного седьмого слоя снизило точность следующего токена всего на 0,4 процентных пункта, а точные чтения файлов упали с 64 из 64 до нуля. Агрегированные метрики такую деградацию не показывают, поэтому нужен отдельный тест на согласованность мира.
Итог
WorldTUI это редкий случай, когда инженерный эксперимент отвечает на концептуальный вопрос. Мировая модель на 360 млн параметров научилась держать состояние терминала, запускать vim, tmux и snake, помнить файлы между сессиями, и всё это без единого правила файловой системы внутри. Побочный результат не менее ценен: работающий рецепт самокоррекции через DAgger, параллельное декодирование строк и наглядная демонстрация того, как дистилляция убивает память при почти неизменной точности.
Демо весит 208 МБ, работает локально в Chrome и просит только одного: попробовать его сломать. Начните с pythoon, а потом проверьте, помнит ли модель файл, который вы создали десять команд назад. Первоисточник с деталями и ссылкой на демо: simedw.com.