Skip to content

vLLM runs out of GPU memory during serving after the prefill token budget is raised

Raising the number of tokens vLLM may batch per step improves time to first token and throughput, and it also raises the peak activation memory a prefill step needs. When that peak collides with the memory already reserved for the KV cache, the failure arrives during serving rather than at startup, after the deployment has looked healthy.

Quick answer

Drop --max-num-batched-tokens to 2048 to 4096 first, then --max-num-seqs, and only then touch gpu_memory_utilization. A runtime failure after a throughput change points at step size, not at cache size.

Memory#vllm#chunked-prefill#max-num-batched-tokens#cuda-oom#throughput-tuning#serving-runtime

What this failure is

A serving-time out-of-memory failure caused by the per-step prefill token budget rather than by the KV cache reservation, appearing only once arriving prompts are long enough to build a maximum-sized batch.

Why it happens (the mechanism)

The batched token budget decides how much prefill work goes into one step, and activation memory for that step scales with it. That memory is not part of the pool the KV cache was sized against, so raising the budget quietly increases peak usage above what startup profiling measured. Profiling runs before any real prompt has arrived, so the configuration that fails under long prompts is one that was never exercised at its own limit.

What you'll observe

  • The server starts cleanly and fails only once real traffic arrives
  • The failure correlates with long prompts rather than with the number of concurrent users
  • A configuration tuned for throughput on one model fails on another of similar size
  • Reducing the served context length does not help, because the prompts were already within it

Common symptoms and what they mean

SymptomWhy it happens
torch.OutOfMemoryError: CUDA out of memory raised during a forward pass while serving rather than during startupTwo different budgets draw on the same device memory. gpu_memory_utilization reserves a pool that the KV cache is carved from, while the batched token limit governs how much work a single step may do and therefore how large the activation tensors for that step become. Raising the second does not consult the first.
Failures beginning immediately after --max-num-batched-tokens or --enable-chunked-prefill was changedThe token budget only binds when a step is large enough to reach it, which requires long prompts or a heavy prefill mix. A deployment can therefore pass startup profiling, serve ordinary traffic for hours, and fail the first time the arriving prompts are long enough to build a full-sized batch.
A log line advising that if out-of-memory occurs during cudagraph capture, consider decreasing gpu_memory_utilization or switching to eager modeThe reason the served context length is not the lever is that the prompts were always inside it. What changed is how many prefill tokens the scheduler is willing to put into one step, which is a throughput setting rather than a capacity one.

Which systems are affected

  • vLLM V1, where chunked prefill is enabled by default whenever possible
  • Deployments tuned toward a batched token budget above 8192 for throughput
  • Mixed traffic combining long prompts with many short decodes in the same step
  • Configurations where max_num_seqs multiplied by max_model_len already consumes most of the cache budget

How to confirm this is the problem

Apply the following checklist to a small reproduction: each box below is a positive signal that you are looking at this exact failure rather than a sibling in the same taxonomy.

  • Check whether the failure began with a change to --max-num-batched-tokens or --enable-chunked-prefill rather than with a change in traffic volume.
  • Correlate the failures against prompt length rather than request rate. A token budget only binds once a step is large enough to reach it.
  • Restart with the budget set to 2048 and replay the same traffic. Success at the lower budget with everything else unchanged confirms the step size rather than the cache was the cause.

Example training logs (fingerprint)

training.log (synthetic fingerprint)
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 3.83 GiB. GPU 0 has a total capacity of 79.15 GiB of which 2.11 GiB is free.
INFO: Chunked prefill is enabled with max_num_batched_tokens=16384.
WARNING: If out-of-memory occurs during cudagraph capture, consider decreasing gpu_memory_utilization or switching to eager mode.

Timestamps and exact values vary across runs, but the pattern. An info-level start, an early WARN, an ERROR carrying the symptom. Is the actual fingerprint you should alert on. The Denpex platform flags this combination automatically.

Root cause, fix & prevention, signed in

Sign up free to see why this failure really happens, the exact remediation steps, and the production-grade prevention pattern. You also get 3 free full diagnoses for your own training logs.

Sign up free. Unlock the full analysis

No credit card · 3 free diagnoses · Instant access

Why the recommended fix works

Lowering the token budget shrinks the largest step the scheduler can build, which lowers the activation peak that collides with the reserved cache. It costs time to first token rather than correctness, and it targets the quantity that actually changed, unlike lowering the served context length, which does nothing when the prompts were already inside it.

Code examples

typical reference pattern
// Typical pattern:
import torch.optim as optim
optimizer = optim.AdamW(model.parameters(), lr=3e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_training_steps)
for step in range(num_training_steps):
    optimizer.zero_grad()
    loss = model(batch)
    loss.backward()
    optimizer.step()
    scheduler.step()

Adapt the snippet to your framework. The same pattern holds for PyTorch Lightning, Hugging Face Trainer, DeepSpeed, Megatron-LM, and vLLM training wrappers. Where the wrapper exposes a config flag (for examplelr_scheduler_type in Trainer), prefer the flag over the imperative API to keep the schedule declarative and reproducible.

Best practices by model family

Model / StackRecommendationNotes
Runtime out-of-memory after a throughput changeLower --max-num-batched-tokens to 2048 to 4096This is the setting that enlarged the step and its activation peak.
Large model or long-context serving--max-num-seqs 128 to 256Bounds how many sequences contribute to one batch.
Throughput-oriented small model on a large GPUA budget above 8192, validated at long-prompt loadBetter time to first token, but only safe if tested at the prompt lengths that fill it.
Failure during graph capture--enforce-eagerRemoves the memory reserved for CUDA graphs from the peak.

With the fix vs without the fix

DimensionWith the fixWithout the fix
When the failure appearsDuring serving, on long promptsExpected at startup, like a cache sizing error
Which budget is responsibleThe per-step batched token limitAssumed to be gpu_memory_utilization
Effect of lowering max_model_lenNo help when prompts were already inside itAssumed to reduce memory in every case

Real engineering notes

There is no closed-form value for this budget, and looking for one wastes time. The working method upstream and in practice is to raise it until the deployment fails at representative load and then step back one notch, because the limit depends on the model, the sequence mix and the card. What matters is that the load used for that experiment contains the longest prompts the service will really see; tuning against short prompts produces a value that fails the first time a long one arrives.

Visual fingerprint

Two budgets, one device
reserved by gpu_memory_utilization   [ weights ][ KV cache pool          ]
needed per step by the token budget  [ activations for max_num_batched_tokens ]
                                                     ^
                                     grows when the budget is raised, until it
                                     collides with the reserved pool at runtime
The KV cache pool is reserved up front, while activation memory is decided per step by the batched token budget. Raising the budget increases the per-step requirement without changing the reservation, so the two meet only when a step large enough to reach the budget is actually built.

Root cause, fix & prevention

Frequently asked questions

Twelve targeted questions that engineers and on-call staff most commonly ask about this failure.

Why did it start fine and fail later?
Startup profiling runs before any real prompt arrives. The token budget only binds when a step is large enough to reach it, which needs long prompts, so the configuration fails the first time such traffic appears.
Should I lower gpu_memory_utilization?
Not first. The setting that changed is the per-step token budget, and lowering the utilization shrinks the KV cache without reducing the activation peak that collided with it.
Is there a formula for max_num_batched_tokens?
No practical one. Raise it until the deployment fails at representative load, then step back one notch, and make sure that load includes the longest prompts the service will really see.
Does lowering max_model_len help?
Only if requests were exceeding it. When the prompts were already inside the served window, the failure is about how many prefill tokens go into one step, not about the window.

Don't just read the fix, diagnose your run

The encyclopedia tells you what went wrong. Denpex tells you what went wrong in YOUR training run. With your logs, your config, and your stack.