# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, Any from vllm.logger import init_logger from vllm_omni.diffusion.request import OmniDiffusionRequest from vllm_omni.diffusion.sched.base_scheduler import _BaseScheduler from vllm_omni.diffusion.sched.interface import ( DiffusionRequestStatus, DiffusionSchedulerOutput, ) if TYPE_CHECKING: from vllm_omni.diffusion.worker.utils import RunnerOutput logger = init_logger(__name__) @dataclass class _StepProgress: current_step: int total_steps: int class StepScheduler(_BaseScheduler): """Placeholder scheduler that advances a request one denoise step per update.""" def __init__(self) -> None: super().__init__() self._request_progress: dict[str, _StepProgress] = {} def _reset_scheduler_state(self) -> None: self._request_progress.clear() def add_request(self, request: OmniDiffusionRequest) -> str: request_id = request.request_id total_steps = self._get_total_steps(request) if total_steps <= 0: raise ValueError(f"Diffusion request {request_id} must have positive total_steps, got {total_steps}") current_step = request.sampling_params.step_index or 0 if current_step < 0 or current_step >= total_steps: raise ValueError( f"Diffusion request {request_id} has invalid initial step_index {current_step} " f"for total_steps={total_steps}" ) request.sampling_params.step_index = current_step request_id = self._add_request_with_request_id(request_id, request) self._request_progress[request_id] = _StepProgress(current_step=current_step, total_steps=total_steps) logger.debug( "StepScheduler add_request: %s (step=%d/%d, waiting=%d)", request_id, current_step, total_steps, len(self._waiting), ) return request_id def schedule(self) -> DiffusionSchedulerOutput: return super().schedule() def update_from_output(self, sched_output: DiffusionSchedulerOutput, output: RunnerOutput) -> set[str]: scheduled_request_ids = sched_output.scheduled_request_ids if not scheduled_request_ids: return set() terminal_statuses: dict[str, DiffusionRequestStatus] = {} terminal_errors: dict[str, str | None] = {} for request_id in scheduled_request_ids: state = self._request_states.get(request_id) progress = self._request_progress.get(request_id) if state is None or progress is None or state.is_finished(): continue req_output = output.get_request_output(request_id) if req_output is None: logger.warning( "No RunnerOutput for request %s, treating as error", request_id, ) terminal_statuses[request_id] = DiffusionRequestStatus.FINISHED_ERROR terminal_errors[request_id] = "No output for request" continue req_result = req_output.result output_error = req_result.error if req_result is not None else None if output_error is not None: terminal_statuses[request_id] = DiffusionRequestStatus.FINISHED_ERROR terminal_errors[request_id] = output_error continue if req_output.step_index is None: logger.warning( "Received RunnerOutput with no step_index for request %s, treating as error", request_id, ) terminal_statuses[request_id] = DiffusionRequestStatus.FINISHED_ERROR terminal_errors[request_id] = "Missing step_index in RunnerOutput" continue # We assume that the decoding stage is executed immediately after the denoising stage completes. progress.current_step = req_output.step_index state.req.sampling_params.step_index = req_output.step_index if req_output.finished: terminal_statuses[request_id] = DiffusionRequestStatus.FINISHED_COMPLETED terminal_errors[request_id] = None else: state.error = None return self._finalize_update_from_output(sched_output, terminal_statuses, terminal_errors) def _pop_extra_request_state(self, request_id: str) -> None: self._request_progress.pop(request_id, None) def _get_total_steps(self, request: OmniDiffusionRequest) -> int: sampling = request.sampling_params if sampling.timesteps is not None: return self._sequence_length(sampling.timesteps) if sampling.sigmas is not None: return len(sampling.sigmas) return int(sampling.num_inference_steps) @staticmethod def _sequence_length(values: Any) -> int: ndim = getattr(values, "ndim", None) if ndim == 0: return 1 shape = getattr(values, "shape", None) if shape is not None: return int(shape[0]) return len(values)