huggingface/transformers
Continuous batching
The technique that turns transformers serve from a toy demo into a production-capable inference endpoint. Instead of running each request as a fresh batch, the scheduler interleaves prefill and decode steps from many concurrent requests in the same forward pass, using a paged KV cache.
The feature shipped on 2025-08-26 in PR #40426 (initial implementation) and has been refined since.
Where it lives
src/transformers/generation/continuous_batching/
├── __init__.py
├── scheduler.py # request queue, prefill/decode interleaving
├── paged_attention.py # block-table builder
├── batch.py # constructs the merged forward inputs
└── ...Companion files:
src/transformers/integrations/eager_paged.py— pure-PyTorch paged attention.src/transformers/integrations/sdpa_paged.py— SDPA paged attention.src/transformers/integrations/flash_paged.py— FlashAttention paged attention.src/transformers/cache_utils.py—PagedCacheand friends.
Why it matters
Without continuous batching:
- Each request occupies a fixed slot until it finishes generating.
- A batch of 8 must wait for the slowest request before freeing GPU memory.
- Throughput collapses when request lengths are heterogeneous.
With continuous batching:
- The scheduler holds a pool of KV blocks and assigns blocks to requests as they grow.
- A single forward pass mixes "prefill" computations (a long prompt being ingested) and "decode" computations (single-token decoding for in-flight requests).
- Throughput stays close to optimal even with variable lengths.
Block-paged KV cache
The KV cache is a flat tensor of shape [num_blocks, block_size, num_heads, head_dim]. Each request owns a logical sequence built from a list of physical block indices (the block table). When a request grows past its current allocation, the scheduler appends a free block.
graph TD
R1[Req 1, 2 blocks] --> Pool[(KV pool, N blocks)]
R2[Req 2, 5 blocks] --> Pool
R3[Req 3, 1 block] --> Pool
Pool --> PA[Paged attention kernel]
PA --> Logits[Per-request logits]The sequence-to-block mapping is the block table, and it is consumed by the paged attention kernels.
Forward pass shape
A continuous-batched forward pass concatenates all active sequences into one flat sequence and uses cumulative sequence lengths plus a block table to keep them logically separate. Conceptually:
input_ids = [tokens of req1, tokens of req2, ...](1D)cu_seqlens = [0, len1, len1+len2, ...]block_table = [...]mapping logical token positions to KV blocks.
Models that support this declare _supports_paged_attention = True (or are routed automatically when SDPA/FlashAttention paged variants are available).
Scheduler
The scheduler in src/transformers/generation/continuous_batching/scheduler.py maintains:
- A pool of free blocks.
- A queue of pending requests with their tokenized prompts.
- A list of in-flight requests with current decoded ids and remaining max length.
Each tick:
- Admit pending requests until the prefill token budget (
cb_max_batch_tokens) is hit. - Emit a forward pass mixing prefill chunks and per-request decode tokens.
- Update each request's KV blocks, and release blocks for finished requests.
Tunables
transformers serve exposes the relevant CLI flags:
| Flag | Purpose |
|---|---|
--continuous-batching |
Enable. |
--cb-block-size |
Tokens per KV block (default model-dependent). |
--cb-num-blocks |
Pool size. Determines the maximum total context held in GPU. |
--cb-max-batch-tokens |
Max tokens admitted into a single forward pass. |
When to use it
Continuous batching helps when:
- The service handles many concurrent users.
- Request lengths are variable.
- Throughput matters more than per-request latency.
It is overkill (and adds complexity) for batch-1 inference; greedy model.generate(...) with a DynamicCache is simpler.
Limitations
- Not all model architectures advertise paged-attention support yet. Encoder-decoder, some hybrid (Mamba / SSM) models, and a few VLMs are still being migrated.
- Custom logits processors that depend on the full causal context (e.g., n-gram penalty) may need extra wiring.
- Scheduler heuristics are conservative; production deployments may want to tune
cb_*knobs.
Integration points
- Generation —
generateis replaced by the scheduler in serve mode. - Cache — paged caches.
- Attention — paged kernels.
- CLI —
transformers serve --continuous-batching. - Serving — the HTTP serving feature relies on this scheduler.
Entry points for modification
- New paged kernel → add to
src/transformers/integrations/<backend>_paged.pyand register in the dispatcher. - Scheduler heuristics →
src/transformers/generation/continuous_batching/scheduler.py. - Tests →
tests/generation/test_continuous_batching*.py.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.