distributed/device_communicators/shm_broadcast.py: MessageQueue
Code version: vLLM @ 3e44c0d · 2026-10-22
Purpose#
vllm/distributed/device_communicators/shm_broadcast.py implements the MessageQueue: one process writes a Python object once, and every reader gets a copy. On one node the bytes go through a ring buffer in shared memory, with no lock and no copy per reader; too-large messages and readers on other nodes go through ZMQ.
| File | vllm/distributed/device_communicators/shm_broadcast.py, 1,148 lines of Python. |
|---|---|
| Main classes | MessageQueue (vllm/distributed/device_communicators/shm_broadcast.py:483), ShmRingBuffer (vllm/distributed/device_communicators/shm_broadcast.py:265), SpinCondition (vllm/distributed/device_communicators/shm_broadcast.py:116), Handle (vllm/distributed/device_communicators/shm_broadcast.py:465-474). |
| Runs in | The writer: the engine core process (executor broadcast) or a worker (its reply). The readers: the workers, or the engine core for replies. Main threads, plus a worker's WorkerAsyncOutputCopy thread, which enqueues replies. |
| Called by | MultiprocExecutor and WorkerProc (vllm/v1/executor/multiproc_executor.py:165-172, vllm/v1/executor/multiproc_executor.py:615-645); GroupCoordinator broadcasters (vllm/distributed/parallel_state.py:562-567); the Ray executor (vllm/v1/executor/ray_executor_v2.py:330-338). |
| Calls | multiprocessing.shared_memory, pickle (protocol 5 with out-of-band buffers), pyzmq (XPUB/SUB, PUB/SUB, PAIR), torch.distributed to share the handle in a group. |
| Not in this file | The RPC protocol on top: the message tuple (method, args, kwargs, output_rank) and the reply (status, value) are defined by the executor (v1/executor/multiproc_executor.py: MultiprocExecutor and WorkerProc). The zero-copy tensor arena is in shm_tensor_arena.py (opt-in, --enable-shm-tensor-arena). |
The page Processes, ZMQ and shared memory introduces the queue; this page reads the file.
Where it sits#
Who calls it#
- Executor broadcast. On the leader node of a DP rank,
MultiprocExecutormakesMessageQueue(world_size, local_world_size, max_chunk_bytes=16 MiB, connect_ip=…)and passes its handle to each worker process (vllm/v1/executor/multiproc_executor.py:148-172). Each call is oneenqueue(vllm/v1/executor/multiproc_executor.py:428). - Worker side. A worker attaches with
create_from_handle(handle, rank)and makes its own reply queueMessageQueue(1, 1)(vllm/v1/executor/multiproc_executor.py:618-625). When a replica spans nodes, the workers use the inner-DP world group's broadcaster and single-reader queues instead (vllm/v1/executor/multiproc_executor.py:626-645). - Busy loops. Workers block in
dequeue(indefinite=True)(vllm/v1/executor/multiproc_executor.py:1046); the executor reads replies withdequeue(timeout=…)(vllm/v1/executor/multiproc_executor.py:441); a worker replies withenqueue(result)(vllm/v1/executor/multiproc_executor.py:1011-1012). - Process groups. A
GroupCoordinatorthat asks for a broadcaster (TP, DCP) makes one with 6 chunks of 4 MiB;broadcast_objectthen uses it (vllm/distributed/parallel_state.py:562-567,vllm/distributed/parallel_state.py:864-866).
What it calls#
shared_memory.SharedMemory(create=True, size=…)after a free-space check (vllm/distributed/device_communicators/shm_broadcast.py:335-343).- ZMQ sockets: XPUB for overflow and remote readers, PUB/SUB for wake-ups, PAIR for cancellation (
vllm/distributed/device_communicators/shm_broadcast.py:508-542,vllm/distributed/device_communicators/shm_broadcast.py:150-176). in_the_same_node_asandbroadcast_object_listto spread the handle in a process group (vllm/distributed/device_communicators/shm_broadcast.py:1068-1148).
Key classes and functions#
| Name | Line | What it does |
|---|---|---|
memory_fence | vllm/distributed/device_communicators/shm_broadcast.py:83-100 | Lock acquire/release as a full memory barrier around flag writes. |
SpinCondition | vllm/distributed/device_communicators/shm_broadcast.py:116-224 | Reader wait: spin for 1 s after a read, then sleep on a ZMQ notify socket. |
check_shm_free_space | vllm/distributed/device_communicators/shm_broadcast.py:230-262 | Raise if /dev/shm (or the cgroup) cannot hold the segment. |
ShmRingBuffer | vllm/distributed/device_communicators/shm_broadcast.py:265-401 | The segment: data chunks, then flag bytes; create or attach by name. |
_rebuild_tensor, _reduce_tensor | vllm/distributed/device_communicators/shm_broadcast.py:404-462 | Pickle CPU tensors as out-of-band buffers instead of copying them into the stream. |
Handle | vllm/distributed/device_communicators/shm_broadcast.py:465-474 | Picklable description of a queue: buffer name, socket addresses, local reader ranks. |
MessageQueue.__init__ / create_from_handle | vllm/distributed/device_communicators/shm_broadcast.py:484-674 | Writer side and reader side of one queue. |
wait_until_ready | vllm/distributed/device_communicators/shm_broadcast.py:676-707 | Collective start-up: wait for every subscription, then send READY. |
acquire_write / acquire_read | vllm/distributed/device_communicators/shm_broadcast.py:716-898 | The lock-free protocol on one chunk. |
enqueue / dequeue / recv | vllm/distributed/device_communicators/shm_broadcast.py:900-1007 | Serialize, write or overflow; read and deserialize. |
broadcast_object | vllm/distributed/device_communicators/shm_broadcast.py:1009-1013 | Writer enqueues, readers dequeue. |
create_from_process_group, …_single_reader | vllm/distributed/device_communicators/shm_broadcast.py:1015-1148 | Build a queue for a torch process group and share the handle. |
Walk through the main methods#
ShmRingBuffer: layout and the flag rules#
The docstring is the specification. Data chunks come first, then max_chunks × (1 + n_reader) flag bytes. The first flag of a chunk is "written"; then one "read" flag per reader (vllm/distributed/device_communicators/shm_broadcast.py:281-302). The four states:
metadata memory layout: each byte is a flag, the first byte is the written
flag, and the rest are reader flags. The flags are set to 0 by default.
+--------------+--------------+--------------+-----+--------------+
| written_flag | reader0_flag | reader1_flag | ... | readerN_flag |
+--------------+--------------+--------------+-----+--------------+
The state of metadata is as follows:
(case 1) 0???...???: the block is not written yet, cannot read, can write
(case 2) 1000...000: the block is just written, can read, cannot write
(case 3) 1???...???: the block is written and read by some readers, can read if not read, cannot write
(case 4) 1111...111: the block is written and read by all readers, cannot read, can write
State transition for readers:
When a reader finds a block that it can read (case 2 or 3), it can yield the block for caller to read.
Only after the caller finishes reading the block, the reader can mark the block as read.
Readers only mark the block as read (from 0 to 1), the writer marks the block as ready to read (from 1 to 0).
State transition for writer:
When the writer writes to a block (case 1 or 4), it first resets the written flag to 0, converting either case
to case 1. Then it can yield the block for caller to write. After the caller finishes writing the block, the writer
can reset the reader flags to 0, and mark the block as written (from 0 to 1).
NOTE: the order is important here, first reset the reader flags (so that we are still in case 1), then mark the block as written. The state transition is atomic. If we do it in the reverse order, it will go through case 3 and then back to case 2, and readers might read the intermediate case 3, which is not correct.The writer creates the segment and zeroes the flags; a reader attaches by name and patches out Python's resource tracker, so a reader process does not unlink a segment it did not create (vllm/distributed/device_communicators/shm_broadcast.py:332-365).
acquire_write: the writer's side of one chunk#
while True:
with self.buffer.get_metadata(self.current_idx) as metadata_buffer:
def check():
memory_fence()
read_count = sum(metadata_buffer[1:])
written_flag = metadata_buffer[0]
return not (written_flag and read_count != self.buffer.n_reader)
if SPINLOOP_EXT_ENABLED and not check():
spinloop(metadata_buffer, check, timeout=SPINLOOP_TIMEOUT_SECONDS)
if not check():
# this block is written and not read by all readers
# for writers, `self.current_idx` is the next block to write
# if this block is not ready to write,
# we need to wait until it is read by all readers
# Release the processor to other threads
sched_yield()
# if we time out, raise an exception
elapsed = time.monotonic() - start_time
if timeout is not None and elapsed > timeout:
raise TimeoutError
# if we wait for a long time, log a message
if elapsed > VLLM_RINGBUFFER_WARNING_INTERVAL * n_warning:
logger.info(
LONG_WAIT_TIME_LOG_MSG, VLLM_RINGBUFFER_WARNING_INTERVAL
)
n_warning += 1
continue
# found a block that is either
# (1) not written
# (2) read by all readers
# mark the block as not written
metadata_buffer[0] = 0
# let caller write to the buffer
with self.buffer.get_data(self.current_idx) as buf:
yield buf
# caller has written to the buffer
# NOTE: order is important here
# first set the read flags to 0
# then set the written flag to 1
# otherwise, the readers may think they already read the block
for i in range(1, self.buffer.n_reader + 1):
# set read flag to 0, meaning it is not read yet
metadata_buffer[i] = 0
# Memory fence here ensures the order of the buffer and flag
# writes. This guarantees that when `metadata_buffer[0] = 1` is
# visible to readers, `buf` can be completely ready. Without
# this, some CPU architectures with weak ordering may incur
# memory inconsistency.
memory_fence()
# mark the block as written
metadata_buffer[0] = 1
# Memory fence ensures the write is visible to readers on other cores
# before we proceed. Without this, readers may spin indefinitely
# waiting for a write that's stuck in our CPU's store buffer.
memory_fence()
self.current_idx = (self.current_idx + 1) % self.buffer.max_chunks
break- Lines 724-728. A chunk is writable unless it is written and not yet read by all readers.
- Lines 733-754. Not writable: yield the CPU and retry. A timeout raises
TimeoutError; everyVLLM_RINGBUFFER_WARNING_INTERVALseconds (60) the writer logs "No available shared memory broadcast block found in 60 seconds". - Line 760. Clear "written" first, so no reader starts on a half-written chunk.
- Lines 770-784. After the caller wrote: reset all read flags, fence, set "written", fence. The comment explains why this order matters: the other order passes through the "read by some" state.
- Line 785. Advance
current_idxaround the ring.
acquire_read: one reader#
def acquire_read(
self,
timeout: float | None = None,
indefinite: bool = False,
):
assert self._is_local_reader, "Only readers can acquire read"
read_timeout = self.ReadTimeoutWithWarnings(
timeout=timeout, should_warn=not indefinite
)
with self.buffer.get_metadata(self.current_idx) as metadata_buffer:
while True:
if not self._wait_until_readable(metadata_buffer, read_timeout):
continue
# found a block that is not read by this reader
# let caller read from the buffer
with self.buffer.get_data(self.current_idx) as buf:
try:
yield buf
finally:
# caller has read from the buffer; set the read flag.
metadata_buffer[self.local_reader_rank + 1] = 1
# Memory fence ensures the read flag is visible to the writer.
# Without this, writer may not see our read completion and
# could wait indefinitely for all readers to finish.
memory_fence()
next_idx = self.current_idx + 1
self.current_idx = next_idx % self.buffer.max_chunks
self._spin_condition.record_read()
break- Lines 876-882. Wait until the current chunk is written and not yet read by this reader. The wait uses
SpinCondition: spin withsched_yieldfor 1 s after the last read, then sleep on the ZMQ notify socket for at most 5 s at a time before it checks the flags again (vllm/distributed/device_communicators/shm_broadcast.py:205-219,vllm/distributed/device_communicators/shm_broadcast.py:71,vllm/distributed/device_communicators/shm_broadcast.py:802-817). - Lines 888-894. After the caller read, set this reader's flag and fence, so the writer sees it.
- Lines 895-897. Advance and record the read time (it controls the spin window).
enqueue: serialize, then write or overflow#
def enqueue(self, obj, timeout: float | None = None):
"""Write to message queue with optional timeout (in seconds)"""
assert self._is_writer, "Only writers can enqueue"
all_buffers: list[SizedBuffer] = [b""]
total_bytes = 6 # 2 bytes for oob buffer count, 4 for main buffer size
def oob_callback(buf: PickleBuffer) -> bool:
raw_buf = buf.raw()
if len(raw_buf) < 1024 * 1024:
# In-line buffers smaller than 1MiB.
return True
all_buffers.append(raw_buf)
nonlocal total_bytes
total_bytes += len(raw_buf) + 4
return False
# CPU tensors are routed through `_reduce_tensor` so that their
# bytes are emitted as out-of-band buffers instead of being
# copied into the pickle stream by torch's default reducer.
# Start from `copyreg.dispatch_table` to preserve globally
# registered reducers (e.g. `re.Pattern`); the per-pickler
# dispatch table would otherwise shadow them.
dispatch_table = dict(copyreg.dispatch_table)
dispatch_table[torch.Tensor] = _reduce_tensor
arena = self.tensor_arena
with io.BytesIO() as bio:
pickler: pickle.Pickler
if arena is not None:
from vllm.distributed.device_communicators.shm_tensor_arena import (
_ArenaPickler,
)
pickler = _ArenaPickler(bio, arena, buffer_callback=oob_callback)
else:
pickler = pickle.Pickler(
bio,
protocol=pickle.HIGHEST_PROTOCOL,
buffer_callback=oob_callback,
)
pickler.dispatch_table = dispatch_table
pickler.dump(obj)
all_buffers[0] = bio.getvalue()
if self.n_local_reader > 0:
if total_bytes + len(all_buffers[0]) >= self.buffer.max_chunk_bytes:
with self.acquire_write(timeout) as buf:
buf[0] = 1 # overflow
self.local_socket.send_multipart(all_buffers, copy=False)
else:
# Byte 0: 0
# Bytes 1-2: Count of buffers
# Then each buffer follows, preceded by 4 bytes containing its length:
# [4 byte int L][L bytes of buffer content] ...
with self.acquire_write(timeout) as buf:
buf[0] = 0 # not overflow
offset = 3
buf[1:offset] = to_bytes_big(len(all_buffers), 2) # oob buf count
for buffer in all_buffers:
buf_len = len(buffer)
# prepend each buffer with 4 bytes containing its size.
buf_offset = offset + 4
buf[offset:buf_offset] = to_bytes_big(buf_len, 4)
buf[buf_offset : (offset := buf_offset + buf_len)] = buffer
self._spin_condition.notify()
if self.n_remote_reader > 0:
self.remote_socket.send_multipart(all_buffers, copy=False)- Lines 906-914. Out-of-band buffers of 1 MiB or more stay outside the pickle stream; smaller ones go inline.
- Lines 922-923. CPU tensors are reduced by
_reduce_tensor, so their bytes become such buffers instead of a copy inside the pickle (vllm/distributed/device_communicators/shm_broadcast.py:433-462). - Lines 943-946. If the total does not fit in one chunk, the chunk carries only the overflow flag, and all buffers go over the local XPUB socket.
- Lines 952-961. Otherwise the chunk holds: byte 0 = 0, bytes 1-2 = buffer count (big-endian), then each buffer with a 4-byte length.
- Line 963. Wake sleeping readers.
- Lines 965-966. Remote readers always get the message over the TCP XPUB socket.
dequeue reverses the steps. It copies each out-of-band buffer into a new bytearray, because the ring slot is reused after the read; on overflow it receives from the SUB socket after it releases the chunk (vllm/distributed/device_communicators/shm_broadcast.py:968-998).
create_from_process_group: one queue for a torch group#
from vllm.distributed.parallel_state import in_the_same_node_as
status = in_the_same_node_as(pg, source_rank=writer_rank)
if group_rank == writer_rank:
if external_writer_handle is not None:
buffer_io = MessageQueue.create_from_handle(
external_writer_handle, group_rank
)
else:
same_node_ranks = [i for i, s in enumerate(status) if s]
n_reader = group_world_size - 1
n_local_reader = len(same_node_ranks) - 1
local_reader_ranks = [i for i in same_node_ranks if i != writer_rank]
buffer_io = MessageQueue(
n_reader=n_reader,
n_local_reader=n_local_reader,
local_reader_ranks=local_reader_ranks,
max_chunk_bytes=max_chunk_bytes,
max_chunks=max_chunks,
)
handle = buffer_io.export_handle()
if isinstance(pg, ProcessGroup):
dist.broadcast_object_list(
[handle], src=global_ranks[writer_rank], group=pg
)
else:
pg.broadcast_obj(handle, writer_rank)
else:
if isinstance(pg, ProcessGroup):
recv = [None]
dist.broadcast_object_list(
recv, src=global_ranks[writer_rank], group=pg
)
handle = recv[0] # type: ignore
else:
handle = pg.broadcast_obj(None, writer_rank)
buffer_io = MessageQueue.create_from_handle(handle, group_rank)
if blocking:
buffer_io.wait_until_ready()
return buffer_ioThe writer counts the ranks on its own node with in_the_same_node_as; those become local readers on the ring, and the others remote readers on TCP. It broadcasts the handle over the gloo group; each reader attaches with it, and wait_until_ready runs the start-up handshake.
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveSizes of every queue in a TP = 2 server
| Queue | Made at | Readers | Chunks × size | Segment (derived) |
|---|---|---|---|---|
executor broadcast (rpc_broadcast_mq) | vllm/v1/executor/multiproc_executor.py:165-171 | 2 local | 10 × 16 MiB | 160 MiB + 30 flag bytes |
worker 0 reply (worker_response_mq) | vllm/v1/executor/multiproc_executor.py:625 | 1 local (the engine core) | 10 × 24 MiB (the defaults, vllm/distributed/device_communicators/shm_broadcast.py:489-492) | 240 MiB + 20 bytes |
| worker 1 reply | same | 1 local | 10 × 24 MiB | 240 MiB + 20 bytes |
TP group broadcaster (tp:0) | vllm/distributed/parallel_state.py:564-567 | 1 local (rank 1) | 6 × 4 MiB | 24 MiB + 12 bytes |
Each segment size is (max_chunk_bytes + 1 + n_reader) × max_chunks (vllm/distributed/device_communicators/shm_broadcast.py:326-328), and check_shm_free_space raises before it creates one that does not fit (vllm/distributed/device_communicators/shm_broadcast.py:248-262). The comment on the 24 MiB default says it is "large enough to accommodate grammar bitmask tensors for large batches (1024 requests)" (vllm/distributed/device_communicators/shm_broadcast.py:489-490). So a TP = 2 engine asks for about 664 MiB of /dev/shm in total (derived). Docker's default /dev/shm is far smaller; the error text suggests --shm-size or --ipc=host.
Deep diveWorked example: one execute_model call with TP = 2
- The executor calls
enqueue(("execute_model", (scheduler_output,), {}, 0)). For a decode step of 64 requests the pickle is small (no tensor of 1 MiB or more), so it fits into one 16 MiB chunk (illustrative size). acquire_writefinds chunk i in state111(read by both workers in an earlier step), clears the written flag, and the payload is copied in:[0][count=1][len][pickle bytes].- Flags become
100; the writer sends one byte on the notify PUB socket and moves to chunk i + 1. - Both workers spin in
dequeue(indefinite=True). They read within the 1-second spin window, so they see the flag without the socket. Each unpickles its own copy and sets its read flag:110, then111. - Worker 0 (the output rank) later replies on its own
MessageQueue(1, 1)with(SUCCESS, None); worker 1 replies nothing (vllm/v1/executor/multiproc_executor.py:1070-1071).
A multimodal request with a 30 MiB image tensor would not fit: the chunk gets the overflow flag, and the pickle plus the image buffer travel over the XPUB socket as separate ZMQ frames, without a copy into the pickle stream (derived from vllm/distributed/device_communicators/shm_broadcast.py:906-946).
Deep diveProcesses, threads and waiting
| Side | Who | How it waits |
|---|---|---|
| writer, ring full | engine core main thread (or a worker's copy thread) | busy loop with sched_yield; INFO every 60 s; TimeoutError only with a timeout (vllm/distributed/device_communicators/shm_broadcast.py:733-754) |
| reader, idle | worker main thread in dequeue(indefinite=True) | spin 1 s, then poll on the notify SUB socket (CONFLATE, so only one pending ping), rechecking every 5 s; no warnings (vllm/distributed/device_communicators/shm_broadcast.py:141-168, vllm/distributed/device_communicators/shm_broadcast.py:788-827) |
| reader, shutdown | another thread of the reader's process | shutdown() sends on an in-process PAIR socket; the reader raises RuntimeError("cancelled") (vllm/distributed/device_communicators/shm_broadcast.py:709-714, vllm/distributed/device_communicators/shm_broadcast.py:861-862) |
The writer's notify socket has a send high-water mark of 1: under load, extra pings are dropped silently, and the reader's SUB socket keeps only the last one (vllm/distributed/device_communicators/shm_broadcast.py:169-176). VLLM_USE_SPINLOOP_EXT=1 replaces the Python spin with an optional C extension, when it can be imported (vllm/distributed/device_communicators/shm_broadcast.py:48-58).
Deep diveError paths: what raises, what only logs
| Situation | Result | Where |
|---|---|---|
not enough /dev/shm | RuntimeError "Insufficient space in /dev/shm … Increase /dev/shm (e.g. --shm-size or --ipc=host)" | vllm/distributed/device_communicators/shm_broadcast.py:248-258 |
| attach on another node | FileNotFoundError suppressed; the object is unused there | vllm/distributed/device_communicators/shm_broadcast.py:361-365 |
| write timeout | TimeoutError | vllm/distributed/device_communicators/shm_broadcast.py:743-745 |
| read timeout (or ZMQ poll timeout on overflow) | TimeoutError | vllm/distributed/device_communicators/shm_broadcast.py:814-816, vllm/distributed/device_communicators/shm_broadcast.py:1001-1007 |
| long wait without a timeout | INFO "No available shared memory broadcast block found in 60 seconds. This typically happens when some processes are hanging or doing some time-consuming work …" | vllm/distributed/device_communicators/shm_broadcast.py:107-113 |
a non-reader calls dequeue | RuntimeError("Only readers can dequeue") | vllm/distributed/device_communicators/shm_broadcast.py:996-998 |
| reader cancelled during shutdown | RuntimeError("cancelled") | vllm/distributed/device_communicators/shm_broadcast.py:861-862 |
Deep diveEnvironment variables
| Variable | Default | Read at | Effect |
|---|---|---|---|
VLLM_MQ_MAX_CHUNK_BYTES_MB | 16 (vllm/envs.py:1856-1858) | vllm/v1/executor/multiproc_executor.py:152 | Chunk size of the executor's broadcast queue only; replies and group broadcasters use their own sizes. |
VLLM_RINGBUFFER_WARNING_INTERVAL | 60 (vllm/envs.py:745-747) | vllm/distributed/device_communicators/shm_broadcast.py:67 (at import) | Seconds between the "No available shared memory broadcast block" messages. |
VLLM_USE_SPINLOOP_EXT | 0 (vllm/envs.py:2260) | vllm/distributed/device_communicators/shm_broadcast.py:49-56 (at import) | Use the optional vllm.spinloop extension for the flag waits. |
VLLM_HOST_IP | "" | through get_ip() (vllm/distributed/device_communicators/shm_broadcast.py:553-554) | The address that remote readers connect to, when the caller gives no connect_ip. |
Related concepts#
- Processes, ZMQ and shared memory: the three channels of
vllm serve. - v1/executor/multiproc_executor.py: MultiprocExecutor and WorkerProc: the RPC protocol on top of the queue.
- Executors and workers: from the engine core to the GPUs: why only one worker replies.
- distributed/parallel_state.py: process groups and GroupCoordinator: the group broadcasters.
Check yourself#
- 1In which order does the writer update the flags after it writes a chunk, and why?easy
Show answer
It resets every read flag first, then sets "written". The other order would pass through "written and read by some", and a reader could skip the new message (
vllm/distributed/device_communicators/shm_broadcast.py:764-780). - 2A message pickles to 3 MiB and has one 20 MiB tensor buffer. How does it travel through the executor's queue?medium
Show answer
The 20 MiB buffer is out of band (≥ 1 MiB). The total, 23 MiB plus framing, does not fit in a 16 MiB chunk, so the chunk only carries the overflow flag and both buffers go over the local XPUB socket (
vllm/distributed/device_communicators/shm_broadcast.py:906-946). - 3Why does an idle worker not print the "No available shared memory broadcast block" message every 60 seconds?medium
Show answer
Workers call
dequeue(indefinite=True)(vllm/v1/executor/multiproc_executor.py:1046), andacquire_readturns warnings off whenindefiniteis true (vllm/distributed/device_communicators/shm_broadcast.py:876-878). - 4How muchhard
/dev/shmdo the queues of one TP = 4 engine core ask for?Show answer
Broadcast: 10 × (16 MiB + 5) ≈ 160 MiB. Four reply queues of 10 × (24 MiB + 2) ≈ 240 MiB each = 960 MiB. The TP group broadcaster: 6 × (4 MiB + 4) ≈ 24 MiB. Total about 1,144 MiB (derived from
vllm/distributed/device_communicators/shm_broadcast.py:326-328,vllm/distributed/device_communicators/shm_broadcast.py:489-492,vllm/v1/executor/multiproc_executor.py:152,vllm/distributed/parallel_state.py:564-567).