Signed-off-by: Semmer <semmer@live.cn> Signed-off-by: jader <yjader@foxmail.com> Signed-off-by: asukaqaq-s <1311722138@qq.com> Co-authored-by: asukaqaq-s <1311722138@qq.com> Co-authored-by: jader <yjader@foxmail.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
207 lines
6.5 KiB
Python
207 lines
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from __future__ import annotations
|
|
|
|
import enum
|
|
import uuid
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
from functools import cached_property
|
|
from typing import TYPE_CHECKING
|
|
|
|
from vllm.logger import init_logger
|
|
|
|
from vllm_omni.diffusion.data import OmniDiffusionConfig
|
|
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
|
|
|
if TYPE_CHECKING:
|
|
from vllm_omni.diffusion.worker.utils import RunnerOutput
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
class DiffusionRequestStatus(enum.IntEnum):
|
|
"""Request status tracked by diffusion scheduler."""
|
|
|
|
WAITING = enum.auto()
|
|
RUNNING = enum.auto()
|
|
PREEMPTED = enum.auto()
|
|
|
|
# if any status is after or equal to FINISHED_COMPLETED, it is considered finished
|
|
FINISHED_COMPLETED = enum.auto()
|
|
FINISHED_ABORTED = enum.auto()
|
|
FINISHED_ERROR = enum.auto()
|
|
|
|
@staticmethod
|
|
def is_finished(status: DiffusionRequestStatus) -> bool:
|
|
return status >= DiffusionRequestStatus.FINISHED_COMPLETED
|
|
|
|
|
|
@dataclass(frozen=True, eq=True)
|
|
class SamplingParamsKey:
|
|
"""Batch-compatibility key derived from ``OmniDiffusionSamplingParams``.
|
|
|
|
Only requests with the same key can be batched together.
|
|
Fields not included here are treated as request-local and do not
|
|
participate in the current homogeneous batching policy.
|
|
"""
|
|
|
|
# Spatial / temporal shape.
|
|
height: int | None = None
|
|
width: int | None = None
|
|
num_frames: int = 1
|
|
resolution: int | str | None = None
|
|
fps: int | None = None
|
|
frame_rate: float | None = None
|
|
boundary_ratio: float | None = None
|
|
|
|
# CFG / guidance.
|
|
do_classifier_free_guidance: bool = False
|
|
guidance_scale: float = 0.0
|
|
guidance_scale_provided: bool = False
|
|
guidance_scale_2: float | None = None
|
|
guidance_rescale: float = 0.0
|
|
true_cfg_scale: float | None = None
|
|
cfg_normalize: bool = False
|
|
|
|
|
|
@dataclass
|
|
class DiffusionRequestState:
|
|
"""Scheduler-owned state for one queued OmniDiffusionRequest."""
|
|
|
|
# Unique scheduler-owned request ID.
|
|
# NOTE: This identifies one OmniDiffusionRequest, which may contain multiple request_ids.
|
|
# TODO: Align this with OmniDiffusionRequest.request_ids once scheduler batching is supported.
|
|
sched_req_id: str
|
|
req: OmniDiffusionRequest
|
|
sampling_params_key: SamplingParamsKey | None = None
|
|
status: DiffusionRequestStatus = DiffusionRequestStatus.WAITING
|
|
error: str | None = None
|
|
|
|
def is_finished(self) -> bool:
|
|
return DiffusionRequestStatus.is_finished(self.status)
|
|
|
|
|
|
@dataclass
|
|
class NewRequestData:
|
|
"""Full request payload for a newly scheduled diffusion request."""
|
|
|
|
sched_req_id: str
|
|
req: OmniDiffusionRequest
|
|
|
|
@classmethod
|
|
def from_state(cls, state: DiffusionRequestState) -> NewRequestData:
|
|
return cls(sched_req_id=state.sched_req_id, req=state.req)
|
|
|
|
|
|
@dataclass
|
|
class CachedRequestData:
|
|
"""Cached diffusion requests that only need their scheduler ids resent."""
|
|
|
|
sched_req_ids: list[str]
|
|
|
|
@classmethod
|
|
def make_empty(cls) -> CachedRequestData:
|
|
return cls(sched_req_ids=[])
|
|
|
|
|
|
@dataclass
|
|
class DiffusionSchedulerOutput:
|
|
"""Output of a single scheduling cycle."""
|
|
|
|
step_id: int # global step index
|
|
scheduled_new_reqs: list[NewRequestData]
|
|
scheduled_cached_reqs: CachedRequestData
|
|
finished_req_ids: set[str]
|
|
num_running_reqs: int
|
|
num_waiting_reqs: int
|
|
|
|
@cached_property
|
|
def scheduled_req_ids(self) -> list[str]:
|
|
"""
|
|
All scheduled request ids in this cycle, including both new and cached ones.
|
|
NOTE:
|
|
This id is generated and owned by the scheduler,
|
|
and may be different from the OmniDiffusionRequest.request_ids.
|
|
"""
|
|
return [
|
|
*(req.sched_req_id for req in self.scheduled_new_reqs),
|
|
*self.scheduled_cached_reqs.sched_req_ids,
|
|
]
|
|
|
|
@property
|
|
def num_scheduled_reqs(self) -> int:
|
|
return len(self.scheduled_req_ids)
|
|
|
|
@property
|
|
def is_empty(self) -> bool:
|
|
return self.num_scheduled_reqs == 0
|
|
|
|
|
|
class SchedulerInterface(ABC):
|
|
"""Abstract lifecycle contract for diffusion schedulers."""
|
|
|
|
def _make_sched_req_id(self, request: OmniDiffusionRequest) -> str:
|
|
"""
|
|
Generate a unique scheduler request ID for the given request.
|
|
The default implementation uses the first request_id from the request if available,
|
|
otherwise generates a random one.
|
|
"""
|
|
if request.request_ids:
|
|
base = request.request_ids[0]
|
|
else:
|
|
logger.warning("Request has no request_ids, generating a random one. Request: %s", request)
|
|
base = f"req_{uuid.uuid4().hex[:8]}"
|
|
|
|
sched_req_id = base
|
|
suffix = 1
|
|
while self.get_request_state(sched_req_id) is not None:
|
|
sched_req_id = f"{base}#{suffix}"
|
|
suffix += 1
|
|
return sched_req_id
|
|
|
|
@abstractmethod
|
|
def initialize(self, od_config: OmniDiffusionConfig) -> None:
|
|
"""Initialize or reset scheduler state."""
|
|
|
|
@abstractmethod
|
|
def add_request(self, request: OmniDiffusionRequest) -> str:
|
|
"""Add a request and return the scheduler-owned request id."""
|
|
|
|
@abstractmethod
|
|
def schedule(self) -> DiffusionSchedulerOutput:
|
|
"""Run one scheduling cycle."""
|
|
|
|
@abstractmethod
|
|
def update_from_output(self, sched_output: DiffusionSchedulerOutput, output: RunnerOutput) -> set[str]:
|
|
"""Update scheduler state from executor output."""
|
|
|
|
@abstractmethod
|
|
def get_request_state(self, sched_req_id: str) -> DiffusionRequestState | None:
|
|
"""Return request state if present."""
|
|
|
|
@abstractmethod
|
|
def has_requests(self) -> bool:
|
|
"""Return whether the scheduler still owns runnable requests."""
|
|
|
|
@abstractmethod
|
|
def get_sched_req_id(self, request_id: str) -> str | None:
|
|
"""Resolve a public request_id to the active scheduler request id."""
|
|
|
|
@abstractmethod
|
|
def pop_request_state(self, sched_req_id: str) -> DiffusionRequestState | None:
|
|
"""Remove and return request state if present."""
|
|
|
|
@abstractmethod
|
|
def preempt_request(self, sched_req_id: str) -> bool:
|
|
"""Preempt a running request back to waiting."""
|
|
|
|
@abstractmethod
|
|
def finish_requests(self, sched_req_ids: str | list[str], status: DiffusionRequestStatus) -> None:
|
|
"""Mark one or more requests finished."""
|
|
|
|
@abstractmethod
|
|
def close(self) -> None:
|
|
"""Release scheduler-owned state."""
|