[diffusion] model: add cosmos3 transfer capability (#34747)

Co-authored-by: Kedi Wu <kediw@nvidia.com>
Co-authored-by: Kedi Wu <31940276+kediwu0331@users.noreply.github.com>
This commit is contained in:
Zhylko Dima
2026-08-28 09:37:22 +08:00
committed by GitHub
co-authored by Kedi Wu Kedi Wu
parent de2fb50120
commit cbd0271574
13 changed files with 2111 additions and 227 deletions
@@ -113,7 +113,7 @@ class Cosmos3Config(PipelineConfig):
vae_precision: str = "bf16"
# Pipeline-level (not sampling) knobs.
max_sequence_length: int = 512
max_sequence_length: int = 4096
use_duration_template: bool = True
use_system_prompt: bool = False
@@ -10,7 +10,7 @@ so the file extension and decode path agree.
"""
from dataclasses import dataclass, field
from typing import Any
from typing import Any, ClassVar
from sglang.multimodal_gen.configs.sample.sampling_params import (
DataType,
@@ -74,6 +74,61 @@ class Cosmos3SamplingParams(SamplingParams):
condition_frame_indexes: list[int] | None = None
condition_video_keep: str = "first"
# Transfer (control-video) conditioning. ``control_path`` points to one or
# more pre-computed control videos (e.g. edge / blur / depth / seg / wsm
# maps). When set, each control clip is VAE-encoded and packed as clean
# vision tokens that prefix the target clip in the GEN sequence; multiple
# paths drive multi-hint transfer (e.g. edge + depth). Control clips reuse
# ``proj_in``, so every Cosmos3 checkpoint supports transfer.
control_path: str | list[str] | None = None
# Optional hint type(s) parallel to ``control_path`` (one of
# ``edge`` / ``blur`` / ``depth`` / ``seg`` / ``wsm``). Used only to apply
# tuned per-hint defaults (``guidance`` / ``control_guidance`` / ``shift``)
# when exactly one control input is given and the user left those unset.
control_hint: str | list[str] | None = None
# Control-CFG scale for transfer. ``1.0`` (default) disables the extra
# control-dropped forward; values > 1.0 amplify the control map's influence
# by blending the with-control and without-control predictions on the
# generated span: ``cond_nc + control_guidance * (cond_full - cond_nc)``.
control_guidance: float = 1.0
# Optional timestep window ``(lo, hi)`` restricting where control-CFG is
# applied (analogous to ``guidance_interval`` for text CFG). ``None`` applies
# it at every step.
control_guidance_interval: tuple[float, float] | None = None
# Long-video transfer controls. Chunks overlap by
# ``num_conditional_frames`` pixel frames; overlap frames from the previous
# decoded chunk are kept clean in the next chunk.
num_video_frames_per_chunk: int = 93
num_conditional_frames: int = 1
num_first_chunk_conditional_frames: int = 0
max_frames: int = 5000
show_control_condition: bool = False
show_input: bool = False
share_vision_temporal_positions: bool = True
# Tuned per-hint defaults applied when exactly one control input is given
# and the corresponding field was not set explicitly (mirrors the
# cosmos-framework ``_TRANSFER_DEFAULTS`` table). ``shift`` maps to
# ``flow_shift``. Multi-hint transfer keeps the request's own values.
_TRANSFER_DEFAULTS: ClassVar[dict[str, dict[str, float | int]]] = {
"edge": {"guidance": 3.0, "control_guidance": 1.5, "shift": 10.0},
"blur": {"guidance": 3.0, "control_guidance": 1.5, "shift": 10.0},
"depth": {"guidance": 3.0, "control_guidance": 1.5, "shift": 10.0},
"seg": {"guidance": 3.0, "control_guidance": 2.0, "shift": 10.0},
"wsm": {
"guidance": 1.0,
"control_guidance": 3.0,
"shift": 10.0,
"num_frames": 101,
"fps": 10,
"num_video_frames_per_chunk": 101,
},
}
supported_resolutions: list[tuple[int, int]] | None = field(
default_factory=lambda: [
(1280, 720),
@@ -103,6 +158,108 @@ class Cosmos3SamplingParams(SamplingParams):
action_stats_path: str | None = None
action_normalization: str = "quantile"
@classmethod
def video_request_extra_fields(cls) -> frozenset[str]:
return frozenset(
{
"generate_sound",
"sound_duration",
"condition_frame_indexes",
"condition_frame_indexes_vision",
"condition_video_keep",
"control_path",
"control_hint",
"control_guidance",
"control_guidance_interval",
"num_video_frames_per_chunk",
"num_conditional_frames",
"num_first_chunk_conditional_frames",
"max_frames",
"show_control_condition",
"show_input",
"share_vision_temporal_positions",
"action_mode",
"domain_id",
"domain_name",
"raw_action_dim",
"action_fps",
"action",
"action_view_point",
"action_normalization",
}
)
def _resolve_control_paths(self) -> list[str]:
cp = self.control_path
if cp is None:
return []
if isinstance(cp, str):
return [cp] if cp else []
return [p for p in cp if isinstance(p, str) and p]
def _resolve_control_hints(self) -> list[str]:
hint = self.control_hint
if hint is None:
return []
hints = [hint] if isinstance(hint, str) else list(hint)
hints = [h for h in hints if h]
for h in hints:
if h not in self._TRANSFER_DEFAULTS:
raise ValueError(
f"Unknown control_hint {h!r}; expected one of "
f"{sorted(self._TRANSFER_DEFAULTS)}"
)
return hints
def _apply_transfer_hint_defaults(self) -> None:
"""Fill tuned per-hint defaults for a single, typed control input.
Mirrors cosmos-framework: defaults apply only when there is exactly one
control input with a known hint type, and only to fields the user did
not pass explicitly (tracked via ``_explicit_fields``). Multi-hint
transfer keeps the request's own ``guidance`` / ``control_guidance`` /
``flow_shift``.
"""
if len(self._resolve_control_paths()) != 1:
return
hints = self._resolve_control_hints()
if len(hints) != 1:
return
defaults = self._TRANSFER_DEFAULTS.get(hints[0])
if defaults is None:
return
explicit = getattr(self, "_explicit_fields", None) or set()
if "control_guidance" not in explicit:
self.control_guidance = defaults["control_guidance"]
if "guidance_scale" not in explicit:
self.guidance_scale = defaults["guidance"]
if "flow_shift" not in explicit and self.flow_shift is None:
self.flow_shift = defaults["shift"]
for field_name in (
"num_frames",
"fps",
"num_video_frames_per_chunk",
):
if field_name in defaults and field_name not in explicit:
setattr(self, field_name, defaults[field_name])
@classmethod
def lower_video_request_kwargs(
cls, request: Any, kwargs: dict[str, Any]
) -> dict[str, Any]:
"""Apply defaults that the generic video endpoint pre-resolves."""
hint = kwargs.get("control_hint")
paths = kwargs.get("control_path")
hints = [hint] if isinstance(hint, str) else list(hint or [])
control_paths = [paths] if isinstance(paths, str) else list(paths or [])
if len(control_paths) == 1 and hints == ["wsm"]:
defaults = cls._TRANSFER_DEFAULTS["wsm"]
if getattr(request, "num_frames", None) is None:
kwargs["num_frames"] = defaults["num_frames"]
if getattr(request, "fps", None) is None:
kwargs["fps"] = defaults["fps"]
return kwargs
def _adjust(self, server_args) -> None:
# adjust distil and edge args — read from the pre-computed config fields
# so no checkpoint download happens at request time.
@@ -130,6 +287,37 @@ class Cosmos3SamplingParams(SamplingParams):
)
action_output = self.action_mode != "forward_dynamics"
# Apply transfer per-hint defaults before the base resolves remaining
# fields (e.g. flow_shift per mode), so an unset flow_shift can pick up
# the hint's tuned shift.
self._apply_transfer_hint_defaults()
control_paths = self._resolve_control_paths()
if control_paths:
if pipeline_config.distilled_sigmas is not None:
raise ValueError(
"Cosmos3 distilled checkpoints do not support transfer inference"
)
if pipeline_config.is_edge:
raise ValueError(
"Cosmos3 Edge checkpoints do not support transfer inference"
)
if self.num_frames == 1:
raise ValueError(
"Cosmos3 transfer inference is supported only for video outputs"
)
if self.image_path is not None:
raise ValueError(
"Cosmos3 transfer accepts control videos and an optional source "
"video, not an image input"
)
if self.action_mode is not None:
raise ValueError(
"Cosmos3 transfer cannot be combined with action generation"
)
if float(self.sound_duration or 0.0) > 0.0:
raise ValueError(
"Cosmos3 transfer cannot be combined with sound generation"
)
super()._adjust(server_args)
# Policy and inverse dynamics produce actions. Forward dynamics consumes
@@ -142,6 +330,39 @@ class Cosmos3SamplingParams(SamplingParams):
self.output_file_name = None
self.output_compression = 0
def _validate(self) -> None:
super()._validate()
paths = self._resolve_control_paths()
hints = self._resolve_control_hints()
if hints and len(hints) != len(paths):
raise ValueError(
"control_hint must contain exactly one entry per control_path "
f"(got {len(hints)} hint(s) for {len(paths)} path(s))"
)
if self.control_guidance_interval is not None:
if len(self.control_guidance_interval) != 2:
raise ValueError(
"control_guidance_interval must contain exactly two values"
)
lo, hi = self.control_guidance_interval
if float(lo) > float(hi):
raise ValueError(
"control_guidance_interval must be ordered as (low, high)"
)
if self.num_video_frames_per_chunk <= 0:
raise ValueError("num_video_frames_per_chunk must be positive")
if self.num_conditional_frames < 0:
raise ValueError("num_conditional_frames must be non-negative")
if self.num_conditional_frames >= self.num_video_frames_per_chunk:
raise ValueError(
"num_conditional_frames must be smaller than "
"num_video_frames_per_chunk"
)
if self.num_first_chunk_conditional_frames < 0:
raise ValueError("num_first_chunk_conditional_frames must be non-negative")
if self.max_frames <= 0:
raise ValueError("max_frames must be positive")
def _guidance_is_explicit(self) -> bool:
explicit = getattr(self, "_explicit_fields", None)
return explicit is not None and "guidance_scale" in explicit
@@ -117,19 +117,6 @@ _MULTIPART_EXTRA_FORM_FIELDS = (
"guardrails",
"video_path",
"video_url",
"generate_sound",
"sound_duration",
"condition_frame_indexes",
"action_mode",
"domain_id",
"domain_name",
"raw_action_dim",
"action_fps",
"action",
"action_view_point",
"action_normalization",
"condition_frame_indexes_vision",
"condition_video_keep",
"quality",
)
@@ -265,6 +252,49 @@ def _coerce_optional_int_list(value: Any) -> list[int] | None:
return [int(value)]
def _coerce_optional_float_list(value: Any) -> list[float] | None:
value = _parse_form_extra_value(value)
if value is None:
return None
if isinstance(value, str) and not value.strip():
return None
if isinstance(value, (list, tuple)):
return [float(item) for item in value]
return [float(value)]
def _coerce_optional_bool(value: Any) -> bool | None:
value = _parse_form_extra_value(value)
if value is None or (isinstance(value, str) and not value.strip()):
return None
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
raise ValueError(f"Invalid boolean value: {value!r}")
return bool(value)
def _coerce_optional_str_list(value: Any) -> str | list[str] | None:
"""Coerce a control_path/control_hint value to str or list[str].
Accepts a JSON list (``["edge.mp4", "depth.mp4"]``), a plain string, or a
native list. Empty values resolve to ``None`` so unset fields don't override
sampling-param defaults.
"""
value = _parse_form_extra_value(value)
if value is None:
return None
if isinstance(value, (list, tuple)):
items = [str(item) for item in value if str(item).strip()]
return items or None
if isinstance(value, str):
return value if value.strip() else None
return str(value)
def _resolve_video_path(req: VideoGenerationsRequest) -> str | None:
video_path = _request_value(req, "video_path") or _request_value(req, "video_url")
if video_path:
@@ -324,6 +354,41 @@ def _cosmos3_sampling_param_kwargs(
if condition_frame_indexes is not None:
kwargs["condition_frame_indexes"] = condition_frame_indexes
# Transfer (control-video) conditioning.
control_path = _coerce_optional_str_list(_request_value(req, "control_path"))
if control_path is not None:
kwargs["control_path"] = control_path
control_hint = _coerce_optional_str_list(_request_value(req, "control_hint"))
if control_hint is not None:
kwargs["control_hint"] = control_hint
control_guidance = _request_value(req, "control_guidance")
if control_guidance is not None:
kwargs["control_guidance"] = float(control_guidance)
control_guidance_interval = _coerce_optional_float_list(
_request_value(req, "control_guidance_interval")
)
if control_guidance_interval is not None:
kwargs["control_guidance_interval"] = tuple(control_guidance_interval)
for name in (
"num_video_frames_per_chunk",
"num_conditional_frames",
"num_first_chunk_conditional_frames",
"max_frames",
):
value = _parse_form_extra_value(_request_value(req, name))
if value is not None and value != "":
kwargs[name] = int(value)
for name in (
"show_control_condition",
"show_input",
"share_vision_temporal_positions",
):
value = _coerce_optional_bool(_request_value(req, name))
if value is not None:
kwargs[name] = value
for name in (
"condition_video_keep",
"action_mode",
@@ -206,6 +206,13 @@ def _maybe_dequantize_fp8(
return full_tensor
def _move_to_device_preserving_meta(model: nn.Module, device: torch.device) -> None:
# Buffers absent from the checkpoint (e.g. cosmos3's RoPE inv_freq) are
# still on the meta device here and .to() cannot copy out of meta; leave
# them for the model's post_load_weights() to rebuild on the real device.
model._apply(lambda t: t if t.is_meta else t.to(device))
def register_fsdp_entrypoints(model: torch.nn.Module) -> None:
"""Let FSDP2 unshard around forward passes that bypass ``__call__``.
@@ -435,7 +442,7 @@ def maybe_load_fsdp_model(
# 3. postprocessing
if weight_postprocess_device is not None:
# move to device to perform postprocessing
model.to(weight_postprocess_device)
_move_to_device_preserving_meta(model, weight_postprocess_device)
for _, module in model.named_modules():
quant_method = getattr(module, "quant_method", None)
@@ -7,7 +7,7 @@ cross-attends from noisy visual tokens to that cache at every denoising step.
"""
import math
from collections.abc import Iterable, Iterator
from collections.abc import Iterable, Iterator, Sequence
from typing import Any
import torch
@@ -1333,12 +1333,30 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
action_frames: int = 0,
action_fps: float | None = None,
action_start_frame_offset: int = 1,
control_frames: int | Sequence[int] = 0,
share_vision_temporal_positions: bool = True,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Compute mRoPE position IDs for UND text and GEN visual + action + sound tokens."""
"""Compute mRoPE position IDs for UND text and GEN tokens.
The GEN sequence is ordered ``[control, video, action, sound]``.
Control and target videos either share matching temporal coordinates or
occupy consecutive temporal ranges, according to
``share_vision_temporal_positions``.
"""
B = text_mask.shape[0]
S_text = text_mask.shape[1]
text_lengths = text_mask.sum(dim=1).long()
effective_fps = fps if fps is not None and T > 1 else None
control_frame_counts = (
[control_frames]
if isinstance(control_frames, int) and control_frames > 0
else (
[int(count) for count in control_frames if int(count) > 0]
if not isinstance(control_frames, int)
else []
)
)
vision_frame_counts = [*control_frame_counts, T]
text_pos_list = []
vis_pos_list = []
@@ -1348,16 +1366,28 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
real_len, temporal_offset=0, device=device
)
media_offset = t_offset + self.temporal_margin
v_pos, _ = compute_mrope_position_ids_vision(
T,
Hp,
Wp,
temporal_offset=media_offset,
device=device,
fps=effective_fps,
base_fps=self.base_fps,
temporal_compression_factor=self.temporal_compression_factor,
)
vision_offset = media_offset
vision_pos_blocks = []
for frame_count in vision_frame_counts:
temporal_offset = (
media_offset if share_vision_temporal_positions else vision_offset
)
vision_pos, vision_offset = compute_mrope_position_ids_vision(
frame_count,
Hp,
Wp,
temporal_offset=temporal_offset,
device=device,
fps=effective_fps,
base_fps=self.base_fps,
temporal_compression_factor=self.temporal_compression_factor,
)
vision_pos_blocks.append(vision_pos)
pos_dtype = vision_pos_blocks[0].dtype
for pos in vision_pos_blocks[1:]:
pos_dtype = torch.promote_types(pos_dtype, pos.dtype)
v_pos = torch.cat([pos.to(pos_dtype) for pos in vision_pos_blocks], dim=1)
if action_frames > 0:
a_pos, _ = compute_mrope_position_ids_action(
action_frames,
@@ -1394,9 +1424,8 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
text_pos_list.append(t_pos)
vis_pos_list.append(v_pos)
text_pos_ids = torch.stack(text_pos_list, dim=1).to(device) # [3, B, S_text]
vis_pos_ids = torch.stack(vis_pos_list, dim=1).to(device) # [3, B, S_gen]
text_pos_ids = torch.stack(text_pos_list, dim=1).to(device)
vis_pos_ids = torch.stack(vis_pos_list, dim=1).to(device)
return text_pos_ids, vis_pos_ids
def reset_cache(self, cache_key: str | None = None):
@@ -1443,6 +1472,8 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
action_noisy_mask: torch.Tensor | None = None,
action_fps: float | None = None,
action_start_frame_offset: int = 1,
control_latents: torch.Tensor | list[torch.Tensor] | None = None,
transfer_share_vision_temporal_positions: bool = True,
**kwargs,
) -> torch.Tensor | tuple[torch.Tensor, ...]:
"""Forward pass for denoising.
@@ -1473,6 +1504,11 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
Defaults to the video fps when None.
action_start_frame_offset: Temporal offset applied to action
position IDs relative to the video's media_offset (default 1).
control_latents: Optional [B, C, T_ctrl, H, W] control-video latents
(transfer / control-net conditioning). They are patchified and
projected with the shared ``proj_in``, prepended to the GEN
sequence as clean (noise-free) tokens that share the video's
temporal positions, and excluded from the output projection.
Returns:
[B, C, T, H, W] velocity prediction, or a tuple
@@ -1505,8 +1541,44 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
device=action_latents.device,
)
# Transfer / control-video conditioning: one or more control clips
# (e.g. edge + depth) are packed as clean vision tokens that prefix the
# target clip in the GEN sequence. Each clip reuses ``proj_in`` and the
# shared transformer; blocks are concatenated in input order.
control_clips: list[torch.Tensor] = []
if control_latents is not None:
control_clips = (
list(control_latents)
if isinstance(control_latents, (list, tuple))
else [control_latents]
)
control_frame_counts: list[int] = []
hidden_control_blocks: list[torch.Tensor] = []
control_token_len = 0
for clip in control_clips:
_, _, c_frames, Hc, Wc = clip.shape
if (Hc, Wc) != (H, W):
raise ValueError(
"control_latents spatial dims "
f"{(Hc, Wc)} must match hidden_states {(H, W)}"
)
block, _ = self.proj_in(
self.patchify(clip.to(hidden_states.dtype), c_frames, Hc, Wc)
)
hidden_control_blocks.append(block)
control_frame_counts.append(c_frames)
control_token_len += block.shape[1]
has_control = len(hidden_control_blocks) > 0
hidden_control = (
torch.cat(hidden_control_blocks, dim=1) if has_control else None
)
extra_frames = action_frames + sound_frames
sequence_shard_enabled = self.sp_size > 1
# When a control clip is present we always assemble the combined GEN
# stream (control prefix + video [+ action] [+ sound]) instead of the
# video-only fast path.
use_assembly_path = extra_frames > 0 or has_control
# Add timestep embedding (computed in float32 for numerical stability, then cast back)
time_embed = self.time_embedder(timestep.float())
@@ -1531,7 +1603,7 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
.to(hidden_gen.dtype)
)
if extra_frames == 0:
if not use_assembly_path:
# Video-only: shard the visual tokens, then add the timestep
# embedding on the local shard.
if sequence_shard_enabled:
@@ -1567,15 +1639,21 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
hidden_gen = hidden_gen + time_embed.unsqueeze(1)
else:
# Multi-modal: assemble the full GEN sequence
# (video[, action][, sound]) with timestep embeddings, then shard
# the combined stream so sequence parallelism splits every modality
# evenly. The per-modality output heads run after the post-loop
# all-gather reassembles the sequence.
# ([control,] video[, action][, sound]) with timestep embeddings,
# then shard the combined stream so sequence parallelism splits
# every modality evenly. The per-modality output heads run after the
# post-loop all-gather reassembles the sequence.
if token_noisy_mask is not None:
hidden_gen = hidden_gen + time_embed.unsqueeze(1) * token_noisy_mask
else:
hidden_gen = hidden_gen + time_embed.unsqueeze(1)
# Control tokens are clean conditioning: prepend them WITHOUT a
# timestep embedding so the GEN tokens can attend to the raw control
# map. They are stripped before the output projection.
if has_control:
hidden_gen = torch.cat([hidden_control, hidden_gen], dim=1)
if action_latents is not None:
hidden_action = self.action_proj_in(
action_latents.to(hidden_gen.dtype), action_domain_ids
@@ -1648,6 +1726,10 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
action_frames=action_frames,
action_fps=action_fps if action_fps is not None else fps,
action_start_frame_offset=action_start_frame_offset,
control_frames=control_frame_counts,
share_vision_temporal_positions=(
transfer_share_vision_temporal_positions
),
)
# UND K/V cache is kept FULL on all ranks (not sharded). Text
# sequence is short, so memory impact is minimal, and the GEN
@@ -1722,7 +1804,7 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
hidden_gen = hidden_gen + residual
hidden_gen = self.norm_moe_gen(hidden_gen)
if extra_frames == 0:
if not use_assembly_path:
# Video-only: project on the local shard and gather the (much
# smaller) patch-space output. With patch_latent_dim ~=
# hidden_size / 21 for cosmos3, this cuts the post-loop SP
@@ -1741,12 +1823,15 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
if seq_shard_pad > 0:
hidden_gen = hidden_gen[:, :seq_len_orig, :]
s_video = seq_len_orig - extra_frames
output, _ = self.proj_out(hidden_gen[:, :s_video, :])
# Sequence layout: [control prefix | video | action | sound]. Control
# tokens are conditioning only and produce no output.
s_video = seq_len_orig - extra_frames - control_token_len
video_start = control_token_len
output, _ = self.proj_out(hidden_gen[:, video_start : video_start + s_video, :])
video_pred = self.unpatchify(output, T, H, W)
extra_outputs: list[torch.Tensor] = []
idx = s_video
idx = video_start + s_video
if action_frames > 0:
action_hidden = hidden_gen[:, idx : idx + action_frames, :]
extra_outputs.append(self.action_proj_out(action_hidden, action_domain_ids))
@@ -1756,6 +1841,10 @@ class Cosmos3OmniTransformer(CachableDiT, LayerwiseOffloadableModuleMixin):
sound_output, _ = self.audio_proj_out(sound_hidden)
extra_outputs.append(sound_output.permute(0, 2, 1).contiguous())
# Control-only conditioning (no action/sound): keep the bare-tensor
# return type identical to the video-only path.
if not extra_outputs:
return video_pred
return (video_pred, *extra_outputs)
def preprocess_loaded_state_dict(
@@ -100,7 +100,11 @@ class Cosmos3Pipeline(LoRAPipeline, ComposedPipelineBase):
self.add_stage(Cosmos3TextGuardrailStage())
self.add_stage(Cosmos3LatentPreparationStage(vae, transformer))
self.add_stage(Cosmos3TimestepPreparationStage(scheduler))
self.add_stage(Cosmos3DenoisingStage(transformer, scheduler, server_args))
self.add_stage(
Cosmos3DenoisingStage(
transformer, scheduler, server_args=server_args, vae=vae
)
)
self.add_stage(
Cosmos3DecodingStage(
vae, guardrails=guardrails_on, sound_tokenizer=sound_tokenizer
@@ -10,12 +10,17 @@ Enabled by default when available; opt out with
from __future__ import annotations
import hashlib
import importlib.util
import os
import shutil
from functools import lru_cache
from pathlib import Path
import numpy as np
import torch
from sglang.multimodal_gen import envs
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
PipelineStage,
@@ -35,6 +40,68 @@ def is_cosmos_guardrail_available() -> bool:
return importlib.util.find_spec("cosmos_guardrail") is not None
def _mirror_symlinked_nltk_data() -> None:
"""Make the guardrail's nltk_data readable under NLTK's hardened opener.
``CosmosSafetyChecker`` registers its HF-hub snapshot's
``blocklist/nltk_data`` directory on ``nltk.data.path``. Hub snapshot files
are symlinks into the blob store, which NLTK builds that ship the
``pathsec`` hardened opener refuse to follow (O_NOFOLLOW, CWE-59 TOCTOU
guard) — every text-safety check then fails with "refusing to follow a
symlink at open time". Mirror each symlink-containing search entry to a
plain-file copy and register the mirror ahead of the original, so NLTK's
data lookup resolves to real files first. No-op for NLTK builds without
the hardened opener and for search entries that are already plain files.
"""
try:
import nltk.data
except ImportError:
return
mirror_root = Path(envs.SGLANG_DIFFUSION_CACHE_ROOT) / "nltk_data_deref"
for entry in list(nltk.data.path):
try:
root = Path(entry)
if not root.is_dir():
continue
if not any(p.is_symlink() for p in root.rglob("*")):
continue
mirror = mirror_root / hashlib.sha256(str(root).encode()).hexdigest()[:16]
if str(mirror) in nltk.data.path:
continue
if not mirror.is_dir():
# Every GPU worker process runs this at pipeline construction on
# a shared filesystem, so stage under a per-process name and
# publish with an atomic rename; whichever process publishes
# first wins and the others adopt its mirror.
staging = mirror.with_name(f"{mirror.name}.{os.getpid()}.tmp")
shutil.rmtree(staging, ignore_errors=True)
# symlinks=False dereferences: the copy holds real file contents.
shutil.copytree(root, staging, symlinks=False)
try:
staging.rename(mirror)
except OSError:
shutil.rmtree(staging, ignore_errors=True)
if not mirror.is_dir():
raise
nltk.data.path.insert(nltk.data.path.index(entry), str(mirror))
logger.info(
"Mirrored symlinked nltk_data %s -> %s (hardened-NLTK compatibility)",
root,
mirror,
)
except OSError as exc:
# Best-effort: an unwritable cache root must not break guardrail
# init. Hardened-NLTK builds may still fail at check time; plain
# NLTK builds work fine without the mirror.
logger.warning(
"Could not mirror symlinked nltk_data %s under %s: %s",
entry,
mirror_root,
exc,
)
def _init_guardrails(offload_to_cpu: bool = False) -> None:
global _checker
if _checker is not None:
@@ -50,6 +117,7 @@ def _init_guardrails(offload_to_cpu: bool = False) -> None:
"Initializing Cosmos3 guardrails (offload_to_cpu=%s) ...", offload_to_cpu
)
_checker = CosmosSafetyChecker()
_mirror_symlinked_nltk_data()
idle_device = "cpu" if offload_to_cpu else current_platform.device_type
for runner in (_checker.text_guardrail, _checker.video_guardrail):
if runner is None or not hasattr(runner, "models"):
@@ -18,7 +18,10 @@ from sglang.multimodal_gen.configs.sample.cosmos3 import (
COSMOS3_EDGE_SUPPORTED_RESOLUTIONS,
Cosmos3SamplingParams,
)
from sglang.multimodal_gen.configs.sample.sampling_params import DataType
from sglang.multimodal_gen.configs.sample.sampling_params import (
DataType,
SamplingParams,
)
from sglang.multimodal_gen.registry import (
_PIPELINE_REGISTRY,
_discover_and_register_pipelines,
@@ -36,6 +39,7 @@ from sglang.multimodal_gen.runtime.entrypoints.openai.protocol import (
)
from sglang.multimodal_gen.runtime.entrypoints.openai.video_api import (
_cosmos3_sampling_param_kwargs,
_multipart_video_extras,
_resolve_sound_duration,
_resolve_video_path,
)
@@ -45,6 +49,7 @@ from sglang.multimodal_gen.runtime.loader.component_loaders.scheduler_loader imp
)
from sglang.multimodal_gen.runtime.loader.utils import get_param_names_mapping
from sglang.multimodal_gen.runtime.models.dits.cosmos3video import (
Cosmos3OmniTransformer,
DomainAwareLinear,
_can_enable_t1_fused_qk_norm_rope,
compute_mrope_position_ids_action,
@@ -59,6 +64,8 @@ from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.c
Cosmos3TimestepPreparationStage,
Cosmos3TokenizationStage,
_inject_caption_metadata,
_pad_transfer_frames,
_resize_center_crop_uint8_cthw,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.cosmos3_action import (
EMBODIMENT_TO_DOMAIN_ID,
@@ -877,7 +884,7 @@ class TestCosmos3ModelResolution(unittest.TestCase):
class TestCosmos3OpenAIProtocol(unittest.TestCase):
"""Verify Cosmos3 modality knobs are exposed by the video HTTP schema."""
"""Verify Cosmos3 modality knobs stay model-specific video extras."""
def test_cosmos3_template_fields_remain_extra_fields(self):
for request_cls in (ImageGenerationsRequest, VideoGenerationsRequest):
@@ -889,31 +896,45 @@ class TestCosmos3OpenAIProtocol(unittest.TestCase):
self.assertNotIn("use_system_prompt", request_cls.model_fields)
self.assertNotIn("use_guardrails", request_cls.model_fields)
def test_cosmos3_modal_fields_pass_through_as_extras(self):
for field_name in ("video_path", "video_url"):
with self.subTest(field_name=field_name):
self.assertIn(field_name, VideoGenerationsRequest.model_fields)
modal_values = {
"generate_sound": True,
"sound_duration": 3.0,
"condition_frame_indexes": [0, 2],
"condition_frame_indexes_vision": [0, 2],
"condition_video_keep": "last",
"action_mode": "policy",
"domain_id": 1,
"domain_name": "umi",
"raw_action_dim": 9,
"action_fps": 30.0,
"action": [0.0, 1.0],
"action_view_point": "ego_view",
"action_normalization": "mean_std",
}
req = VideoGenerationsRequest(prompt="test", **modal_values)
for field_name, value in modal_values.items():
def test_cosmos3_modal_fields_are_model_specific_video_extras(self):
for field_name in (
"generate_sound",
"sound_duration",
"condition_frame_indexes",
"condition_frame_indexes_vision",
"condition_video_keep",
"control_path",
"control_hint",
"control_guidance",
"control_guidance_interval",
"num_video_frames_per_chunk",
"num_conditional_frames",
"num_first_chunk_conditional_frames",
"max_frames",
"show_control_condition",
"show_input",
"share_vision_temporal_positions",
"action_mode",
"domain_id",
"domain_name",
"raw_action_dim",
"action_fps",
"action",
"action_view_point",
"action_normalization",
):
with self.subTest(field_name=field_name):
self.assertNotIn(field_name, VideoGenerationsRequest.model_fields)
self.assertEqual(getattr(req, field_name), value)
self.assertIn(
field_name, Cosmos3SamplingParams.video_request_extra_fields()
)
self.assertIn("video_path", VideoGenerationsRequest.model_fields)
self.assertIn("video_url", VideoGenerationsRequest.model_fields)
self.assertNotIn("action_stats_path", VideoGenerationsRequest.model_fields)
self.assertNotIn(
"action_stats_path", Cosmos3SamplingParams.video_request_extra_fields()
)
def test_cosmos3_http_aliases_map_to_sampling_params(self):
req = VideoGenerationsRequest(
@@ -927,6 +948,17 @@ class TestCosmos3OpenAIProtocol(unittest.TestCase):
raw_action_dim=9,
action_fps=30.0,
action_view_point="ego_view",
control_path=["edge.mp4", "depth.mp4"],
control_hint=["edge", "depth"],
control_guidance=1.5,
control_guidance_interval=[0.0, 500.0],
num_video_frames_per_chunk=97,
num_conditional_frames=5,
num_first_chunk_conditional_frames=2,
max_frames=1200,
show_control_condition="true",
show_input="false",
share_vision_temporal_positions="false",
)
self.assertEqual(_resolve_video_path(req), "https://example.com/input.mp4")
@@ -940,6 +972,48 @@ class TestCosmos3OpenAIProtocol(unittest.TestCase):
self.assertEqual(kwargs["raw_action_dim"], 9)
self.assertEqual(kwargs["action_fps"], 30.0)
self.assertEqual(kwargs["action_view_point"], "ego_view")
self.assertEqual(kwargs["control_path"], ["edge.mp4", "depth.mp4"])
self.assertEqual(kwargs["control_hint"], ["edge", "depth"])
self.assertEqual(kwargs["control_guidance"], 1.5)
self.assertEqual(kwargs["control_guidance_interval"], (0.0, 500.0))
self.assertEqual(kwargs["num_video_frames_per_chunk"], 97)
self.assertEqual(kwargs["num_conditional_frames"], 5)
self.assertEqual(kwargs["num_first_chunk_conditional_frames"], 2)
self.assertEqual(kwargs["max_frames"], 1200)
self.assertTrue(kwargs["show_control_condition"])
self.assertFalse(kwargs["show_input"])
self.assertFalse(kwargs["share_vision_temporal_positions"])
def test_cosmos3_multipart_extras_are_model_specific(self):
raw_form = {
"generate_sound": "true",
"control_path": '["edge.mp4", "depth.mp4"]',
"control_hint": '["edge", "depth"]',
"action_mode": "policy",
"action_stats_path": "/tmp/action_stats.json",
}
generic = _multipart_video_extras(
raw_form,
extra_body=None,
extra_params=None,
sampling_params_cls=SamplingParams,
)
self.assertNotIn("generate_sound", generic)
self.assertNotIn("control_path", generic)
self.assertNotIn("action_mode", generic)
cosmos = _multipart_video_extras(
raw_form,
extra_body=None,
extra_params=None,
sampling_params_cls=Cosmos3SamplingParams,
)
self.assertIs(cosmos["generate_sound"], True)
self.assertEqual(cosmos["control_path"], ["edge.mp4", "depth.mp4"])
self.assertEqual(cosmos["control_hint"], ["edge", "depth"])
self.assertEqual(cosmos["action_mode"], "policy")
self.assertNotIn("action_stats_path", cosmos)
def test_generate_sound_false_disables_sound_duration(self):
req = VideoGenerationsRequest(
@@ -1069,6 +1143,403 @@ class TestCosmos3MRoPE(unittest.TestCase):
self.assertAlmostEqual(float(vid[0, 1]), float(act[0, 4]), places=4)
class TestCosmos3Transfer(unittest.TestCase):
"""Transfer (control-video) conditioning: rope packing, control-CFG, defaults."""
DEVICE = torch.device("cpu")
@staticmethod
def _transformer_self() -> Cosmos3OmniTransformer:
model = Cosmos3OmniTransformer.__new__(Cosmos3OmniTransformer)
model.temporal_margin = 15000
model.base_fps = 24.0
model.temporal_compression_factor = 4
model.sound_latent_fps = 25.0
model.temporal_compression_factor_sound = 1
return model
def test_single_control_shares_video_positions(self):
model = self._transformer_self()
text_mask = torch.ones(1, 4)
T, Hp, Wp = 3, 2, 2
tpc = T * Hp * Wp
_, base = model._compute_rope_position_ids(
text_mask, T, Hp, Wp, fps=None, device=self.DEVICE, control_frames=0
)
_, with_ctrl = model._compute_rope_position_ids(
text_mask, T, Hp, Wp, fps=None, device=self.DEVICE, control_frames=T
)
self.assertEqual(tuple(with_ctrl.shape), (3, 1, 2 * tpc))
# control prefix == video block, and video block is unchanged.
self.assertTrue(torch.equal(with_ctrl[:, :, :tpc], with_ctrl[:, :, tpc:]))
self.assertTrue(torch.equal(with_ctrl[:, :, tpc:], base))
def test_multi_control_prepended_in_order(self):
model = self._transformer_self()
text_mask = torch.ones(1, 4)
T, Hp, Wp = 3, 2, 2
tpc = T * Hp * Wp
_, base = model._compute_rope_position_ids(
text_mask, T, Hp, Wp, fps=None, device=self.DEVICE, control_frames=0
)
_, multi = model._compute_rope_position_ids(
text_mask, T, Hp, Wp, fps=None, device=self.DEVICE, control_frames=[T, T]
)
self.assertEqual(tuple(multi.shape), (3, 1, 3 * tpc))
c0 = multi[:, :, :tpc]
c1 = multi[:, :, tpc : 2 * tpc]
vid = multi[:, :, 2 * tpc :]
self.assertTrue(torch.equal(c0, vid))
self.assertTrue(torch.equal(c1, vid))
self.assertTrue(torch.equal(vid, base))
def test_control_positions_can_be_sequential(self):
model = self._transformer_self()
text_mask = torch.ones(1, 4)
T, Hp, Wp = 3, 1, 1
_, positions = model._compute_rope_position_ids(
text_mask,
T,
Hp,
Wp,
fps=None,
device=self.DEVICE,
control_frames=[T, T],
share_vision_temporal_positions=False,
)
c0 = positions[0, 0, :T]
c1 = positions[0, 0, T : 2 * T]
video = positions[0, 0, 2 * T :]
self.assertLess(c0.max().item(), c1.min().item())
self.assertLess(c1.max().item(), video.min().item())
def test_transfer_chunk_count_and_reflection_padding(self):
self.assertEqual(
Cosmos3ImagePreprocessStage._get_transfer_num_chunks(93, 93, 1),
(1, 93),
)
self.assertEqual(
Cosmos3ImagePreprocessStage._get_transfer_num_chunks(186, 93, 1),
(3, 92),
)
frames = torch.tensor([0, 1, 2], dtype=torch.uint8).view(1, 1, 3, 1, 1)
frames = frames.expand(1, 3, 3, 1, 1)
padded = _pad_transfer_frames(frames, 5)
self.assertEqual(padded[0, 0, :, 0, 0].tolist(), [0, 1, 2, 2, 1])
def test_transfer_resize_matches_vllm_omni(self):
frames = torch.arange(3 * 2 * 2 * 3, dtype=torch.uint8).reshape(3, 2, 2, 3)
actual = _resize_center_crop_uint8_cthw(frames, height=3, width=3)
resized = torch.nn.functional.interpolate(
frames.permute(1, 0, 2, 3).float(),
size=(3, 5),
mode="bilinear",
align_corners=False,
)
expected = (
resized[:, :, :, 1:4]
.round()
.clamp(0, 255)
.to(torch.uint8)
.permute(1, 0, 2, 3)
.contiguous()
)
self.assertTrue(torch.equal(actual, expected))
self.assertEqual(tuple(actual.shape), (3, 2, 3, 3))
self.assertEqual(actual.dtype, torch.uint8)
def test_transfer_chunks_stitch_without_duplicate_overlap(self):
class Scheduler:
def __init__(self):
self.timesteps = torch.tensor([])
self.calls = 0
def set_timesteps(self, steps, device):
self.calls += 1
self.timesteps = torch.arange(steps, device=device)
stage = Cosmos3DenoisingStage.__new__(Cosmos3DenoisingStage)
stage.vae = torch.nn.Linear(1, 1, bias=False)
stage.transformer = torch.nn.Linear(1, 1, bias=False)
stage.scheduler = Scheduler()
stage._prepare_transfer_chunk = mock.Mock(side_effect=[0, 1])
stage._denoise_once = mock.Mock(side_effect=lambda batch, *_a, **_k: batch)
stage._decode_transfer_latents = mock.Mock(
side_effect=[
torch.arange(5).view(1, 1, 5, 1, 1).float() / 10,
torch.arange(5, 10).view(1, 1, 5, 1, 1).float() / 10,
]
)
generator = torch.Generator(device="cpu").manual_seed(7)
batch = types.SimpleNamespace(
latents=torch.zeros(1),
generator=generator,
seed=7,
num_inference_steps=2,
is_warmup=False,
extra={
"transfer_plan": {
"num_chunks": 2,
"total_frames": 9,
}
},
)
server_args = types.SimpleNamespace(vae_cpu_offload=False)
stage.server_args = server_args
stage._forward_transfer(batch, server_args)
stitched = batch.extra["transfer_decoded_output"].flatten()
self.assertTrue(
torch.allclose(
stitched,
torch.tensor([0.0, 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9]),
)
)
self.assertEqual(stage.scheduler.calls, 2)
for call in stage._prepare_transfer_chunk.call_args_list:
self.assertIs(call.args[3], generator)
for call in stage._denoise_once.call_args_list:
self.assertIs(call.kwargs["generator"], generator)
def test_transfer_display_composes_input_controls_and_output(self):
output = torch.full((1, 3, 2, 1, 2), 0.5)
control = torch.full((1, 3, 2, 1, 2), 255, dtype=torch.uint8)
source = torch.zeros((1, 3, 2, 1, 2), dtype=torch.uint8)
batch = types.SimpleNamespace(
sampling_params=types.SimpleNamespace(
show_control_condition=True,
show_input=True,
),
extra={
"transfer_plan": {"total_frames": 2},
"preprocessed_control": [control],
"preprocessed_transfer_video": source,
},
)
composed = Cosmos3DecodingStage._compose_transfer_display(output, batch)
self.assertEqual(tuple(composed.shape), (1, 3, 2, 1, 6))
self.assertTrue(torch.equal(composed[..., :2], torch.zeros_like(output)))
self.assertTrue(torch.equal(composed[..., 2:4], torch.ones_like(output)))
self.assertTrue(torch.equal(composed[..., 4:], output))
def test_control_cfg_blend_math(self):
stage = Cosmos3DenoisingStage.__new__(Cosmos3DenoisingStage)
# Per-branch forward values (each run un-batched, bs=1): cond_full
# (control in, cond text) -> 20; cond_nc (control dropped) -> 10; uncond
# (control in, uncond text) -> 2. The unified executor reduces the
# coefficient-weighted branch sum built by _control_cfg_branches.
def fake_run(**kw):
bs = kw["latents"].shape[0]
if kw["control_latents"] is None:
return torch.full((bs,), 10.0) # cond_nc, control dropped
if kw["cache_key"] == "uncond":
return torch.full((bs,), 2.0) # uncond, control in
return torch.full((bs,), 20.0) # cond_full, control in
stage._run_transformer = fake_run
cond_text_ids = torch.zeros(1)
cond_text_mask = torch.ones(1)
uncond_text_ids = torch.zeros(1)
uncond_text_mask = torch.ones(1)
control_latents = [torch.zeros(1)]
def _run(text_g, control_g):
branches = stage._control_cfg_branches(
cond_text_ids,
cond_text_mask,
uncond_text_ids,
uncond_text_mask,
cond_text_seq_len=None,
uncond_text_seq_len=None,
control_latents=control_latents,
text_guidance_scale=text_g,
control_guidance_scale=control_g,
)
return stage._predict_noise_cfg(
branches,
latents=torch.zeros(1),
timestep=torch.zeros(1),
video_shape=(1, 1, 1),
fps=24.0,
cfg_rank=0,
cfg_world_size=1,
)
# control-CFG only (g=1): cg*cond_full + (1-cg)*cond_nc
# = 2*20 + (-1)*10 = 30
out = _run(text_g=1.0, control_g=2.0)
self.assertTrue(torch.allclose(out, torch.full((1,), 30.0)))
# control-CFG + text CFG (g=3): g*cg*cond_full + g*(1-cg)*cond_nc
# + (1-g)*uncond = 6*20 + (-3)*10 + (-2)*2 = 86
out2 = _run(text_g=3.0, control_g=2.0)
self.assertTrue(torch.allclose(out2, torch.full((1,), 86.0)))
def test_text_cfg_batched_single_gpu(self):
"""Single-GPU text CFG batches both branches into one bs=2 forward."""
stage = Cosmos3DenoisingStage.__new__(Cosmos3DenoisingStage)
def fake_run(**kw):
self.assertIsNone(kw["control_latents"])
self.assertEqual(kw["latents"].shape[0], 2) # batched [uncond, cond]
return torch.tensor([2.0, 20.0])
stage._run_transformer = fake_run
out = stage._predict_noise_cfg_batched(
latents=torch.zeros(1),
timestep=torch.zeros(1),
cond_text_ids=torch.zeros(1),
cond_text_mask=torch.ones(1),
uncond_text_ids=torch.zeros(1),
uncond_text_mask=torch.ones(1),
video_shape=(1, 1, 1),
fps=24.0,
guidance_scale=3.0,
)
# uncond + g*(cond - uncond) = 2 + 3*(20-2) = 56
self.assertTrue(torch.allclose(out, torch.full((1,), 56.0)))
def test_control_cfg_parallel_distribution(self):
"""Round-robin branch distribution + all-reduce equals the serial blend."""
stage = Cosmos3DenoisingStage.__new__(Cosmos3DenoisingStage)
seen = {}
def make_run(rank):
seen[rank] = []
def fake_run(**kw):
seen[rank].append(kw["cache_key"])
bs = kw["latents"].shape[0]
if kw["control_latents"] is None:
return torch.full((bs,), 10.0) # cond_nc
if kw["cache_key"] == "uncond":
return torch.full((bs,), 2.0) # uncond
return torch.full((bs,), 20.0) # cond_full
return fake_run
def run(rank, world):
stage._run_transformer = make_run(rank)
branches = stage._control_cfg_branches(
torch.zeros(1),
torch.ones(1),
torch.zeros(1),
torch.ones(1),
cond_text_seq_len=None,
uncond_text_seq_len=None,
control_latents=[torch.zeros(1)],
text_guidance_scale=3.0,
control_guidance_scale=2.0,
)
return stage._predict_noise_cfg(
branches,
latents=torch.zeros(1),
timestep=torch.zeros(1),
video_shape=(1, 1, 1),
fps=24.0,
cfg_rank=rank,
cfg_world_size=world,
)
target = (
"sglang.multimodal_gen.runtime.pipelines_core.stages."
"model_specific_stages.cosmos3.cfg_model_parallel_all_reduce"
)
# Stand in for the all-reduce with identity so each rank returns its own
# partial; summing them mimics the cross-rank reduction.
with mock.patch(target, side_effect=lambda x: x):
# 2 ranks: rank 0 runs the two control-in forwards (cond_full +
# uncond), rank 1 runs only the control-dropped cond_nc.
r0 = run(0, 2)
r1 = run(1, 2)
self.assertEqual(seen[0], ["cond", "uncond"])
self.assertEqual(seen[1], ["cond_nc"])
self.assertTrue(torch.allclose(r0 + r1, torch.full((1,), 86.0)))
seen.clear()
# 4 ranks for 3 branches: rank 3 is idle and contributes zeros.
parts = [run(r, 4) for r in range(4)]
self.assertEqual(seen[0], ["cond"])
self.assertEqual(seen[1], ["cond_nc"])
self.assertEqual(seen[2], ["uncond"])
self.assertEqual(seen[3], [])
self.assertTrue(torch.allclose(parts[3], torch.zeros(1)))
self.assertTrue(torch.allclose(sum(parts), torch.full((1,), 86.0)))
def test_single_hint_defaults_applied(self):
sp = Cosmos3SamplingParams(
prompt="t", control_path="edge.mp4", control_hint="edge"
)
sp._explicit_fields = {"prompt", "control_path", "control_hint"}
sp._apply_transfer_hint_defaults()
self.assertEqual(sp.control_guidance, 1.5)
self.assertEqual(sp.guidance_scale, 3.0)
self.assertEqual(sp.flow_shift, 10.0)
def test_wsm_uses_transfer_spec_defaults(self):
sp = Cosmos3SamplingParams(
prompt="t", control_path="wsm.mp4", control_hint="wsm"
)
sp._explicit_fields = {"prompt", "control_path", "control_hint"}
sp._apply_transfer_hint_defaults()
self.assertEqual(sp.guidance_scale, 1.0)
self.assertEqual(sp.control_guidance, 3.0)
self.assertEqual(sp.flow_shift, 10.0)
self.assertEqual(sp.num_frames, 101)
self.assertEqual(sp.fps, 10)
self.assertEqual(sp.num_video_frames_per_chunk, 101)
lowered = Cosmos3SamplingParams.lower_video_request_kwargs(
types.SimpleNamespace(num_frames=None, fps=None),
{
"control_path": "wsm.mp4",
"control_hint": "wsm",
"num_frames": 121,
"fps": 24,
},
)
self.assertEqual(lowered["num_frames"], 101)
self.assertEqual(lowered["fps"], 10)
def test_explicit_value_overrides_hint_default(self):
sp = Cosmos3SamplingParams(
prompt="t",
control_path="seg.mp4",
control_hint="seg",
control_guidance=4.2,
)
sp._explicit_fields = {"control_path", "control_hint", "control_guidance"}
sp._apply_transfer_hint_defaults()
self.assertEqual(sp.control_guidance, 4.2)
def test_multi_hint_defaults_not_applied(self):
sp = Cosmos3SamplingParams(
prompt="t",
control_path=["edge.mp4", "depth.mp4"],
control_hint=["edge", "depth"],
)
base_guidance = sp.guidance_scale
sp._explicit_fields = {"control_path", "control_hint"}
sp._apply_transfer_hint_defaults()
self.assertEqual(sp.control_guidance, 1.0)
self.assertEqual(sp.guidance_scale, base_guidance)
def test_unknown_hint_rejected(self):
with self.assertRaises(ValueError):
Cosmos3SamplingParams(
prompt="t", control_path="x.mp4", control_hint="bogus"
)
class TestCosmos3DomainAwareLinear(unittest.TestCase):
"""Per-domain action projection."""
@@ -48,6 +48,23 @@ class _CustomEntrypointModel(_UniformDtypeModel):
pass
class _MetaBufferModel(_UniformDtypeModel):
"""Mirrors cosmos3: a non-checkpoint buffer stays on meta until
post_load_weights() rebuilds it."""
def __init__(self) -> None:
super().__init__()
self.proj = ReplicatedLinear(4, 4, bias=False)
self.register_buffer("inv_freq", torch.empty(2), persistent=False)
def post_load_weights(self) -> None:
if self.inv_freq.is_meta:
device = next(self.parameters()).device
self.register_buffer(
"inv_freq", torch.ones(2, device=device), persistent=False
)
class TestFSDPMixedPrecisionPolicy(unittest.TestCase):
def test_quant_config_detection_uses_the_runtime_instance(self):
self.assertTrue(fsdp_load._is_bitsandbytes_quant_config(BitsAndBytesConfig()))
@@ -218,6 +235,35 @@ class TestOrdinaryWeightLoading(unittest.TestCase):
torch.testing.assert_close(model.proj.weight, checkpoint_weight)
class TestDevicePostprocessMove(unittest.TestCase):
def test_postprocess_move_preserves_meta_buffers_for_post_load_weights(self):
# The pre-postprocess device move must not copy buffers that are
# still on meta awaiting post_load_weights() (cosmos3's RoPE inv_freq).
load_plan = WeightLoadPlan(
checkpoint_load_device=torch.device("cpu"),
weight_postprocess_device=torch.device("cpu"),
)
checkpoint_weight = torch.arange(16, dtype=torch.float32).reshape(4, 4)
with patch.object(fsdp_load.current_platform, "is_mps", return_value=False):
model = fsdp_load.maybe_load_fsdp_model(
model_cls=_MetaBufferModel,
init_params={},
weight_dir_list=[],
device=torch.device("cpu"),
hsdp_replicate_dim=1,
hsdp_shard_dim=1,
param_dtype=torch.float32,
reduce_dtype=torch.float32,
weight_load_plan=load_plan,
weights_iterator=iter((("proj.weight", checkpoint_weight),)),
)
self.assertFalse(model.inv_freq.is_meta)
torch.testing.assert_close(model.inv_freq, torch.ones(2))
torch.testing.assert_close(model.proj.weight, checkpoint_weight)
class TestRankLocalSafetensorsRead(unittest.TestCase):
def _source(
self,
@@ -0,0 +1,145 @@
# SPDX-License-Identifier: Apache-2.0
"""Serialized ModelOpt FP8 checkpoints must postprocess on device even under
layerwise offload: requantize_with_max_scale() runs scaled_fp8_quant(), a
CUDA-only kernel, so a CPU-resident postprocess must never come back."""
import unittest
import torch
from torch import nn
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
maybe_init_distributed_environment_and_model_parallel,
model_parallel_is_initialized,
)
from sglang.multimodal_gen.runtime.layers.linear import MergedColumnParallelLinear
from sglang.multimodal_gen.runtime.layers.quantization.modelopt_quant import (
ModelOptFp8Config,
)
from sglang.multimodal_gen.runtime.loader import fsdp_load
from sglang.multimodal_gen.runtime.loader.weight_load_plan import WeightLoadPlan
from sglang.multimodal_gen.test.single_test_file.component_accuracy.utils import (
ensure_distributed_env_defaults,
)
_IN_FEATURES = 32
_SHARD_OUT = 16
class _FusedFp8Model(nn.Module):
"""Minimal stand-in for a serialized ModelOpt FP8 DiT: one fused linear
with per-shard scales, plus a cosmos3-style non-checkpoint meta buffer."""
param_names_mapping = {}
_fsdp_forward_methods: tuple[str, ...] = ()
def __init__(self, quant_config: ModelOptFp8Config) -> None:
super().__init__()
self.qkv = MergedColumnParallelLinear(
input_size=_IN_FEATURES,
output_sizes=[_SHARD_OUT, _SHARD_OUT],
bias=False,
quant_config=quant_config,
prefix="qkv",
)
self.register_buffer("inv_freq", torch.empty(4), persistent=False)
def post_load_weights(self) -> None:
if self.inv_freq.is_meta:
device = next(self.parameters()).device
self.register_buffer(
"inv_freq",
torch.ones(4, dtype=torch.float32, device=device),
persistent=False,
)
def _make_serialized_fp8_checkpoint() -> tuple[dict[str, torch.Tensor], torch.Tensor]:
"""Quantize a reference weight shard-by-shard, as ModelOpt exports do."""
torch.manual_seed(0)
weight_ref = torch.randn(2 * _SHARD_OUT, _IN_FEATURES, dtype=torch.float32) * 0.05
fp8_max = torch.finfo(torch.float8_e4m3fn).max
shard_scales = []
fp8_shards = []
for shard in weight_ref.split(_SHARD_OUT, dim=0):
scale = shard.abs().max() / fp8_max
shard_scales.append(scale)
fp8_shards.append((shard / scale).to(torch.float8_e4m3fn))
state_dict = {
"qkv.weight": torch.cat(fp8_shards, dim=0),
"qkv.weight_scale": torch.stack(shard_scales),
"qkv.input_scale": torch.tensor([0.5, 0.5], dtype=torch.float32),
}
return state_dict, weight_ref
@unittest.skipUnless(torch.cuda.is_available(), "requires CUDA for scaled_fp8_quant")
class TestModelOptFp8LayerwiseOffloadLoad(unittest.TestCase):
def test_serialized_checkpoint_loads_with_component_starting_on_cpu(self):
if not model_parallel_is_initialized():
ensure_distributed_env_defaults()
maybe_init_distributed_environment_and_model_parallel(tp_size=1, sp_size=1)
state_dict, weight_ref = _make_serialized_fp8_checkpoint()
expected_max_scale = state_dict["qkv.weight_scale"].max()
# The plan a layerwise-offload component gets when
# _needs_device_weight_postprocess() returns True: load and postprocess
# on GPU, then defer the CPU placement.
load_plan = WeightLoadPlan.for_component(
checkpoint_load_device=torch.device("cuda"),
needs_device_weight_postprocess=True,
component_starts_on_cpu=True,
)
self.assertTrue(load_plan.defer_cpu_placement)
model = fsdp_load.maybe_load_fsdp_model(
model_cls=_FusedFp8Model,
init_params={
"quant_config": ModelOptFp8Config(is_checkpoint_fp8_serialized=True)
},
weight_dir_list=[],
device=torch.device("cuda"),
hsdp_replicate_dim=1,
hsdp_shard_dim=1,
param_dtype=torch.bfloat16,
reduce_dtype=torch.float32,
component_starts_on_cpu=True,
weight_load_plan=load_plan,
weights_iterator=iter(state_dict.items()),
)
# Postprocess ran: the weight was requantized to the shared max scale
# and rebound transposed.
weight = model.qkv.weight
self.assertEqual(weight.dtype, torch.float8_e4m3fn)
self.assertEqual(tuple(weight.shape), (_IN_FEATURES, 2 * _SHARD_OUT))
weight_scale = model.qkv.weight_scale
torch.testing.assert_close(
weight_scale.flatten(),
expected_max_scale.expand(weight_scale.numel()),
check_device=False,
)
torch.testing.assert_close(
model.qkv.input_scale.flatten().max(), torch.tensor(0.5), check_device=False
)
# The round trip through both quantizations stays close to the source.
# Loose on purpose: this guards against garbage (wrong scale, wrong
# shard order), not fp8 precision.
dequant = weight.t().float().cpu() * expected_max_scale
torch.testing.assert_close(
dequant, weight_ref, rtol=0.5, atol=float(expected_max_scale) * 8
)
# Layerwise offload contract: the component lands on CPU afterwards,
# with the non-checkpoint buffer rebuilt.
self.assertEqual(weight.device.type, "cpu")
self.assertFalse(model.inv_freq.is_meta)
self.assertEqual(model.inv_freq.device.type, "cpu")
if __name__ == "__main__":
unittest.main()
@@ -895,12 +895,19 @@ class TestTransformerQuantHelpers(unittest.TestCase):
warning.assert_called_once()
def test_modelopt_fp8_serialized_checkpoint_needs_device_postprocess(self):
def test_modelopt_fp8_always_needs_device_weight_postprocess(self):
# Even a serialized checkpoint requantizes fused shards through
# scaled_fp8_quant(), which cannot process CPU tensors.
self.assertTrue(
_needs_device_weight_postprocess(
ModelOptFp8Config(is_checkpoint_fp8_serialized=True)
)
)
self.assertTrue(
_needs_device_weight_postprocess(
ModelOptFp8Config(is_checkpoint_fp8_serialized=False)
)
)
def test_online_fp8_needs_device_weight_postprocess(self):
self.assertTrue(_needs_device_weight_postprocess(Fp8Config()))