Инференс на PHP: RAG без Python-сервиса
Клиент просит семантический поиск по каталогу из сорока тысяч товаров. Ответ приходит раньше, чем вопрос: поднять Python-сервис, загрузить sentence-transformer, обернуть всё в HTTP, вызвать из PHP.
Код: FerryAI, лицензия MIT.
План рабочий. Он же добавляет второй рантайм, второй конвейер выкладки, менеджер процессов, проверку живости и сетевой переход на каждый запрос. Для функции, которая возвращает двенадцать товаров, инфраструктура больше функции.
Инференс на PHP убирает посредника. FerryAI загружает ONNX Runtime и llama.cpp через FFI в том же процессе, который отдаёт страницу, и вызов модели становится вызовом функции.
Сколько стоит отдельный сервис
Счёт идёт не за модель, а за всё вокруг неё.
Два рантайма означают два набора зависимостей и Python-окружение, которое должно совпадать с боевым образом. Упавшая установка пакета во время выкладки это простой с необычным текстом ошибки. Отладка идёт через два процесса, и медленный ответ требует замеров с обеих сторон, прежде чем кто-то скажет, где ушло время.
Память тихо дороже всего. Сервис на восемь воркеров PHP либо масштабирует свой пул, либо выстраивает запросы в очередь за одним экземпляром модели. В обоих случаях за модель платят дважды: в Python и в странице, которая её позвала.
Сетевой переход на каждый инференс третий. В локальной сети это миллисекунда, что звучит мало, пока страница не делает двадцать вызовов эмбеддинга, чтобы отсортировать результаты.
Что работает внутри процесса
Эмбеддинги даёт ONNX Runtime. Подходит любая модель .onnx, а sentence-transformer вроде all-MiniLM-L6-v2 возвращает векторы размерности 384 для семантического поиска. GPU-провайдеры подхватываются, когда они есть, и вызов откатывается на CPU, если установка CUDA неполная. Тихий откат это верное поведение для веб-запроса: медленный ответ лучше пятисотой ошибки.
Генерация идёт из llama.cpp через библиотеку-обёртку с тем же мостом FFI. Чат, потоковая выдача по токенам и вывод по грамматике работают локально на CPU или CUDA. Грамматика важнее, чем кажется: переданная JSON-схема ограничивает сэмплер, и модель не может вернуть невалидный JSON. Это снимает класс ошибок парсинга, который регулярка с повтором не лечит никогда.
Векторы лежат в SQLite или в PostgreSQL с pgvector. SQLite использует sqlite-vec для нативного индекса ближайших соседей, где расширение доступно, и перебор иначе, что нормально до нескольких сотен тысяч строк. PostgreSQL с pgvector держит индексы HNSW и параллельную запись, и это вариант для боевого каталога.
Ничего из перечисленного не требует Python в рантайме.
Где инференс на PHP обычно ломается
Первый барьер это нативные библиотеки. Объект, собранный под другую архитектуру, отсутствующая библиотека CUDA, токенизатор, который ждёт не тот формат, что лежит в папке модели. В комплекте есть диагностика ferry-ai check: она показывает версию PHP, расширения, доступные бэкенды и состояние кеша до того, как первый запрос упадёт на боевом сервере.
Второй это загрузка моделей. Скачивать 90 мегабайт при старте недопустимо, поэтому модель забирается один раз с HuggingFace, проверяется по SHA-256 и лежит в кеше с ограничением размера. Оборвавшаяся загрузка повторяется по умолчанию, а не по исключению.
Третий это память. Загрузка весов на каждый запрос исчерпает любой пул. Ограниченный LRU-пул держит экземпляры живыми, а воркеры делят веса через разделяемую память, когда расширение доступно. Это разница между тёплой страницей и ошибкой памяти на пике трафика.
Когда отдельный сервис всё ещё прав
Дообучение на больших данных, тяжёлые пакетные задачи на GPU или команда, которая уже ведёт Python-сервисы с нормальным мониторингом. Если модели нужен распределённый конвейер обучения, PHP не то место.
Граница, которой мы пользуемся, простая: если работа укладывается в запрос и модель уже обучена, она идёт в процессе, который обслуживает запрос. Если работа занимает минуты или нужна очередь на GPU, она остаётся снаружи.
Что получает клиент
Для RAG-функций слой поиска работает на PHP рядом с контентом, а векторное хранилище становится таблицей в базе, которая у сайта уже есть. Резервные копии её покрывают, стенд ведёт себя так же, и хостингу не нужен новый сервис.
FerryAI распространяется по MIT, требует PHP 8.3 и расширение FFI, показывает 793 проходящих теста и PHPStan уровня 8. Это ранний релиз: фиксируйте версию и читайте список изменений при обновлении.
Подробнее о том, как мы собираем поисковую часть: RAG-разработка. Про стек под ней: разработка на PHP. Самая маленькая версия той же архитектуры, одно дерево JSON за эндпоинтом MCP, разобрана в статье про био страницу на своём сервере.
Что почитать: ONNX Runtime и llama.cpp.
