Transformers documentation
Непрерывная пакетная обработка
Непрерывная пакетная обработка
Continuous batching максимизирует использование GPU, динамически пересобирая батч на каждом шаге генерации. По мере завершения запросов новые добавляются в батч немедленно, а не ждут завершения всего батча. GPU остаётся загруженным, а пропускная способность — высокой.
Для производственных развёртываний используйте transformers serve. Он основан на ContinuousBatchingManager и предоставляет HTTP-эндпоинт, совместимый с OpenAI.
generate_batch
Поддержка непрерывного батчинга (continuous batching) реализована через generate_batch(). Передайте список токенизированных промптов и получите результаты для всех из них по завершении обработки. generate_batch управляет планированием внутри и блокируется до завершения всех запросов.
Для сценариев обслуживания и потоковой передачи используйте ContinuousBatchingManager напрямую для управления запросами.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation import ContinuousBatchingConfig, GenerationConfig
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-4B",
attn_implementation="flash_attention_2",
device_map="auto",
dtype=torch.bfloat16,
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B")
prompts = [
"What's up?",
"Name a cat breed.",
"Write a detailed history of quantum mechanics.",
]
inputs = [tokenizer.encode(p) for p in prompts]
generation_config = GenerationConfig(
max_new_tokens=64,
eos_token_id=tokenizer.eos_token_id,
)
outputs = model.generate_batch(inputs=inputs, generation_config=generation_config)
for request_id, output in outputs.items():
text = tokenizer.decode(output.generated_tokens, skip_special_tokens=True)
print(f"[{request_id}] {text}")ContinuousBatchingManager
ContinuousBatchingManager запускает фоновый поток и позволяет отправлять запросы и получать результаты независимо друг от друга. На каждом шаге генерации он проверяет завершённые запросы и планирует новые для добавления в батч. Это полезно для потоковой передачи, обслуживания в реальном времени или отправки запросов по мере их поступления.
Используйте continuous_batching_context_manager() для безопасного запуска и остановки менеджера. В примере ниже представлены входные данные переменной длины. Как только самый короткий промпт завершается, он покидает батч, а более длинные промпты продолжают генерацию. При статическом батчинге пришлось бы дополнять все промпты до одинаковой длины. Continuous batching освобождает завершённый промпт, позволяя сразу начать обработку следующего.
with model.continuous_batching_context_manager(generation_config=generation_config) as manager:
manager.add_request(
input_ids=tokenizer.encode("Write a detailed history of quantum mechanics."),
request_id="long",
max_new_tokens=512,
)
manager.add_request(
input_ids=tokenizer.encode("What's up?"),
request_id="short_0",
max_new_tokens=32,
)
manager.add_request(
input_ids=tokenizer.encode("Name a cat breed."),
request_id="short_1",
max_new_tokens=32,
)
for result in manager:
text = tokenizer.decode(result.generated_tokens, skip_special_tokens=True)
print(f"[{result.request_id}] {text}")Вы также можете вызвать init_continuous_batching(), чтобы самостоятельно управлять жизненным циклом.
manager = model.init_continuous_batching(generation_config=generation_config)
manager.start()
# submit and retrieve requests...Остановка менеджера
Менеджер запускает фоновый поток и управляет распределёнными ресурсами. Завершение работы происходит в два этапа, что позволяет выбрать, как обрабатывать незавершённые задачи.
Вызовите stop(), чтобы остановить фоновый поток. По умолчанию менеджер прекращает приём новых запросов и ожидает завершения всех запросов в очереди и активных запросов перед выходом из потока.
manager.stop()
Передайте hard_stop=True, чтобы немедленно прекратить выполнение ожидающих задач. Очередные и активные запросы завершаются с ошибкой RuntimeError вместо нормального завершения.
manager.stop(hard_stop=True)После вызова stop методы add_request() и add_requests() отклоняют новые запросы и записывают предупреждение в журнал. Вы можете снова вызвать start, чтобы запустить новую сессию генерации с тем же менеджером.
Вызовите destroy(), чтобы освободить распределённые ресурсы. destroy сначала останавливает менеджер, если он ещё работает, после чего перезапустить его невозможно. Используйте этот метод, когда работа с непрерывным батчингом завершена на всё время жизни процесса.
manager.destroy()
continuous_batching_context_manager() управляет этим процессом. При выходе он вызывает stop, а destroy — только если вы не передали persistent_manager=True для кэширования менеджера в модели для следующей сессии.
Добавление запросов
add_request() отправляет один запрос. Укажите request_id или позвольте менеджеру сгенерировать его автоматически.
manager.add_request(input_ids=input_ids, request_id="my_request")add_requests() отправляет пакет запросов сразу. Он автоматически сортирует входные данные, чтобы максимизировать попадания в кэш префиксов, когда включено разделение блоков.
manager.add_requests(inputs=inputs)
Отмените запрос с помощью cancel_request().
manager.cancel_request(request_id="my_request")Параметры выборки для каждого запроса
Включите per_request_processors, чтобы применять temperature, top_k и top_p независимо для каждого запроса в рамках одного прохода, что позволяет использовать разные параметры выборки для разных запросов (например, креативные выводы с высокой температурой и точные выводы с низкой температурой).
cb_config = ContinuousBatchingConfig(per_request_processors=True)
# each request gets its own sampling parameters
manager.add_request(input_ids=inputs_a, temperature=0.9, top_p=0.95)
manager.add_request(input_ids=inputs_b, temperature=0.1, top_k=10)Каждый параметр в GenerationConfig должен иметь значение, отличное от значения по умолчанию, чтобы соответствующий logits-процессор был создан во время выполнения. Например, установите temperature в значение, отличное от None или 1, чтобы обеспечить управление температурой для каждого запроса. Запросы с температурой 1 по-прежнему можно создавать впоследствии.
Получение результатов
Итерируйте по менеджеру, чтобы получать результаты по мере их поступления.
for result in manager:
print(tokenizer.decode(result.generated_tokens, skip_special_tokens=True))get_result() получает следующий результат из очереди вывода. Передайте request_id, чтобы отфильтровать конкретный запрос. Если следующий результат в очереди не совпадает, он возвращается в очередь, а метод возвращает None.
# next available result
result = manager.get_result()
# filter for a specific request
result = manager.get_result(request_id="my_request")Потоковая передача
Установите streaming=True в запросе, затем используйте request_id_iter(), чтобы перебирать частичные результаты по мере генерации токенов.
from transformers.generation.continuous_batching import RequestStatus
manager.add_request(input_ids=input_ids, request_id="streamed", streaming=True)
for chunk in manager.request_id_iter(request_id="streamed"):
token = tokenizer.decode(chunk.generated_tokens[-1:], skip_special_tokens=True)
print(token, end="", flush=True)
if chunk.status == RequestStatus.FINISHED:
breakContinuousBatchingConfig
ContinuousBatchingConfig управляет KV-кэшем, планированием, CUDA-графиками, использованием памяти и другими параметрами. Передайте его в generate_batch или init_continuous_batching() с аргументом continuous_batching_config, отдельно от GenerationConfig. Эти два конфигурационных объекта описывают разные вещи. GenerationConfig ориентирован на модель и содержит параметры выборки и остановки, тогда как ContinuousBatchingConfig ориентирован на аппаратное обеспечение и содержит параметры памяти и планирования.
Назначение
continuous_batching_configв GenerationConfig устарело, вызываетFutureWarningи будет удалено в v5.19.
По умолчанию max_batch_tokens равно 8192, ограничено доступной памятью GPU и никогда не опускается ниже 256, а num_blocks заполняет оставшуюся память. Используйте таблицу ниже, чтобы подобрать подходящие параметры.
| Возможность | Память | Пропускная способность | Задержка |
|---|---|---|---|
max_memory_percent / page_size | ✓ управляет бюджетом KV | ||
max_batch_tokens | ↑ более крупные входные буферы | ✓ более крупные батчи префилла | ✓ TTFT при ограничении на prefill |
scheduler | ✓ политика планирования | ✓ TTFT | |
| CUDA-графы | ↑ хранилище графа | ✓ меньшие накладные расходы на диспетчеризацию | ✓ |
| Асинхронная пакетная обработка | ↑ ~2× буферы ввода-вывода | ✓ перекрывает CPU/GPU | |
| Компиляция | ↑ только warmup-time | ✓ более быстрые прямые проходы | ✓ |
| Быстрый путь декодирования | ↑ таблица блоков на запрос | ✓ более быстрые шаги только декодирования | ✓ |
| Размещение на CPU | ↑ закрепленная память CPU | ✓ пропускает некоторые повторные префиллы | |
| Кэширование префиксов | ↓ общие блоки KV | ✓ пропускает избыточный prefill | ✓ TTFT |
| Страничная аттенция | ↓ без фрагментации | ✓ динамический состав батча | |
| Скользящее окно | ↓ ограниченный KV на слой | ||
| Процессоры для каждого запроса | ✓ смешанные параметры выборки в рамках одной пачки | ||
max_requests_per_batch | ↓ ограничивает буфер логитов | ✓ ограничивает размер батча | |
safety_margin | ✓ защищает задержку декодирования |
from transformers.generation import ContinuousBatchingConfig
cb_config = ContinuousBatchingConfig(
max_memory_percent=0.8,
page_size=256, # KV cache page size in tokens
scheduler_type="fifo", # "fifo" or "prefill_first"
)
outputs = model.generate_batch(
inputs=inputs,
generation_config=generation_config,
continuous_batching_config=cb_config,
)Размер страницы кэша KV
page_size определяет, сколько токенов KV хранит полная страница внимания для одного слоя. Значение должно быть не менее 4, иначе при инициализации будет выброшено исключение ValueError. Значение по умолчанию 256 подходит для большинства задач.
Параметр
block_sizeустарел и заменён наpage_size.
Увеличение размера страницы сокращает накладные расходы на управление памятью, но приводит к потере места, если последовательность обрывается посередине страницы. Меньшие страницы тратят меньше места, но требуют больше накладных расходов. Как правило, настраивать page_size не нужно. Если в вашей рабочей нагрузке много коротких запросов, попробуйте уменьшить значение. Если же есть несколько очень длинных запросов, попробуйте увеличить значение. О том, как страницы, блоки и фрагменты соотносятся между собой, см. в разделе Управление памятью.
cb_config = ContinuousBatchingConfig(page_size=128)Размер батча префилла
max_batch_tokens задаёт бюджет токенов для одного прохода вперёд, то есть максимальное количество query-токенов, которые модель обрабатывает за один раз. Увеличение бюджета позволяет упаковать больше токенов промпта в каждый prefill, что повышает пропускную способность prefill и снижает время до первого токена в нагрузках с преобладанием промптов. Планировщик распределяет промпты, превышающие бюджет, по нескольким шагам. См. chunked prefill для описания обработки промптов, превышающих бюджет.
По умолчанию max_batch_tokens равно 8192. Значение ограничивается доступной памятью GPU, чтобы входные тензоры в рамках батча не вытесняли KV-кэш, и никогда не опускается ниже 256. На GPU с небольшим количеством свободной памяти это ограничение снижает значение ниже 8192.
max_batch_tokens и num_blocks используют один и тот же бюджет памяти, поэтому они находятся в обратной зависимости друг от друга. Увеличение бюджета токенов выделяет более крупные входные буферы и оставляет меньше блоков кэша KV для одновременных или длинных запросов. Увеличивайте max_batch_tokens, чтобы повысить пропускную способность префилла, если у вас есть свободная память, и уменьшайте его, чтобы освободить память для дополнительных блоков KV или избежать ошибок нехватки памяти.
cb_config = ContinuousBatchingConfig(max_batch_tokens=16384)Ограничения по пакетам и планированию
max_requests_per_batch ограничивает количество запросов, обрабатываемых за один проход. Модель формирует тензор logits, размер которого определяется числом токенов в батче и размером словаря, а затем он приводится к fp32 для выборки, что удваивает занимаемую память. Для каждого запроса требуется предсказание только одного следующего токена, поэтому меньшее ограничение позволяет уменьшить размер этого тензора и избежать ошибок нехватки памяти в батчах с преобладанием prefill и большими словарями. По умолчанию значение устанавливается равным числу отправленных промптов, с запасным значением 1024, после чего оно ограничивается в соответствии с max_batch_tokens и num_blocks.
cb_config = ContinuousBatchingConfig(max_requests_per_batch=256)safety_margin резервирует долю свободного кэша для активных запросов. Когда свободная ёмкость падает ниже safety_margin (доли от общего размера кэша), планировщик перестаёт принимать новые prefill-запросы и продолжает только декодирование уже запущенных запросов. Это позволяет завершить активную работу в приоритете перед началом новых запросов, что защищает задержку декодирования и откладывает offloading. Значение должно находиться в диапазоне от 0 до 1, где 0 отключает резервирование.
cb_config = ContinuousBatchingConfig(safety_margin=0.15)Значение по умолчанию зависит от планировщика. Для планировщика FIFO оно равно 0.15, а для prefill_first — 0.0. См. раздел Admission, чтобы узнать, как запас взаимодействует с планированием.
Логарифмические вероятности
ContinuousBatchingConfig возвращает лог-вероятность каждого сгенерированного токена, если return_logprobs=True. Это полезно для RL, где logprobs являются входными данными для некоторых циклов обучения.
cb_config = ContinuousBatchingConfig(return_logprobs=True)
# generate_batch()
for request_id, output in outputs.items():
for token_id, log_prob in zip(output.generated_tokens, output.logprobs):
token = tokenizer.decode([token_id])
print(f"{token} | logprob: {log_prob}")CUDA-графы
CUDA-графы устраняют накладные расходы на диспетчеризацию на CPU, записывая граф выполнения на GPU один раз и воспроизводя его для батчей с совпадающими формами. Включите их явно с помощью use_cuda_graph=True.
cb_config = ContinuousBatchingConfig(use_cuda_graph=True)Когда менеджер активен, он дополняет длины запросов и KV до фиксированных интервалов, чтобы формы повторялись, а графы переиспользовались. Меньшие значения q_padding_interval_size и kv_padding_interval_size снижают потери вычислительных ресурсов на дополнения, но это означает, что графу приходится записывать и хранить больше уникальных форм, что требует больше памяти.
cb_config = ContinuousBatchingConfig(
use_cuda_graph=True,
q_padding_interval_size=64,
kv_padding_interval_size=16384,
)Асинхронная пакетная обработка
Асинхронное батчинг (Async batching) перекрывает планирование следующего батча на CPU с вычислениями текущего батча на GPU. Для этого требуются CUDA-графы, а объём VRAM, используемый для входных тензоров, примерно удваивается.
cb_config = ContinuousBatchingConfig(
use_cuda_graph=True,
use_async_batching=True,
)Компиляция
default_compile_level применяет torch.compile к прямым проходам модели. Компиляция обменивает разовые затраты на прогрев на ускорение прямых проходов во время генерации. Более высокие уровни выполняют более агрессивную оптимизацию, что повышает пропускную способность, но увеличивает время прогрева. Держите уровень низким для тестов и итераций бенчмарков, а для длительных сценариев обслуживания, где затраты на прогрев окупаются за множество запросов, повышайте его.
Значение уровня варьируется от 0 до 3. Уровень 0 является значением по умолчанию и полностью пропускает компиляцию.
| Уровень | mode | dynamic | Компромисс |
|---|---|---|---|
| 0 | — | — | Компиляция не выполняется (по умолчанию), самый быстрый запуск |
| 1 | default | True | Небольшое ускорение, короткая разминка |
| 2 | max-autotune-no-cudagraphs | True | Большее ускорение, более длительная разминка |
| 3 | max-autotune-no-cudagraphs | False | Максимальная пропускная способность, самая длительная разминка |
cb_config = ContinuousBatchingConfig(default_compile_level=1)Уровень предоставляет конфигурацию по умолчанию CompileConfig для путей выполнения varlen и decode. Она применяется только к тому пути, для которого не задана явная конфигурация, поэтому varlen_compile_config и decode_compile_config имеют приоритет, если они установлены. В случае использования FlashAttention путь varlen пропускает компиляцию, так как max_length_k вызывает частую перекомпиляцию, поэтому в этом случае уровень влияет только на путь decode.
Быстрый путь декодирования
Если пакет содержит только запросы декодирования (один токен запроса на последовательность), менеджер может направить выполнение в ядро flash_attn_with_kvcache вместо ядра переменной длины. Это быстрее, чем путь varlen, поскольку ядро читает и записывает постраничную KV-кэш на месте через таблицу блоков, а не через ручное обновление. Подробности на уровне ядра см. в разделе Paged attention.
Быстрый путь определяется размером max_blocks_per_request, который задаёт размеры таблицы блоков для каждого запроса. По умолчанию это значение выводится автоматически. Если в менеджере заданы max_prompt_length и max_generated_length, таблица блоков рассчитывается так, чтобы вместить максимальную длину последовательности. В противном случае используется значение по умолчанию (32 блока на запрос).
Установите max_blocks_per_request в конкретное значение, чтобы явно задать размер таблицы блоков. Это полезно, если вам известна максимальная длина последовательности для каждого запроса и вы хотите ограничить затраты памяти на таблицу блоков.
cb_config = ContinuousBatchingConfig(max_blocks_per_request=64)Установите max_blocks_per_request=0, чтобы отключить быстрый путь и заставить каждый батч проходить через ядро varlen. Это восстанавливает поведение по умолчанию до его изменения и полезно, когда быстрый путь недоступен для вашей реализации внимания (менеджер также отключает его автоматически, если базовое ядро не может быть использовано).
cb_config = ContinuousBatchingConfig(max_blocks_per_request=0)Быстрый путь опирается на ядро flash_attn_with_kvcache, которое доступно для двух комбинаций устройства и реализации внимания.
| Устройство | attn_implementation |
|---|---|
| CUDA | flash_attention_2 или flash_attention_3 |
| XPU | flash_attention_2 |
Для любой другой комбинации или в случае, когда ядро не может быть импортировано, менеджер переключается на путь varlen. Предупреждение записывается в журнал только при явном задании max_blocks_per_request.
Полное внимание поддерживает таблицы блоков ядра, но скользящее внимание — нет. Если в модели есть хотя бы один слой скользящего внимания, включая гибридные модели, использующие также полное внимание, при инициализации значение max_blocks_per_request принудительно устанавливается в 0, а любое ненулевое значение, заданное пользователем, переопределяется. Затем менеджер фиксирует в журнале, что быстрый путь декодирования недоступен, поскольку некоторые типы внимания в модели не поддерживают таблицы блоков ядра.
Размещение на CPU
CPU offloading копирует вытесненные блоки KV-кэша в предварительно выделенный закреплённый буфер CPU, когда GPU KV-кэш заполнен. После того, как в кэше освобождается место, менеджер копирует блоки обратно на GPU и возобновляет запрос без повторного вычисления его промпта и сгенерированных токенов.
Установите cpu_offload_space в размер области подкачки на CPU в ГиБ. Значение по умолчанию, 0.0, отключает выгрузку на CPU.
cb_config = ContinuousBatchingConfig(cpu_offload_space=8.0)По умолчанию cpu_offload_space_safety_threshold=0.8 ограничивает запрашиваемое пространство 80% доступной системной оперативной памяти, если установлен psutil. Установите cpu_offload_space=None, чтобы размер пула подкачки определялся на основе порога безопасности.
Тайм-аут параллелизма тензоров
При тензорном параллелизме менеджер создаёт группу CPU-коммуникации для координации отправки запросов, их отмены и завершения работы между рангами. cpu_group_timeout ограничивает время, в течение которого коллективная операция в этой группе может блокироваться, прежде чем процесс завершится с ошибкой. Если один ранг зависает, таймаут не даёт остальным ждать бесконечно.
Установите более длительный тайм-аут для рабочих нагрузок, которые редко выполняют коллективные операции, или передайте None, чтобы отключить его.
cb_config = ContinuousBatchingConfig(cpu_group_timeout=600.0)Кэширование префиксов
Когда несколько запросов имеют общий префикс, например системный промпт, менеджер повторно использует их блоки KV-кэша вместо повторного вычисления. allow_block_sharing по умолчанию установлено в True. Разделение префикса активно только тогда, когда все типы аллокаторов внимания поддерживают его, поэтому его используют только модели с полным вниманием. Чисто скользящие и гибридные модели отключают его, даже если allow_block_sharing равно True.
cb_config = ContinuousBatchingConfig(
allow_block_sharing=True, # default
)Выберите бэкенд внимания
Continuous batching поддерживает FlashAttention, SDPA и "paged|eager". Укажите attn_implementation при загрузке модели.
| Бэкенд | attn_implementation | Требования |
|---|---|---|
| FlashAttention 2 | “flash_attention_2” | пакет flash-attn или kernels |
| FlashAttention 3 | “flash_attention_3” | flash-attn-3 или пакет kernels |
| SDPA (нативная реализация PyTorch) | “sdpa” | None |
| Eager | “paged|eager” | None |
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-4B",
attn_implementation="flash_attention_2",
device_map="auto",
dtype=torch.bfloat16,
)Continuous batching работает быстрее с FlashAttention, чем с eager или SDPA, в основном потому, что FlashAttention не требует маски внимания. Если модель поддерживает FlashAttention, модель, загруженная с "eager" или "sdpa", переключается на FlashAttention 3, а если FlashAttention 3 не установлен — на FlashAttention 2, и выводится предупреждение. Если ни одна из версий не установлена, бэкенд не меняется.
Чтобы сохранить eager или SDPA, например при тестировании, установите auto_switch_to_flash=False в ContinuousBatchingConfig. Если бэкенд остаётся "eager", continuous batching выполняется в режиме "paged|eager".
Параллелизм тензоров
Для моделей, которые слишком велики, чтобы поместиться на одном GPU, распределите веса по устройствам с помощью тензорного параллелизма. Укажите количество устройств с помощью DistributedConfig(tp_size=N). Непрерывная пакетная обработка (continuous batching) читает размер тензорного параллелизма из модели, чтобы определить размер страничной KV-кэши для каждого шарда. Список поддерживаемых архитектур и описание работы шардирования приведены в разделе Тензорный параллелизм.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, DistributedConfig
from transformers.generation import ContinuousBatchingConfig, GenerationConfig
distributed_config = DistributedConfig(tp_size=4)
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-32B",
attn_implementation="flash_attention_2",
distributed_config=distributed_config,
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-32B")
inputs = [tokenizer.encode(p) for p in ["What's up?", "Name a cat breed."]]
generation_config = GenerationConfig(max_new_tokens=64, eos_token_id=tokenizer.eos_token_id)
outputs = model.generate_batch(inputs=inputs, generation_config=generation_config)Запустите скрипт с помощью torchrun, установив --nproc-per-node в количество GPU, на которые вы хотите распределить шарды.
torchrun --nproc-per-node 4 cb_tp.py
Размер тензорного параллелизма должен делить num_key_value_heads модели (проверьте конфигурацию модели). В противном случае paged cache будет вызывать ошибку при запуске, поэтому выберите подходящее значение --nproc-per-node.
Не задавайте
device_mapвместе сdistributed_config. Эти параметры конфликтуют, так какdevice_mapразмещает целые модули на конкретных GPU, тогда как тензорное параллелизм распределяет те же параметры по всем GPU.
Внимание со скользящим окном
Модели со скользящим окном внимания (Mistral, Gemma 2) работают с непрерывным батчингом. Скользящие слои используют SlidingAttentionCacheAllocator, который сохраняет только фиксированное окно последних токенов, перерабатывая небольшой набор страниц. Тип внимания определяется по layer_types или sliding_window модели, а не по ContinuousBatchingConfig.
from transformers import AutoConfig, AutoModelForCausalLM
config = AutoConfig.from_pretrained("google/gemma-2-2b")
config.sliding_window = 4096
model = AutoModelForCausalLM.from_pretrained(
"google/gemma-2-2b",
config=config,
attn_implementation="sdpa",
device_map="auto",
dtype=torch.bfloat16,
)Кэширование префиксов и быстрый путь декодирования отключаются при наличии скользящего внимания, включая гибридные модели, которые также содержат слои полного внимания.
Дальнейшие шаги
- Статья Continuous batching blog post рассматривает кэширование KV, chunked prefill и динамическое планирование с приведением цифр бенчмарков производительности.
- Для более подробного изучения работы системы непрерывного батчинга см. документацию Архитектура непрерывного батчинга.