Что такое continuous batching и как оно отличается от static batching? Как это реализовано в vLLM/TGI?
Краткий тезис
batching|Continuous batching (batching|непрерывная пакетная обработка) — это техника оптимизации инференса LLM, при которой батч динамически обновляется на каждой итерации генерации: завершённые запросы удаляются, а новые добавляются без ожидания окончания всего батча. В отличие от static batching, где все запросы в батче обрабатываются синхронно до завершения самого длинного, batching|continuous batching значительно повышает throughput (пропускную способность) и снижает latency (задержку) для коротких запросов. Реализации в vLLM и TGI (Text Generation Inference) используют планировщики на уровне итераций и механизмы управления памятью (например, PagedAttention в vLLM) для эффективной реализации этой техники.
1. Термины: Batching, Static Batching, Continuous Batching
Batching — объединение нескольких запросов в один пакет для параллельной обработки на GPU. Это ключевой приём для утилизации вычислительных ресурсов, так как GPU эффективнее работают с большими матрицами.
Static batching (статический батчинг) — традиционный подход: все запросы собираются в батч фиксированного размера, и модель обрабатывает их одновременно. Генерация идёт до тех пор, пока не завершится самый длинный запрос в батче. Короткие запросы вынуждены ждать завершения длинных, что приводит к неэффективности.
Continuous batching (непрерывный батчинг) — динамический подход: на каждом шаге генерации (итерации) планировщик проверяет, какие запросы завершились (сгенерировали токен <eos> или достигли лимита длины), удаляет их из батча и добавляет новые ожидающие запросы. Таким образом, батч постоянно «обновляется», и ресурсы GPU не простаивают.
2. Проблема Static Batching
При статическом батчинге возникает проблема «хвостовой задержки» (tail latency). Представьте батч из 10 запросов: 9 коротких (генерируют 10 токенов) и 1 длинный (генерирует 100 токенов). Все 9 коротких запросов будут ждать, пока длинный не завершится, хотя их генерация уже закончилась. Это приводит к:
- Низкому throughput — за единицу времени обрабатывается меньше запросов.
- Высокой latency для коротких запросов — они задерживаются из-за длинных.
- Неэффективному использованию GPU — часть вычислений тратится на уже завершённые запросы (их скрытые состояния всё ещё участвуют в forward pass).
3. Принцип Continuous Batching (In-flight Batching)
Continuous batching также называют in-flight batching или iteration-level batching. Основная идея:
- Генерация разбивается на итерации (каждая итерация — один шаг декодирования, т.е. генерация одного токена для каждого активного запроса).
- После каждой итерации планировщик (scheduler) анализирует состояние запросов:
- Завершённые (сгенерировали <eos> или достигли max_new_tokens) удаляются из батча.
- Новые запросы из очереди ожидания добавляются в батч (если есть свободные слоты).
- Таким образом, батч никогда не «застывает»: он постоянно меняет состав.
Это требует:
- Динамического управления памятью — кэш ключей и значений (KV cache) для каждого запроса должен выделяться и освобождаться на лету.
- Планировщика — алгоритма, решающего, какие запросы добавить/удалить, с учётом приоритетов, лимитов памяти и т.д.
4. Сравнение Static и Continuous Batching
| Характеристика | Static Batching | Continuous Batching |
|---|---|---|
| Состав батча | Фиксирован на всё время генерации | Меняется на каждой итерации |
| Ожидание завершения | Все запросы ждут самого длинного | Короткие запросы не ждут длинных |
| Throughput | Низкий при разнородной длине запросов | Высокий, особенно при смешанной нагрузке |
| Latency коротких запросов | Высокая (зависит от длинных) | Низкая (завершаются сразу) |
| Использование GPU | Может быть неэффективным (пустые вычисления) | Эффективное, минимальный простой |
| Сложность реализации | Простая (один forward pass на батч) | Сложная (планировщик, управление памятью) |
| Поддержка в фреймворках | Hugging Face Transformers (по умолчанию) | vLLM, TGI, TensorRT-LLM |
5. Реализация в vLLM
vLLM — библиотека для высокопроизводительного инференса LLM. Ключевые компоненты для continuous batching:
5.1 PagedAttention
PagedAttention — механизм управления KV cache, вдохновлённый страничной памятью ОС. KV cache разбивается на блоки (pages) фиксированного размера. Каждый запрос получает не непрерывный участок памяти, а набор страниц, которые могут быть расположены в памяти не последовательно. Это позволяет:
- Избежать фрагментации — память выделяется по мере необходимости.
- Эффективно добавлять/удалять запросы — при завершении запроса его страницы освобождаются и могут быть переиспользованы новыми запросами.
- Поддерживать copy-on-write для shared prefixes (если несколько запросов имеют общий префикс, они могут разделять страницы).
5.2 Scheduler (Планировщик)
Планировщик vLLM работает на уровне итераций. На каждом шаге он:
- Получает список активных запросов (те, которые ещё генерируют).
- Проверяет, какие из них завершились (по
<eos>илиmax_tokens). - Удаляет завершённые, освобождая их страницы.
- Добавляет новые запросы из очереди ожидания, если есть свободные слоты (с учётом лимита памяти и
max_num_seqs). - Формирует батч для текущей итерации.
Планировщик также поддерживает приоритеты (например, можно обрабатывать интерактивные запросы раньше пакетных).
5.3 Пример кода (концептуальный)
# Упрощённая логика планировщика vLLM
class Scheduler:
def __init__(self, max_num_seqs, max_model_len):
self.waiting_queue = deque()
self.running = [] # активные запросы
self.max_num_seqs = max_num_seqs
def schedule(self):
# Удаляем завершённые
self.running = [req for req in self.running if not req.is_finished()]
# Добавляем новые, пока есть место
while len(self.running) < self.max_num_seqs and self.waiting_queue:
new_req = self.waiting_queue.popleft()
self.running.append(new_req)
return self.running
def step(self):
batch = self.schedule()
# Выполняем один forward pass для batch
outputs = model.generate_step(batch)
# Обновляем состояния запросов
for req, out in zip(batch, outputs):
req.update(out)
6. Реализация в TGI (Text Generation Inference)
TGI — библиотека от Hugging Face для инференса LLM. Continuous batching в TGI реализован через flash attention и собственный планировщик.
6.1 Flash Attention
Flash Attention — алгоритм точного attention, который минимизирует чтение/запись в HBM (High Bandwidth Memory) за счёт тайлинга (разбиения на блоки). В контексте continuous batching flash attention позволяет эффективно обрабатывать батчи с переменной длиной последовательностей, так как не требует паддинга (дополнения до максимальной длины).
6.2 Планировщик TGI
Планировщик TGI также работает на уровне итераций. Он использует token bucket для ограничения скорости добавления новых запросов и prefill/decoding phases:
- Prefill — этап обработки промпта (входных токенов). Для новых запросов сначала выполняется prefill, затем они переходят в режим декодирования.
- Decoding — генерация токенов по одному.
Планировщик может комбинировать prefill и decoding в одном батче (mixed batch), что ещё больше повышает эффективность.
6.3 Отличия от vLLM
| Аспект | vLLM | TGI |
|---|---|---|
| Управление KV cache | PagedAttention (страницы) | Continuous KV cache (непрерывный, но с перераспределением) |
| Механизм attention | PagedAttention + FlashAttention | FlashAttention (v2/v3) |
| Планировщик | Iteration-level с приоритетами | Iteration-level с token bucket |
| Поддержка моделей | Широкий спектр (Llama, Mistral, Qwen и др.) | В основном модели Hugging Face |
| Простота использования | Требует установки и настройки | Интегрирован с Hugging Face Hub |
7. Преимущества и недостатки Continuous Batching
Преимущества
- Высокий throughput — до 2-4x по сравнению со static batching при смешанной нагрузке.
- Низкая latency для коротких запросов — они не ждут длинные.
- Лучшая утилизация GPU — меньше простоев.
- Масштабируемость — легко добавлять новые запросы в реальном времени.
Недостатки
- Сложность реализации — требуется планировщик и динамическое управление памятью.
- Overhead планировщика — на каждой итерации нужно принимать решения, что добавляет микро-задержки.
- Потенциальная нестабильность — при неправильной настройке может возникнуть thrashing (частая смена состава батча).
- Зависимость от аппаратного обеспечения — эффективность сильно зависит от скорости памяти GPU (HBM).
8. Влияние на Latency и Throughput
Рассмотрим сценарий: сервер получает 100 запросов в секунду, длины генерации варьируются от 10 до 200 токенов.
- Static batching: батч фиксированного размера (например, 8 запросов). Каждый батч обрабатывается за время, равное максимальной длине в батче. Средняя latency = (сумма длин всех запросов в батче) / 8, но из-за ожидания длинных запросов фактическая latency для коротких может быть в 10 раз выше.
- Continuous batching: батч постоянно обновляется. Короткие запросы завершаются за 10 итераций и сразу покидают батч. Средняя latency для коротких запросов близка к их собственной длине, для длинных — чуть выше из-за конкуренции за ресурсы.
Throughput (запросов в секунду) при continuous batching может быть в 2-3 раза выше, особенно при высокой вариативности длины.
9. Когда использовать Continuous Batching
Continuous batching особенно эффективен в сценариях:
- Чат-боты и интерактивные приложения — где важна низкая latency для коротких ответов.
- Смешанная нагрузка — запросы разной длины (например, суммаризация длинных документов и короткие ответы на вопросы).
- Высоконагруженные системы — где throughput критичен.
Static batching может быть оправдан, если:
- Все запросы примерно одинаковой длины (например, пакетная обработка фиксированных промптов).
- Нагрузка низкая, и простои GPU не критичны.
- Используется очень маленькая модель, где overhead планировщика не оправдан.
10. Пет-проект для закрепления
Задача: Реализовать упрощённый симулятор continuous batching на Python, сравнивающий throughput static и continuous подходов.
Инструменты: Python, NumPy, симуляция времени генерации.
Шаги:
- Создайте класс
Requestс полями:id,prompt_length(константа),generation_length(случайная от 10 до 200),status(waiting, running, finished). - Реализуйте
StaticBatcher:- Собирает запросы в батч фиксированного размера (например, 8).
- Обрабатывает батч за время, равное
max(generation_length)в батче. - После завершения всего батча переходит к следующему.
- Реализуйте
ContinuousBatcher:- На каждой итерации (шаг = 1 токен) проверяет завершённые запросы, удаляет их, добавляет новые из очереди.
- Время итерации фиксировано (например, 10 мс).
- Запустите симуляцию на 1000 запросов, замерьте:
- Среднюю latency для каждого запроса.
- Throughput (запросов в секунду).
- Время выполнения всех запросов.
- Постройте графики зависимости latency от длины генерации.
Ожидаемый результат: Continuous batching покажет значительно меньшую latency для коротких запросов и более высокий throughput, особенно при высокой дисперсии длин.
11. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 200 | Архитектура Agentic RAG (общие принципы) |
| 202 | PagedAttention и управление KV cache |
| 203 | Flash Attention и его роль в инференсе |
| 204 | Оптимизация latency LLM (кэширование, streaming) |
| 205 | Сравнение vLLM, TGI, TensorRT-LLM |
| 206 | Speculative decoding |
12. Навигация
- Предыдущий: 200
- Следующий: 202
- Индекс: 00. Индекс разборов
Навигация
- Предыдущий: 200
- Следующий: 202
- Индекс: 00. Индекс разборов