Качество поиска по векторной базе определяется двумя вещами: качеством самой модели эмбеддингов и тем, насколько дёшево её можно гонять по всему индексу. Инженеры Perplexity опубликовали статью «Fast Embeddings on GPUs» — разбор второй части: инфраструктуры serving для моделей pplx-embed и ранжирования, которые используются в Perplexity Search, Computer и API Platform.
По оценке команды, инференс эмбеддингов на GPU на зрелом железе Hopper и Blackwell уже почти сходится между движками. Выигрыш теперь сосредоточен в рантайме и обвязке вокруг модели: управление CUDA-графами, асинхронная абстракция отслеживания результатов и Rust-путь обработки запроса.
Два паттерна трафика — один движок
Perplexity делит serving эмбеддингов на две нагрузки.
Batch embedding — при построении или переиндексации векторной базы, где важна пропускная способность и минимизация стоимости. Online embedding — в момент запроса, когда короткий запрос нужно заэмбеддить быстро. Между ними — скоринг: после векторного поиска ранжируются большие пакеты документов, балансируя оба требования.
Ключевое решение — Perplexity не стала строить отдельный движок эмбеддингов. Модели эмбеддингов — это маленькие трансформеры: batch embedding похож на compute-bound prefill, а online embedding (часто несколько токенов) — на memory-bound decode. Поэтому команда переиспользовала ядра prefill и decode из своего LLM-стека.
Ivy, Tulip и ROSE: три сервиса на запрос
Обработкой запроса занимаются три сервиса.
Ivy — Rust HTTP-шлюз. Выполняет работу на CPU: парсинг JSON, токенизацию, шаблонизацию входа, разбиение батчей — и переводит запросы в собственный gRPC-протокол. Он же дробит крупные batch-запросы на части и балансирует их между репликами, что исправляет дисбаланс нагрузки при неоднородном размере продакшн-пейлоадов.
Tulip — интерфейс inference-сервера: gRPC-сервер на Rust, tokio и tonic. Занимается планированием и батчингом перед отправкой в движок.
ROSE (Runtime-Optimized Serving Engine) — реализует сам инференс модели. Написан в основном на Python, предоставляет ядра, слои и определения моделей, управляет CUDA-графами и отдаёт Tulip функцию step().
Почему планировщик намеренно простой
Tulip выбирает последовательности в порядке очереди (first-come, first-served), пока запросы накапливаются. Простота оправдана измерением: для маленьких моделей эмбеддингов на длинах последовательностей Perplexity линейная стоимость плотных слоёв доминирует над квадратичной стоимостью внимания. Поэтому задержка примерно пропорциональна числу токенов, а не числу последовательностей. Когда батч насыщает GPU — около 512 токенов на модели меньше миллиарда параметров, — добавление новых последовательностей уже не повышает эффективность.
CUDA-графы и LazyTensor
На маленьких батчах запуск ядер на CPU может перевешивать само исполнение на GPU. Perplexity строит полные CUDA-графы модели для всех эмбеддингов, захватывая каждый запуск в один вызов драйвера. Из-за небольшого размера моделей точка перегиба, где работа GPU превышает стоимость запуска, достигается на батчах в тысячи токенов и десятки последовательностей. Некоторые реализации внимания блокируют полные графы модели зависимостью от динамических входных данных на хосте — Perplexity отправила изменения в FlashInfer, чтобы сделать захват возможным.
Графы захватываются по конфигурациям, поэтому количество токенов дополняется до корзин, кратных 64 или 256. Это всё равно даёт тысячи графов и минуты захвата на модель. Решение — ленивый захват: каждая конфигурация получает прогревочный прогон, а захват и воспроизведение запускаются при втором обращении. Это стоит p99-задержки при старте, но размазывает минуты eager-работы на часы.
Вторая часть — LazyTensor, который отслеживает закреплённый в памяти хост-буфер плюс cudaMemcpyAsync и CUDA-событие. Вместо блокировки step() на устройстве он возвращает LazyTensor, позволяя Rust-асинхронной задаче ждать батч N, пока CPU ставит в очередь N+1.
Ядра всё ещё имеют значение
ROSE поддерживает несколько бэкендов внимания для рваных (ragged) входов: FlashInfer 2, FlashInfer 3 и FlashAttention 4. По данным Perplexity, FlashAttention 4 в целом быстрее, но FlashInfer 3 обходит его на моделях на базе Qwen при очень длинных последовательностях, поэтому выбор бэкенда делается индивидуально. При serving модели эмбеддингов ROSE не создаёт KV-кэш и отправляет вычисления в ragged-варианты внимания, избегая дополнения (padding).
Бенчмарки против vLLM
Perplexity сравнивает свой стек с vLLM v0.22.0 в BF16 на реальных весах и входах, полученных из eval-наборов, с прогревочными прогонами, проверяющими расхождение косинусной близости в пределах 0,1%. Четыре набора тестов: низколатентные эмбеддинги (batch 1; 128/512/4096 токенов), низколатентный скоринг (batch 5/25/50 при 512 токенах), высокопроизводительные эмбеддинги (batch 100, четыре параллельных процесса) и эмбеддинги с высокой конкурентностью (от 1 до 16 параллельных запросов, включая токенизацию Ivy и сетевые издержки).
Ключевые выводы
- Стек эмбеддингов Perplexity переиспользует ядра prefill/decode из LLM-стека, а не запускает отдельный движок.
- Задержка зависит от числа токенов, а не последовательностей; около 512 токенов насыщают модель меньше 1 млрд параметров.
- Полные CUDA-графы модели с ленивым захватом убирают издержки запуска без минутной инициализации.
- LazyTensor перекрывает подготовку батча на CPU с выполняемой работой GPU вместо блокировки на синхронизации.
- Ivy, Tulip и ROSE — внутренние сервисы; pplx-embed доступен через Embeddings API Perplexity.
Частые вопросы
Что такое pplx-embed?
Это модель эмбеддингов Perplexity, доступная через Embeddings API компании. Она используется в поиске, Perplexity Computer и API Platform для превращения текста в векторные представления.
Зачем Perplexity три отдельных сервиса?
Они разделяют обязанности: Ivy — HTTP-шлюз с работой на CPU, Tulip — планировщик и батчинг, ROSE — сам инференс модели. Такое разделение позволяет масштабировать части независимо.
Почему планировщик работает по принципу очереди?
Для маленьких моделей эмбеддингов задержка определяется числом токенов, а не последовательностей, поэтому сложные алгоритмы планирования не дают выигрыша. После насыщения GPU (~512 токенов) добавление последовательностей неэффективно.
Что такое LazyTensor и зачем он нужен?
Это абстракция, которая позволяет не блокировать CPU при выполнении GPU: вместо ожидания синхронизации Rust-задача ждёт батч N асинхронно, пока CPU готовит батч N+1. Это перекрывает подготовку данных с вычислениями.
Что дают CUDA-графы в этом стеке?
На маленьких батчах запуск ядер на CPU дороже самого вычисления. Полные CUDA-графы модели сводят все запуски в один вызов драйвера, устраняя издержки на CPU. Ленивый захват позволяет избежать минутной инициализации при старте.
Почему Perplexity сравнивает себя с vLLM?
vLLM — популярный открытый движок инференса, и сравнение с ним даёт ориентир индустрии. Бенчмарки идут в BF16 на реальных весах с проверкой косинусной близости в пределах 0,1%.
Могу ли я использовать этот стек сам?
Нет, Ivy, Tulip и ROSE — внутренние сервисы Perplexity. Внешним пользователям доступна только модель pplx-embed через API, а не serving-инфраструктура вокруг неё. Впрочем, похожие результаты можно получить на открытых инструментах: например, NVIDIA PAIR маршрутизирует локальный ИИ-инференс между RTX, DGX Spark и Mac, а MCP-серверы расширяют возможности агентов, хотя и создают новую поверхность атаки.
Источник: marktechpost.com