AI для PHP-разработчиков. Часть 5: От массивов к GPU — как PHP-экосистема приходит к настоящему ML

AI для PHP-разработчиков. Часть 5: От массивов к GPU — как PHP-экосистема приходит к настоящему ML

Почему PHP-массивы плохо подходят для математики, как появились Tensor и NDArray, и зачем RubixML в итоге решил пойти в сторону GPU.

1. Введение: а можно ли вообще делать ML в PHP?

Реакция на тему «машинное обучение на PHP» обычно предсказуема: усмешки, ссылки на Python или минусы. Вывод — это не та задача для этого языка.

В этом есть доля правды. PHP никогда не проектировался как платформа для численных вычислений. У него нет встроенной поддержки векторных операций, контроля над памятью или доступа к low-level оптимизациям, привычным в scientific computing.

Но машинное обучение в PHP существует. И не как эксперимент, а как часть реальных систем:

  • inference прямо в веб-приложениях
  • обработка данных внутри SaaS
  • автоматизация и встроенная аналитика

И с каждым месяцем его использование растёт. Экосистема ML в PHP не шумная, но зрелая. Она состоит из четырёх слоёв:

  • библиотеки классического ML
  • математический фундамент
  • инструменты интеграции с современными ML-системами
  • интеграция с внешними ML-сервисами

В этой статье мы сосредоточимся на первых двух слоях.

Развитие экосистемы шло не благодаря языку, а вопреки его ограничениям — благодаря энтузиазму отдельных разработчиков.

Проблема не в алгоритмах, а в том, как устроена сама среда выполнения.

Сначала матрицы реализовывались как обычные массивы. Потом появились попытки оптимизации, затем — нативные структуры на C и Rust. И стало ясно: даже этого недостаточно.

Следующий шаг был неизбежен — вынос вычислений на GPU.

Мы пройдём этот путь: от наивных реализаций до современных решений вроде Tensor, NDArray и GPU-бэкендов в RubixML. И увидим, как менялось не только API, но и само понимание: где заканчивается PHP — и начинается настоящая вычислительная система.

Это не история о том, как делать ML в PHP. Это история о том, почему его пришлось делать по-другому.

2. Первая попытка: когда матрица — это просто массив

Матрица — это таблица чисел. А в PHP есть массивы. Значит, можно представить матрицу как массив массивов.

На этой идее строились первые библиотеки ML:

  • PHP-ML — от Arkadiusz Kondas
  • ранние версии RubixML — от Andrew DalPino
  • и другие

Всё делалось в чистом PHP, без расширений и нативного кода. Вычисления выполнялись интерпретатором.

Для разработчика это было удобно: не нужно компилировать, можно дебажить привычными инструментами, код прозрачен.

Как это выглядело в коде

Операции вроде умножения матриц реализовывались «в лоб» — тремя вложенными циклами.

Скалярное произведение, прямой проход в нейросети — всё писалось вручную, без библиотек.

Этот подход всё ещё используется — в учебных целях, чтобы понять, как работают алгоритмы «под капотом».

Например, реализация forward pass одного слоя нейросети на чистом PHP показывает, что:

  • подход остаётся понятным
  • он полезен для обучения и экспериментов

Но при росте данных и в продакшене его ограничения становятся очевидны.

Почему это было удобно

Главное преимущество — простота. За вечер можно реализовать:

  • k-NN
  • линейную регрессию
  • базовый классификатор

И всё — в рамках PHP. На маленьких данных это работает нормально. Для прототипов и обучения — более чем достаточно.

3. Почему это перестаёт работать

Сначала всё выглядит нормально. Потом данные растут — и код тормозит. Сначала чуть, потом в разы, а затем становится непригодным.

Первая мысль — «надо оптимизировать циклы». Но проблема не в коде. Она глубже — в устройстве самого PHP.

Число — это не просто число

В PHP число — это не примитив, а структура zval, которая хранит тип, значение и служебную информацию (например, счётчик ссылок).

Вместо 8 байт, как в C, каждый элемент занимает значительно больше. Миллион элементов — миллион структур zval.

Массив — это хеш-таблица

PHP-массив — это не непрерывная память, а хеш-таблица. Элементы не лежат подряд.

Каждый доступ включает:

  • поиск ключа
  • проход по внутренним структурам
  • извлечение zval

Это серия операций, а не прямой доступ к памяти.

Из-за этого:

  • хуже работает CPU cache
  • доступ к данным дороже
  • невозможна векторизация

Для численных вычислений это критично.

Copy-on-write

PHP может неявно копировать массивы при изменении. Мы не всегда контролируем, когда это происходит.

В задачах с матрицами это приводит к:

  • резкому росту потребления памяти
  • лишним аллокациям

Нет векторизации

Все операции выполняются через циклы. В NumPy та же операция — одна инструкция на уровне C с SIMD (Single Instruction, Multiple Data).

Алгоритм правильный, код простой — но каждая операция стоит слишком дорого.

Как это выглядит на практике

Сравним умножение матриц 500x500:

  • PHP-массивы — ~10–20 сек
  • Tensor (CPU) — ~0.3–0.8 сек
  • GPU (NumPower) — ~0.05–0.2 сек

Цифры зависят от железа, но разница — на порядки.

Главное не абсолютные значения, а разница на порядки!

Дело не в том, что PHP «медленный». Дело в том, что он решает задачу не тем инструментом.

4. Попытки оптимизировать — и почему они не спасают

Можно оптимизировать циклы: вызывать count() один раз, использовать локальные переменные, минимизировать обращения к массивам.

Это даёт небольшой прирост, но быстро упирается в предел. Это оптимизация «на уровне синтаксиса».

Фундаментальные ограничения остаются:

  • нет контроля над памятью
  • нет contiguous storage
  • нет SIMD
  • нет BLAS

Узкое место — модель памяти PHP, а не реализация цикла.

Оптимизация: транспонирование

Один из трюков — предварительное транспонирование матрицы.

Проблема: при умножении $a[$i][$k] — это строка, а $b[$k][$j] — столбец. Доступ к столбцу в PHP неэффективен.

Решение: заранее транспонировать вторую матрицу. Теперь все обращения — к строкам.

Почему это быстрее:

  • меньше «прыжков» по памяти
  • лучше используется CPU cache
  • доступ предсказуем

На практике это даёт заметный прирост даже в чистом PHP.

Но есть нюанс: мы всё ещё:

  • работаем с zval
  • используем хеш-таблицу
  • остаёмся в интерпретаторе

Мы делаем вычисления чуть менее плохими, но не по-настоящему эффективными.

Даже продвинутые трюки на уровне PHP не заменят нормальную модель данных.

PHP просто не предназначен для численных вычислений.

Оптимизация: CPU-кэш

В C и NumPy важна cache locality — данные лежат подряд, CPU читает их блоками (cache lines).

Попытка оптимизации в PHP: писать код, идущий по памяти последовательно, избегая «прыжков».

Но в PHP это почти не работает. Даже «плоский» массив — это:

  • не последовательность float-ов
  • а массив zval
  • внутри — всё ещё хеш-таблица

Результат:

  • данные не лежат компактно
  • между ними — служебные структуры
  • CPU не может эффективно использовать cache

Мы пишем код так, как будто оптимизируем cache — но реальной выгоды почти нет.

Оптимизация: packed arrays

В PHP 7+ появились packed arrays — более компактное хранение для простых массивов.

Это ускоряет работу, но в задачах с матрицами почти не помогает. При вложенности:

  • массив массивов
  • непрерывность теряется
  • каждый элемент — всё ещё zval
  • contiguous memory нет

К тому же packed array легко «сломать» — например, удалением элемента. После этого PHP возвращается к хеш-таблице.

В ML-задачах такие операции — постоянны: фильтрация, reshaping, работа с индексами.

Мы можем немного ускорить код — но не можем изменить фундамент!

Проблема не в циклах — проблема в том, как данные представлены в памяти.

Неприметный аспект

Многие библиотеки того периода просто перестали развиваться. Интернет завален заброшенными PHP-проектами по ML.

Не потому что идея была плохой. А потому что разработчики упирались в один и тот же потолок.

Можно было переписать внутренности, добавить оптимизаций, улучшить API. Но кардинального прироста не было.

Сколько бы ни оптимизировали PHP-массивы, они не превратятся в структуру, подходящую для численных вычислений.

Оставалось два пути:

  • переписывать на C / Rust
  • потерять мотивацию

Часто происходило второе. Энтузиазм угасал, потому что каждый шаг давал всё меньший результат.

Дело было не в библиотеках — дело было в самой модели.

5. Поворот: когда матрицы перестают быть массивами

Следующий этап — отказ от идеи, что матрица должна быть PHP-массивом.

Появились библиотеки, хранящие данные в contiguous memory, как в NumPy:

  • Rubix Tensor — от Andrew DalPino
  • NDArray — реализация на Rust от Kyrian Obikwelu

Теперь:

  • нет zval на каждый элемент
  • нет хеш-таблицы
  • данные лежат плотно
  • CPU работает с ними эффективно

Код остаётся похожим, но внутри — совсем другое.

Интересно, что Rust используется как способ получить производительность без проблем C с памятью. Это пример того, как PHP делегирует тяжёлую работу другим языкам.

Ускорение — в разы. Но и этого оказывается недостаточно.

Как изменилась архитектура

Эволюция:

  • раньше: PHP занимался вычислениями
  • теперь: PHP управляет процессом (orchestration)

Код становится менее «PHP-шным» и ближе к математической модели.

Более сложные операции

Сложение, нормализация — теперь выглядят как математические формулы, а не набор циклов.

Но остаётся ощущение, что чего-то не хватает. API всё ещё «объектный», а не математический.

NumPower и новый уровень абстракции

Проект NumPower от Henrique Borba идёт дальше. Он не только ускоряет вычисления, но и меняет способ их записи.

Теперь можно писать:

  • $a + $b * $c
  • sin($x) ** 2

Это очень близко к NumPy. Это не синтаксический сахар — это попытка сделать математические выражения нативными для языка.

6. Новый потолок: когда CPU уже не справляется

Даже с идеальными структурами данных и нативным кодом CPU ограничен: мало ядер, низкая пропускная способность.

ML — это огромные матричные операции. Embedding-векторы, нейросети — быстро упираются в объёмы, где CPU сдаёт.

Следующий шаг — неизбежен.

7. Почему всё пришло к GPU

GPU заточен под параллельные вычисления. Тысячи потоков, высокая пропускная способность памяти — идеально для линейной алгебры.

Выжимать максимум из CPU — тупик. Нужно менять архитектуру.

Сейчас RubixML v3 движется в сторону GPU через проекты вроде NumPower.

Это смена парадигмы:

  • PHP → orchestration
  • Tensor / NumPower → вычисления
  • GPU → heavy math

Проект в стадии активной разработки. Это момент, когда можно подключиться.

Это не просто «ускорим вычисления». Это переход на другую модель: PHP больше не считает — он управляет.

8. Что в итоге произошло

PHP использовался как вычислительная среда. Потом стало ясно: он для этого не подходит. Вычисления начали выноситься — сначала в нативные структуры, потом в расширения, теперь — на GPU.

Сегодня PHP в ML — это orchestration layer. Он:

  • связывает компоненты
  • управляет процессом
  • обрабатывает данные

А тяжёлая математика — в других местах:

  • GPU
  • Rust
  • нативные библиотеки

Пример — Transformers PHP от Kyrian Obikwelu. Это доступ к современным моделям в контексте PHP. Но под капотом:

  • PHP управляет пайплайном
  • загружает модели
  • обрабатывает результаты

Вычисления — вне PHP: через нативные библиотеки или внешние рантаймы.

Похожую роль играет LLPhant — для LLM-сценариев:

  • генерация текста
  • embeddings
  • retrieval (RAG)
  • чат-интерфейсы

Код остаётся прикладным. Мы больше не думаем:

  • о матрицах
  • о циклах
  • о памяти

Мы думаем:

  • о данных
  • о запросах
  • о поведении системы

Появляются и более высокоуровневые инструменты. Например, Neuron AI — не про матрицы и модели, а про построение AI-приложений:

  • агенты
  • цепочки (chains)
  • интеграции с LLM

Код ещё дальше от «низкого уровня».

Роль PHP изменилась:

  • раньше — реализовывали математику
  • потом — выносили её в нативные структуры
  • теперь — описываем поведение системы

Весь путь:

  • умножаем матрицы в массивах
  • оптимизируем — упираемся в потолок
  • выносим в нативные структуры
  • в C / Rust
  • и, наконец, на GPU

PHP не становится быстрее в ML. Он просто перестаёт считать.

Он больше не вычислительный движок — он диспетчер, оркестратор, клей между компонентами.

И, возможно, именно это и есть его шанс выжить в большой игре.

Но тогда остаётся вопрос: игра ещё идёт? Или PHP уже давно играет не в ту игру?

Читать оригинал