JIT-компилятор Numba заработал в браузере через JupyterLite

Компания QuantStack показала первую рабочую версию JIT-компилятора Numba, которая работает целиком внутри браузера, через платформу интерактивных блокнотов JupyterLite и пакетный менеджер для WebAssembly emscripten-forge. JupyterLite и раньше умела выполнять код на Python прямо в браузере: её ядра работают локально через технологию WebAssembly, поэтому статический сайт может дать полноценную вычислительную среду без выделения сервера каждому пользователю, это удешевляет распространение блокнотов для документации, обучения и демонстраций. Но одного элемента научного стека Python в браузере не хватало, Numba, JIT-компилятора, который на лету превращает обычные Python-функции в быстрый машинный код и лежит в основе многих научных и вычислительных библиотек.

Запрос на поддержку Numba в WebAssembly обсуждался много лет: он появился в проекте Numba ещё в 2018 году (issue numba/numba#3284), а сообщество Pyodide отдельно отслеживало саму задачу упаковки (pyodide-recipes#192). Сложность была не в упаковке как таковой: Numba, это компилятор, и его модель исполнения опирается на библиотеку llvmlite и LLVM, а на обычном компьютере скомпилированный машинный код кладётся прямо в исполняемую память и вызывается напрямую. Браузер намеренно запрещает приложениям создавать или изменять исполняемую память таким образом, это ограничение безопасности платформы, а не недоработка. Поэтому перенос Numba в браузер требовал не доработки сборки, а нового движка исполнения для llvmlite, способа вызвать линковщик LLVM внутри работающего процесса браузера и механизма динамической подгрузки скомпилированного кода в постоянно работающее Python-ядро.

Решение выросло из более раннего проекта той же команды, Xeus-Cpp, интерпретатора C++ на основе Clang-Repl для JupyterLite (этой более ранней работе посвящён отдельный доклад QuantStack на FOSDEM 2026). Clang-Repl тоже не может использовать штатный JIT-механизм LLVM в WebAssembly, поэтому для него построили отдельный конвейер: сгенерированный LLVM IR компилируется в объектный файл WebAssembly; линковщик wasm-ld собирает этот объект в «сайд-модуль» WebAssembly, аналог динамически подключаемой библиотеки; сайд-модуль подгружается в работающее приложение, после чего его символы разрешаются, а скомпилированная функция вызывается. Каждый новый фрагмент кода добавляется в уже работающую программу и разделяет память с основным приложением, а его экспортируемые символы доступны модулям, которые загрузятся позже. Центральная идея нового проекта, применить точно такую же архитектуру к llvmlite: команда реализовала движок исполнения WebAssembly, который берёт LLVM-модули, полученные через llvmlite, превращает их в объекты WebAssembly, вызывает линковщик LLD через его re-entrant-драйвер прямо в процессе браузера, отдельный процесс wasm-ld внутри браузера запустить нельзя, поэтому такой запуск LLD «в процессе» авторы называют обязательным условием, и загружает результат как сайд-модуль Emscripten. Модули загружаются глобально и остаются в памяти, что позволяет библиотекам исполнения, вспомогательному коду компилятора и пользовательским функциям находить друг друга.

Дальше эта же схема легла в основу самого Numba. Пользователь пишет обычную Python-функцию и помечает её декоратором @jit или @njit; Numba разбирает байт-код Python, строит собственное промежуточное представление, определяет конкретные типы аргументов и промежуточных значений и через llvmlite опускает типизированную программу до LLVM IR. На обычном компьютере дальше в дело вступает штатный JIT-движок LLVM, а в JupyterLite, новый движок WebAssembly: модуль компилируется, линкуется как сайд-модуль, загружается в работающее ядро Xeus-Python и становится доступен через таблицу функций WebAssembly, так Numba может его вызвать. Одной пользовательской функции может понадобиться сразу несколько модулей, например, поддержка исполнения Numba Runtime (NRT), затем вспомогательный код, сгенерированный компилятором, и, наконец, модуль с самой функцией и её обёрткой, вызываемой из CPython, и загружать их нужно строго в правильном порядке, иначе более поздний код не найдёт символы, которые предоставляют более ранние модули. Отдельно команда добавила постоянное кеширование через @njit(cache=True): при повторном использовании функции в новой сессии браузера Numba восстанавливает данные компиляции из постоянной файловой системы JupyterLite, а llvmlite переиспользует сохранённый объект WebAssembly, заново линкуя и загружая свежий сайд-модуль в новый процесс ядра.

В показанном примере ускорение получилось выше, чем при нативном запуске: в WebAssembly внутри браузера Numba даёт, по словам авторов, «примерно» 250-кратное ускорение относительно обычного Python, на скриншоте демонстрации указана точная цифра 249×, тогда как при запуске того же примера нативно, не в браузере, ускорение составило около 90 раз. Авторы объясняют более высокий относительный выигрыш в браузере тем, что там отказ от накладных расходов интерпретатора Python даёт ещё больший эффект. Методика замера, какое оборудование, какой браузер, какая именно нагрузка использовались, в материале не раскрывается: цифры относятся к одному показанному примеру, а не к развёрнутому набору бенчмарков.

Для распространения пакет Numba теперь опубликован в emscripten-forge, дистрибутиве программ для WebAssembly в браузере, построенном поверх стека conda/mamba и conda-forge. Emscripten-forge даёт полноценное управление пакетами для браузера и включает заметную часть научного стека Python (NumPy, SciPy, LLVM, Clang, LLD), а также R и ряд нативных консольных программ и инструментальных цепочек. Это значит, что Numba можно установить прямо из терминала JupyterLite командой mamba, без отдельной сборки.

Авторы подчёркивают, что дело не в одной функции сложения из демонстрационного примера, а в целой экосистеме, которая открывается поверх Numba: PyTensor умеет компилировать символьные численные графы, используя собственный линковщик для Numba; PyMC, построенная на PyTensor, приносит в браузер привычные вероятностные модели; interpolation.py даёт процедуры интерполяции, ускоренные Numba и применяемые в численной экономике; Dolo.py опирается на этот же стек и даёт экономистам численные процедуры, рассчитанные на JIT-компиляцию, для стохастических процессов, правил принятия решений и оптимизированных методов решения задач динамического программирования. Иначе говоря, один перенесённый компилятор открывает браузеру путь сразу для нескольких направлений, статистики, теории вероятностей, экономики и научных вычислений, которым раньше приходилось исключать поддержку WebAssembly из своих планов.

Команда называет текущую работу сквозной архитектурой, а не готовым решением: следующие шаги, постепенно предложить изменения апстриму Numba и llvmlite отдельными точечными вкладами, расширить тестовое покрытие обоих проектов, улучшить производительность и постоянное кеширование, а также проверить в браузере больше пакетов из более широкой экосистемы Numba. Как перспективное направление для численных ядер отдельно назвали поддержку возможностей WebAssembly вроде SIMD. Даты или обязательства по срокам для этих пунктов в материале не приводятся; в основные репозитории Numba и llvmlite изменения пока не влиты.

Материал подписан Анутошем Бхатом, разработчиком научного ПО в QuantStack, мейнтейнером LLVM и соавтором Jupyter-ядра для C++ Xeus-Cpp; по данным поста, именно он руководил интеграцией WebAssembly для llvmlite и Numba. Работа выполнена в QuantStack и опирается на вклад сообществ вокруг LLVM, Clang-Repl, Emscripten, llvmlite, Numba, Xeus-Python, JupyterLite, Graphviz и самого emscripten-forge.

Ключевые факты

  • QuantStack показала первую рабочую версию JIT-компилятора Numba, которая целиком выполняется в браузере, через JupyterLite и пакетный менеджер emscripten-forge, без Python-сервера.
  • Раньше это не удавалось: браузеры запрещают приложениям создавать и изменять исполняемую память, а именно так Numba обычно размещает и запускает скомпилированный код; запрос на поддержку в WebAssembly обсуждался в проекте Numba с 2018 года (issue numba/numba#3284).
  • Решение, новый движок исполнения WebAssembly для библиотеки llvmlite: LLVM IR компилируется в объект WebAssembly и линкуется линковщиком LLD прямо в процессе браузера (без отдельного подпроцесса) в «сайд-модуль», который подгружается в работающее ядро Xeus-Python; архитектура выросла из более раннего проекта QuantStack, интерпретатора C++ Xeus-Cpp.
  • В показанном примере ускорение в браузере (WebAssembly) относительно обычного Python составило около 250 раз (на скриншоте, точная цифра 249×), больше, чем около 90 раз при запуске того же кода нативно; методика замера в материале не раскрыта.
  • Поддержка Numba в браузере открывает путь для целого стека: PyTensor, построенной на нём PyMC для вероятностного моделирования, а также interpolation.py и Dolo.py для численной экономики.

Почему это важно

JupyterLite и раньше умела запускать Python прямо в браузере, её ядра работают локально через WebAssembly, поэтому статический сайт может дать полноценную вычислительную среду без сервера на каждого пользователя, что удешевляет распространение блокнотов для документации, обучения и демонстраций. Но одного элемента научного стека Python в браузере не хватало, Numba, JIT-компилятора, который на лету превращает обычные Python-функции в быстрый машинный код и лежит в основе многих научных и вычислительных библиотек. Проблема была не технической мелочью: Numba зависит от llvmlite и LLVM и обычно кладёт скомпилированный код прямо в исполняемую память процесса, а браузер намеренно запрещает приложениям создавать или изменять исполняемую память таким образом, это ограничение безопасности платформы. Команда QuantStack закрыла запрос, который в проекте Numba обсуждали ещё с 2018 года, переиспользовав архитектуру, уже обкатанную той же командой на интерпретаторе C++ Xeus-Cpp, и представляет результат как настоящую компиляцию и исполнение Numba в браузере, не имитацию через интерпретатор и не скрытый удалённый сервис.

Кому это важно

В первую очередь, разработчикам и исследователям, которые пишут численный и научный код на Python и используют для ускорения Numba, а также тем, кто строит интерактивные учебные материалы и документацию на Jupyter-блокнотах без выделенного сервера. Отдельно это касается пользователей библиотек, которые опираются на Numba или на схожие бэкенды компиляции: PyTensor, построенной на ней PyMC для вероятностного моделирования, interpolation.py и Dolo.py, инструментов численной экономики (стохастические процессы, правила принятия решений, динамическое программирование). Шире это интересно сообществу вокруг llvmlite и LLVM в WebAssembly, новый движок исполнения переиспользует архитектуру, которую команда уже обкатала на интерпретаторе C++ Xeus-Cpp, то есть подход применим и к другим компиляторным инструментам на основе LLVM. К обычным пользователям ИИ-продуктов эта новость прямого отношения не имеет, это инфраструктура для научных вычислений и разработки, а не готовый потребительский продукт.

Как это применить

Numba теперь входит в emscripten-forge, дистрибутив пакетов для WebAssembly на основе стека conda/mamba и conda-forge, и Numba можно поставить прямо из браузерного терминала JupyterLite командой mamba, без отдельной сборки. Numba здесь используется так же, как обычно: Python-функция помечается декоратором @jit или @njit и вызывается как всегда, а JupyterLite сама прогоняет результат через новый движок WebAssembly. Для проектов с повторными сессиями полезен @njit(cache=True), он сохраняет скомпилированные объекты в постоянной файловой системе JupyterLite, и при следующем заходе llvmlite переиспользует уже посчитанный объект WebAssembly вместо повторной компиляции с нуля (сайд-модуль при этом всё равно линкуется и загружается заново в новый процесс ядра). Команда выложила несколько отдельных демонстраций: работу Numba, более низкоуровневый конвейер llvmlite и Graphviz, а также полный путь наверх, до PyTensor, PyMC, interpolation.py и Dolo.py.

Можно ли доверять

Источник, материал в блоге QuantStack, написанный от первого лица инженером (Анутош Бхат, мейнтейнер LLVM и соавтор Jupyter-ядра Xeus-Cpp), который сам указан как автор интеграции; независимых отзывов, реакций сообщества или проверок со стороны в тексте нет. Архитектурное решение прямо опирается на более раннюю, уже опубликованную работу той же команды над Xeus-Cpp, что повышает правдоподобность подхода. Обе ключевые цифры скорости относятся к одному показанному примеру, а не к развёрнутому бенчмарку: в тексте, «примерно» 250-кратное ускорение в браузере, на скриншоте демонстрации, точная цифра 249×, при нативном запуске того же примера, около 90 раз; методика измерения (оборудование, браузер, характер нагрузки) не раскрыта. Сама команда называет результат «первой рабочей версией», то есть ранним этапом, а не финальным продуктом.

Риски и подводные камни

Это начальный, а не финальный этап: изменения ещё не влиты в основные репозитории Numba и llvmlite, команда описывает это как желаемый следующий шаг, без сроков и обязательств, наравне с расширением тестового покрытия, доработкой производительности и кеширования и проверкой большего числа пакетов экосистемы. Сама механика хрупкая по конструкции: несколько модулей WebAssembly для одной пользовательской функции (рантайм Numba, вспомогательный код компилятора, сам код функции) необходимо грузить строго в правильном порядке, иначе более поздний код не найдёт нужные символы; а запуск линковщика LLD именно «в процессе» браузера авторы называют не оптимизацией, а обязательным условием, потому что альтернативы в виде отдельного подпроцесса wasm-ld внутри браузера попросту не существует. Это признаки конструкции, собранной вокруг жёстких ограничений платформы, а не универсального решения без компромиссов.