vllm.distributed.device_communicators.shm_tensor_arena
¶
Zero-copy shared-memory arena for large CPU tensors (multimodal fast path).
Problem: MessageQueue.enqueue (see shm_broadcast.py) pickles the whole
payload, and torch's default tensor pickling copies bytes into the pickle
stream on the writer and back out on every local reader. For a large
pixel_values tensor (up to hundreds of MB) this blocks the EngineCore step
loop for ~1s with all GPUs idle.
Fast path: _ArenaPickler.reducer_override intercepts qualifying CPU
tensors during pickling, memcpys them ONCE into a free arena slot, and
pickles a tiny (arena_name, slot, nbytes, dtype, shape) stub. Readers
rebuild a zero-copy view of the mapped slot (torch.frombuffer).
Slot lifecycle: single writer, n_reader readers, per-slot metadata
[written_flag, reader0_done, ..., readerN_done] (same protocol as
ShmRingBuffer). A slot is not released when rebuilt: the rebuilt tensor is
the SOURCE of an async H2D copy, and callers like chunked-prefill
prompt_embeds retain it across many steps. Release is queued only once the
tensor's storage is freed, i.e. once no tensor or view of the slot remains
(see get_tensor), and is further gated on a CUDA event on the pinned fast
path, since the H2D can outlive the step that issued it (see
flush_releases).
The writer NEVER blocks: no free slot (or an oversized tensor) falls back to the default pickle path, so worst case matches today's behavior.
Physical memory: slots are paged in lazily, so an arena on a queue that
never carries big tensors costs ~0. Creation is guarded by
check_shm_free_space (like ShmRingBuffer) so an undersized /dev/shm
fails fast instead of a SIGBUS mid-copy.
Classes:
-
ShmTensorArena–Slotted shared-memory arena: one writer, n_reader zero-copy readers.
ShmTensorArena
¶
Slotted shared-memory arena: one writer, n_reader zero-copy readers.
Memory layout: [slot0 | slot1 | ... | slotN-1 | meta0 | meta1 | ... ] where each meta is (1 + n_reader) bytes: [written_flag, reader_done...]. Slot states mirror ShmRingBuffer: written=0 -> free (never written / being written) written=1, some reader_done=0 -> in use, cannot reuse written=1, all reader_done=1 -> consumed, can reuse
Methods:
-
flush_releases–Retire this reader's arena slots that are ready for writer reuse.
-
get_tensor–Zero-copy view of a slot as a tensor. The slot is queued for
-
write_tensor–Copy tensor bytes into a free slot; return slot idx or None
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
_ensure_pinned()
¶
Pin the whole arena mapping via cudaHostRegister in THIS process (lazy, once). Without it the HtoD of a zero-copy tensor pays first-touch page faults on the tmpfs mapping plus pageable staging (~hundreds of ms for 200MB); registration allocates+pins the pages once, making every later HtoD a true DMA. Failure (no CUDA in this process, etc.) is fine — the copy still works, just slower.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_mark_released(idxs)
¶
Set THIS reader's done flag on the given slots; a slot becomes reusable once every reader has done so.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_record_release_event()
¶
Record a CUDA event on the current (compute) stream, or return None if this process has no usable CUDA context. Called from flush_releases, which runs after the previous step's H2D was enqueued on that same stream, so the event is ordered strictly after that H2D.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_unpin()
¶
Undo the pin from _ensure_pinned via cudaHostUnregister; must run before the mapping is closed. Failures are ignored — at interpreter shutdown the CUDA context may already be gone, and the registration dies with the process anyway.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
flush_releases()
¶
Retire this reader's arena slots that are ready for writer reuse.
Called on every MessageQueue.dequeue. _pending_release holds
slots whose tensor storage was just freed (see get_tensor). On
the pinned fast path that tensor was the source of an async
non_blocking=True H2D that can outlive the collection, so instead
of marking released immediately we record a CUDA event (ordered
after that H2D on the same stream) and move the slot to
_deferred_releases until event.query() says it's done -- a
not-yet-complete slot just waits one more dequeue. Unpinned (or no
CUDA context), the H2D already staged synchronously, so the slot is
safe to release right away.
A slot only becomes free for the writer (write_tensor) once every
reader has marked it released (_mark_released).
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
get_tensor(idx, nbytes, dtype, shape)
¶
Zero-copy view of a slot as a tensor. The slot is queued for
release once the returned tensor's storage is freed, i.e. once the
caller has dropped it along with any views/slices or wrappers (such
as torch.nn.Parameter) sharing that storage.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
write_tensor(t)
¶
Copy tensor bytes into a free slot; return slot idx or None (caller must then fall back to the default pickle path).
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_ArenaPickler
¶
Bases: Pickler
Pickler that diverts large contiguous CPU tensors into the arena.
reducer_override is consulted before an object's normal reduction. For
a qualifying tensor (exact type in _ARENA_REBUILD_FNS, CPU, strided,
contiguous, not requires_grad, above the divert-size threshold -- the
same criteria _reduce_tensor uses, plus size) it does one memcpy into a
free slot and returns a tiny rebuild stub. Anything that doesn't qualify,
or finds the arena full, returns NotImplemented and falls through to
the normal out-of-band pickle path, exactly as if no arena were attached.
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_rebuild_arena_parameter(arena_name, slot_idx, nbytes, dtype_str, shape)
¶
Unpickle hook for torch.nn.Parameter (shares the slot's storage).
Source code in vllm/distributed/device_communicators/shm_tensor_arena.py
_rebuild_arena_tensor(arena_name, slot_idx, nbytes, dtype_str, shape)
¶
Unpickle hook: rebuild a tensor as a zero-copy view of an arena slot.