vLLM изнутри: как устроен движок для быстрого инференса LLM

vLLM изнутри: как устроен движок для быстрого инференса LLM

29 августа 2025 года вышел первый пост из запланированной серии, подробно разбирающей внутреннее устройство vLLM, библиотеки для высокопроизводительного инференса языковых моделей. Анализ построен на конкретном коммите vLLM, 42172ad от 9 августа 2025 года, и сфокусирован на движке V1 (более старый V0 объявлен устаревшим, но упоминается для сравнения). Пост разбит на пять частей: движок и его ядро (планировщик, постраничное внимание, paged attention, непрерывный батчинг), продвинутые возможности (чанкованное предзаполнение, кэширование префиксов, управляемая и спекулятивная генерация, разнесение предзаполнения и декодирования по разным узлам), масштабирование с одного GPU на множество, слой обслуживания запросов по сети и, наконец, бенчмарки с автотюнингом.

Автор разбирает конструктор движка: он состоит из конфигурации vLLM, процессора (превращает сырые запросы в задачи ядра через валидацию и токенизацию), клиента ядра движка и постпроцессора вывода. Само ядро движка включает исполнитель модели (в офлайн-режиме на одном GPU это UniProcExecutor, для многих GPU, MultiProcExecutor), менеджер структурированного вывода, планировщик с политикой FCFS («первым пришёл, первым обслужен») или по приоритету, а также менеджер KV-кэша, сердце постраничного внимания. Менеджер KV-кэша держит пул свободных блоков кэша (free_block_queue), из которого блоки выдаются под конкретные запросы.

Запрос проходит три стадии на каждом шаге движка: планирование (выбор, какие запросы обрабатывать, декодирование и/или предзаполнение), прямой проход модели с сэмплированием токена и постобработка (добавление токена к запросу, детокенизация, проверка условий остановки). Ключевое различие: запросы предзаполнения, это проход по всем токенам подсказки целиком, они упираются в вычислительную мощность GPU; запросы декодирования, проход только по последнему токену, поскольку остальные KV-векторы уже закэшированы, и они упираются в пропускную способность памяти. Планировщик V1 умеет смешивать оба типа запросов в одном шаге за счёт более продуманной архитектуры, движок V0 мог обрабатывать за раз только один тип.

Выделение памяти под KV-кэш происходит через функцию allocate_slots: она считает, сколько новых блоков нужно (каждый блок по умолчанию хранит 16 токенов, например, для запроса предзаполнения на 17 новых токенов нужно ceil(17/16) = 2 блока), проверяет, хватает ли свободных блоков в пуле, и, если нет, может вытеснить менее приоритетные выполняющиеся запросы с повторным вычислением их состояния (recompute preemption) либо просто пропустить планирование этого запроса на текущем шаге.

Прямой проход модели идёт по цепочке исполнитель → воркер → раннер модели: обновляются состояния (удаляются завершённые запросы, обновляются метаданные для обхода постраничного KV-кэша), готовятся входные данные (копирование буферов с CPU на GPU, расчёт позиций, построение карты слотов и метаданных внимания), выполняется сам проход, все последовательности в батче склеиваются в одну длинную «супер-последовательность», а маски внимания и индексы позиций следят, чтобы каждая последовательность обращалась только к своим токенам, после чего для последней позиции каждой последовательности извлекаются скрытые состояния, считаются логиты и сэмплируется токен.

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

  • Пост опубликован 29 августа 2025 года и открывает серию материалов о внутреннем устройстве vLLM; анализ построен на конкретном коммите vLLM, 42172ad от 9 августа 2025 года.
  • Разбор посвящён движку V1 (V0 объявлен устаревшим, но упоминается для сравнения) и ориентирован на тех, кто хочет понять или доработать vLLM или похожие движки вроде SGLang.
  • Планировщик V1 умеет смешивать запросы предзаполнения и декодирования в одном шаге, движок V0 мог обрабатывать за раз только один тип запросов.
  • Каждый блок KV-кэша по умолчанию хранит 16 токенов; для запроса предзаполнения на 17 новых токенов нужно ceil(17/16) = 2 блока.
  • При нехватке свободных блоков KV-кэша движок может вытеснить менее приоритетные выполняющиеся запросы с повторным вычислением их состояния либо пропустить их планирование на текущем шаге.

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

vLLM, один из самых распространённых открытых движков для обслуживания языковых моделей, и его архитектурные решения (постраничное внимание, непрерывный батчинг, планировщик, который смешивает предзаполнение и декодирование) стали образцом для целого класса высокопроизводительных инференс-систем. Разбор не описывает новый релиз или открытие, это образовательный технический разбор существующей архитектуры, но именно такие разборы дают инженерам точное понимание того, как устроены системы, на которых сегодня крутится большинство продакшен-развёртываний LLM.

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

Материал адресован инженерам, которые разворачивают и обслуживают языковые модели в продакшене, а также тем, кто хочет разобраться во внутренностях vLLM или SGLang вплоть до контрибьютинга в код. Полезен и всем, кто хочет получить точную ментальную модель того, как работает современный высокопроизводительный инференс-движок, а не только пользователям конкретно vLLM.

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

В посте разобран офлайн-пример: создание движка через LLM(model=...) и вызов generate() на списке промптов. Отдельно объясняется параметр gpu_memory_utilization, например, значение 0.8 резервирует под движок 80% доступной видеопамяти GPU. Поскольку анализ привязан к конкретному коммиту от 9 августа 2025 года, часть внутренних деталей (имена классов, устройство отдельных компонентов) в последующих версиях vLLM могла измениться.

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

Это первая часть заявленной серии постов независимого технического блогера, построенная на прямом чтении исходного кода vLLM с привязкой к конкретному коммиту, а не на документации или пресс-релизах проекта. Такой уровень детализации, вплоть до конкретных функций вроде allocate_slots и структур данных вроде free_block_queue, типичен для добросовестного инженерного разбора.

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

Главный риск, устаревание деталей: vLLM активно развивается, и привязка к коммиту от 9 августа 2025 года означает, что часть описанных внутренних механизмов и имён классов в текущей версии проекта уже может отличаться. Кроме того, это первая часть серии, бенчмарки, автотюнинг и полное описание слоя обслуживания запросов по сети обещаны в последующих постах и не раскрыты в этом материале.

«Движок LLM сам по себе уже обеспечивает высокую пропускную способность инференса, но только в офлайн-режиме. Обслуживать клиентов через веб на этом этапе ещё нельзя.»

— автор поста