[Model] feat: FastGen DMD2-distilled Wan 2.1 pipelines (T2V, I2V) (#2749)

Signed-off-by: ayushag <ayushag@nvidia.com>
This commit is contained in:
Ayush Agarwal authored and GitHub committed 2026-04-20 21:21:43 +08:00
1 parent 71d81d475a
commit dc8a9e2f17
17 files changed
+501 -38

No files matched your search

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from vllm_omni.diffusion.models.dmd2.mixin import DMD2PipelineMixin
__all__ = [
"DMD2PipelineMixin",
]
+88
View File
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import logging
import os
from vllm_omni.diffusion.data import DiffusionOutput
from vllm_omni.diffusion.models.schedulers import DMD2EulerScheduler
from vllm_omni.diffusion.models.utils import _load_json
from vllm_omni.diffusion.request import OmniDiffusionRequest
logger = logging.getLogger(__name__)
class DMD2PipelineMixin:
"""Mixin for FastGen DMD2-distilled models. Must appear before the base pipeline in MRO."""
def __init_dmd2__(self) -> None:
"""Call after super().__init__() to apply DMD2 scheduler and read model_index."""
local_files_only = os.path.exists(self.od_config.model)
try:
model_index = _load_json(self.od_config.model, "model_index.json", local_files_only)
except Exception:
model_index = {}
dmd2_timesteps = model_index.get("dmd2_denoising_timesteps", [999, 937, 833, 624])
self.num_inference_steps = model_index.get("dmd2_num_inference_steps", 4)
shift = model_index.get("dmd2_scheduler_shift", 1.0)
self.dmd2_guidance_scale = model_index.get("dmd2_guidance_scale", 1.0)
self.scheduler = DMD2EulerScheduler(
num_train_timesteps=1000,
shift=shift,
dmd2_timesteps=dmd2_timesteps,
)
def _sanitize_dmd2_request(self, req: OmniDiffusionRequest) -> None:
"""Sanitize CFG-related fields in-place. Mutates req.sampling_params and req.prompts."""
sp = req.sampling_params
if sp.num_inference_steps and sp.num_inference_steps != self.num_inference_steps:
logger.warning(
"DMD2: ignoring num_inference_steps=%d, forcing %d.",
sp.num_inference_steps,
self.num_inference_steps,
)
sp.num_inference_steps = self.num_inference_steps
if sp.guidance_scale_provided and sp.guidance_scale != self.dmd2_guidance_scale:
logger.warning(
"DMD2: ignoring guidance_scale=%.2f, forcing %.2f.",
sp.guidance_scale,
self.dmd2_guidance_scale,
)
sp.guidance_scale = self.dmd2_guidance_scale
sp.guidance_scale_provided = False
if sp.guidance_scale_2 is not None:
logger.warning("DMD2: ignoring guidance_scale_2.")
sp.guidance_scale_2 = None
if sp.true_cfg_scale is not None:
logger.warning("DMD2: ignoring true_cfg_scale.")
sp.true_cfg_scale = None
sp.do_classifier_free_guidance = False
sp.is_cfg_negative = False
fixed = []
for p in req.prompts:
if isinstance(p, dict) and "negative_prompt" in p:
logger.warning("DMD2: ignoring negative_prompt.")
p = {k: v for k, v in p.items() if k != "negative_prompt"}
fixed.append(p)
req.prompts = fixed
def forward(self, req: OmniDiffusionRequest, **kwargs) -> DiffusionOutput:
self._sanitize_dmd2_request(req)
kwargs.pop("guidance_scale", None)
kwargs.pop("num_inference_steps", None)
return super().forward(
req,
guidance_scale=self.dmd2_guidance_scale,
num_inference_steps=self.num_inference_steps,
**kwargs,
)
@@ -4,12 +4,14 @@
from vllm_omni.diffusion.models.ltx2.ltx2_transformer import LTX2VideoTransformer3DModel
from vllm_omni.diffusion.models.ltx2.pipeline_ltx2 import (
LTX2Pipeline,
LTX2T2VDMD2Pipeline,
LTX2TwoStagesPipeline,
create_transformer_from_config,
get_ltx2_post_process_func,
load_transformer_config,
)
from vllm_omni.diffusion.models.ltx2.pipeline_ltx2_image2video import (
LTX2I2VDMD2Pipeline,
LTX2ImageToVideoPipeline,
LTX2ImageToVideoTwoStagesPipeline,
)
@@ -17,7 +19,9 @@ from vllm_omni.diffusion.models.ltx2.pipeline_ltx2_latent_upsample import LTX2La
__all__ = [
"LTX2Pipeline",
"LTX2T2VDMD2Pipeline",
"LTX2ImageToVideoPipeline",
"LTX2I2VDMD2Pipeline",
"LTX2LatentUpsamplePipeline",
"LTX2TwoStagesPipeline",
"LTX2ImageToVideoTwoStagesPipeline",
@@ -33,6 +33,7 @@ from vllm_omni.diffusion.distributed.parallel_state import (
from vllm_omni.diffusion.distributed.utils import get_local_device
from vllm_omni.diffusion.lora.manager import DiffusionLoRAManager
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
from vllm_omni.diffusion.models.dmd2 import DMD2PipelineMixin
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.lora.request import LoRARequest
@@ -1304,3 +1305,11 @@ class LTX2TwoStagesPipeline(nn.Module):
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
loader = AutoWeightsLoader(self)
return loader.load_weights(weights)
class LTX2T2VDMD2Pipeline(DMD2PipelineMixin, LTX2Pipeline):
"""LTX-2 T2V pipeline for FastGen DMD2-distilled models."""
def __init__(self, *, od_config: OmniDiffusionConfig, prefix: str = ""):
super().__init__(od_config=od_config, prefix=prefix)
self.__init_dmd2__()
@@ -25,6 +25,7 @@ from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
from vllm_omni.diffusion.distributed.utils import get_local_device
from vllm_omni.diffusion.lora.manager import DiffusionLoRAManager
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
from vllm_omni.diffusion.models.dmd2 import DMD2PipelineMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.lora.request import LoRARequest
@@ -889,3 +890,11 @@ class LTX2ImageToVideoTwoStagesPipeline(nn.Module):
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
loader = AutoWeightsLoader(self)
return loader.load_weights(weights)
class LTX2I2VDMD2Pipeline(DMD2PipelineMixin, LTX2ImageToVideoPipeline):
"""LTX-2 I2V pipeline for FastGen DMD2-distilled models."""
def __init__(self, *, od_config: OmniDiffusionConfig, prefix: str = ""):
super().__init__(od_config=od_config, prefix=prefix)
self.__init_dmd2__()
@@ -48,6 +48,7 @@ from vllm_omni.diffusion.model_loader.diffusers_loader import (
)
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin
from vllm_omni.diffusion.models.t5_encoder.t5_gemma_encoder import T5GemmaEncoderModelTP
from vllm_omni.diffusion.models.utils import _load_json
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import (
DiffusionPipelineProfilerMixin,
)
@@ -1640,20 +1641,6 @@ def get_magi_human_post_process_func(*args, **kwargs):
# ===========================================================================
def _load_json(model_path: str, filename: str, local_files_only: bool = True) -> dict:
"""Load a JSON config file from a local path or HuggingFace Hub repo."""
if local_files_only:
path = os.path.join(model_path, *filename.split("/"))
with open(path) as f:
return json.load(f)
else:
from huggingface_hub import hf_hub_download
cached = hf_hub_download(repo_id=model_path, filename=filename)
with open(cached) as f:
return json.load(f)
def _resolve_subdir(
model_path: str,
subfolder: str,
@@ -1,10 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from vllm_omni.diffusion.models.schedulers.scheduling_dmd2_euler import DMD2EulerScheduler
from vllm_omni.diffusion.models.schedulers.scheduling_flow_unipc_multistep import (
FlowUniPCMultistepScheduler,
)
__all__ = [
"DMD2EulerScheduler",
"FlowUniPCMultistepScheduler",
]
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import torch
from diffusers import FlowMatchEulerDiscreteScheduler
class DMD2EulerScheduler(FlowMatchEulerDiscreteScheduler):
"""Euler scheduler that always uses the fixed DMD2 training timestep schedule."""
def __init__(self, *args, dmd2_timesteps: list[int], **kwargs):
super().__init__(*args, **kwargs)
self._dmd2_timesteps = dmd2_timesteps
def set_timesteps(
self,
num_inference_steps: int | None = None,
device: str | torch.device | None = None,
**kwargs,
) -> None:
super().set_timesteps(timesteps=self._dmd2_timesteps, device=device)
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import json
import os
def _load_json(model_path: str, filename: str, local_files_only: bool = True) -> dict:
"""Load a JSON config file from a local path or HuggingFace Hub repo."""
if local_files_only:
path = os.path.join(model_path, *filename.split("/"))
with open(path) as f:
return json.load(f)
else:
from huggingface_hub import hf_hub_download
cached = hf_hub_download(repo_id=model_path, filename=filename)
with open(cached) as f:
return json.load(f)
@@ -3,6 +3,7 @@
from .pipeline_wan2_2 import (
Wan22Pipeline,
WanT2VDMD2Pipeline,
create_transformer_from_config,
get_wan22_post_process_func,
get_wan22_pre_process_func,
@@ -11,6 +12,7 @@ from .pipeline_wan2_2 import (
)
from .pipeline_wan2_2_i2v import (
Wan22I2VPipeline,
WanI2VDMD2Pipeline,
get_wan22_i2v_post_process_func,
get_wan22_i2v_pre_process_func,
)
@@ -28,6 +30,7 @@ from .wan2_2_transformer import WanTransformer3DModel
from .wan2_2_vace_transformer import VaceWanTransformerBlock, WanVACETransformer3DModel
__all__ = [
"WanT2VDMD2Pipeline",
"Wan22Pipeline",
"get_wan22_post_process_func",
"get_wan22_pre_process_func",
@@ -35,6 +38,7 @@ __all__ = [
"load_transformer_config",
"create_transformer_from_config",
"Wan22I2VPipeline",
"WanI2VDMD2Pipeline",
"get_wan22_i2v_post_process_func",
"get_wan22_i2v_pre_process_func",
"Wan22TI2VPipeline",
@@ -22,6 +22,7 @@ from vllm_omni.diffusion.distributed.autoencoders.autoencoder_kl_wan import Dist
from vllm_omni.diffusion.distributed.cfg_parallel import CFGParallelMixin
from vllm_omni.diffusion.distributed.utils import get_local_device
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
from vllm_omni.diffusion.models.dmd2 import DMD2PipelineMixin
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin, _is_rank_zero
from vllm_omni.diffusion.models.schedulers import FlowUniPCMultistepScheduler
from vllm_omni.diffusion.models.wan2_2.scheduling_wan_euler import WanEulerScheduler
@@ -955,3 +956,16 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
if boundary_ratio is None and guidance_scale_2 is not None:
raise ValueError("`guidance_scale_2` is only supported when `boundary_ratio` is set.")
# ---------------------------------------------------------------------------
# DMD2-distilled variant
# ---------------------------------------------------------------------------
class WanT2VDMD2Pipeline(DMD2PipelineMixin, Wan22Pipeline):
"""Wan 2.x T2V pipeline for FastGen DMD2-distilled models."""
def __init__(self, *, od_config: OmniDiffusionConfig, prefix: str = ""):
super().__init__(od_config=od_config, prefix=prefix)
self.__init_dmd2__()
@@ -23,8 +23,10 @@ from vllm_omni.diffusion.distributed.autoencoders.autoencoder_kl_wan import Dist
from vllm_omni.diffusion.distributed.cfg_parallel import CFGParallelMixin
from vllm_omni.diffusion.distributed.utils import get_local_device
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
from vllm_omni.diffusion.models.dmd2 import DMD2PipelineMixin
from vllm_omni.diffusion.models.interface import SupportImageInput
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin, _is_rank_zero
from vllm_omni.diffusion.models.utils import _load_json
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
build_wan_scheduler,
create_transformer_from_config,
@@ -43,29 +45,6 @@ logger = logging.getLogger(__name__)
DEBUG_PERF = False
def _load_model_index(model: str, local_files_only: bool) -> dict:
"""Load model_index.json from local path or HF Hub."""
if local_files_only:
model_index_path = os.path.join(model, "model_index.json")
if os.path.exists(model_index_path):
import json
with open(model_index_path) as f:
return json.load(f)
else:
try:
import json
from huggingface_hub import hf_hub_download
model_index_path = hf_hub_download(model, "model_index.json")
with open(model_index_path) as f:
return json.load(f)
except Exception:
pass
return {}
def get_wan22_i2v_post_process_func(
od_config: OmniDiffusionConfig,
):
@@ -196,7 +175,10 @@ class Wan22I2VPipeline(
]
# Load model_index.json to detect available components
model_index = _load_model_index(model, local_files_only)
try:
model_index = _load_json(model, "model_index.json", local_files_only)
except Exception:
model_index = {}
# Check if this is a two-stage model (MoE with transformer_2)
self.has_transformer_2 = "transformer_2" in model_index
@@ -903,3 +885,16 @@ class Wan22I2VPipeline(
"""Load weights using AutoWeightsLoader for vLLM integration."""
loader = AutoWeightsLoader(self)
return loader.load_weights(weights)
# ---------------------------------------------------------------------------
# DMD2-distilled variant
# ---------------------------------------------------------------------------
class WanI2VDMD2Pipeline(DMD2PipelineMixin, Wan22I2VPipeline):
"""Wan 2.x I2V pipeline for FastGen DMD2-distilled models."""
def __init__(self, *, od_config: OmniDiffusionConfig, prefix: str = ""):
super().__init__(od_config=od_config, prefix=prefix)
self.__init_dmd2__()