Files
vllm-omni/vllm_omni/diffusion/worker/diffusion_worker.py
T

401 lines
16 KiB
Python
Raw Normal View History

2025-11-28 14:22:11 +08:00
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
2026-01-17 19:49:13 +08:00
"""
Diffusion Worker for vLLM-Omni.
Handles GPU infrastructure initialization and delegates model operations
to DiffusionModelRunner.
2026-01-17 19:49:13 +08:00
"""
2025-11-28 14:22:11 +08:00
import multiprocessing as mp
import os
from contextlib import AbstractContextManager, nullcontext
2025-11-28 14:22:11 +08:00
import torch
import zmq
2026-01-17 19:49:13 +08:00
from vllm.config import VllmConfig
2025-11-28 14:22:11 +08:00
from vllm.distributed.device_communicators.shm_broadcast import MessageQueue
from vllm.logger import init_logger
2026-01-17 19:49:13 +08:00
from vllm.utils.mem_utils import GiB_bytes
2025-11-28 14:22:11 +08:00
from vllm_omni.diffusion.data import (
DiffusionOutput,
OmniDiffusionConfig,
)
from vllm_omni.diffusion.distributed.parallel_state import (
destroy_distributed_env,
init_distributed_environment,
initialize_model_parallel,
)
from vllm_omni.diffusion.forward_context import set_forward_context
from vllm_omni.diffusion.lora.manager import DiffusionLoRAManager
from vllm_omni.diffusion.profiler import CurrentProfiler
2025-11-28 14:22:11 +08:00
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.worker.diffusion_model_runner import DiffusionModelRunner
from vllm_omni.lora.request import LoRARequest
from vllm_omni.platforms import current_omni_platform
2025-11-28 14:22:11 +08:00
logger = init_logger(__name__)
class DiffusionWorker:
2025-11-28 14:22:11 +08:00
"""
2026-01-17 19:49:13 +08:00
A worker that manages GPU infrastructure and delegates to the model runner.
This class handles infrastructure initialization only:
- Device setup (CUDA device selection)
- Distributed environment (NCCL, model parallel)
- Memory management (sleep/wake)
All model-related operations (loading, compilation, execution) are
delegated to DiffusionModelRunner.
2025-11-28 14:22:11 +08:00
"""
def __init__(
self,
local_rank: int,
rank: int,
od_config: OmniDiffusionConfig,
):
self.local_rank = local_rank
self.rank = rank
self.od_config = od_config
2026-01-17 19:49:13 +08:00
self.device: torch.device | None = None
self.vllm_config: VllmConfig | None = None
self.model_runner: DiffusionModelRunner | None = None
self._sleep_saved_buffers: dict[str, torch.Tensor] = {}
self.lora_manager: DiffusionLoRAManager | None = None
2026-01-17 19:49:13 +08:00
self.init_device()
2025-11-28 14:22:11 +08:00
2026-01-17 19:49:13 +08:00
def init_device(self) -> None:
"""Initialize the device and distributed environment."""
2025-11-28 14:22:11 +08:00
world_size = self.od_config.num_gpus
rank = self.rank
2026-01-17 19:49:13 +08:00
2025-11-28 14:22:11 +08:00
# Set environment variables for distributed initialization
os.environ["MASTER_ADDR"] = "localhost"
os.environ["MASTER_PORT"] = str(self.od_config.master_port)
os.environ["LOCAL_RANK"] = str(self.local_rank)
os.environ["RANK"] = str(rank)
os.environ["WORLD_SIZE"] = str(world_size)
2026-01-17 19:49:13 +08:00
# Setup device
self.device = current_omni_platform.get_torch_device(rank)
current_omni_platform.set_device(self.device)
2025-11-28 14:22:11 +08:00
2026-01-17 19:49:13 +08:00
# Create vllm_config for parallel configuration
2025-11-28 14:22:11 +08:00
vllm_config = VllmConfig()
vllm_config.parallel_config.tensor_parallel_size = self.od_config.parallel_config.tensor_parallel_size
vllm_config.parallel_config.data_parallel_size = self.od_config.parallel_config.data_parallel_size
self.vllm_config = vllm_config
2026-01-17 19:49:13 +08:00
# Initialize distributed environment
with set_forward_context(vllm_config=vllm_config, omni_diffusion_config=self.od_config):
init_distributed_environment(world_size=world_size, rank=rank)
logger.info(f"Worker {self.rank}: Initialized device and distributed environment.")
2026-01-17 19:49:13 +08:00
parallel_config = self.od_config.parallel_config
initialize_model_parallel(
data_parallel_size=parallel_config.data_parallel_size,
cfg_parallel_size=parallel_config.cfg_parallel_size,
sequence_parallel_size=parallel_config.sequence_parallel_size,
ulysses_degree=parallel_config.ulysses_degree,
ring_degree=parallel_config.ring_degree,
tensor_parallel_size=parallel_config.tensor_parallel_size,
pipeline_parallel_size=parallel_config.pipeline_parallel_size,
)
2025-11-28 14:22:11 +08:00
2026-01-17 19:49:13 +08:00
# Create model runner and load model
self.model_runner = DiffusionModelRunner(
2026-01-17 19:49:13 +08:00
vllm_config=self.vllm_config,
od_config=self.od_config,
device=self.device,
)
2026-01-17 19:49:13 +08:00
self.model_runner.load_model(
memory_pool_context_fn=self._maybe_get_memory_pool_context,
)
assert self.model_runner.pipeline is not None
self.lora_manager = DiffusionLoRAManager(
pipeline=self.model_runner.pipeline,
device=self.device,
dtype=self.od_config.dtype,
max_cached_adapters=self.od_config.max_cpu_loras,
lora_path=self.od_config.lora_path,
lora_scale=self.od_config.lora_scale,
)
2026-01-17 19:49:13 +08:00
logger.info(f"Worker {self.rank}: Initialization complete.")
2025-12-12 09:59:56 +05:30
def generate(self, request: OmniDiffusionRequest) -> DiffusionOutput:
2026-01-17 19:49:13 +08:00
"""Generate output for the given requests."""
return self.execute_model(request, self.od_config)
2025-12-25 22:26:30 +08:00
@classmethod
def start_profile(cls, trace_path_template: str) -> str:
"""Start profiling for this GPU worker."""
return CurrentProfiler.start(trace_path_template)
@classmethod
def stop_profile(cls) -> dict | None:
"""Stop profiling and return the result dictionary."""
return CurrentProfiler.stop()
def execute_model(self, req: OmniDiffusionRequest, od_config: OmniDiffusionConfig) -> DiffusionOutput:
2026-01-17 19:49:13 +08:00
"""Execute a forward pass by delegating to the model runner."""
assert self.model_runner is not None, "Model runner not initialized"
if self.lora_manager is not None:
try:
self.lora_manager.set_active_adapter(req.sampling_params.lora_request, req.sampling_params.lora_scale)
except Exception as exc:
if req.sampling_params.lora_request is not None:
raise
logger.warning("LoRA activation skipped: %s", exc)
return self.model_runner.execute_model(req)
2026-01-17 19:49:13 +08:00
def load_weights(self, weights) -> set[str]:
"""Load weights by delegating to the model runner."""
assert self.model_runner is not None, "Model runner not initialized"
return self.model_runner.load_weights(weights)
def remove_lora(self, adapter_id: int) -> bool:
return self.lora_manager.remove_adapter(adapter_id)
def add_lora(self, lora_request: LoRARequest, lora_scale: float = 1.0) -> bool:
return self.lora_manager.add_adapter(lora_request, lora_scale)
def list_loras(self) -> list[int]:
return self.lora_manager.list_adapters()
def pin_lora(self, adapter_id: int) -> bool:
return self.lora_manager.pin_adapter(adapter_id)
def sleep(self, level: int = 1) -> bool:
"""
2026-01-17 19:49:13 +08:00
Put the worker to sleep, offloading model weights.
Args:
2026-01-17 19:49:13 +08:00
level: Sleep level. Level 1 offloads weights, level 2 also saves buffers.
"""
from vllm.device_allocator.cumem import CuMemAllocator
free_bytes_before_sleep = current_omni_platform.get_free_memory()
# Save the buffers before level 2 sleep
2026-01-17 19:49:13 +08:00
if level == 2 and self.model_runner is not None:
model = self.model_runner.pipeline
self._sleep_saved_buffers = {name: buffer.cpu().clone() for name, buffer in model.named_buffers()}
allocator = CuMemAllocator.get_instance()
allocator.sleep(offload_tags=("weights",) if level == 1 else tuple())
free_bytes_after_sleep = current_omni_platform.get_free_memory()
device_id = self.device.index if self.device.index is not None else 0
total = current_omni_platform.get_device_total_memory(device_id)
freed_bytes = free_bytes_after_sleep - free_bytes_before_sleep
used_bytes = total - free_bytes_after_sleep
assert freed_bytes >= 0, "Memory usage increased after sleeping."
logger.info(
"Sleep mode freed %.2f GiB memory, %.2f GiB memory is still in use.",
freed_bytes / GiB_bytes,
used_bytes / GiB_bytes,
)
return True
def wake_up(self, tags: list[str] | None = None) -> bool:
"""
Wake up the worker from sleep mode. See the sleep function
method for more details.
Args:
tags: An optional list of tags to reallocate the worker memory
for specific memory allocations. Values must be in
`("weights")`. If None, all memory is reallocated.
wake_up should be called with all tags (or None) before the
worker is used again.
"""
from vllm.device_allocator.cumem import CuMemAllocator
allocator = CuMemAllocator.get_instance()
allocator.wake_up(tags)
# Restore the buffers after level 2 sleep
2026-01-17 19:49:13 +08:00
if len(self._sleep_saved_buffers) and self.model_runner is not None:
model = self.model_runner.pipeline
for name, buffer in model.named_buffers():
if name in self._sleep_saved_buffers:
buffer.data.copy_(self._sleep_saved_buffers[name].data)
self._sleep_saved_buffers = {}
return True
def _maybe_get_memory_pool_context(self, tag: str) -> AbstractContextManager:
2026-01-17 19:49:13 +08:00
"""Get memory pool context for sleep mode support."""
if self.od_config.enable_sleep_mode:
from vllm.device_allocator.cumem import CuMemAllocator
allocator = CuMemAllocator.get_instance()
if tag == "weights":
assert allocator.get_current_usage() == 0, "Sleep mode can only be used for one instance per process."
return allocator.use_memory_pool(tag=tag)
else:
return nullcontext()
def shutdown(self) -> None:
2026-01-17 19:49:13 +08:00
"""Shutdown the worker and cleanup distributed environment."""
destroy_distributed_env()
2025-11-28 14:22:11 +08:00
class WorkerProc:
"""Wrapper that runs one Worker in a separate process."""
def __init__(
self,
od_config: OmniDiffusionConfig,
gpu_id: int,
broadcast_handle,
):
self.od_config = od_config
# Inter-process Communication
self.context = zmq.Context(io_threads=2)
2026-01-17 19:49:13 +08:00
# Initialize MessageQueue reader from handle
2025-11-28 14:22:11 +08:00
self.mq = MessageQueue.create_from_handle(broadcast_handle, gpu_id)
self.result_mq = None
self.result_mq_handle = None
2026-01-17 19:49:13 +08:00
# Setup result sender (only for rank 0)
2025-11-28 14:22:11 +08:00
if gpu_id == 0:
self.result_mq = MessageQueue(n_reader=1, n_local_reader=1, local_reader_ranks=[0])
self.result_mq_handle = self.result_mq.export_handle()
logger.info(f"Worker {gpu_id} created result MessageQueue")
assert od_config.master_port is not None
self.worker = self._create_worker(gpu_id, od_config)
self.gpu_id = gpu_id
self._running = True
def _create_worker(self, gpu_id: int, od_config: OmniDiffusionConfig) -> DiffusionWorker:
"""Create a worker instance. Override in subclasses for different worker types."""
return DiffusionWorker(
2025-11-28 14:22:11 +08:00
local_rank=gpu_id,
rank=gpu_id,
od_config=od_config,
)
def return_result(self, output: DiffusionOutput):
2026-01-17 19:49:13 +08:00
"""Reply to client, only on rank 0."""
2025-11-28 14:22:11 +08:00
if self.result_mq is not None:
self.result_mq.enqueue(output)
2025-12-25 22:26:30 +08:00
def recv_message(self):
2026-01-17 19:49:13 +08:00
"""Receive messages from broadcast queue."""
return self.mq.dequeue(indefinite=True)
2025-11-28 14:22:11 +08:00
2025-12-25 22:26:30 +08:00
def execute_rpc(self, rpc_request: dict) -> tuple[object | None, bool]:
"""Execute an RPC request and indicate whether to reply."""
method = rpc_request["method"]
args = rpc_request.get("args", ())
kwargs = rpc_request.get("kwargs", {})
output_rank = rpc_request.get("output_rank")
exec_all_ranks = rpc_request.get("exec_all_ranks", False)
should_execute = exec_all_ranks or output_rank is None or output_rank == self.gpu_id
should_reply = (output_rank is None or output_rank == self.gpu_id) and self.result_mq is not None
if not should_execute:
return None, False
try:
if isinstance(method, str):
func = getattr(self.worker, method)
result = func(*args, **kwargs)
else:
result = method(self.worker, *args, **kwargs)
return result, should_reply
except Exception as e:
logger.error(f"Error executing RPC: {e}", exc_info=True)
raise e
2025-12-25 22:26:30 +08:00
2025-11-28 14:22:11 +08:00
def worker_busy_loop(self) -> None:
2026-01-17 19:49:13 +08:00
"""Main busy loop for Multiprocessing Workers."""
2025-11-28 14:22:11 +08:00
logger.info(f"Worker {self.gpu_id} ready to receive requests via shared memory")
while self._running:
2025-12-25 22:26:30 +08:00
msg = None
2025-11-28 14:22:11 +08:00
try:
2025-12-25 22:26:30 +08:00
msg = self.recv_message()
2025-11-28 14:22:11 +08:00
except Exception as e:
logger.error(
2025-12-25 22:26:30 +08:00
f"Error receiving message in worker loop: {e}",
2025-11-28 14:22:11 +08:00
exc_info=True,
)
continue
if msg is None or len(msg) == 0:
logger.warning("Worker %s: Received empty payload, ignoring", self.gpu_id)
continue
2025-12-25 22:26:30 +08:00
# Route message based on type
if isinstance(msg, dict) and msg.get("type") == "rpc":
try:
result, should_reply = self.execute_rpc(msg)
if should_reply:
self.return_result(result)
except Exception as e:
logger.error(f"Error processing RPC: {e}", exc_info=True)
if self.result_mq is not None:
self.return_result(DiffusionOutput(error=str(e)))
2025-11-28 14:22:11 +08:00
2025-12-25 22:26:30 +08:00
elif isinstance(msg, dict) and msg.get("type") == "shutdown":
logger.info("Worker %s: Received shutdown message", self.gpu_id)
self._running = False
2025-11-28 14:22:11 +08:00
continue
2025-12-25 22:26:30 +08:00
else:
2026-01-17 19:49:13 +08:00
# Handle generation request
2025-12-25 22:26:30 +08:00
try:
output = self.worker.execute_model(msg, self.od_config)
except Exception as e:
logger.error(
f"Error executing forward in event loop: {e}",
exc_info=True,
)
output = DiffusionOutput(error=str(e))
try:
self.return_result(output)
except zmq.ZMQError as e:
logger.error(f"ZMQ error sending reply: {e}")
continue
2025-11-28 14:22:11 +08:00
logger.info("event loop terminated.")
try:
self.worker.shutdown()
2026-01-17 19:49:13 +08:00
except Exception as exc:
logger.warning("Worker %s: Shutdown encountered an error: %s", self.gpu_id, exc)
2025-11-28 14:22:11 +08:00
self.context.term()
@staticmethod
def worker_main(
rank: int,
od_config: OmniDiffusionConfig,
pipe_writer: mp.connection.Connection,
broadcast_handle,
) -> None:
"""Worker initialization and execution loops."""
from vllm_omni.plugins import load_omni_general_plugins
load_omni_general_plugins()
2025-11-28 14:22:11 +08:00
worker_proc = WorkerProc(
od_config,
gpu_id=rank,
broadcast_handle=broadcast_handle,
)
logger.info(f"Worker {rank}: Scheduler loop started.")
pipe_writer.send(
{
"status": "ready",
"result_handle": worker_proc.result_mq_handle if rank == 0 else None,
}
)
worker_proc.worker_busy_loop()
logger.info(f"Worker {rank}: Shutdown complete.")