2026-03-09 19:03:51 +08:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
2026-09-10 17:50:54 +08:00
|
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
2026-03-09 19:03:51 +08:00
|
|
|
|
|
|
|
|
"""IPC utilities for transferring large tensors via POSIX shared memory.
|
|
|
|
|
|
|
|
|
|
Used by Hop1 (GPU worker <-> scheduler) to avoid pickling large video tensors
|
|
|
|
|
through the MessageQueue. Tensors above ``_SHM_TENSOR_THRESHOLD`` are copied
|
|
|
|
|
into a named shared-memory segment; only a lightweight metadata dict is
|
|
|
|
|
serialised through the queue.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-09-10 17:50:54 +08:00
|
|
|
from collections.abc import Iterator
|
2026-09-30 11:37:57 +08:00
|
|
|
from contextlib import suppress
|
|
|
|
|
from typing import Any, TypeGuard
|
2026-03-09 19:03:51 +08:00
|
|
|
|
2026-08-08 08:24:42 +08:00
|
|
|
import numpy as np
|
2026-03-09 19:03:51 +08:00
|
|
|
import torch
|
2026-09-30 11:37:57 +08:00
|
|
|
from vllm.logger import init_logger
|
2026-03-09 19:03:51 +08:00
|
|
|
|
|
|
|
|
from vllm_omni.diffusion.data import DiffusionOutput
|
2026-09-10 17:50:54 +08:00
|
|
|
from vllm_omni.diffusion.media import (
|
|
|
|
|
DiffusionMediaOutput,
|
|
|
|
|
FloatVideoConsumer,
|
|
|
|
|
VideoColorModel,
|
|
|
|
|
VideoMediaOutput,
|
|
|
|
|
VideoTensorEncoding,
|
|
|
|
|
VideoTensorLayout,
|
|
|
|
|
VideoTensorSpec,
|
|
|
|
|
VideoTransportConstraints,
|
|
|
|
|
VideoValueRange,
|
|
|
|
|
)
|
2026-03-09 19:03:51 +08:00
|
|
|
|
|
|
|
|
_SHM_TENSOR_THRESHOLD = 1_000_000 # 1 MB
|
2026-06-13 15:42:16 +08:00
|
|
|
DIFFUSION_RPC_RESULT_ENVELOPE = "diffusion_rpc_result"
|
2026-09-10 17:50:54 +08:00
|
|
|
_DIFFUSION_MEDIA_WIRE_TYPE = "diffusion_media_v1"
|
2026-09-30 11:37:57 +08:00
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
|
|
|
|
# An uncertain CUDA cleanup must not unmap memory still used by the device.
|
|
|
|
|
# Retain failed transfers until worker exit and reject further registrations.
|
|
|
|
|
_failed_shm_copies: list[tuple[Any, torch.Tensor, torch.Tensor, torch.Stream]] = []
|
2026-09-10 17:50:54 +08:00
|
|
|
|
|
|
|
|
# Sentinel so compute-then-assign packing can tell "field not packed" apart from
|
|
|
|
|
# "field packed to None".
|
|
|
|
|
_UNSET = object()
|
2026-03-09 19:03:51 +08:00
|
|
|
|
|
|
|
|
|
2026-09-30 11:37:57 +08:00
|
|
|
class _SharedMemoryLease:
|
|
|
|
|
"""Keep an unlinked shared-memory mapping alive while array views use it."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, shm: Any, array: np.ndarray) -> None:
|
|
|
|
|
self.shm = shm
|
|
|
|
|
self.__array_interface__ = array.__array_interface__
|
|
|
|
|
|
|
|
|
|
def __del__(self) -> None:
|
|
|
|
|
with suppress(BufferError):
|
|
|
|
|
self.shm.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _new_shm_array(shape: tuple[int, ...], dtype: np.dtype) -> tuple[Any, np.ndarray, dict[str, Any]]:
|
2026-08-08 08:24:42 +08:00
|
|
|
from multiprocessing import shared_memory
|
|
|
|
|
|
2026-09-30 11:37:57 +08:00
|
|
|
nbytes = int(np.prod(shape, dtype=np.int64)) * dtype.itemsize
|
|
|
|
|
shm = shared_memory.SharedMemory(create=True, size=nbytes)
|
|
|
|
|
array = np.ndarray(shape, dtype=dtype, buffer=shm.buf[:nbytes])
|
|
|
|
|
handle = {
|
|
|
|
|
"name": shm.name,
|
|
|
|
|
"shape": list(shape),
|
|
|
|
|
"numpy_dtype": str(dtype),
|
|
|
|
|
"nbytes": nbytes,
|
|
|
|
|
}
|
|
|
|
|
return shm, array, handle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _array_to_shm(array: np.ndarray) -> dict[str, Any]:
|
|
|
|
|
"""Copy a contiguous NumPy-compatible array into shared memory."""
|
2026-08-08 08:24:42 +08:00
|
|
|
if array.dtype.hasobject:
|
|
|
|
|
raise TypeError("NumPy object arrays cannot be transferred through raw shared memory")
|
|
|
|
|
|
|
|
|
|
array = np.ascontiguousarray(array)
|
2026-09-30 11:37:57 +08:00
|
|
|
shm, shm_array, handle = _new_shm_array(array.shape, array.dtype)
|
2026-08-08 08:24:42 +08:00
|
|
|
np.copyto(shm_array, array)
|
2026-09-30 11:37:57 +08:00
|
|
|
del shm_array
|
2026-08-08 08:24:42 +08:00
|
|
|
shm.close()
|
|
|
|
|
return handle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _array_from_shm(handle: dict[str, Any]) -> np.ndarray:
|
2026-09-30 11:37:57 +08:00
|
|
|
"""Materialize or borrow an array from shared memory and unlink its name."""
|
2026-08-08 08:24:42 +08:00
|
|
|
from multiprocessing import shared_memory
|
|
|
|
|
|
|
|
|
|
shm = shared_memory.SharedMemory(name=handle["name"])
|
2026-09-30 11:37:57 +08:00
|
|
|
array = np.ndarray(
|
|
|
|
|
handle["shape"],
|
|
|
|
|
dtype=np.dtype(handle["numpy_dtype"]),
|
|
|
|
|
buffer=shm.buf[: handle["nbytes"]],
|
|
|
|
|
)
|
|
|
|
|
if handle.get("borrow_on_unpack"):
|
|
|
|
|
# A base owner survives np.asarray(), slicing, and torch.from_numpy().
|
|
|
|
|
# An ndarray subclass attribute does not survive np.asarray().
|
|
|
|
|
borrowed = np.asarray(_SharedMemoryLease(shm, array))
|
|
|
|
|
shm.unlink()
|
|
|
|
|
return borrowed
|
2026-08-08 08:24:42 +08:00
|
|
|
try:
|
2026-09-30 11:37:57 +08:00
|
|
|
return array.copy()
|
2026-08-08 08:24:42 +08:00
|
|
|
finally:
|
2026-09-30 11:37:57 +08:00
|
|
|
del array
|
2026-08-08 08:24:42 +08:00
|
|
|
shm.close()
|
|
|
|
|
shm.unlink()
|
2026-09-30 11:37:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cuda_registered_shm_copy(tensor: torch.Tensor, d2h_stream: torch.Stream) -> dict[str, Any]:
|
|
|
|
|
"""Copy a CUDA tensor directly into a CUDA-registered POSIX SHM segment."""
|
|
|
|
|
if _failed_shm_copies:
|
|
|
|
|
raise RuntimeError("A previous registered SHM transfer failed CUDA cleanup; restart the worker")
|
|
|
|
|
original_dtype = tensor.dtype
|
|
|
|
|
source = tensor.detach()
|
|
|
|
|
# NumPy has no BF16 dtype. Transfer the original bits, without allocating
|
|
|
|
|
# an FP32 accelerator tensor or converting the borrowed CPU result.
|
|
|
|
|
if original_dtype == torch.bfloat16:
|
|
|
|
|
numpy_dtype = np.dtype(np.uint16)
|
|
|
|
|
else:
|
|
|
|
|
numpy_dtype = torch.empty((), dtype=source.dtype).numpy().dtype
|
|
|
|
|
cudart = torch.cuda.cudart()
|
|
|
|
|
shm, shm_array, handle = _new_shm_array(tuple(source.shape), numpy_dtype)
|
|
|
|
|
pointer = shm_array.ctypes.data
|
|
|
|
|
host_tensor = None
|
|
|
|
|
registered = False
|
|
|
|
|
succeeded = False
|
|
|
|
|
try:
|
|
|
|
|
host_tensor = torch.from_numpy(shm_array).view(original_dtype)
|
|
|
|
|
error = cudart.cudaHostRegister(pointer, handle["nbytes"], 0)
|
|
|
|
|
if int(error) != 0:
|
|
|
|
|
raise RuntimeError(f"cudaHostRegister failed: {cudart.cudaGetErrorString(error)}")
|
|
|
|
|
registered = True
|
|
|
|
|
old_stream = torch.accelerator.current_stream()
|
|
|
|
|
torch.accelerator.set_stream(d2h_stream)
|
|
|
|
|
try:
|
|
|
|
|
host_tensor.copy_(source, non_blocking=True)
|
|
|
|
|
finally:
|
|
|
|
|
torch.accelerator.set_stream(old_stream)
|
|
|
|
|
d2h_stream.synchronize()
|
|
|
|
|
error = cudart.cudaHostUnregister(pointer)
|
|
|
|
|
if int(error) != 0:
|
|
|
|
|
raise RuntimeError(f"cudaHostUnregister failed: {cudart.cudaGetErrorString(error)}")
|
|
|
|
|
registered = False
|
|
|
|
|
handle["borrow_on_unpack"] = True
|
|
|
|
|
handle.update(
|
|
|
|
|
{
|
|
|
|
|
"__tensor_shm__": True,
|
|
|
|
|
"torch_dtype": str(original_dtype),
|
|
|
|
|
"torch_dtype_view": original_dtype == torch.bfloat16,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
succeeded = True
|
|
|
|
|
return handle
|
|
|
|
|
finally:
|
|
|
|
|
if registered:
|
|
|
|
|
try:
|
|
|
|
|
# A copy may have been enqueued before a later operation failed.
|
|
|
|
|
d2h_stream.synchronize()
|
|
|
|
|
error = cudart.cudaHostUnregister(pointer)
|
|
|
|
|
if int(error) != 0:
|
|
|
|
|
raise RuntimeError(f"cudaHostUnregister failed: {cudart.cudaGetErrorString(error)}")
|
|
|
|
|
registered = False
|
|
|
|
|
except Exception:
|
|
|
|
|
assert host_tensor is not None
|
|
|
|
|
_failed_shm_copies.append((shm, host_tensor, source, d2h_stream))
|
|
|
|
|
logger.exception("Retaining registered SHM after CUDA cleanup failure; worker restart required")
|
|
|
|
|
del host_tensor, shm_array
|
|
|
|
|
if not registered:
|
|
|
|
|
shm.close()
|
|
|
|
|
if not succeeded:
|
|
|
|
|
shm.unlink()
|
2026-08-08 08:24:42 +08:00
|
|
|
|
|
|
|
|
|
2026-09-10 17:50:54 +08:00
|
|
|
def _unlink_shm_handle(handle: dict[str, Any]) -> None:
|
|
|
|
|
"""Best-effort removal of a shared-memory segment created during packing."""
|
|
|
|
|
from multiprocessing import shared_memory
|
|
|
|
|
|
|
|
|
|
name = handle.get("name")
|
|
|
|
|
if not name:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
shm = shared_memory.SharedMemory(name=name)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
shm.close()
|
|
|
|
|
finally:
|
|
|
|
|
try:
|
|
|
|
|
shm.unlink()
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _unlink_shm_handles(handles: list[dict[str, Any]]) -> None:
|
|
|
|
|
for handle in handles:
|
|
|
|
|
try:
|
|
|
|
|
_unlink_shm_handle(handle)
|
|
|
|
|
except Exception:
|
|
|
|
|
# Cleanup is best-effort; never mask the original packing failure.
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 01:21:55 +08:00
|
|
|
def _tensor_to_shm(
|
|
|
|
|
tensor: torch.Tensor,
|
|
|
|
|
d2h_stream: torch.Stream | None = None,
|
2026-09-10 17:50:54 +08:00
|
|
|
created: list[dict[str, Any]] | None = None,
|
2026-09-30 11:37:57 +08:00
|
|
|
*,
|
|
|
|
|
enable_registered_shm: bool = False,
|
2026-07-30 01:21:55 +08:00
|
|
|
) -> dict[str, Any]:
|
2026-03-09 19:03:51 +08:00
|
|
|
"""Copy a tensor into POSIX shared memory and return a metadata handle.
|
|
|
|
|
|
|
|
|
|
The shared memory segment remains alive after this call (the local fd is
|
|
|
|
|
closed, but the segment persists until ``_tensor_from_shm`` unlinks it).
|
2026-07-30 01:21:55 +08:00
|
|
|
|
|
|
|
|
If *d2h_stream* is provided, the D2H copy uses ``copy_()`` with
|
|
|
|
|
``pin_memory=True`` on that stream instead of the synchronous ``.cpu()``
|
|
|
|
|
path. The caller must synchronize *d2h_stream* after all tensors are
|
|
|
|
|
packed.
|
2026-03-09 19:03:51 +08:00
|
|
|
"""
|
2026-04-03 16:05:19 +08:00
|
|
|
original_dtype = tensor.dtype
|
2026-09-30 11:37:57 +08:00
|
|
|
if (
|
|
|
|
|
d2h_stream is not None
|
|
|
|
|
and enable_registered_shm
|
|
|
|
|
and tensor.device.type == "cuda"
|
|
|
|
|
# ROCm also uses the "cuda" device type, but not this CUDA runtime API.
|
|
|
|
|
and torch.version.cuda is not None
|
|
|
|
|
):
|
|
|
|
|
handle = _cuda_registered_shm_copy(tensor, d2h_stream)
|
|
|
|
|
if created is not None:
|
|
|
|
|
created.append(handle)
|
|
|
|
|
return handle
|
2026-07-30 01:21:55 +08:00
|
|
|
if d2h_stream is not None:
|
|
|
|
|
# Non-blocking D2H: copy on side stream to pinned CPU memory.
|
|
|
|
|
old_stream = torch.accelerator.current_stream()
|
|
|
|
|
torch.accelerator.set_stream(d2h_stream)
|
|
|
|
|
try:
|
|
|
|
|
t = tensor.detach()
|
|
|
|
|
cpu = torch.empty(t.shape, dtype=t.dtype, pin_memory=True)
|
|
|
|
|
cpu.copy_(t, non_blocking=True)
|
|
|
|
|
finally:
|
|
|
|
|
torch.accelerator.set_stream(old_stream)
|
|
|
|
|
d2h_stream.synchronize()
|
|
|
|
|
tensor = cpu
|
2026-09-10 17:50:54 +08:00
|
|
|
# numpy has no bfloat16, so widen on the host after D2H. Never widen on
|
|
|
|
|
# the accelerator before the copy: a payload that already fell back after
|
|
|
|
|
# an fp32 OOM would OOM again on a second device-side fp32 allocation.
|
|
|
|
|
if original_dtype == torch.bfloat16:
|
|
|
|
|
tensor = tensor.to(torch.float32)
|
2026-07-30 01:21:55 +08:00
|
|
|
else:
|
|
|
|
|
tensor = tensor.detach().cpu().contiguous()
|
|
|
|
|
if original_dtype == torch.bfloat16:
|
|
|
|
|
tensor = tensor.to(torch.float32)
|
2026-08-08 08:24:42 +08:00
|
|
|
handle = _array_to_shm(tensor.numpy())
|
|
|
|
|
handle.update(
|
|
|
|
|
{
|
|
|
|
|
"__tensor_shm__": True,
|
|
|
|
|
"torch_dtype": str(original_dtype),
|
|
|
|
|
}
|
|
|
|
|
)
|
2026-09-10 17:50:54 +08:00
|
|
|
if created is not None:
|
|
|
|
|
created.append(handle)
|
2026-03-09 19:03:51 +08:00
|
|
|
return handle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _tensor_from_shm(handle: dict[str, Any]) -> torch.Tensor:
|
|
|
|
|
"""Reconstruct a tensor from a shared-memory handle and free the segment."""
|
2026-08-08 08:24:42 +08:00
|
|
|
tensor = torch.from_numpy(_array_from_shm(handle))
|
|
|
|
|
# Restore the original dtype if it differs from the numpy-compatible
|
|
|
|
|
# dtype used for the SHM transfer (e.g. bfloat16 → float32 → bfloat16).
|
|
|
|
|
torch_dtype_str = handle.get("torch_dtype", "")
|
|
|
|
|
if torch_dtype_str:
|
|
|
|
|
original_dtype = getattr(torch, torch_dtype_str.replace("torch.", ""), None)
|
|
|
|
|
if original_dtype is not None and tensor.dtype != original_dtype:
|
2026-09-30 11:37:57 +08:00
|
|
|
tensor = tensor.view(original_dtype) if handle.get("torch_dtype_view") else tensor.to(original_dtype)
|
2026-03-09 19:03:51 +08:00
|
|
|
return tensor
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 01:21:55 +08:00
|
|
|
def _pack_tensor_if_large(
|
|
|
|
|
val: torch.Tensor,
|
|
|
|
|
d2h_stream: torch.Stream | None = None,
|
2026-09-10 17:50:54 +08:00
|
|
|
created: list[dict[str, Any]] | None = None,
|
2026-09-30 11:37:57 +08:00
|
|
|
*,
|
|
|
|
|
enable_registered_shm: bool = False,
|
2026-07-30 01:21:55 +08:00
|
|
|
) -> torch.Tensor | dict:
|
2026-08-15 18:27:51 +08:00
|
|
|
"""Replace a tensor with an SHM handle if it exceeds the threshold.
|
|
|
|
|
|
|
|
|
|
Batch outputs are split into per-request views that share one storage.
|
|
|
|
|
Pickle serializes a view's whole storage, so a batch of small views costs
|
|
|
|
|
the full batch tensor once per request. Size the decision on the storage
|
|
|
|
|
to keep those views off the wire; packing copies just the view.
|
|
|
|
|
"""
|
|
|
|
|
view_bytes = val.nelement() * val.element_size()
|
|
|
|
|
try:
|
|
|
|
|
storage_bytes = val.untyped_storage().nbytes()
|
|
|
|
|
except Exception:
|
|
|
|
|
storage_bytes = view_bytes
|
|
|
|
|
if max(view_bytes, storage_bytes) > _SHM_TENSOR_THRESHOLD:
|
2026-09-30 11:37:57 +08:00
|
|
|
return _tensor_to_shm(val, d2h_stream=d2h_stream, created=created, enable_registered_shm=enable_registered_shm)
|
2026-04-08 10:43:01 +08:00
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
|
2026-09-10 17:50:54 +08:00
|
|
|
def _ndarray_to_shm(array: np.ndarray, created: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
2026-08-08 08:24:42 +08:00
|
|
|
"""Copy a contiguous NumPy array into POSIX shared memory."""
|
|
|
|
|
handle = _array_to_shm(array)
|
|
|
|
|
handle["__ndarray_shm__"] = True
|
2026-09-10 17:50:54 +08:00
|
|
|
if created is not None:
|
|
|
|
|
created.append(handle)
|
2026-08-08 08:24:42 +08:00
|
|
|
return handle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ndarray_from_shm(handle: dict[str, Any]) -> np.ndarray:
|
|
|
|
|
"""Reconstruct a NumPy array from shared memory and free the segment."""
|
|
|
|
|
return _array_from_shm(handle)
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 01:21:55 +08:00
|
|
|
def _pack_value_if_large(
|
|
|
|
|
val: object,
|
|
|
|
|
d2h_stream: torch.Stream | None = None,
|
2026-09-10 17:50:54 +08:00
|
|
|
created: list[dict[str, Any]] | None = None,
|
2026-07-30 01:21:55 +08:00
|
|
|
) -> object:
|
2026-06-01 22:04:31 +02:00
|
|
|
"""Recursively replace large tensors with SHM handles.
|
|
|
|
|
|
|
|
|
|
Walks the container shapes pipelines return as ``DiffusionOutput.output``:
|
|
|
|
|
bare tensors, dicts (e.g. Cosmos3 ``{"image"/"video": ...}``), and
|
2026-08-26 09:31:12 +08:00
|
|
|
tuples/lists (e.g. LTX2 ``(video, audio)``). Other values pass
|
2026-06-01 22:04:31 +02:00
|
|
|
through unchanged. ``_unpack_if_shm_handle`` must mirror these shapes — keep
|
|
|
|
|
the two in sync.
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(val, torch.Tensor):
|
2026-09-10 17:50:54 +08:00
|
|
|
return _pack_tensor_if_large(val, d2h_stream=d2h_stream, created=created)
|
2026-08-08 08:24:42 +08:00
|
|
|
if isinstance(val, np.ndarray):
|
2026-09-10 17:50:54 +08:00
|
|
|
return (
|
|
|
|
|
_ndarray_to_shm(val, created=created)
|
|
|
|
|
if not val.dtype.hasobject and val.nbytes > _SHM_TENSOR_THRESHOLD
|
|
|
|
|
else val
|
|
|
|
|
)
|
2026-06-01 22:04:31 +02:00
|
|
|
if isinstance(val, dict):
|
2026-09-10 17:50:54 +08:00
|
|
|
return {key: _pack_value_if_large(value, d2h_stream=d2h_stream, created=created) for key, value in val.items()}
|
2026-06-01 22:04:31 +02:00
|
|
|
if isinstance(val, list):
|
2026-09-10 17:50:54 +08:00
|
|
|
return [_pack_value_if_large(item, d2h_stream=d2h_stream, created=created) for item in val]
|
2026-06-01 22:04:31 +02:00
|
|
|
if isinstance(val, tuple):
|
2026-09-10 17:50:54 +08:00
|
|
|
return tuple(_pack_value_if_large(item, d2h_stream=d2h_stream, created=created) for item in val)
|
2026-06-01 22:04:31 +02:00
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
|
2026-04-08 10:43:01 +08:00
|
|
|
def _unpack_if_shm_handle(val: object) -> object:
|
2026-06-01 22:04:31 +02:00
|
|
|
"""Reconstruct tensors from SHM handles, mirroring ``_pack_value_if_large``."""
|
2026-04-08 10:43:01 +08:00
|
|
|
if isinstance(val, dict) and val.get("__tensor_shm__"):
|
|
|
|
|
return _tensor_from_shm(val)
|
2026-08-08 08:24:42 +08:00
|
|
|
if isinstance(val, dict) and val.get("__ndarray_shm__"):
|
|
|
|
|
return _ndarray_from_shm(val)
|
2026-06-01 22:04:31 +02:00
|
|
|
if isinstance(val, dict):
|
|
|
|
|
return {key: _unpack_if_shm_handle(value) for key, value in val.items()}
|
|
|
|
|
if isinstance(val, list):
|
|
|
|
|
return [_unpack_if_shm_handle(item) for item in val]
|
|
|
|
|
if isinstance(val, tuple):
|
|
|
|
|
return tuple(_unpack_if_shm_handle(item) for item in val)
|
2026-04-08 10:43:01 +08:00
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
|
2026-09-10 17:50:54 +08:00
|
|
|
def _pack_diffusion_media(
|
|
|
|
|
media: DiffusionMediaOutput,
|
|
|
|
|
*,
|
|
|
|
|
d2h_stream: torch.Stream | None,
|
|
|
|
|
created: list[dict[str, Any]] | None = None,
|
2026-09-30 11:37:57 +08:00
|
|
|
enable_registered_shm: bool = False,
|
2026-09-10 17:50:54 +08:00
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
media.validate()
|
|
|
|
|
if not media.prepared_for_transport:
|
|
|
|
|
raise ValueError("Diffusion media must be prepared before IPC packing")
|
|
|
|
|
video = media.video
|
2026-09-30 11:37:57 +08:00
|
|
|
packed_tensor = _pack_tensor_if_large(
|
|
|
|
|
video.tensor, d2h_stream=d2h_stream, created=created, enable_registered_shm=enable_registered_shm
|
|
|
|
|
)
|
2026-09-10 17:50:54 +08:00
|
|
|
if isinstance(packed_tensor, torch.Tensor) and packed_tensor.device.type != "cpu":
|
|
|
|
|
packed_tensor = packed_tensor.detach().cpu().contiguous()
|
|
|
|
|
return {
|
|
|
|
|
"__type__": _DIFFUSION_MEDIA_WIRE_TYPE,
|
|
|
|
|
"schema_version": video.schema_version,
|
|
|
|
|
"prepared_for_transport": True,
|
|
|
|
|
"video": {
|
|
|
|
|
"tensor": packed_tensor,
|
|
|
|
|
"layout": video.spec.layout.value,
|
|
|
|
|
"encoding": video.spec.encoding.value,
|
|
|
|
|
"value_range": video.spec.value_range.value,
|
|
|
|
|
"color_model": video.spec.color_model.value,
|
|
|
|
|
"pending_float_consumers": sorted(consumer.value for consumer in video.constraints.pending_float_consumers),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _unpack_diffusion_media(packed: dict[str, Any]) -> DiffusionMediaOutput:
|
|
|
|
|
if packed.get("__type__") != _DIFFUSION_MEDIA_WIRE_TYPE:
|
|
|
|
|
raise ValueError("Invalid diffusion media wire payload")
|
|
|
|
|
video_payload = packed.get("video")
|
|
|
|
|
if not isinstance(video_payload, dict):
|
|
|
|
|
raise ValueError("Diffusion media wire payload is missing video metadata")
|
|
|
|
|
tensor = _unpack_if_shm_handle(video_payload.get("tensor"))
|
|
|
|
|
if not isinstance(tensor, torch.Tensor):
|
|
|
|
|
raise ValueError("Diffusion media wire payload contains an invalid video tensor")
|
|
|
|
|
schema_version = packed.get("schema_version")
|
|
|
|
|
if schema_version != 1:
|
|
|
|
|
raise ValueError(f"Unsupported video media schema version: {schema_version}")
|
|
|
|
|
if packed.get("prepared_for_transport") is not True:
|
|
|
|
|
raise ValueError("Diffusion media wire payload was not prepared for transport")
|
|
|
|
|
media = DiffusionMediaOutput(
|
|
|
|
|
video=VideoMediaOutput(
|
|
|
|
|
tensor=tensor,
|
|
|
|
|
spec=VideoTensorSpec(
|
|
|
|
|
layout=VideoTensorLayout(video_payload["layout"]),
|
|
|
|
|
encoding=VideoTensorEncoding(video_payload["encoding"]),
|
|
|
|
|
value_range=VideoValueRange(video_payload["value_range"]),
|
|
|
|
|
color_model=VideoColorModel(video_payload["color_model"]),
|
|
|
|
|
),
|
|
|
|
|
constraints=VideoTransportConstraints(
|
|
|
|
|
pending_float_consumers=frozenset(
|
|
|
|
|
FloatVideoConsumer(value) for value in video_payload.get("pending_float_consumers", [])
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
schema_version=1,
|
|
|
|
|
),
|
|
|
|
|
prepared_for_transport=True,
|
|
|
|
|
)
|
|
|
|
|
media.validate()
|
|
|
|
|
return media
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 01:21:55 +08:00
|
|
|
def _pack_diffusion_fields(
|
|
|
|
|
output: DiffusionOutput,
|
|
|
|
|
d2h_stream: torch.Stream | None = None,
|
2026-09-10 17:50:54 +08:00
|
|
|
created: list[dict[str, Any]] | None = None,
|
|
|
|
|
pending_updates: list[tuple[DiffusionOutput, str, object]] | None = None,
|
2026-09-30 11:37:57 +08:00
|
|
|
*,
|
|
|
|
|
enable_registered_shm: bool = False,
|
2026-07-30 01:21:55 +08:00
|
|
|
) -> DiffusionOutput:
|
2026-09-10 17:50:54 +08:00
|
|
|
if output.media is not None and output.output is not None:
|
|
|
|
|
raise ValueError("DiffusionOutput cannot contain both media and legacy output")
|
|
|
|
|
if output.media is not None and not isinstance(output.media, DiffusionMediaOutput):
|
|
|
|
|
raise TypeError(f"DiffusionOutput.media must be DiffusionMediaOutput, got {type(output.media).__name__}")
|
|
|
|
|
|
|
|
|
|
# Pack into locals first so a mid-payload failure leaves ``output`` unmutated
|
|
|
|
|
# (never enqueue a half-packed object); the caller unlinks any SHM segments
|
|
|
|
|
# accumulated in ``created`` on failure.
|
|
|
|
|
packed_output = _UNSET
|
2026-09-30 11:37:57 +08:00
|
|
|
if enable_registered_shm and output.video_output_index is not None:
|
|
|
|
|
index = output.video_output_index
|
|
|
|
|
values = output.output
|
|
|
|
|
if not isinstance(values, tuple) or type(index) is not int or not 0 <= index < len(values):
|
|
|
|
|
raise ValueError("video_output_index must identify a video tensor in the legacy output tuple")
|
|
|
|
|
if not isinstance(values[index], torch.Tensor):
|
|
|
|
|
raise TypeError("The legacy video output must be a tensor")
|
|
|
|
|
packed_output = tuple(
|
|
|
|
|
_pack_tensor_if_large(value, d2h_stream=d2h_stream, created=created, enable_registered_shm=True)
|
|
|
|
|
if position == index
|
|
|
|
|
else _pack_value_if_large(value, d2h_stream=d2h_stream, created=created)
|
|
|
|
|
for position, value in enumerate(values)
|
|
|
|
|
)
|
|
|
|
|
elif output.output is not None:
|
2026-09-10 17:50:54 +08:00
|
|
|
packed_output = _pack_value_if_large(output.output, d2h_stream=d2h_stream, created=created)
|
|
|
|
|
packed_media = _UNSET
|
|
|
|
|
if output.media is not None:
|
2026-09-30 11:37:57 +08:00
|
|
|
packed_media = _pack_diffusion_media(
|
|
|
|
|
output.media, d2h_stream=d2h_stream, created=created, enable_registered_shm=enable_registered_shm
|
|
|
|
|
)
|
2026-09-10 17:50:54 +08:00
|
|
|
packed_latents = _UNSET
|
|
|
|
|
if isinstance(output.trajectory_latents, torch.Tensor):
|
|
|
|
|
packed_latents = _pack_tensor_if_large(output.trajectory_latents, d2h_stream=d2h_stream, created=created)
|
|
|
|
|
packed_timesteps = _UNSET
|
|
|
|
|
if isinstance(output.trajectory_timesteps, torch.Tensor):
|
|
|
|
|
packed_timesteps = _pack_tensor_if_large(output.trajectory_timesteps, d2h_stream=d2h_stream, created=created)
|
|
|
|
|
packed_log_probs = _UNSET
|
|
|
|
|
if isinstance(output.trajectory_log_probs, torch.Tensor):
|
|
|
|
|
packed_log_probs = _pack_tensor_if_large(output.trajectory_log_probs, d2h_stream=d2h_stream, created=created)
|
|
|
|
|
|
|
|
|
|
updates = [
|
|
|
|
|
(output, field_name, value)
|
|
|
|
|
for field_name, value in (
|
|
|
|
|
("output", packed_output),
|
|
|
|
|
("media", packed_media),
|
|
|
|
|
("trajectory_latents", packed_latents),
|
|
|
|
|
("trajectory_timesteps", packed_timesteps),
|
|
|
|
|
("trajectory_log_probs", packed_log_probs),
|
|
|
|
|
)
|
|
|
|
|
if value is not _UNSET
|
|
|
|
|
]
|
|
|
|
|
if pending_updates is None:
|
|
|
|
|
for target, field_name, value in updates:
|
|
|
|
|
setattr(target, field_name, value)
|
|
|
|
|
else:
|
|
|
|
|
pending_updates.extend(updates)
|
2026-03-09 19:03:51 +08:00
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
2026-09-30 11:37:57 +08:00
|
|
|
def _is_rpc_result_envelope(output: object) -> TypeGuard[dict[str, Any]]:
|
2026-06-13 15:42:16 +08:00
|
|
|
return isinstance(output, dict) and output.get("type") == DIFFUSION_RPC_RESULT_ENVELOPE
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 01:21:55 +08:00
|
|
|
def pack_diffusion_output_shm(
|
|
|
|
|
output: object,
|
|
|
|
|
d2h_stream: torch.Stream | None = None,
|
2026-09-30 11:37:57 +08:00
|
|
|
*,
|
|
|
|
|
enable_registered_shm: bool = False,
|
2026-07-30 01:21:55 +08:00
|
|
|
) -> object:
|
2026-03-20 19:21:50 +08:00
|
|
|
"""Replace large tensors in diffusion worker outputs with SHM handles.
|
|
|
|
|
|
2026-06-30 00:09:19 +08:00
|
|
|
Supports a bare ``DiffusionOutput``, a wrapper object carrying one in
|
|
|
|
|
``.result`` (for example ``RunnerOutput``), an RPC result envelope carrying
|
2026-07-30 21:35:38 +08:00
|
|
|
the diffusion output in ``["result"]``, a batch wrapper carrying
|
|
|
|
|
``RunnerOutput`` objects in ``.runner_outputs``, or a DP-tagged dict
|
|
|
|
|
``{"dp_rank": int, "output": DiffusionOutput}`` used by DP multi-concurrency.
|
2026-07-30 01:21:55 +08:00
|
|
|
|
|
|
|
|
If *d2h_stream* is provided, D2H copies use that stream (non-blocking on
|
|
|
|
|
the default stream). The caller must synchronize *d2h_stream* afterward.
|
2026-09-10 17:50:54 +08:00
|
|
|
|
2026-09-30 11:37:57 +08:00
|
|
|
``enable_registered_shm`` opts explicitly identified video tensors into
|
|
|
|
|
direct CUDA-to-SHM transfer and borrowed CPU views. Other payloads keep
|
|
|
|
|
their existing transport. Registration is per transfer, without pooling.
|
|
|
|
|
|
2026-09-10 17:50:54 +08:00
|
|
|
Packing is failure-atomic: every SHM segment created during the call is
|
|
|
|
|
tracked and unlinked if any part of the payload fails to pack, and field
|
|
|
|
|
mutations are committed only after the complete payload succeeds.
|
2026-03-20 19:21:50 +08:00
|
|
|
"""
|
2026-09-10 17:50:54 +08:00
|
|
|
created: list[dict[str, Any]] = []
|
|
|
|
|
pending_updates: list[tuple[DiffusionOutput, str, object]] = []
|
|
|
|
|
try:
|
|
|
|
|
packed = _pack_output_shm(
|
|
|
|
|
output,
|
|
|
|
|
d2h_stream=d2h_stream,
|
|
|
|
|
created=created,
|
|
|
|
|
pending_updates=pending_updates,
|
2026-09-30 11:37:57 +08:00
|
|
|
enable_registered_shm=enable_registered_shm,
|
2026-09-10 17:50:54 +08:00
|
|
|
)
|
|
|
|
|
except BaseException:
|
|
|
|
|
_unlink_shm_handles(created)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
# Commit mutations only after every field and batch entry packed. Until
|
|
|
|
|
# this point, a failure leaves the complete caller-owned payload untouched.
|
|
|
|
|
for target, field_name, value in pending_updates:
|
|
|
|
|
setattr(target, field_name, value)
|
|
|
|
|
return packed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _iter_diffusion_outputs(output: object) -> Iterator[DiffusionOutput]:
|
|
|
|
|
"""Yield every packable ``DiffusionOutput`` leaf in a worker payload."""
|
2026-03-20 19:21:50 +08:00
|
|
|
if isinstance(output, DiffusionOutput):
|
2026-09-10 17:50:54 +08:00
|
|
|
yield output
|
|
|
|
|
return
|
2026-03-20 19:21:50 +08:00
|
|
|
|
2026-07-30 21:35:38 +08:00
|
|
|
# DP multi-concurrency: {"dp_rank": int, "output": DiffusionOutput}
|
|
|
|
|
if isinstance(output, dict) and "dp_rank" in output and "output" in output:
|
|
|
|
|
inner = output["output"]
|
|
|
|
|
if isinstance(inner, DiffusionOutput):
|
2026-09-10 17:50:54 +08:00
|
|
|
yield inner
|
|
|
|
|
return
|
2026-07-30 21:35:38 +08:00
|
|
|
|
2026-06-13 15:42:16 +08:00
|
|
|
if _is_rpc_result_envelope(output):
|
2026-09-10 17:50:54 +08:00
|
|
|
yield from _iter_diffusion_outputs(output.get("result"))
|
|
|
|
|
return
|
2026-06-13 15:42:16 +08:00
|
|
|
|
2026-03-20 19:21:50 +08:00
|
|
|
result = getattr(output, "result", None)
|
|
|
|
|
if isinstance(result, DiffusionOutput):
|
2026-09-10 17:50:54 +08:00
|
|
|
yield result
|
2026-06-30 00:09:19 +08:00
|
|
|
|
|
|
|
|
runner_outputs = getattr(output, "runner_outputs", None)
|
|
|
|
|
if isinstance(runner_outputs, list):
|
|
|
|
|
for runner_output in runner_outputs:
|
2026-09-10 17:50:54 +08:00
|
|
|
yield from _iter_diffusion_outputs(runner_output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def payload_carries_typed_media(output: object) -> bool:
|
|
|
|
|
"""True if *output* carries any typed ``DiffusionOutput.media`` payload."""
|
|
|
|
|
return any(item.media is not None for item in _iter_diffusion_outputs(output))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _pack_output_shm(
|
|
|
|
|
output: object,
|
|
|
|
|
*,
|
|
|
|
|
d2h_stream: torch.Stream | None,
|
|
|
|
|
created: list[dict[str, Any]],
|
|
|
|
|
pending_updates: list[tuple[DiffusionOutput, str, object]],
|
2026-09-30 11:37:57 +08:00
|
|
|
enable_registered_shm: bool = False,
|
2026-09-10 17:50:54 +08:00
|
|
|
) -> object:
|
2026-09-30 11:37:57 +08:00
|
|
|
transport_options = {"enable_registered_shm": True} if enable_registered_shm else {}
|
2026-09-10 17:50:54 +08:00
|
|
|
for item in _iter_diffusion_outputs(output):
|
|
|
|
|
_pack_diffusion_fields(
|
|
|
|
|
item,
|
|
|
|
|
d2h_stream=d2h_stream,
|
|
|
|
|
created=created,
|
|
|
|
|
pending_updates=pending_updates,
|
2026-09-30 11:37:57 +08:00
|
|
|
**transport_options,
|
2026-09-10 17:50:54 +08:00
|
|
|
)
|
2026-03-20 19:21:50 +08:00
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _unpack_diffusion_fields(output: DiffusionOutput) -> DiffusionOutput:
|
2026-04-08 10:43:01 +08:00
|
|
|
output.output = _unpack_if_shm_handle(output.output)
|
2026-09-10 17:50:54 +08:00
|
|
|
if isinstance(output.media, dict):
|
|
|
|
|
output.media = _unpack_diffusion_media(output.media)
|
2026-04-08 10:43:01 +08:00
|
|
|
output.trajectory_latents = _unpack_if_shm_handle(output.trajectory_latents)
|
|
|
|
|
output.trajectory_timesteps = _unpack_if_shm_handle(output.trajectory_timesteps)
|
|
|
|
|
output.trajectory_log_probs = _unpack_if_shm_handle(output.trajectory_log_probs)
|
2026-03-09 19:03:51 +08:00
|
|
|
return output
|
2026-03-20 19:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def unpack_diffusion_output_shm(output: object) -> object:
|
|
|
|
|
"""Reconstruct tensors from SHM handles in diffusion worker outputs."""
|
|
|
|
|
if isinstance(output, DiffusionOutput):
|
|
|
|
|
return _unpack_diffusion_fields(output)
|
|
|
|
|
|
2026-07-30 21:35:38 +08:00
|
|
|
# DP multi-concurrency: {"dp_rank": int, "output": DiffusionOutput}
|
|
|
|
|
if isinstance(output, dict) and "dp_rank" in output and "output" in output:
|
|
|
|
|
inner = output["output"]
|
|
|
|
|
if isinstance(inner, DiffusionOutput):
|
|
|
|
|
output["output"] = _unpack_diffusion_fields(inner)
|
|
|
|
|
return output
|
|
|
|
|
|
2026-06-13 15:42:16 +08:00
|
|
|
if _is_rpc_result_envelope(output):
|
|
|
|
|
result = output.get("result")
|
2026-08-08 08:24:42 +08:00
|
|
|
output["result"] = unpack_diffusion_output_shm(result)
|
2026-06-13 15:42:16 +08:00
|
|
|
return output
|
|
|
|
|
|
2026-03-20 19:21:50 +08:00
|
|
|
result = getattr(output, "result", None)
|
|
|
|
|
if isinstance(result, DiffusionOutput):
|
2026-09-30 11:37:57 +08:00
|
|
|
setattr(output, "result", _unpack_diffusion_fields(result))
|
2026-06-30 00:09:19 +08:00
|
|
|
|
|
|
|
|
runner_outputs = getattr(output, "runner_outputs", None)
|
|
|
|
|
if isinstance(runner_outputs, list):
|
|
|
|
|
for runner_output in runner_outputs:
|
|
|
|
|
unpack_diffusion_output_shm(runner_output)
|
2026-03-20 19:21:50 +08:00
|
|
|
return output
|