Files
vllm-omni/vllm_omni/diffusion/sched/interface.py
T
2026-08-15 23:58:36 +08:00

262 lines
8.5 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import enum
from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING, Any, TypedDict
from vllm_omni.diffusion.diffusion_kv.metadata import DiffusionKVMetadata
from vllm_omni.diffusion.request import OmniDiffusionRequest
if TYPE_CHECKING:
from vllm_omni.diffusion.diffusion_kv.request import DiffusionKVRequest
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)
class _AdmissionWaitDecision:
"""Internal scheduler policy for delaying the next admission wave."""
should_wait: bool
deadline: float | None = None
stable_window_s: float = 0.0
max_batch: int = 1
@dataclass(frozen=True, eq=True)
class StepBatchSamplingParamsKey:
"""Denoise step level 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
# Request-scoped execution policy. A batch must use one quality mode
# because model acceleration hooks are shared by the whole worker batch.
quality: str | None = None
# Output count. Requests with different num_outputs_per_prompt produce
# differently shaped outputs and cannot share a batch.
num_outputs_per_prompt: int = 1
# LoRA identity. Requests with different adapters or scales must run in
# separate batches so the worker can activate exactly one adapter per step.
lora_int_id: int | None = None
lora_scale: float = 1.0
@dataclass(frozen=True, eq=True)
class RequestBatchSamplingParamsKey:
"""Request level Batch-compatibility key derived from ``OmniDiffusionSamplingParams``.
Only request-batch-wide fields belong here. Request-local values such as
seeds, generators, latent tensors, timesteps, and request-local
``extra_args`` are read per request from
``DiffusionRequestBatch.sampling_params_list``. Structural ``extra_args``
that affect batching are listed explicitly below.
"""
# Spatial / temporal shape.
height: int | None = None
width: int | None = None
num_frames: int = 1
resolution: int = 640
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
strength: float | None = None
# Scheduling / output shape.
num_inference_steps: int | None = None
sigmas: list[float] | None = None
max_sequence_length: int | None = None
num_outputs_per_prompt: int = 1
eta: float = 0.0
decode_timestep: float | list[float] | None = None
decode_noise_scale: float | list[float] | None = None
output_type: str | None = None
# Model-specific batch defaults used by request-mode pipelines.
layers: int = 4
use_en_prompt: bool = False
# Request-scoped execution policy. Keep accelerated and reference-path
# requests in separate worker batches. ``None`` delegates the default to
# the model and must remain distinct from explicit ``lossless``.
quality: str | None = None
# Wan scheduler structure is carried through extra_args. Requests using
# different solvers or flow shifts must not share a request batch.
sample_solver: str | None = None
flow_shift: float | None = None
# Pipeline-specific condition structure populated during preprocessing.
# It prevents independently valid requests with incompatible conditions
# from being admitted to the same request batch.
condition_key: tuple[Any, ...] | None = None
# LoRA identity.
lora_int_id: int | None = None
lora_scale: float = 1.0
@dataclass
class SchedulerRequestState:
"""Scheduler-owned state for one queued OmniDiffusionRequest."""
request_id: str
req: OmniDiffusionRequest
sampling_params_key: StepBatchSamplingParamsKey | RequestBatchSamplingParamsKey | None = None
diffusion_kv_requests: tuple[DiffusionKVRequest, ...] = ()
status: DiffusionRequestStatus = DiffusionRequestStatus.WAITING
error: str | None = None
def is_finished(self) -> bool:
return DiffusionRequestStatus.is_finished(self.status)
@dataclass
class NewRequestData:
"""Payload for a newly scheduled diffusion request.
Carries the already-initialized request object so executors and workers do
not re-run ``OmniDiffusionRequest.__post_init__`` and mutate sentinel-based
fields like ``guidance_scale_provided``.
"""
request_id: str
req: OmniDiffusionRequest
diffusion_kv_metadata: DiffusionKVMetadata | None = None
@classmethod
def from_state(
cls,
state: SchedulerRequestState,
*,
diffusion_kv_metadata: DiffusionKVMetadata | None = None,
) -> NewRequestData:
return cls(
request_id=state.request_id,
req=state.req,
diffusion_kv_metadata=diffusion_kv_metadata,
)
def validate_new_request_data_identity(new_req: NewRequestData) -> None:
"""Ensure an envelope and its forwarded request describe the same request."""
forwarded_request_id = new_req.req.request_id
if new_req.request_id != forwarded_request_id:
raise ValueError(
"Diffusion request identity mismatch: "
f"envelope request_id={new_req.request_id!r}, "
f"forwarded request_id={forwarded_request_id!r}"
)
metadata = new_req.diffusion_kv_metadata
if metadata is not None and metadata.request_id != forwarded_request_id:
raise ValueError(
"Diffusion request identity mismatch: "
f"metadata request_id={metadata.request_id!r}, "
f"forwarded request_id={forwarded_request_id!r}"
)
@dataclass
class CachedRequestData:
"""Cached diffusion requests that only need their request ids resent."""
request_ids: list[str]
@classmethod
def make_empty(cls) -> CachedRequestData:
return cls(request_ids=[])
class KVPrefetchJob(TypedDict):
"""Descriptor for prefetching the next request's received KV cache."""
request_id: str
kv_sender_info: dict[str, Any]
@dataclass
class DiffusionSchedulerOutput:
"""Output of a single scheduling cycle."""
# Stable scheduler-cycle diagnostics for engines, injected schedulers, and
# instrumentation; they are intentionally retained even without a direct
# production consumer for every field.
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
# next request to background-prefetch KV
kv_prefetch_job: KVPrefetchJob | None = None
@cached_property
def scheduled_request_ids(self) -> list[str]:
"""
All scheduled request ids in this cycle, including both new and cached ones.
"""
return [
*(req.request_id for req in self.scheduled_new_reqs),
*self.scheduled_cached_reqs.request_ids,
]
@property
def num_scheduled_reqs(self) -> int:
return len(self.scheduled_request_ids)
@property
def is_empty(self) -> bool:
return self.num_scheduled_reqs == 0