Files
vllm-omni/vllm_omni/diffusion/sched/sigma_schedule.py
T
2026-08-31 12:04:58 +08:00

87 lines
3.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from __future__ import annotations
import math
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Any
BASE_SCHEDULE_KEY = "base_schedule"
@dataclass(frozen=True)
class DMD2SigmaSchedule:
"""Continuous rectified-flow positions pinned by a distilled checkpoint.
A DMD2 student only ever sees the few noise levels it was trained on, so a
distilled release ships the exact positions instead of letting the server
derive a uniform schedule from ``num_inference_steps``.
This is deliberately distinct from
``vllm_omni.diffusion.models.dmd2.DMD2Config.denoising_timesteps``, which
carries integer scheduler timesteps for scheduler-backed pipelines. Here the
entries are continuous positions in ``[0, 1]`` that still need a per-modality
time shift applied, which is what lets one schedule drive several coupled
modalities at different shift scales.
"""
base_schedule: tuple[float, ...]
def __post_init__(self) -> None:
values = self.base_schedule
if len(values) < 2:
raise ValueError("DMD2 base_schedule needs at least 2 entries")
# Ordering comparisons are all false against NaN, so non-finite entries
# have to be rejected before the monotonicity check rather than after it.
if any(not math.isfinite(value) for value in values):
raise ValueError("DMD2 base_schedule entries must be finite")
# A student whose training capped the noise level (``max_timestep_ratio``)
# starts just below 1.0 rather than at it, so the opening position is
# bounded rather than pinned; the terminal one is always clean latents.
if not 0.0 < values[0] <= 1.0 or values[-1] != 0.0:
raise ValueError("DMD2 base_schedule must start in (0.0, 1.0] and end at 0.0")
if any(curr <= nxt for curr, nxt in zip(values, values[1:], strict=False)):
raise ValueError("DMD2 base_schedule must be strictly decreasing")
@classmethod
def from_positions(cls, base_schedule: Sequence[float]) -> DMD2SigmaSchedule:
return cls(base_schedule=tuple(float(value) for value in base_schedule))
@classmethod
def from_metadata(
cls,
metadata: Mapping[str, Any],
*,
key: str = BASE_SCHEDULE_KEY,
) -> DMD2SigmaSchedule | None:
"""Read a schedule from checkpoint metadata.
An absent key means the release is not distilled and keeps the legacy
uniform schedule. An explicitly empty value is a malformed contract and
is rejected rather than silently falling back.
"""
raw = metadata.get(key)
if raw is None:
return None
return cls.from_positions(raw)
@property
def num_inference_steps(self) -> int:
"""Denoising steps, i.e. one per interval between sigma boundaries."""
return len(self.base_schedule) - 1
def shifted_sigmas(self, shift_scale: float) -> list[float]:
"""Apply the rectified-flow time shift for one modality."""
if shift_scale <= 0:
raise ValueError("DMD2 shift_scale must be > 0")
shift = float(shift_scale)
return [shift * u / (1 + (shift - 1) * u) for u in self.base_schedule]
__all__ = [
"BASE_SCHEDULE_KEY",
"DMD2SigmaSchedule",
]