中文翻译暂不可用,显示俄语原文。

Как работает динамическое бэтчирование в TGI vs vLLM?

Краткий тезис

Динамическое бэтчирование позволяет инференс-серверам обрабатывать несколько запросов одновременно, эффективно используя GPU. TGI и vLLM решают эту задачу по-разному: TGI использует token-level scheduler с FIFO-очередью без вытеснения, а vLLM — iteration-level scheduler с поддержкой preemption (вытеснения). Ключевое различие — способность vLLM динамически перераспределять память между запросами, что даёт более высокую утилизацию GPU за счёт потенциального роста латентности для отдельных запросов.


1. Что такое динамическое бэтчирование и зачем оно нужно

Динамическое бэтчирование (batching|dynamic batching) — техника, при которой инференс-сервер (например, TGI или vLLM) собирает несколько входящих запросов в общий батч, выполняет их параллельно и возвращает результаты. В отличие от статического бэтчинга (все запросы должны быть собраны до начала инференса), динамическое бэтчирование позволяет добавлять новые запросы в текущий батч «на лету» или переставлять их, чтобы минимизировать простои GPU.

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

Проблема запросы разной длины (генерация от 10 до 2000 токенов) и времени поступления. Простейшая очередь «первым пришёл — первым обслужен» приводит к тому, что короткие запросы ждут завершения длинных, а пустые слоты в батче простаивают.


2. TGI (Text Generation Inference): FIFO + token-level scheduling

TGI от Hugging Face использует подход token-level scheduler. Логика выглядит так:

  • Шаг 1 все входящие запросы попадают в FIFO-очередь («первый пришёл — первый обслужен»).
  • Шаг 2 при запуске очередного шага декодирования (одна итерация генерации) scheduler выбирает из очереди столько запросов, сколько влезает в память GPU (с учётом длины их KV-кэша).
  • Шаг 3 запросы, которые уже обрабатываются, остаются в батче до завершения или пока не будет заполнен их max_new_tokens.
  • Шаг 4 preemption не поддерживается — если для нового запроса не хватает памяти, он просто отклоняется (возвращается ошибка 503 или ожидает в очереди, но не может вытеснить уже выполняющийся запрос).

Особенности TGI

  • Простота легко понять и деплоить.
  • Starvation (голодание) коротких запросов если в начале запускается длинный запрос, короткие будут ждать его завершения.
  • Потеря пропускной способности если в батче есть свободное место, но все запросы в очереди уже обрабатываются, GPU может простаивать в конце генерации.

3. vLLM: iteration-level scheduler + preemption

vLLM использует более агрессивный iteration-level scheduler. Основные отличия:

  • Каждый шаг декодирования пересматривает состав батча. Scheduler анализирует не только новые запросы, но и текущие. Если суммарная память KV-кэша превышает лимит, он может вытеснить один или несколько выполняемых запросов.
  • Preemption (вытеснение) — ключевая механика. Вытесненный запрос не удаляется, его KV-кэш свапается на CPU-диск (swap to disk). Освободившееся место на GPU занимает новый или более приоритетный запрос.
  • Вытесненный запрос позже возобновляется с контрольной точки (сохранённого KV-кэша), но его латентность растёт на время swap.

Детали preemption

  • Preemption mode может быть swap (сохранить весь KV-кэш) или recompute (сбросить кэш и заново прогнать префикс). vLLM по умолчанию использует swap для больших кэшей, recompute — для коротких.
  • Priority scheme vLLM поддерживает приоритеты. При нехватке памяти вытесняется запрос с наименьшим приоритетом или с наибольшим оставшимся временем.
  • Гранулярность можно вытеснять не весь запрос, а только его часть (один шаг), но обычно вытесняется целый запрос.

4. Сравнительная таблица: TGI vs vLLM

ХарактеристикаTGIvLLM
Тип планировщикаToken-level (один раз на начало генерации)Iteration-level (каждый шаг)
ОчередьFIFOFIFO + priority (опционально)
Preemption (вытеснение)НетДа (swap / recompute)
Работа с переполнением памятиОтклонение запроса / ожиданиеВытеснение и своп на CPU
Утилизация GPUСредняя (простои при неравномерной длине)Высокая (батч максимально заполнен)
Латентность (p50)Предсказуемая, но может быть высокой при ожиданииНиже для коротких запросов, выше для вытесненных
Пропускная способность (throughput)Ниже (особенно при high concurrency)Выше (до 2–4x в бенчмарках)
Сложность деплояНизкая (один Docker‑образ, минимум конфигов)Средняя (требуется настройка swap, max_model_len)
Поддержка prefix cachingДа (Shared Prefix Cache)Да (Automatic Prefix Caching)
Поддержка LoRA‑адаптеровДа (ограниченно)Да (гибко, несколько LoRA в батче)

5. Как preemption влияет на latency и как с этим работать

Preemption — компромисс между памятью и латентностью. Когда запрос вытесняется, его KV-кэш копируется на CPU (или диск), а затем обратно. Это добавляет overhead:

  • Swap latency копирование tensors через PCI‑e bus. Для модели 7B и кэша 4096 токенов может составлять 50–300 мс.
  • Recompute latency если модель recompute, то заново выполняется forward pass для префикса (без генерации новых токенов).

Как vLLM смягчает overhead

  • Priority‑based scheduling сценарии, где latency критична (например, чат-агенты), могут иметь высокий приоритет и реже вытесняться.
  • Dynamic swap threshold можно настроить max_num_batched_tokens так, чтобы вытеснения происходили реже, ценой снижения утилизации.
  • Chunked prefill предзагрузка (prefill) разбивается на чанки, что позволяет вставлять декад-шаги других запросов между чанками — уменьшает вероятность вытеснения.

6. Когда выбирать TGI, а когда vLLM

Выбираем TGI, если:

  • Требуется предсказуемая латентность (SLA с жёсткими P99).
  • Нагрузка невысокая (concurrency < 10) или запросы примерно одинаковой длины.
  • Команда не готова к сложной конфигурации vLLM.
  • Используются очень длинные контексты (32k+), где swap может стать узким местом.

Выбираем vLLM, если:

  • Высокая пропускная способность важнее, чем латентность «хвоста».
  • Смешанная нагрузка (короткие и длинные запросы вперемешку).
  • Используются LoRA‑адаптеры, prefix caching или сложные сценарии агентов (множество инференс-вызовов).
  • Готовы тестировать конфигурации и мониторить swap‑активность.

7. Детали реализации: настройки и примеры конфигов

Пример конфигурации TGI (ENV или CLI):

docker run ghcr.io/huggingface/text-generation-inference:latest \
  --model-id meta-llama/Llama-2-7b-chat-hf \
  --num-shard 1 \
  --max-total-tokens 4096 \
  --max-batch-prefill-tokens 2048

Здесь max-batch-prefill-tokens ограничивает размер одного prefill‑шага.

Пример конфигурации vLLM (при запуске сервера):

python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-2-7b-chat-hf \
  --max-model-len 4096 \
  --max-num-batched-tokens 8192 \
  --swap-space 16  # 16 GB CPU swap

--swap-space выделяет память на CPU для хранения вытесненных кэшей. Если этот параметр мал, preemption будет невозможен, и vLLM будет работать как TGI (отклонять запросы).


8. Влияние на Agentic RAG-системы

В архитектуре Agentic RAG агент делает много последовательных вызовов LLM: анализирует запрос, решает, какие документы искать, генерирует ответ. При этом:

  • Запросы агента обычно короткие (до 1000 токенов), но их много.
  • Критичное требование — низкая латентность каждого шага, чтобы агент работал интерактивно.
  • vLLM с iteration-level scheduler и приоритетами для коротких запросов часто даёт лучший P50/P90, но может «выкинуть» длинный контекст (например, при загрузке документа) в swap.

Рекомендация для агентов используйте vLLM с настройкой --priority-scheduler (если поддерживается) или разделяйте длинные prefetch-запросы и короткие reasoning-запросы на отдельные инстансы.


Пет-проект для закрепления

Задача Сравнить пропускную способность и латентность TGI и vLLM при синтетической нагрузке с разной длиной запросов.

Инструменты

  • Docker, k6 или locust для генерации нагрузки.
  • TGI (официальный образ) и vLLM (pip install vllm + openai‑совместимый сервер).
  • Jupyter + matplotlib для визуализации.

Шаги:

  1. Запустите оба сервера с одной и той же моделью (например, Qwen2.5-7B-Instruct) на одном GPU.
  2. Напишите скрипт на Python, который отправляет запросы с длиной prompt = 50..2000 токенов и max_tokens = 10..500 (распределение uniform).
  3. Сгенерируйте 1000 запросов, засеките время ответа для каждого.
  4. Повторите для разного уровня concurrency (1, 4, 8, 16).
  5. Вычислите метрики: средний throughput (req/s), P50, P95, P99 latency.
  6. Постройте гистограммы. Для vLLM дополнительно отслеживайте количество preemptions через логи (/metrics endpoint).

Ожидаемый результат

  • При низком concurrency TGI и vLLM показывают близкие результаты.
  • При concurrency > 4 vLLM даёт на 30-100% больше throughput, но P99 может быть выше на 200-500 мс из-за вытеснений.
  • TGI при переполнении памяти возвращает 503 ошибки, vLLM — успешно обрабатывает все запросы (если swap-space > 0).

Связь с другими вопросами

ВопросТема
849Оптимизации инференса: PagedAttention, FlashAttention
850Сравнение серверов: TGI, vLLM, Triton Inference Server
847Влияние batch size на latency и throughput
835Архитектура Agentic RAG: требования к latency
840Кэширование KV-кэша в агентах
853Профилирование инференс-сервера в production

Навигация