Перейти к содержимому

Инференс на PHP: RAG без Python-сервиса

Инференс на 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.