English translation is not available yet. Showing Russian content.

Почему vLLM быстрее TGI (Hugging Face Text Generation Inference)?

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

vLLM превосходит TGI по скорости в 2–3 раза на больших батчах за счёт трёх ключевых технологий: Attention|Paged Attention, batching|Continuous Batching на уровне итераций и оптимизированных CUDA-ядер. Эти механизмы радикально снижают фрагментацию памяти (с ~70% до ~5%) и позволяют более эффективно утилизировать GPU при обработке запросов разной длины.


1. Терминология: vLLM и TGI

vLLM — open-source библиотека для инференса LLM, разработанная UC Berkeley. Ключевая инновация — Attention|Paged Attention, вдохновлённая механизмом виртуальной памяти в ОС.

TGI (Text Generation Inference) — решение от Hugging Face для деплоя LLM в production. Предоставляет аналогичный функционал: батчирование, управление KV-кэшем, поддержка популярных моделей.

Обе системы решают одну задачу — эффективно обслуживать LLM на GPU, но архитектурные различия дают vLLM значительное преимущество в производительности.


2. Paged Attention — главный фактор ускорения

Paged Attention — это механизм управления KV-кэшем (кэш ключей и значений), разбивающий его на блоки фиксированного размера (страницы). Страницы хранятся в непрерывной памяти GPU, а таблица страниц (page table) отображает логические позиции на физические адреса.

Проблема в TGI (статический KV-кэш)

TGI обычно выделяет непрерывный блок памяти под KV-кэш каждого запроса заранее, ориентируясь на максимальную длину последовательности (max_seq_len). Это приводит к двум проблемам:

  • Внутренняя фрагментация если запрос короткий, большая часть выделенной памяти простаивает (~60-70% потери).
  • Внешняя фрагментация при освобождении одних запросов и поступлении других память разбивается на маленькие куски, не позволяющие вместить новый запрос без дополнительного копирования.

Решение vLLM: страничная организация

vLLM выделяет страницы (обычно по 16–32 токенов) по мере необходимости. KV-кэш для запроса может храниться в непоследовательных физических страницах. При генерации нового токена система выделяет страницы «на лету». Это снижает фрагментацию до ~5%.

Формула экономии

Эффективность использования памяти ≈ (сумма длин всех последовательностей) / (общий объём памяти, занятой KV-кэшем)

Для TGI с max_seq_len = 4096 и реальной средней длиной последовательности 512 токенов эффективность ~12.5% (при равномерном распределении). Для vLLM с постраничным выделением эффективность может превышать 90%.

Пример кода (концептуально)

# Псевдокод: выделение KV-кэша в TGI (статическое)
def allocate_tgi(batch_size, max_len, hidden_dim):
    # Занимает max_len * batch_size * hidden_dim * 2 (K и V) байт
    return torch.empty(batch_size, max_len, hidden_dim * 2, device='cuda')

# Псевдокод: vLLM Paged Attention
class PageTable:
    def __init__(self, page_size=16, total_pages=1024):
        self.page_size = page_size
        self.blocks = torch.empty(total_pages, page_size, hidden_dim*2)
        self.free_list = list(range(total_pages))
    def alloc(self, num_tokens):
        pages_needed = (num_tokens + page_size - 1) // page_size
        return [self.free_list.pop() for _ in range(pages_needed)]

3. Continuous Batching — агрессивное планирование

Continuous Batching (также «iteration-level scheduling») — это техника, при которой планировщик пересматривает состав батча после каждой итерации (генерации одного токена). В отличие от статического батчирования TGI, где батч фиксируется на весь цикл генерации.

Как работает в TGI (статическое батчирование)

  • Все запросы в батче начинают генерацию одновременно.
  • Если один запрос завершился раньше (сгенерировал <eos>), освободившееся место не используется до конца батча.
  • Новые запросы могут быть добавлены только после полного завершения текущего батча.
  • → Простои GPU, низкая утилизация при вариативной длине ответов.

Как работает в vLLM (continuous batching)

  • Планировщик после каждого шага декодирования проверяет статус всех активных запросов.
  • Завершённые запросы удаляются, на их место сразу добавляются новые из очереди ожидания.
  • Батч переформировывается каждую итерацию.
  • GPU постоянно загружен, пропускная способность растёт.

Сравнение

ПараметрTGI (статическое)vLLM (continuous)
Частота обновления батчаТолько между раундами генерацииКаждая итерация (каждый токен)
Возможность дозагрузки запросовПосле полного завершения батчаНа каждом шаге
Утилизация GPU при разных длинахНизкая (зависит от самого длинного запроса)Высокая (адаптивная)
Типичный gain по throughput1x (база)1.5–2.5x

4. Оптимизированные CUDA-ядра

vLLM поставляется с набором кастомных CUDA kernels, написанных для современных архитектур (A100, H100). Они включают:

  • FlashAttention-подобные ядра для эффективного вычисления внимания без материализации полной матрицы внимания.
  • PagedAttention kernel — специализированная версия, работающая напрямую со страничным KV-кэшем, избегающая копирования в непрерывный буфер.
  • Fused kernels: объединение операций (например, LayerNorm + residual + QKV projection) для уменьшения числа вызовов ядер.

TGI также использует оптимизированные ядра, но они более универсальны (поддержка разных моделей) и не оптимизированы под конкретные страничные структуры. Поскольку vLLM жертвует частью гибкости в пользу скорости, её ядра дают дополнительный выигрыш ~20–30%.


5. Количественное сравнение производительности

СценарийvLLM (токенов/сек)TGI (токенов/сек)Ускорение
Одиночный запрос (batch=1)~120~110~1.1x
Маленький батч (batch=8)~400~250~1.6x
Средний батч (batch=32)~1200~500~2.4x
Большой батч (batch=64)~1800~600~3.0x

Цифры приблизительны, на модели Llama-2-7B с H100, при одинаковых max_tokens=512. Источники: бенчмарки сообщества, Lmsys.

Почему разница растёт с размером батча

  • Paged Attention сильнее снижает фрагментацию, когда много запросов разной длины — типично для больших батчей.
  • Continuous Batching даёт эффект тем больше, чем больше вариативность длин.
  • При малых батчах overhead управления страницами может перевешивать выгоду.

6. Дополнительные факторы

  • Prefix caching (с версии 0.4): vLLM может кэшировать KV-кэш для общих префиксов (например, системный промпт), экономя prefill-время. TGI поддерживает аналогичную фичу, но реализация менее эффективна.
  • Поддержка моделей vLLM быстрее адаптируется к новым архитектурам благодаря модульной системе, но TGI имеет более широкую официальную поддержку (особенно для Hugging Face моделей).
  • Memory management vLLM использует асинхронное освобождение страниц и copy-on-write для снижения latency.

7. Когда TGI может быть предпочтительнее?

Несмотря на скорость, TGI имеет свои сильные стороны:

  • Простота развёртки — один Docker-контейнер с настройками под большинство моделей.
  • Стабильность — Hugging Face активно поддерживает, меньше критических багов.
  • Встроенные фичи — встроенные watermarking, детекция токсичности, message API.
  • Гибкость — легче кастомизировать генерацию (grammar, JSON-schema).

Если приоритет — максимальная пропускная способность (high-throughput) и вы готовы пожертвовать некоторой гибкостью — выбирайте vLLM. Если нужно быстрое прототипирование или специфические требования — TGI.


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

Задача Сравнить производительность vLLM и TGI на модели Llama-3.1-8B при различных размерах батча.

Инструменты Docker, Python, Hugging Face Hub, vLLM, TGI с CUDA, библиотека time для замеров.

Шаги:

  1. Разверните vLLM: docker run --gpus all -p 8000:8000 vllm/vllm-openai:latest --model meta-llama/Meta-Llama-3.1-8B
  2. Разверните TGI: docker run --gpus all -p 8080:80 ghcr.io/huggingface/text-generation-inference:latest --model-id meta-llama/Meta-Llama-3.1-8B
  3. Напишите скрипт-бенчмарк:
    • Сгенерируйте 100 запросов одинаковой длины (например, 64 токена ввода, 128 токенов вывода).
    • Отправляйте запросы с разным concurrency (1, 4, 16, 32).
    • Измерьте total time и вычислите throughput (токенов/сек).
  4. Повторите с разными max_new_tokens и batch sizes.
  5. Постройте график зависимости throughput от размера батча для обеих систем.

Ожидаемый результат Вы увидите, что при малых батчах разница незначительна, а при больших — vLLM в 2-3 раза быстрее. Также обратите внимание на variance (vLLM обычно более стабильна).


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

ВопросТема
830Что такое KV-кэш и зачем его оптимизировать?
832Как работает FlashAttention?
835Что такое prefix caching?
837Как выбрать оптимальный batch size?
838Что такое speculative decoding?

Навигация