Skip to content

vLLM workers time out reading the shared-memory broadcast ring and the engine dies

vLLM distributes each step to its workers through a shared-memory ring buffer. When a worker fails to publish within the read deadline, the reader raises a bare TimeoutError from acquire_read, the execute_model call that was waiting on it fails, and the engine tears down — so a stalled worker is reported as a timeout in the transport rather than as a problem in the worker.

Quick answer

A bare TimeoutError from acquire_read usually means a slow step, not a dead worker. Check whether the workers are still alive, then shorten the step by lowering the batched token budget rather than raising the timeout.

Communication#vllm#shm-broadcast#timeouterror#execute_model#worker-rpc#tensor-parallel

What this failure is

A fatal timeout raised by vLLM's shared-memory broadcast reader when a worker does not publish its step result inside the read deadline, reported at the transport boundary rather than by the worker responsible.

Why it happens (the mechanism)

Work is handed to workers through a ring buffer whose reader waits for a fixed period. Waiting is the only thing the reader does, so a deadline is the only failure it can express, and it expresses it identically whether the worker crashed, stalled on a device, or simply had more to do than the deadline allowed. The traceback therefore describes the waiting rather than the cause.

What you'll observe

  • The traceback names a contextlib helper and a ring-buffer read rather than any model code
  • The exception carries no message at all, so there is nothing in it to search for
  • It appears under load or on long steps and cannot be reproduced with a single short request
  • Raising the engine iteration timeout postpones the failure without changing anything

Common symptoms and what they mean

SymptomWhy it happens
File vllm/distributed/device_communicators/shm_broadcast.py, line 443, in acquire_read raise TimeoutErrorThe ring buffer read is bounded by a deadline, not by liveness. The reader cannot distinguish a worker that has crashed from one that is merely slow, so both produce the same bare TimeoutError, and the exception is raised at the point of waiting rather than at the point of trouble.
TimeoutError with no message, followed by The above exception was the direct cause of the following exceptionThat is why the traceback is so unhelpful: the frames belong to the transport that noticed, and the worker that caused it is a different process which may still be running and may have printed nothing. The failure surfaces at the boundary between processes, which is the one place with no information about either side.
An RPC call to execute_model timing out immediately before the engine reports it has diedBecause the deadline is fixed while step duration is not, anything that lengthens a step pushes it toward the limit. A step that takes longer than the reader will wait is indistinguishable from a dead worker, so a purely performance problem is delivered as a fatal transport error.
Environment lines showing NCCL_CUMEM_ENABLE=0 and TORCHINDUCTOR_COMPILE_THREADS=1 captured alongside the tracebackThe ring buffer read is bounded by a deadline, not by liveness. The reader cannot distinguish a worker that has crashed from one that is merely slow, so both produce the same bare TimeoutError, and the exception is raised at the point of waiting rather than at the point of trouble.

Which systems are affected

  • Tensor-parallel and pipeline-parallel vLLM, where every step is fanned out to worker processes
  • Deployments where one worker is slower than its peers, whether from compilation, paging or a busy device
  • Long prefill steps that exceed the reader's patience while the worker is still legitimately working
  • Containers whose shared memory is contended by other tenants

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 worker processes are still present after the failure. Survivors indicate a slow step; absence indicates the worker really is gone and the cause lies in its own output.
  • Compare step duration in the period before the failure against the read deadline. A distribution whose tail approaches the deadline explains an intermittent failure that no single request reproduces.
  • Re-run the same traffic with the batched token budget halved. Surviving at the smaller step size confirms duration rather than transport as the cause.

Example training logs (fingerprint)

training.log (synthetic fingerprint)
File "/root/anaconda3/envs/vllm_0.8.5/lib/python3.12/site-packages/vllm/distributed/device_communicators/shm_broadcast.py", line 443, in acquire_read
    raise TimeoutError
TimeoutError
The above exception was the direct cause of the following exception:

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

Reducing how much work goes into one fan-out brings the step's worst case back inside the deadline, so the reader stops being the component that fails. Warming compilation removes the single longest step from the served path. Neither touches the transport, because the transport was never the thing that was wrong.

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
Workers still alive after the failureShorten the step: lower the batched token budget and concurrencyThe step outgrew the deadline; the transport reported it.
Workers gone after the failureRead the worker's own output for the real exceptionThe timeout is then only how the parent noticed.
Fails on the first request onlyWarm compilation before serving trafficA compiling step can exceed a deadline every later step meets.
Intermittent under loadAlert on step duration approaching the deadlineBy the time the timeout fires the deployment is already down.

With the fix vs without the fix

DimensionWith the fixWithout the fix
What a bare TimeoutError provesThat the deadline passedTaken as proof the worker crashed
Where the traceback pointsAt the ring-buffer read that was waitingAssumed to point at the cause
Effect of raising the timeoutDelays detection of a genuinely dead workerAssumed to be a safe mitigation

Real engineering notes

The instinct is to raise the timeout, and it does make the error go away for a while. What it actually does is widen the window in which a genuinely dead worker goes unnoticed, so the next occurrence takes longer to detect and looks like a hang instead of a crash. Diagnose which of the two you have before changing the deadline, because the two failures want opposite responses.

Visual fingerprint

The reader can only measure time, not health
  engine  --step-->  [ shm ring buffer ]  <--publish--  worker
     |                      |
     |     waits up to the read deadline
     |                      |
     +-- deadline passes ---+--> bare TimeoutError

  worker crashed   -> same error
  worker just slow -> same error
The reader raises the identical bare TimeoutError whether the worker died or simply took longer than the deadline allowed, so the exception alone cannot distinguish an outage from a performance problem.

Root cause, fix & prevention

Frequently asked questions

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

The TimeoutError has no message. What failed?
The read deadline passed. That is all the reader knows, because waiting is all it does. Whether a worker crashed or merely ran late has to be established separately.
Should I raise the engine iteration timeout?
Only as a diagnostic. It postpones the failure and lengthens the time a genuinely dead worker goes undetected, so it can turn a crash into an apparent hang.
Why can I not reproduce it with one request?
A single short request produces a short step. The failure needs a step long enough to approach the deadline, which usually means concurrency, long prompts or a first-time compilation.
Is this the same as the engine core dying?
It is one of the causes. The engine death is what the API server reports afterwards; this timeout is what happened first.

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.