Назад к блогу

Mojo стал открытым исходным кодом: что именно опубликовано и как устроен стек MAX

Mojo стал открытым исходным кодом: что именно опубликовано и как устроен стек MAX

Mojo перешёл на открытую лицензию, и вместе с ним Modular раскрыла исходники ключевых компонентов платформы MAX — от графового компилятора до инференс-сервера. Статья разбирает, что именно попало в репозиторий, как устроены механизмы компиляции моделей и какие ограничения компания признаёт сама. Полезно тем, кто следит за развитием альтернатив Python в ML и хочет понять архитектуру стека изнутри.

18 августа 2026 года Modular опубликовала Mojo под Apache 2.0 с LLVM-исключениями и раскрыла исходники компонентов платформы MAX. В блоге компании этот шаг описан как продолжение линии на открытость компилятора, начатой с Mojo 1.1, когда компилятор впервые открыли для внешних вкладов. Ниже — что попало в открытый доступ, как устроены ключевые части стека и какие ограничения авторы признают сами.

Что именно открыли

По README репозитория modular/modular, исходники доступны для компилятора Mojo (/Mojo, с августа 2026), стандартной библиотеки (/Mojo/stdlib, с марта 2024), GPU-абстракций MAX (/max/mojo/max/gpu, с мая 2025), библиотеки ускорителей MAX (/max/kernels, с мая 2025), графового компилятора (/GraphCompiler), рантайма (/MLRT), драйвера (/max/driver), AI-фреймворка (/max/python/max, с ноября 2025), пайплайнов моделей (/max/python/max/pipelines, с мая 2025), инференс-сервера (/max/python/max/serve, с мая 2025) и примеров кода.

Лицензирование раздельное: Mojo и его вклад — Apache 2.0 с LLVM-исключениями; часть исходников MAX и его бинарная поставка — под Licenses/MAX_LICENSE, остальное — под той же Apache 2.0 с LLVM-исключениями. Выбор лицензии в блоге объясняют гибкостью применения в разных приложениях, а LLVM-исключения — свободами для сборки и распространения скомпилированных бинарников.

Как устроена компиляция моделей в MAX

Публичный API движка предлагает три пути компиляции. compile собирает модель без привязки весов и памяти устройства, блокируется до завершения и бросает ошибку при сбое; результат передаётся в инициализацию. compile_async возвращается сразу после планирования: возвращённый объект оборачивает ожидающую компиляцию, которая идёт в пуле воркеров рантайма, а ошибки всплывают при ожидании результата. Компиляции при этом сериализуются на весь процесс.

Третий путь, compile_reusing_mefs, при наличии хранилища артефактов сначала запрашивает у него готовый артефакт для модели-графа и, если тот найден, компилирует его вместо исходной модели; иначе ведёт себя как обычная компиляция. Артефакты — это сериализованные скомпилированные модели в формате MEF. Такой артефакт отличается от скомпилированной модели тем, что хранится на диске и не требует инициализации на устройстве: его можно экспортировать прямо из скомпилированного артефакта и затем передать путь к файлу в компиляцию как сохранённую модель. При экспорте в хранилище артефакт пишется по выданному хранилищем пути, после чего записывается манифест.

Каталог исходников кастомных расширений собирается в файл .mojoc — .mojoc; такие пути принимаются в параметре custom_extensions.

Граф-API: операции, блоки и библиотеки ядер

Операции в графе строятся в теле графа в заданной точке вставки. Python-объекты граф-API преобразуются в MLIR-значения, созданная операция верифицируется, а результаты оборачиваются обратно в значения граф-API. Для клиентов, которым нужны только результаты, есть отдельная обёртка.

Библиотека ядер даёт доступ к кастомным операциям и ядрам из двух источников: готовых .mojoc-пакетов и каталогов Mojo-исходников. Бинарный пакет подключается напрямую, исходники — после сборки; любой другой путь приводит к ошибке. Набор путей библиотеки записывается в атрибут модуля как массив строк.

Блок графа позволяет строить тело региона до создания владеющей операции. Для этого используется приватная scratch-операция. Завершение блока добавляет терминатор, при этом верификация на время приостанавливается. Сама операция графа — первая операция в блоке тела модуля.

Компиляторный пайплайн KGEN

Ядро в пайплайне опознаётся по числовому идентификатору, который записывается в LLVM-метаданные функции под ключом kgen.offload.kernelid. Поиск по этому идентификатору позволяет найти ядро после элаборации, даже если символы переименованы для offload-цели.

Offload-компиляция собирает ядро под конкретную target-конфигурацию: из модуля вырезается отдельный модуль с корнем в нужной функции, подставляются triple, cpu, features, abi и data layout из описания цели, создаётся объектный компилятор и — если backend не владеет offload-lowering — target machine. Результат эмитится в одном из поддерживаемых видов вывода. Если backend владеет offload-путём, он сам запускает lowering и внешний toolchain, а LLVM-эмиссия не применяется.

Проход элаборации генераторов получает колбэки для сборки ассемблера элаборатора и для offload-компиляции, выполняет элаборацию и последующий пайплайн. Offload-компиляция обходит цели и группы, для каждого ядра собирает все нужные виды эмиссии и складывает результаты вместе с именами модулей; при заданном префиксе вывода записи ставятся в очередь на запись. Кэш трансформаций строится по пути kMojoCacheBaseDirName/transform, а пайплайн выполняется как кэшированная трансформация с ожиданием результата. Для ассемблерного пути входной генератор помечается фиксированным идентификатором kAsmEntryKernelId, чтобы найти его после элаборации.

Фьюжн-паттерны графового компилятора

В Fusion.cpp реализован набор свёрток. Matmul+Add превращает add(matmul, residual) или add(matmul, broadcast(bias)) в mo.matmul_add; допускается и цепочка add(add(matmul, bias), residual), где bias сворачивается в residual одним elementwise-add. Условия: единственный потребитель результата matmul, bfloat16 на входах и выходе, нединамические N и K, только GPU.

RMSNorm+add сворачивает rms_norm(add(a, b)) в композитную операцию, выдающую и нормированный выход, и промежуточный a + b. Паттерн не срабатывает, если один из операндов add сам является RMSNorm — такую двойную норму обрабатывает отдельный паттерн.

Связка allreduce+add+RMSNorm+quant FP8 ищет bundled-allreduce внутри параллельного региона, чей результат единственно используется в yield, а результат региона единственно потребляется распаковкой bundle, причём число элементов bundle должно совпадать с числом запусков. Дальше по цепочке проверяются add с residual слева и allreduce справа, RMSNorm и динамическое квантование в FP8 с tokenwise group size и умножением до приведения типа.

Отдельный паттерн RMSNorm+quant FP8 требует GPU, единственного потребителя входа, group size, равный -1 или последней размерности, и умножение до приведения типа. LayerNorm+ragged RoPE сворачивает цепочку layer_norm → reshape-unsqueeze → slice по последней оси → ragged RoPE вместе с passthrough slice и concat; требуется interleaved-режим RoPE и не rope_first, допускается downcast между layer_norm и срезами. Паттерн маски внимания распознаёт select с broadcast скаляра меньше -1E+38 и допускает ветки в любом порядке.

Предыстория: что было открыто раньше

Открытие шло постепенно — в README прямо сказано, что проект продолжает открывать платформу со временем. Стандартная библиотека Mojo доступна с марта 2024, GPU-абстракции и библиотека ускорителей MAX — с мая 2025, AI-фреймворк — с ноября 2025, компилятор Mojo — с августа 2026. Сам релиз Mojo 1.0 датирован 11 августа 2026 года; язык развивается с первого релиза в 2023 году.

Зачем это делают

Аргумент Modular сформулирован через изначальную ставку: четыре с половиной года назад компания исходила из того, что AI не останется на одном виде кремния, а программный стек придётся перестроить под разнородное железо и усложняющиеся нагрузки. Проблему в блоге описывают так: стек был заперт на конкретном железе, и смена означала переписывание всего уже выпущенного. Решение — аппаратная гибкость: одна и та же модель, код и контейнер работают на NVIDIA, AMD и новом оборудовании.

В документации MAX независимость раскрывается через отсутствие зависимости от PyTorch, CUDA и ROCm: нечего собирать в бандл, патчить или синхронизировать, что даёт заметно меньшие контейнеры и более быстрый холодный старт. Один и тот же пакет работает на NVIDIA, AMD и Apple silicon без подбора версии CUDA, а новые поколения оборудования в датацентре MAX вводит в работу быстрее всех. Для моделирования предлагается PyTorch-подобный Python API, для ядер — Mojo как питонический язык, рассчитанный на переносимость между оборудованием без вендор-специфичных инструментов.

Что это меняет на практике

Установка — одна команда:

uv pip install "max[all]"

Генерация текста через CLI требует совместимого GPU: запускается max с выбранной моделью. Пайплайн генерации можно запустить и напрямую, указав модель Qwen/Qwen3.5-0.8B, размер батча 1, максимальную длину 10000 и начальный промпт.

Поддержка Qwen реализована регистрацией архитектуры Qwen2ForCausalLM с задачей генерации текста, примером репозитория Qwen/Qwen2.5-7B-Instruct, форматом весов safetensors, кодировкой bfloat16 по умолчанию и стратегией KV-кэша PAGED. Всего заявлены сотни моделей, включая DeepSeek, Gemma и Qwen, с OpenAI-совместимым API на NVIDIA или AMD и в любом облаке.

Ограничения и открытые вопросы

Авторы фиксируют несколько ограничений прямо в коде. Параллельная компиляция пока защищена глобальной блокировкой, помеченной как временная мера до тех пор, пока параллельная компиляция не станет безопасной. Проверка свёртки к константам во фьюжне ослаблена до узкого набора операций из-за падения ядра по тикету KERN-1160. Pre-packing на CUDA не поддерживается, упаковка работает только на host-устройстве. Фьюжн rms_norm.RoPE ограничен GPU, поскольку для других устройств ядра нет.

Открытие платформы продолжается: часть компонентов ещё не открыта, но их перечень не приводится. Новые аппаратные платформы, помимо NVIDIA, AMD и Apple silicon, на ModCon 2026 обещаны как «new hardwares to be announced» — то есть ещё не объявлены.

Источники

Похожее