Files
vllm-omni/vllm_omni/diffusion/models/wan2_2/chunked_mp4.py
T
2026-09-17 23:33:56 +08:00

88 lines
3.9 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Worker-side MP4 encoding for Wan VAE temporal chunks."""
from __future__ import annotations
from typing import Any
from vllm_omni.diffusion.utils.media_utils import normalize_preencode_batch_frames, normalize_video_codec_options
# Matches the fallback the video serving layer applies when a request omits fps,
# so the bytes the worker encodes carry the rate the response advertises.
WAN_DEFAULT_OUTPUT_FPS = 24
# Keep worker-side encoding consistent with the one-shot serving encoder when
# a request does not choose codec options explicitly.
WAN_DEFAULT_VIDEO_CODEC_OPTIONS = {"preset": "ultrafast", "threads": "0"}
# Wan publishes one latent frame group per callback, which is far finer than one
# host transfer is worth. 17 frames matches MiniMax-H3's native clip size.
WAN_DEFAULT_BATCH_FRAMES = 17
def wan_preencoded_mp4_payload(video: Any) -> dict[str, Any] | None:
"""Return a post-process payload when the worker already encoded the MP4.
Returns ``None`` for the ordinary frame-tensor output so callers fall
through to their existing post-processing.
No fps is reported here: :func:`resolve_wan_output_fps` mirrors the fallback
the serving layer applies, so the rate the response advertises already
matches the rate these bytes were encoded at.
"""
if isinstance(video, (bytes, bytearray, memoryview)):
video = [video]
if not isinstance(video, list) or not all(isinstance(item, (bytes, bytearray, memoryview)) for item in video):
return None
return {"payload": {"video": [bytes(item) for item in video]}, "metadata": {}}
def resolve_wan_preencode_mp4(sampling_params: Any, *, output_type: str) -> bool:
"""Return whether this request wants the worker to pre-encode the MP4.
Reject rather than silently downgrade the combinations the pre-encoded path
cannot serve: it emits MP4 bytes, so nothing downstream can interpolate
frames or hand back an array.
"""
extra_args = getattr(sampling_params, "extra_args", None) or {}
if not extra_args.get("preencode_mp4", False):
return False
if getattr(sampling_params, "enable_frame_interpolation", False):
raise ValueError(
"preencode_mp4 cannot be combined with enable_frame_interpolation: interpolation "
"needs the decoded frames that the pre-encoded path no longer materializes"
)
if output_type != "np":
raise ValueError(f"preencode_mp4 returns MP4 bytes and cannot serve output_type={output_type!r}")
return True
def resolve_wan_preencode_batch_frames(sampling_params: Any, *, default: int = WAN_DEFAULT_BATCH_FRAMES) -> int:
"""Read the MP4 batching threshold before starting expensive generation."""
extra_args = getattr(sampling_params, "extra_args", None) or {}
return normalize_preencode_batch_frames(extra_args.get("preencode_batch_frames", default))
def resolve_wan_video_codec_options(sampling_params: Any) -> dict[str, str] | None:
"""Read the request's encoder options, preserving serving defaults.
An absent key inherits the one-shot serving encoder's defaults. Explicit
``None`` and an empty mapping remain distinct caller choices.
"""
extra_args = getattr(sampling_params, "extra_args", None) or {}
return normalize_video_codec_options(extra_args.get("video_codec_options", WAN_DEFAULT_VIDEO_CODEC_OPTIONS))
def resolve_wan_output_fps(sampling_params: Any) -> int:
"""Resolve the fps the worker encodes with.
The serving layer resolves output fps from the request's ``fps`` field, not
from ``frame_rate``, so pre-encoding has to read the same field or the bytes
would carry a rate the response does not advertise.
"""
fps = getattr(sampling_params, "fps", None)
if isinstance(fps, list):
fps = fps[0] if fps else None
return int(fps) if fps else WAN_DEFAULT_OUTPUT_FPS