Merge main into dev/vllm-align for vLLM 0.31 release

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
tzhouam committed 2026-10-03 21:05:17 +08:00
commit 008fa25263
252 files changed
+27589 -1237

No files matched your search

@@ -7,6 +7,10 @@ from typing import Any, Generic, Literal, TypeVar
import torch
from vllm_omni.diffusion.attention.capabilities import (
ExecutionContext,
ExecutionPathResult,
)
from vllm_omni.platforms import current_omni_platform
@@ -132,6 +136,15 @@ class AttentionBackend(ABC):
return False
@classmethod
def resolve_capabilities(cls, context: ExecutionContext) -> ExecutionPathResult:
"""Return conservative pre-construction capabilities.
Unmigrated backends remain advisory so introducing the contract cannot
reject an execution path that works today.
"""
return ExecutionPathResult.unmigrated(cls.get_name(), context)
@dataclass(frozen=True, slots=True)
class QueryRange:
@@ -218,6 +231,9 @@ class AttentionMetadata:
# "valid_kv_length": int — contiguous valid K/V prefix length. Local
# backends may slice it directly; ring backends trim each circulated
# K/V block from the same global prefix.
# "attention_mask_mode": "none" | "padding" | "arbitrary" — producer
# assertion used by capability planning. "none" means a present mask
# is semantically a no-op; absent means its contents are runtime-dependent.
# "npu_attn_varlen": bool — model opt-in for the NPU packed varlen path
# (TND npu_fusion_attention driven by cu_seqlens, mask never read).
# Requires the [real, pad] two-document packing contract; see
@@ -277,6 +293,21 @@ class AttentionImpl(ABC, Generic[T]):
return True
return kv_cache_dtype in cls._supported_kv_cache_dtypes.get(platform_key, set())
def resolve_execution_path(
self,
context: ExecutionContext,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: T | None,
) -> ExecutionPathResult:
"""Resolve the complete path after backend initialization.
Subclasses report verified results; this inspection API does not enforce them.
"""
del query, key, value, attn_metadata
return ExecutionPathResult.unmigrated(type(self).__name__, context)
def forward(
self,
query: torch.Tensor,
@@ -3,7 +3,9 @@
import os
from collections.abc import Callable
from dataclasses import replace
from functools import cache, partial
from typing import NamedTuple
import torch
from vllm.logger import init_logger
@@ -14,6 +16,16 @@ from vllm_omni.diffusion.attention.backends.utils.piecewise_attn import (
piecewise_attn,
run_paged_piecewise_plan,
)
from vllm_omni.diffusion.attention.capabilities import (
CapabilityResult,
CompilationMode,
ExecutionContext,
ExecutionPathResult,
MaskMode,
PackingMode,
ParallelStrategy,
SupportStatus,
)
from vllm_omni.diffusion.config import get_current_diffusion_config_or_none
from vllm_omni.platforms import current_omni_platform
@@ -29,6 +41,133 @@ def _get_npu_compressed_causal_mask(device: torch.device) -> torch.Tensor:
).contiguous()
if not hasattr(torch.ops.vllm_omni, "fa4_dense_attention"):
@torch.library.custom_op("vllm_omni::fa4_dense_attention", mutates_args=())
def _fa4_dense_attention_op(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
softmax_scale: float,
causal: bool,
deterministic: bool,
) -> torch.Tensor:
from vllm_omni.diffusion.attention.backends.utils.fa import flash_attn_func
if flash_attn_func is None:
raise RuntimeError("CuTe FlashAttention-4 is unavailable")
kwargs = {
"causal": causal,
"softmax_scale": softmax_scale,
}
if deterministic:
kwargs["deterministic"] = True
out = flash_attn_func(query, key, value, **kwargs)
return out[0] if isinstance(out, tuple) else out
@_fa4_dense_attention_op.register_fake
def _fa4_dense_attention_fake(
query,
key,
value,
softmax_scale,
causal,
deterministic,
):
# FA4 returns contiguous output even when Q is noncontiguous; V sets its head dimension.
return query.new_empty((*query.shape[:-1], value.shape[-1]))
_fa4_dense_attention_op = torch.ops.vllm_omni.fa4_dense_attention
_PACKED_KEYS = ("cu_seqlens_q", "cu_seqlens_k", "max_seqlen_q", "max_seqlen_k")
class _FlashAttentionMetadataPlan(NamedTuple):
attention_mask: torch.Tensor | None
mask_mode: MaskMode
full_attn_spans: list[list[tuple[int, int]]] | None
extra: dict
packing_mode: PackingMode
def _normalize_flash_attention_metadata(
attn_metadata: AttentionMetadata | None,
) -> _FlashAttentionMetadataPlan:
attention_mask = attn_metadata.attn_mask if attn_metadata is not None else None
full_attn_spans = attn_metadata.full_attn_spans if attn_metadata is not None else None
extra = attn_metadata.extra if attn_metadata is not None else {}
published_mask_mode = extra.get("attention_mask_mode")
if attention_mask is None:
mask_mode = MaskMode.NONE
elif published_mask_mode is None:
mask_mode = MaskMode.UNKNOWN
else:
try:
mask_mode = MaskMode(published_mask_mode)
except ValueError as error:
raise ValueError(f"Unknown attention_mask_mode {published_mask_mode!r}") from error
if mask_mode is MaskMode.UNKNOWN:
raise ValueError("attention_mask_mode='unknown' is reserved for unpublished semantics")
# Piecewise dispatch takes precedence and does not consume packed metadata.
present_packed_keys = [key for key in _PACKED_KEYS if key in extra] if full_attn_spans is None else []
if present_packed_keys and len(present_packed_keys) != len(_PACKED_KEYS):
missing = sorted(set(_PACKED_KEYS) - set(present_packed_keys))
raise ValueError(f"Incomplete packed FlashAttention metadata; missing {missing}")
packing_mode = PackingMode.NONE
if present_packed_keys:
cu_seqlens_q = extra["cu_seqlens_q"]
packing_mode = PackingMode.MULTI_DOCUMENT if cu_seqlens_q.shape[0] > 3 else PackingMode.PACKED_PADDING
return _FlashAttentionMetadataPlan(
attention_mask=attention_mask,
mask_mode=mask_mode,
full_attn_spans=full_attn_spans,
extra=extra,
packing_mode=packing_mode,
)
def _flash_attention_execution_path(
context: ExecutionContext,
) -> ExecutionPathResult:
if context.mask_mode is MaskMode.UNKNOWN:
return ExecutionPathResult.unmigrated(
"FLASH_ATTN",
context,
path="runtime_mask_dependent",
)
if not (
context.platform == "cuda"
and context.kernel_variant == "fa4"
and context.dtype in {"bfloat16", "torch.bfloat16"}
and context.causal is False
and context.mask_mode is MaskMode.NONE
and context.packing_mode is PackingMode.NONE
and not context.piecewise
and not context.paged_kv
and context.kv_cache_dtype is None
and context.parallel_strategy is ParallelStrategy.NONE
and not context.outer_boundaries
):
return ExecutionPathResult.unmigrated(
"FLASH_ATTN",
context,
path="unverified",
)
return ExecutionPathResult(
backend="FLASH_ATTN",
path="fa4_dense",
support=CapabilityResult.supported(),
compilation_mode=CompilationMode.CUSTOM_OP,
platform=context.platform,
kernel_variant=context.kernel_variant,
parallel_strategy=context.parallel_strategy,
)
class FlashAttentionBackend(AttentionBackend):
accept_output_buffer: bool = True
supports_piecewise_spans: bool = True
@@ -74,6 +213,10 @@ class FlashAttentionBackend(AttentionBackend):
def get_impl_cls() -> type["FlashAttentionImpl"]:
return FlashAttentionImpl
@classmethod
def resolve_capabilities(cls, context: ExecutionContext) -> ExecutionPathResult:
return _flash_attention_execution_path(replace(context, kernel_variant=None))
class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
# Per-platform attention quantization support.
@@ -108,11 +251,135 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
self.softmax_scale = softmax_scale
self.qkv_layout = qkv_layout
self.is_cross_attn = role == "cross"
from vllm_omni.diffusion.attention.backends.utils.fa import IS_AITER, IS_FLASH_ATTN_4
self._kernel_variant = "fa4" if IS_FLASH_ATTN_4 else "aiter" if IS_AITER else None
cfg = get_current_diffusion_config_or_none()
self.fa_deterministic = bool(getattr(cfg, "fa_deterministic", False)) if cfg is not None else False
if backend_kwargs:
logger.warning("FlashAttentionImpl ignoring backend_kwargs: %s", list(backend_kwargs.keys()))
def resolve_execution_path(
self,
context: ExecutionContext,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: AttentionMetadata | None,
) -> ExecutionPathResult:
if context.platform == "npu":
return self._resolve_npu_execution_path(context, query, key, attn_metadata)
metadata = _normalize_flash_attention_metadata(attn_metadata)
context = replace(
context,
kernel_variant=self._kernel_variant,
dtype=str(query.dtype).removeprefix("torch."),
causal=self.causal,
mask_mode=metadata.mask_mode,
packing_mode=metadata.packing_mode,
piecewise=metadata.full_attn_spans is not None,
kv_cache_dtype=metadata.extra.get("kv_cache_dtype"),
)
if context.platform == "rocm":
return self._resolve_rocm_execution_path(context)
result = _flash_attention_execution_path(context)
if result.support.status is not SupportStatus.SUPPORTED:
return result
from vllm_omni.diffusion.attention.backends.utils.fa import validate_fa4_head_dims
try:
if query.dtype != key.dtype or query.dtype != value.dtype:
raise ValueError("Q, K, and V dtypes must match")
if query.device != key.device or query.device != value.device:
raise ValueError("Q, K, and V devices must match")
if query.shape[-1] != key.shape[-1]:
raise ValueError("Q and K head dimensions must match")
verified = validate_fa4_head_dims(query.shape[-1], value.shape[-1], 16 // value.element_size())
except (AssertionError, ValueError) as error:
return replace(
result,
support=CapabilityResult.unsupported(f"FA4: {error} Select compatible inputs or another backend."),
)
if not verified:
return replace(
result,
support=CapabilityResult.unmigrated("Selected FA4 kernel has no available head-dimension validator"),
compilation_mode=CompilationMode.EAGER_ONLY,
)
return result
@staticmethod
def _resolve_rocm_execution_path(context: ExecutionContext) -> ExecutionPathResult:
"""Describe AITER dispatch; device and compiler validation is pending."""
result = ExecutionPathResult.unmigrated("FLASH_ATTN", context, path="rocm_unverified")
if (
context.kernel_variant != "aiter"
or context.parallel_strategy is not ParallelStrategy.NONE
or context.outer_boundaries
or context.paged_kv
or context.piecewise
or context.kv_cache_dtype is not None
):
return result
if context.packing_mode is not PackingMode.NONE:
path = "rocm_packed_varlen"
elif context.mask_mode is MaskMode.UNKNOWN:
path = "rocm_runtime_mask_dependent"
elif context.mask_mode is not MaskMode.NONE:
path = "rocm_masked_varlen"
else:
path = "rocm_dense"
return replace(
result,
path=path,
support=CapabilityResult.unmigrated("AITER routing only; kernel and compilation validation require ROCm"),
)
def _resolve_npu_execution_path(
self,
context: ExecutionContext,
query: torch.Tensor,
key: torch.Tensor,
attn_metadata: AttentionMetadata | None,
) -> ExecutionPathResult:
"""Describe existing MindIE routing without claiming hardware validation.
NPU packing has its own opt-in and [real, pad] contract. In particular,
incomplete packed metadata can fall back to a mask, so CUDA metadata
normalization must not run here. No tensor values are inspected.
"""
context = replace(context, kernel_variant="mindiesd")
result = ExecutionPathResult.unmigrated("FLASH_ATTN", context, path="npu_unverified")
extra = attn_metadata.extra if attn_metadata else {}
if (
context.parallel_strategy is not ParallelStrategy.NONE
or context.outer_boundaries
or context.paged_kv
or self.causal
or extra.get("kv_cache_dtype") not in (None, "float", "auto")
or (attn_metadata is not None and attn_metadata.full_attn_spans is not None)
):
return result
path = "npu_masked" if attn_metadata is not None and attn_metadata.attn_mask is not None else "npu_dense"
if extra.get("npu_attn_varlen", False):
if self._resolve_packed_seq_npu(query, key, extra) is not None:
path = (
"npu_prefix_kv_slice"
if os.environ.get("MINDIE_SD_FA_TYPE") == "ascend_laser_attention"
else "npu_packed_varlen"
)
else:
# This route may reject the request if neither an explicit mask
# nor usable valid_kv_length is supplied; it is not support.
path = "npu_masked_fallback"
return replace(
result,
path=path,
support=CapabilityResult.unmigrated("NPU routing only; kernel and compilation validation require Ascend"),
)
def _warn_fa_deterministic_non_dense(self, path: str) -> None:
if not self.fa_deterministic:
return
@@ -401,6 +668,7 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
"""CUDA/ROCm/MUSA flash attention implementation."""
from vllm_omni.diffusion.attention.backends.utils.fa import (
HAS_FLASH_ATTN,
IS_FLASH_ATTN_4,
flash_attn_func,
flash_attn_varlen_func,
)
@@ -412,9 +680,10 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
"Otherwise, use SDPA backend by setting DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA"
)
attention_mask = attn_metadata.attn_mask if attn_metadata is not None else None
full_attn_spans = attn_metadata.full_attn_spans if attn_metadata is not None else None
extra = attn_metadata.extra if attn_metadata is not None else {}
metadata_plan = _normalize_flash_attention_metadata(attn_metadata)
attention_mask = metadata_plan.attention_mask
full_attn_spans = metadata_plan.full_attn_spans
extra = metadata_plan.extra
# Try piecewise attention
if full_attn_spans is not None:
@@ -443,12 +712,7 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
query_ranges=None if attn_metadata is None else attn_metadata.query_ranges,
)
packed_keys = ("cu_seqlens_q", "cu_seqlens_k", "max_seqlen_q", "max_seqlen_k")
present_packed_keys = [key for key in packed_keys if key in extra]
if present_packed_keys:
if len(present_packed_keys) != len(packed_keys):
missing = sorted(set(packed_keys) - set(present_packed_keys))
raise ValueError(f"Incomplete packed FlashAttention metadata; missing {missing}")
if metadata_plan.packing_mode is not PackingMode.NONE:
self._warn_fa_deterministic_non_dense("packed-varlen")
return self._forward_varlen_packed(
query,
@@ -460,7 +724,14 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
max_seqlen_k=extra["max_seqlen_k"],
)
if attention_mask is not None and torch.any(~attention_mask):
use_masked_path = False
if attention_mask is not None:
use_masked_path = (
torch.any(~attention_mask)
if metadata_plan.mask_mode is MaskMode.UNKNOWN
else metadata_plan.mask_mode is not MaskMode.NONE
)
if use_masked_path:
self._warn_fa_deterministic_non_dense("masked-varlen")
return self._forward_varlen_masked(
query,
@@ -470,6 +741,15 @@ class FlashAttentionImpl(AttentionImpl[AttentionMetadata]):
)
if flash_attn_func is not None:
if IS_FLASH_ATTN_4:
return _fa4_dense_attention_op(
query,
key,
value,
self.softmax_scale,
self.causal,
self.fa_deterministic,
)
fa_kwargs = {
"causal": self.causal,
"softmax_scale": self.softmax_scale,
@@ -59,6 +59,8 @@ def is_flash_attn_4_available() -> bool:
# Bind via aliases so mypy does not treat each candidate import as a redefinition.
flash_attn_func: FlashAttnFn | None = None
flash_attn_varlen_func: FlashAttnFn | None = None
IS_FLASH_ATTN_4 = False
IS_AITER = False
if current_omni_platform.is_rocm():
# ROCm: try Aiter first
@@ -71,6 +73,7 @@ if current_omni_platform.is_rocm():
flash_attn_func = _fa_func
flash_attn_varlen_func = _fa_varlen
IS_AITER = True
except (ImportError, ModuleNotFoundError):
pass
elif current_omni_platform.is_xpu():
@@ -102,6 +105,7 @@ else:
flash_attn_func = _fa_func
flash_attn_varlen_func = _fa_varlen
IS_FLASH_ATTN_4 = True
logger.info("Using CuTe FlashAttention-4 on Blackwell")
except Exception as exc:
# Optional FA4 dependencies may be present but ABI-incompatible
@@ -166,6 +170,27 @@ else:
HAS_FLASH_ATTN = flash_attn_func is not None or flash_attn_varlen_func is not None
def validate_fa4_head_dims(head_dim: int, head_dim_v: int, alignment: int) -> bool:
"""Use the selected FA4 implementation's constraints during path resolution.
Return False when its validator is unavailable; propagate kernel validation
errors for the backend to report. Keep the private FA4 API dependency here,
rather than duplicating architecture/version-specific dimension rules.
This is a planning-time call, never part of compiled tensor execution.
"""
try:
from flash_attn.cute.interface import _get_device_arch, _validate_head_dims
except ImportError:
return False
arch = _get_device_arch() // 10
# FA4 routes SM80/SM120 through separate kernels and bypasses this validator.
if arch not in (9, 10, 11):
return False
_validate_head_dims(head_dim, head_dim_v, arch, alignment)
return True
def _choose_vllm_flash_attn_version(
device_major: int,
requested: str | int | None,
@@ -0,0 +1,126 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
class SupportStatus(str, Enum):
SUPPORTED = "supported"
UNSUPPORTED = "unsupported"
UNMIGRATED = "unmigrated"
class CompilationMode(str, Enum):
TRACEABLE = "traceable"
CUSTOM_OP = "custom_op"
EAGER_ONLY = "eager_only"
class PackingMode(str, Enum):
NONE = "none"
PACKED_PADDING = "packed_padding"
MULTI_DOCUMENT = "multi_document"
class MaskMode(str, Enum):
NONE = "none"
PADDING = "padding"
ARBITRARY = "arbitrary"
UNKNOWN = "unknown"
class ParallelStrategy(str, Enum):
NONE = "none"
ULYSSES = "ulysses"
RING = "ring"
HYBRID_ULYSSES_RING = "hybrid_ulysses_ring"
ALLGATHER_KV = "allgather_kv"
class OuterBoundary(str, Enum):
HSDP = "hsdp"
@dataclass(frozen=True, slots=True)
class CapabilityResult:
status: SupportStatus
reason: str | None = None
def __post_init__(self) -> None:
if self.status is SupportStatus.UNSUPPORTED and not self.reason:
raise ValueError("UNSUPPORTED capability results require an actionable reason")
@classmethod
def supported(cls) -> CapabilityResult:
return cls(SupportStatus.SUPPORTED)
@classmethod
def unsupported(cls, reason: str) -> CapabilityResult:
return cls(SupportStatus.UNSUPPORTED, reason)
@classmethod
def unmigrated(cls, reason: str | None = None) -> CapabilityResult:
return cls(SupportStatus.UNMIGRATED, reason)
@dataclass(frozen=True, slots=True)
class ExecutionContext:
platform: str
kernel_variant: str | None = None
dtype: str | None = None
causal: bool | None = None
mask_mode: MaskMode = MaskMode.NONE
packing_mode: PackingMode = PackingMode.NONE
piecewise: bool = False
paged_kv: bool = False
kv_cache_dtype: str | None = None
parallel_strategy: ParallelStrategy = ParallelStrategy.NONE
require_fullgraph: bool = False
outer_boundaries: frozenset[OuterBoundary] = frozenset()
@dataclass(frozen=True, slots=True)
class ExecutionPathResult:
backend: str
path: str
support: CapabilityResult
compilation_mode: CompilationMode
platform: str
kernel_variant: str | None
parallel_strategy: ParallelStrategy
@classmethod
def unmigrated(
cls,
backend: str,
context: ExecutionContext,
*,
path: str = "unmigrated",
) -> ExecutionPathResult:
return cls(
backend=backend,
path=path,
support=CapabilityResult.unmigrated(),
compilation_mode=CompilationMode.EAGER_ONLY,
platform=context.platform,
kernel_variant=context.kernel_variant,
parallel_strategy=context.parallel_strategy,
)
def requested_support(self, context: ExecutionContext) -> CapabilityResult:
"""Validate request-level guarantees without changing path migration state."""
if context.require_fullgraph and self.support.status is SupportStatus.UNMIGRATED:
return CapabilityResult.unsupported(
f"{self.backend} path {self.path!r} has no verified fullgraph declaration; "
"use a verified path or disable the guaranteed-fullgraph request"
)
if self.support.status is not SupportStatus.SUPPORTED:
return self.support
if context.require_fullgraph and self.compilation_mode is CompilationMode.EAGER_ONLY:
return CapabilityResult.unsupported(
f"{self.backend} path {self.path!r} does not support guaranteed fullgraph compilation"
)
return CapabilityResult.supported()
+79 -14
View File
@@ -19,7 +19,13 @@ from vllm.model_executor.models.utils import extract_layer_index
from vllm.v1.kv_cache_interface import FullAttentionSpec, KVCacheSpec
from vllm_omni.diffusion.attention.backends.abstract import AttentionBackend, AttentionImpl, AttentionMetadata
from vllm_omni.diffusion.attention.backends.sdpa import SDPABackend
from vllm_omni.diffusion.attention.backends.sdpa import SDPABackend, SDPAImpl
from vllm_omni.diffusion.attention.capabilities import (
ExecutionContext,
ExecutionPathResult,
OuterBoundary,
ParallelStrategy,
)
from vllm_omni.diffusion.attention.parallel import build_parallel_attention_strategy
from vllm_omni.diffusion.attention.parallel.base import NoParallelAttention
from vllm_omni.diffusion.attention.parallel.ring import RingParallelAttention
@@ -119,6 +125,8 @@ class Attention(nn.Module):
config = get_current_diffusion_config_or_none()
attention_config = config.diffusion_attention_config if config is not None else None
parallel_config = getattr(config, "parallel_config", None)
self._hsdp_compile_boundary_enabled = bool(getattr(parallel_config, "use_hsdp", False))
from vllm_omni.diffusion.model_metadata import get_diffusion_model_metadata
@@ -131,6 +139,9 @@ class Attention(nn.Module):
is DiffusionKVCacheMode.PAGED_SCHEDULER
)
self._scheduler_paged_kv = scheduler_paged_kv
self.attn_backend: type[AttentionBackend] | None
self.attention: AttentionImpl | nn.Module
self.sdpa_fallback: SDPAImpl | None
if custom_attention is None:
attn_backend_cls, spec = get_attn_backend_for_role(
role=role,
@@ -166,7 +177,6 @@ class Attention(nn.Module):
attn_backend_cls.get_name(),
dense_backend_name,
)
parallel_config = getattr(config, "parallel_config", None)
allgather_degree = getattr(parallel_config, "allgather_degree", 1)
# TODO: Move AllGather-KV compatibility into an AttentionBackend capability
# so validation does not depend on backend names.
@@ -279,6 +289,7 @@ class Attention(nn.Module):
if self.paged_kv_cache_role is None:
return None
assert self.attn_backend is not None # Custom attention cannot opt into paged KV.
dtype = self.paged_kv_cache_dtype or vllm_config.model_config.dtype
# Keep backend layout discovery under the same config context used by
# upstream vLLM's attention-spec collector. vLLM 0.29 moved the
@@ -378,17 +389,68 @@ class Attention(nn.Module):
value: torch.Tensor,
attn_metadata: AttentionMetadata | None = None,
) -> torch.Tensor:
if torch.compiler.is_compiling() and is_forward_context_available():
od_config = get_forward_context().omni_diffusion_config
parallel_config = getattr(od_config, "parallel_config", None)
if getattr(parallel_config, "use_hsdp", False):
# Keep HSDP/FSDP2 parameter all-gather outside Inductor's
# attention graph; otherwise scheduler dependency analysis can
# fail on the fused attention region.
return self._forward_hsdp_compile_boundary(query, key, value, attn_metadata)
if torch.compiler.is_compiling() and self._uses_hsdp_compile_boundary():
# Keep HSDP/FSDP2 parameter all-gather outside Inductor's
# attention graph; otherwise scheduler dependency analysis can
# fail on the fused attention region.
return self._forward_hsdp_compile_boundary(query, key, value, attn_metadata)
return self._forward_impl(query, key, value, attn_metadata)
def _uses_hsdp_compile_boundary(self) -> bool:
if self._hsdp_compile_boundary_enabled:
return True
if not is_forward_context_available():
return False
od_config = get_forward_context().omni_diffusion_config
parallel_config = getattr(od_config, "parallel_config", None)
return bool(getattr(parallel_config, "use_hsdp", False))
def resolve_execution_path(
self,
context: ExecutionContext,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: AttentionMetadata | None,
) -> ExecutionPathResult:
"""Compose backend capabilities with outer Attention boundaries."""
boundaries = set(context.outer_boundaries)
if self._uses_hsdp_compile_boundary():
boundaries.add(OuterBoundary.HSDP)
active_strategy = self._get_active_parallel_strategy()
strategy_name = active_strategy.name
if self.use_ring and active_strategy.enabled and strategy_name == "ulysses":
parallel_strategy = ParallelStrategy.HYBRID_ULYSSES_RING
elif self.use_ring and active_strategy.enabled:
parallel_strategy = ParallelStrategy.RING
else:
parallel_strategy = {
"allgather_kv": ParallelStrategy.ALLGATHER_KV,
"ulysses": ParallelStrategy.ULYSSES,
}.get(strategy_name, ParallelStrategy.NONE)
resolved_context = replace(
context,
outer_boundaries=frozenset(boundaries),
paged_kv=self.is_paged_kv_active(),
parallel_strategy=parallel_strategy,
)
resolver = getattr(self.attention, "resolve_execution_path", None)
if not callable(resolver):
return ExecutionPathResult.unmigrated(
type(self.attention).__name__,
resolved_context,
)
if not resolved_context.paged_kv:
attn_metadata = self._with_kv_cache_dtype(attn_metadata)
return resolver(
resolved_context,
query,
key,
value,
attn_metadata,
)
@torch.compiler.disable
def _forward_hsdp_compile_boundary(
self,
@@ -422,10 +484,11 @@ class Attention(nn.Module):
)
use_paged_attention = paged_adapter is not None and self.paged_kv_cache_role is not None
if use_paged_attention and not getattr(self.attn_backend, "supports_paged_kv", False):
assert self.attn_backend is not None
backend_name = (
self.attn_backend.get_name() if self.attn_backend is not None else type(self.attention).__name__
)
raise NotImplementedError(
f"Diffusion paged KV requires an Omni backend with paged support; "
f"selected {self.attn_backend.get_name()}"
f"Diffusion paged KV requires an Omni backend with paged support; selected {backend_name}"
)
if use_paged_attention and strategy is not self._no_parallel_strategy:
strategy_name = strategy.name
@@ -554,9 +617,11 @@ class Attention(nn.Module):
self._scheduler_paged_kv
and self.paged_kv_cache_role is not None
and in_kv_memory_profile
and cast(type[AttentionBackend], self.attn_backend).get_name() == "FLASH_ATTN"
and self.attn_backend is not None
and self.attn_backend.get_name() == "FLASH_ATTN"
and not current_omni_platform.supports_diffusion_dense_flash_attention()
):
assert self.sdpa_fallback is not None
logger.warning_once(
"The startup KV memory profile is using SDPA because dense FLASH_ATTN is unavailable. "
"Formal paged requests still use the platform-native paged attention backend."
+7
View File
@@ -983,6 +983,9 @@ class OmniDiffusionConfig:
enable_broadcast_weight_load: bool = False
num_weight_load_threads: int = 4
# Shard meta parameters before reading rank-local HF safetensors slices.
hsdp_weight_load_strategy: str = "full"
# Enable sleep mode
enable_sleep_mode: bool = False
@@ -1196,6 +1199,10 @@ class OmniDiffusionConfig:
)
def __post_init__(self):
if self.hsdp_weight_load_strategy not in {"full", "pre_sharded"}:
raise ValueError(
f"hsdp_weight_load_strategy must be 'full' or 'pre_sharded', got {self.hsdp_weight_load_strategy!r}"
)
from vllm_omni.diffusion.offloader.config import (
OffloadStrategy,
materialize_legacy_offload_flags,
+87 -29
View File
@@ -46,6 +46,14 @@ class HSDPInferenceConfig:
reshard_after_forward: bool = True
@dataclass
class HSDPShardContext:
"""Reusable state for incrementally sharding one HSDP model."""
hsdp_kwargs: dict[str, Any]
ignored_params: set[nn.Parameter] | None = None
def _create_hsdp_mesh(
device_type: str,
replicate_size: int,
@@ -111,6 +119,38 @@ def apply_hsdp_to_model(
Returns:
HSDP-wrapped model ready for inference
"""
context = prepare_hsdp_shard_context(
model,
hsdp_config,
target_device=target_device,
)
hsdp_shard_conditions = getattr(model, "_hsdp_shard_conditions", None)
if not hsdp_shard_conditions:
raise ValueError(f"Model {type(model).__name__} has no _hsdp_shard_conditions defined")
# Apply HSDP sharding, this will automatically handle weight distribution
shard_model(
model,
hsdp_shard_conditions=hsdp_shard_conditions,
context=context,
)
logger.info("HSDP applied to model: %s", type(model).__name__)
return model
def prepare_hsdp_shard_context(
model: nn.Module,
hsdp_config: HSDPInferenceConfig,
target_device: torch.device | None = None,
) -> HSDPShardContext:
"""Prepare mesh, precision, and ignored-parameter state for HSDP.
The returned context can be reused to shard completed child modules while
checkpoint weights are still streaming, then to shard the root once all
children have been loaded.
"""
if not hsdp_config.enabled:
raise ValueError("HSDP is not enabled in config")
@@ -160,7 +200,7 @@ def apply_hsdp_to_model(
)
hsdp_shard_conditions = getattr(model, "_hsdp_shard_conditions", None)
if not hsdp_shard_conditions or len(hsdp_shard_conditions) == 0:
if not hsdp_shard_conditions:
raise ValueError(f"Model {type(model).__name__} has no _hsdp_shard_conditions defined")
# Collect parameters of any modules the model wants excluded from FSDP sharding.
@@ -188,7 +228,11 @@ def apply_hsdp_to_model(
except AttributeError:
logger.warning("_hsdp_ignored_modules entry %r not found on model", mod_name)
continue
sub_mod.to(target_device)
has_meta_tensor = any(tensor.device.type == "meta" for tensor in (*sub_mod.parameters(), *sub_mod.buffers()))
if has_meta_tensor:
sub_mod.to_empty(device=target_device)
else:
sub_mod.to(target_device)
ignored_params.update(sub_mod.parameters())
if ignored_params:
logger.info(
@@ -224,21 +268,36 @@ def apply_hsdp_to_model(
target_device,
)
# Apply HSDP sharding, this will automatically handle weight distribution
shard_model(
model,
reshard_after_forward=hsdp_config.reshard_after_forward,
mp_policy=mp_policy,
mesh=device_mesh,
hsdp_shard_conditions=hsdp_shard_conditions,
ignored_params=ignored_params if ignored_params else None,
return HSDPShardContext(
hsdp_kwargs={
"reshard_after_forward": hsdp_config.reshard_after_forward,
"mesh": device_mesh,
"mp_policy": mp_policy,
},
ignored_params=ignored_params or None,
)
for param in model.parameters():
def shard_hsdp_module(module: nn.Module, context: HSDPShardContext) -> None:
"""Shard one completed child module with a prepared HSDP context."""
module_kwargs = dict(context.hsdp_kwargs)
if context.ignored_params:
module_ignored_params = context.ignored_params.intersection(module.parameters())
if module_ignored_params:
module_kwargs["ignored_params"] = module_ignored_params
fully_shard(module, **module_kwargs)
for param in module.parameters():
param.requires_grad = False
logger.info("HSDP applied to model: %s", type(model).__name__)
return model
def finalize_hsdp_root(model: nn.Module, context: HSDPShardContext) -> None:
"""Shard the model root after all selected child modules are sharded."""
root_kwargs = dict(context.hsdp_kwargs)
if context.ignored_params:
root_kwargs["ignored_params"] = context.ignored_params
fully_shard(model, **root_kwargs)
for param in model.parameters():
param.requires_grad = False
def shard_model(
@@ -249,6 +308,7 @@ def shard_model(
mesh: DeviceMesh | None = None,
hsdp_shard_conditions: list[Callable[[str, nn.Module], bool]],
ignored_params: set[nn.Parameter] | None = None,
context: HSDPShardContext | None = None,
) -> None:
"""Apply HSDP sharding to model modules based on shard conditions.
@@ -259,32 +319,30 @@ def shard_model(
This is required for packed integer parameters inside sharded transformer
blocks; the root wrap receives the full set for all remaining parameters.
"""
hsdp_kwargs: dict[str, Any] = {
"reshard_after_forward": reshard_after_forward,
"mesh": mesh,
"mp_policy": mp_policy,
}
if context is None:
context = HSDPShardContext(
hsdp_kwargs={
"reshard_after_forward": reshard_after_forward,
"mesh": mesh,
"mp_policy": mp_policy,
},
ignored_params=ignored_params,
)
elif any(value is not None for value in (mp_policy, mesh, ignored_params)) or not reshard_after_forward:
raise ValueError("Pass either context or individual HSDP shard options, not both")
num_sharded = 0
for name, module in reversed(list(model.named_modules())):
if any(cond(name, module) for cond in hsdp_shard_conditions):
module_kwargs = dict(hsdp_kwargs)
if ignored_params:
module_ignored_params = ignored_params.intersection(module.parameters())
if module_ignored_params:
module_kwargs["ignored_params"] = module_ignored_params
fully_shard(module, **module_kwargs)
shard_hsdp_module(module, context)
num_sharded += 1
if num_sharded == 0:
raise ValueError("No modules were sharded")
root_kwargs = dict(hsdp_kwargs)
if ignored_params:
root_kwargs["ignored_params"] = ignored_params
fully_shard(model, **root_kwargs)
finalize_hsdp_root(model, context)
logger.info(
"Sharded %d modules + root (ignored_params=%d)",
num_sharded,
len(ignored_params) if ignored_params else 0,
len(context.ignored_params) if context.ignored_params else 0,
)
@@ -16,6 +16,7 @@ import torch
from torch import nn
from vllm.config.load import LoadConfig
from vllm.logger import init_logger
from vllm.model_executor.layers.linear import UnquantizedLinearMethod
from vllm.model_executor.layers.quantization.base_config import QuantizeMethodBase
from vllm.model_executor.model_loader.weight_utils import (
download_weights_from_hf,
@@ -30,7 +31,14 @@ from vllm.utils.torch_utils import set_default_torch_dtype
from vllm_omni.diffusion.config import set_current_diffusion_config
from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.diffusion.distributed.hsdp import HSDPInferenceConfig, apply_hsdp_to_model
from vllm_omni.diffusion.distributed.hsdp import (
HSDPInferenceConfig,
HSDPShardContext,
apply_hsdp_to_model,
finalize_hsdp_root,
prepare_hsdp_shard_context,
shard_hsdp_module,
)
from vllm_omni.diffusion.lora.manager import LoRABackend
from vllm_omni.diffusion.model_loader.checkpoint_adapters import (
get_checkpoint_adapter,
@@ -38,6 +46,8 @@ from vllm_omni.diffusion.model_loader.checkpoint_adapters import (
from vllm_omni.diffusion.model_loader.host_weight_loader import HWRLoaderMixin, _HWRCommitError
from vllm_omni.diffusion.model_loader.host_weight_plan import (
HostWeightPlan,
TensorBinding,
build_checkpoint_binding_plan,
build_checkpoint_mmap_plan,
has_online_quantization,
)
@@ -49,7 +59,7 @@ from vllm_omni.diffusion.offloader.config import (
OffloadStrategy,
resolve_offload,
)
from vllm_omni.diffusion.offloader.module_collector import ModuleDiscovery
from vllm_omni.diffusion.offloader.module_collector import ModuleDiscovery, PipelineModules
from vllm_omni.diffusion.offloader.offload_plan import get_offload_plan
from vllm_omni.diffusion.registry import initialize_model
from vllm_omni.model_executor.model_loader.weight_utils import download_weights_from_hf_specific
@@ -138,6 +148,36 @@ def _resolve_custom_pipeline_cls(custom_pipeline_name: str | type | None) -> typ
)
def _is_distributed_rank_zero() -> bool:
return (
not torch.distributed.is_available()
or not torch.distributed.is_initialized()
or torch.distributed.get_rank() == 0
)
@dataclasses.dataclass(frozen=True)
class _HSDPLoadRoot:
name: str
module: nn.Module
context: HSDPShardContext
@dataclasses.dataclass(frozen=True)
class _HSDPModuleLoadGroup:
name: str
module: nn.Module
context: HSDPShardContext
@dataclasses.dataclass(frozen=True)
class _PreShardedHSDPLoadPlan:
roots: list[_HSDPLoadRoot]
groups: list[_HSDPModuleLoadGroup]
bindings: dict[str, TensorBinding]
binding_names: frozenset[str]
class DiffusersPipelineLoader(HWRLoaderMixin):
"""Model loader that can load diffusers pipeline components from disk."""
@@ -1222,6 +1262,451 @@ class DiffusersPipelineLoader(HWRLoaderMixin):
raise ValueError(f"Unknown load_format: {load_format}")
return model
def _load_model_with_pre_sharded_hsdp(
self,
model: nn.Module,
discovered_modules: PipelineModules,
hsdp_config: HSDPInferenceConfig,
target_device: torch.device,
) -> nn.Module:
"""Shard meta DiT parameters before reading their rank-local checkpoint slices."""
from torch.distributed.checkpoint import HuggingFaceStorageReader
from torch.distributed.checkpoint import load as dcp_load
if self.counter_before_loading_weights == 0.0:
self.counter_before_loading_weights = time.perf_counter()
load_plan = self._prepare_pre_sharded_hsdp_load(
model,
discovered_modules,
hsdp_config,
target_device,
)
self._check_no_unplanned_meta_tensors(model, load_plan.binding_names)
self._validate_pre_sharded_hsdp_bindings(load_plan)
self._process_pre_sharded_hsdp_groups_on_meta(load_plan)
for group in load_plan.groups:
logger.debug("Applying meta-first HSDP shard to %s", group.name)
shard_hsdp_module(group.module, group.context)
for root in load_plan.roots:
logger.debug("Finalizing meta-first HSDP root %s", root.name)
finalize_hsdp_root(root.module, root.context)
self._materialize_pre_sharded_hsdp_state(model, load_plan, target_device)
targets = self._get_pre_sharded_hsdp_targets(model, load_plan)
checkpoint_states, local_payload_bytes = self._prepare_pre_sharded_checkpoint_states(
load_plan,
targets,
)
thread_count = int(getattr(self.od_config, "num_weight_load_threads", 4))
if _is_distributed_rank_zero():
directory_label = "directory" if len(checkpoint_states) == 1 else "directories"
logger.info(
"Pre-sharded HSDP loading %.2f GiB of rank-local tensor slices directly into "
"FSDP-owned storage from %d HF safetensors %s with %d reader thread(s) per rank",
local_payload_bytes / 1024**3,
len(checkpoint_states),
directory_label,
thread_count,
)
for checkpoint_dir in sorted(checkpoint_states, key=str):
reader = HuggingFaceStorageReader(str(checkpoint_dir), thread_count=thread_count)
# Each worker has the full checkpoint available and each DTensor
# already describes its rank-local destination slice. Avoid DCP's
# coordinator collectives, which can leave rank 0 with less GPU
# memory headroom than the other HSDP workers.
dcp_load(checkpoint_states[checkpoint_dir], storage_reader=reader, no_dist=True)
remaining_meta = self._pre_sharded_meta_target_names(model, load_plan)
if remaining_meta:
raise ValueError(
"Pre-sharded HSDP checkpoint loading left tensors on meta; "
f"first missing tensors: {remaining_meta[:16]}"
)
self._finalize_pre_sharded_hsdp_loaded_weights(load_plan)
self.counter_after_loading_weights = time.perf_counter()
logger.info_once(
"Loading weights took %.2f seconds",
self.counter_after_loading_weights - self.counter_before_loading_weights,
)
self._move_non_hsdp_modules(discovered_modules, target_device)
return model
@staticmethod
def _validate_pre_sharded_hsdp_bindings(load_plan: _PreShardedHSDPLoadPlan) -> None:
unsupported = [name for name, binding in load_plan.bindings.items() if binding.transform is not None]
if unsupported:
raise ValueError(
"Pre-sharded HSDP requires checkpoint tensors in runtime layout; "
f"tensor transforms are unsupported: {unsupported[:16]}"
)
non_safetensors = sorted(
{
binding.file_path
for binding in load_plan.bindings.values()
if not binding.file_path.endswith(".safetensors")
}
)
if non_safetensors:
raise ValueError(
f"Pre-sharded HSDP requires existing HF safetensors files; unsupported files: {non_safetensors[:8]}"
)
@staticmethod
def _get_pre_sharded_hsdp_targets(
model: nn.Module,
load_plan: _PreShardedHSDPLoadPlan,
) -> dict[str, torch.Tensor]:
targets: dict[str, torch.Tensor] = {
**dict(model.named_buffers()),
**dict(model.named_parameters()),
}
missing_targets = sorted(load_plan.binding_names - targets.keys())
if missing_targets:
raise ValueError(
f"Pre-sharded HSDP changed checkpoint target names; first missing targets: {missing_targets[:16]}"
)
return targets
@staticmethod
def _materialize_pre_sharded_hsdp_state(
model: nn.Module,
load_plan: _PreShardedHSDPLoadPlan,
target_device: torch.device,
) -> None:
"""Materialize the installed FSDP rank-local state before checkpoint I/O.
``fully_shard`` supports meta initialization through ``Module.to_empty``:
its registered ``_apply`` hooks update the internal sharded storage to
point at the newly allocated rank-local tensors. Modules are materialized
in the same HSDP-group order and child-first traversal used by ordinary
``fully_shard`` initialization instead of the forward traversal used by
a recursive ``to_empty`` call.
PyTorch's swap-on-conversion mode preserves Parameter identity so that
post-load kernel objects created before sharding cannot retain stale
references to the original meta Parameters.
A complete checkpoint plan covers every parameter and persistent buffer
in each DiT. Non-persistent buffers are not checkpoint entries, so keep
their initialized values across ``to_empty``.
"""
unplanned_parameters: list[str] = []
saved_buffers: dict[str, torch.Tensor] = {}
for root in load_plan.roots:
prefix = f"{root.name}."
for local_name, _parameter in root.module.named_parameters():
full_name = prefix + local_name
if full_name not in load_plan.binding_names:
unplanned_parameters.append(full_name)
for local_name, buffer in root.module.named_buffers():
full_name = prefix + local_name
if full_name in load_plan.binding_names:
continue
if buffer.device.type == "meta":
raise ValueError(f"Pre-sharded HSDP cannot preserve an unplanned meta buffer: {full_name}")
saved_buffers[full_name] = buffer.detach().cpu().clone()
if unplanned_parameters:
raise ValueError(
"Pre-sharded HSDP requires checkpoint coverage for every DiT parameter; "
f"first unplanned parameters: {unplanned_parameters[:16]}"
)
previous_swap_mode = torch.__future__.get_swap_module_params_on_conversion()
torch.__future__.set_swap_module_params_on_conversion(True)
try:
sharded_group_roots = {group.module for group in load_plan.groups}
materialized_modules: set[nn.Module] = set()
for group in load_plan.groups:
for module in DiffusersPipelineLoader._hsdp_managed_modules_post_order(
group.module,
nested_hsdp_roots=sharded_group_roots - {group.module},
):
if module not in materialized_modules:
module.to_empty(device=target_device, recurse=False)
materialized_modules.add(module)
for root in load_plan.roots:
for module in DiffusersPipelineLoader._hsdp_managed_modules_post_order(
root.module,
nested_hsdp_roots=sharded_group_roots,
):
if module not in materialized_modules:
module.to_empty(device=target_device, recurse=False)
materialized_modules.add(module)
finally:
torch.__future__.set_swap_module_params_on_conversion(previous_swap_mode)
if saved_buffers:
materialized_buffers = dict(model.named_buffers())
with torch.no_grad():
for name, value in saved_buffers.items():
target = materialized_buffers[name]
target.copy_(value.to(device=target.device, dtype=target.dtype))
@staticmethod
def _hsdp_managed_modules_post_order(
root: nn.Module,
*,
nested_hsdp_roots: set[nn.Module],
) -> list[nn.Module]:
"""Mirror FSDP's child-first managed-module traversal.
Nested FSDP roots own their own parameters and are materialized in their
corresponding load group, so the enclosing traversal must not descend
into them.
"""
modules: list[nn.Module] = []
visited: set[nn.Module] = set()
def visit(module: nn.Module) -> None:
if module in visited:
return
visited.add(module)
for child in module.children():
if child not in nested_hsdp_roots:
visit(child)
modules.append(module)
visit(root)
return modules
@staticmethod
def _prepare_pre_sharded_checkpoint_states(
load_plan: _PreShardedHSDPLoadPlan,
targets: dict[str, torch.Tensor],
) -> tuple[dict[Path, dict[str, torch.Tensor]], int]:
from torch.distributed.tensor import DTensor
checkpoint_states: dict[Path, dict[str, torch.Tensor]] = {}
checkpoint_owners: dict[tuple[Path, str], str] = {}
local_payload_bytes = 0
for name, binding in load_plan.bindings.items():
target = targets[name]
checkpoint_dir = Path(binding.file_path).parent
owner_key = (checkpoint_dir, binding.checkpoint_key)
if previous_owner := checkpoint_owners.get(owner_key):
raise ValueError(
"Pre-sharded HSDP cannot bind one checkpoint tensor to multiple runtime tensors: "
f"{binding.checkpoint_key!r} maps to both {previous_owner!r} and {name!r}"
)
checkpoint_owners[owner_key] = name
if target.device.type == "meta":
raise ValueError(f"Pre-sharded HSDP target {name!r} was not materialized")
local_target = target.to_local() if isinstance(target, DTensor) else target
local_payload_bytes += local_target.numel() * local_target.element_size()
checkpoint_states.setdefault(checkpoint_dir, {})[binding.checkpoint_key] = target
return checkpoint_states, local_payload_bytes
@staticmethod
def _pre_sharded_meta_target_names(
model: nn.Module,
load_plan: _PreShardedHSDPLoadPlan,
) -> list[str]:
from torch.distributed.tensor import DTensor
targets = {
**dict(model.named_buffers()),
**dict(model.named_parameters()),
}
return [
name
for name in load_plan.binding_names
if (target := targets[name]).device.type == "meta"
or (isinstance(target, DTensor) and target.to_local().device.type == "meta")
]
@staticmethod
def _process_pre_sharded_hsdp_groups_on_meta(load_plan: _PreShardedHSDPLoadPlan) -> None:
modules_to_process: list[tuple[nn.Module, QuantizeMethodBase]] = []
for group in load_plan.groups:
for _, module in group.module.named_modules():
quant_method = getattr(module, "quant_method", None)
if quant_method is None or not isinstance(quant_method, QuantizeMethodBase):
continue
modules_to_process.append((module, quant_method))
if not modules_to_process:
return
for root in load_plan.roots:
if not getattr(root.module, "_hsdp_pre_sharded_meta_post_load", False):
raise ValueError(
f"Model {type(root.module).__name__} has not declared its post-load processing safe "
"for pre-sharded meta initialization"
)
for module, quant_method in modules_to_process:
if type(quant_method) is not UnquantizedLinearMethod:
raise ValueError(
"Pre-sharded HSDP does not support the selected post-load processing; "
f"{type(module).__name__} uses {type(quant_method).__name__}"
)
for module, quant_method in modules_to_process:
quant_method.process_weights_after_loading(module)
def _prepare_pre_sharded_hsdp_load(
self,
model: nn.Module,
discovered_modules: PipelineModules,
hsdp_config: HSDPInferenceConfig,
target_device: torch.device,
) -> _PreShardedHSDPLoadPlan:
outer_dit_names, outer_dits = discovered_modules.outermost_dits()
if not outer_dits:
raise ValueError("No DiT modules discovered for HSDP sharding")
sources = self._get_weight_sources(model)
plan_result = build_checkpoint_binding_plan(
model,
dit_modules=tuple(zip(outer_dit_names, outer_dits)),
sources=sources,
model_path=str(getattr(self.od_config, "model", "")) or None,
tensor_parallel_size=int(getattr(self.parallel_config, "tensor_parallel_size", 1)),
online_quantization=False,
)
if plan_result.plan is None:
raise ValueError(f"Pre-sharded HSDP checkpoint loading is incompatible: {plan_result.fallback_reason}")
plan = plan_result.plan
unsupported_sources = [source.prefix for source in sources if source.prefix not in plan.planned_source_prefixes]
if unsupported_sources:
raise ValueError(
"Pre-sharded HSDP checkpoint loading requires dedicated DiT sources; "
f"unsupported source prefixes: {unsupported_sources}"
)
roots: list[_HSDPLoadRoot] = []
groups: list[_HSDPModuleLoadGroup] = []
for outer_name, outer_dit in zip(outer_dit_names, outer_dits):
conditions = getattr(outer_dit, "_hsdp_shard_conditions", None)
if not conditions:
raise ValueError(f"Model {type(outer_dit).__name__} has no _hsdp_shard_conditions defined")
context = prepare_hsdp_shard_context(
outer_dit,
hsdp_config,
target_device=target_device,
)
roots.append(_HSDPLoadRoot(outer_name, outer_dit, context))
for local_name, module in reversed(list(outer_dit.named_modules())):
if not any(condition(local_name, module) for condition in conditions):
continue
if not local_name:
raise ValueError("_hsdp_shard_conditions must not select the DiT root")
full_name = f"{outer_name}.{local_name}" if local_name else outer_name
groups.append(_HSDPModuleLoadGroup(full_name, module, context))
parameters = dict(model.named_parameters())
targets: dict[str, torch.Tensor] = {**dict(model.named_buffers()), **parameters}
preserved_params = {param for root in roots for param in root.context.ignored_params or ()}
binding_names = frozenset(plan.bindings)
self._release_hsdp_checkpoint_targets_to_meta(
model,
targets,
parameters,
binding_names,
preserved_params,
)
return _PreShardedHSDPLoadPlan(
roots=roots,
groups=groups,
bindings=plan.bindings,
binding_names=binding_names,
)
@staticmethod
def _check_no_unplanned_meta_tensors(model: nn.Module, planned_names: frozenset[str]) -> None:
unplanned_meta = [
name
for name, tensor in (*model.named_parameters(), *model.named_buffers())
if tensor.device.type == "meta" and name not in planned_names
]
if unplanned_meta:
raise ValueError(
"Pre-sharded HSDP initialized tensors on meta that are not covered by the "
f"checkpoint plan. First unplanned tensors: {unplanned_meta[:16]}"
)
@staticmethod
def _release_hsdp_checkpoint_targets_to_meta(
model: nn.Module,
targets: dict[str, torch.Tensor],
parameters: dict[str, nn.Parameter],
binding_names: frozenset[str],
preserved_params: set[nn.Parameter],
) -> None:
released: dict[str, tuple[int, float]] = {}
for name in binding_names:
target = targets[name]
if target in preserved_params or target.device.type == "meta":
continue
logical_gib = target.numel() * target.element_size() / 1024**3
device_type = target.device.type
count, gib = released.get(device_type, (0, 0.0))
released[device_type] = (count + 1, gib + logical_gib)
if name in parameters:
replacement = DiffusersPipelineLoader._make_parameter_like(
target,
torch.empty_like(target, device="meta"),
)
torch.utils.swap_tensors(target, replacement)
else:
replacement = torch.empty_like(target, device="meta")
torch.utils.swap_tensors(target, replacement)
if released:
logger.info("Released HSDP checkpoint-covered tensors to meta before pre-sharded load: %s", released)
@staticmethod
def _make_parameter_like(template: nn.Parameter, tensor: torch.Tensor) -> nn.Parameter:
"""Create a Parameter on new storage while preserving loader metadata."""
if type(template) is nn.Parameter:
replacement = nn.Parameter(tensor, requires_grad=template.requires_grad)
replacement.__dict__.update(getattr(template, "__dict__", {}))
return replacement
replacement = template.__class__.__new__(template.__class__, tensor)
replacement.__dict__.update(getattr(template, "__dict__", {}))
replacement.requires_grad_(template.requires_grad)
return replacement
@staticmethod
def _finalize_pre_sharded_hsdp_loaded_weights(
load_plan: _PreShardedHSDPLoadPlan,
) -> None:
"""Run model post-load hooks for tensors materialized by pre-sharded loading."""
for root in load_plan.roots:
post_load = getattr(root.module, "post_load_weights", None)
if callable(post_load):
post_load()
root.module.eval()
validate = getattr(root.module, "validate_loaded_weights", None)
if callable(validate):
prefix = f"{root.name}."
validate({name.removeprefix(prefix) for name in load_plan.binding_names if name.startswith(prefix)})
@staticmethod
def _move_non_hsdp_modules(
discovered_modules: PipelineModules,
target_device: torch.device,
) -> None:
modules_to_move: list[nn.Module] = []
if discovered_modules.vaes is not None:
modules_to_move.extend(discovered_modules.vaes)
if discovered_modules.encoders is not None:
modules_to_move.extend(discovered_modules.encoders)
if discovered_modules.resident_modules is not None:
modules_to_move.extend(discovered_modules.resident_modules)
for module in modules_to_move:
module.to(target_device)
def _load_model_with_hsdp(
self,
target_device: torch.device,
@@ -1251,6 +1736,29 @@ class DiffusersPipelineLoader(HWRLoaderMixin):
# mapping (QKV fusion, etc.).
if load_format == "diffusers":
raise ValueError("HSDP is not supported with the diffusers adapter load format")
strategy = getattr(self.od_config, "hsdp_weight_load_strategy", "full")
if strategy == "pre_sharded":
if load_format != "default":
raise ValueError(
f"hsdp_weight_load_strategy={strategy!r} currently supports only diffusion_load_format='default'"
)
if self.quant_config is not None:
raise ValueError(f"hsdp_weight_load_strategy={strategy!r} does not support quantization yet")
if getattr(self.od_config, "lora_path", None):
raise ValueError(f"hsdp_weight_load_strategy={strategy!r} does not support LoRA yet")
elif strategy != "full":
raise ValueError(f"Unknown hsdp_weight_load_strategy: {strategy!r}")
if strategy == "pre_sharded":
model = self._init_from_load_format(load_format, target_device, custom_pipeline_name, is_hsdp=True)
discovered_modules = ModuleDiscovery.discover(model)
return self._load_model_with_pre_sharded_hsdp(
model,
discovered_modules,
hsdp_config,
target_device,
)
# Same host-fallback bound as the ordinary path: with a quant config the
# HSDP path initializes on the accelerator (hsdp_defer_to_cpu=False), so
# over-wide NPU-unquantizable fallback weights would otherwise round-trip
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Loader-owned host-weight plans shared with diffusion offload backends."""
"""Loader-owned host-weight plans shared by direct-checkpoint consumers."""
from __future__ import annotations
@@ -205,7 +205,7 @@ def _build_source_map(
if not model_to_ckpt:
raise _PlanIncompatibleError("no compatible safetensors entries were found in the loader's model sources")
logger.info(
"Indexed %d runtime tensor names from %d checkpoint keys for DLO preflight",
"Indexed %d runtime tensor names from %d checkpoint keys for checkpoint preflight",
len(model_to_ckpt),
indexed_keys,
)
@@ -313,28 +313,28 @@ def _validate_source_metadata(
)
def build_checkpoint_mmap_plan(
def build_checkpoint_binding_plan(
pipeline: nn.Module,
*,
dit_modules: Sequence[tuple[str, nn.Module]],
sources: Sequence[object],
model_path: str | None,
tensor_parallel_size: int,
use_hsdp: bool,
online_quantization: bool,
use_pipeline_key_remap: bool = True,
has_distilled_lora: bool = False,
) -> HostWeightPlanResult:
"""Build a complete direct-checkpoint plan or return a fallback reason."""
"""Build a complete binding plan for the pre-sharded HSDP consumer."""
if tensor_parallel_size != 1:
return HostWeightPlanResult(None, f"TP={tensor_parallel_size} requires the ordinary loader")
if use_hsdp:
return HostWeightPlanResult(None, "HSDP requires the ordinary loader")
if online_quantization:
return HostWeightPlanResult(None, "online quantization requires the ordinary loader")
if has_distilled_lora:
return HostWeightPlanResult(None, "distilled LoRA requires the ordinary loader")
remap_fn = getattr(type(pipeline), "_remap_ckpt_key", None)
if not callable(remap_fn) and use_pipeline_key_remap:
remap_fn = getattr(pipeline, "remap_checkpoint_key", None)
if not callable(remap_fn):
remap_fn = None
adapter = get_direct_mmap_adapter(pipeline)
@@ -387,10 +387,37 @@ def build_checkpoint_mmap_plan(
)
def build_checkpoint_mmap_plan(
pipeline: nn.Module,
*,
dit_modules: Sequence[tuple[str, nn.Module]],
sources: Sequence[object],
model_path: str | None,
tensor_parallel_size: int,
use_hsdp: bool,
online_quantization: bool,
has_distilled_lora: bool = False,
) -> HostWeightPlanResult:
"""Build the DLO mmap plan without changing its HSDP eligibility."""
if use_hsdp:
return HostWeightPlanResult(None, "HSDP requires the ordinary loader")
return build_checkpoint_binding_plan(
pipeline,
dit_modules=dit_modules,
sources=sources,
model_path=model_path,
tensor_parallel_size=tensor_parallel_size,
online_quantization=online_quantization,
use_pipeline_key_remap=False,
has_distilled_lora=has_distilled_lora,
)
__all__ = [
"HostWeightPlan",
"HostWeightPlanResult",
"TensorBinding",
"build_checkpoint_binding_plan",
"build_checkpoint_mmap_plan",
"has_online_quantization",
]
@@ -0,0 +1,148 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Triton kernels for the AuK codec's alias-free SnakeBeta activation.
The eager module oversamples with a grouped transposed convolution, applies
SnakeBeta, and decimates with a grouped strided convolution. Both grouped
convolutions carry one 12-tap filter shared by every channel, and cuDNN and
ATen serve them with generic depthwise kernels that dominate the decode (more
than half of a 12 s decode on H200). Here the oversampling is written as its
polyphase form fused with the activation, and the decimation as a direct
strided FIR, each one pass over memory per row. The arithmetic is the same
as the eager module in fp32; only the summation order of the taps differs.
The ops are registered with ``torch.library.triton_op`` so the compiled
decode buckets trace them instead of breaking the graph.
"""
from __future__ import annotations
import torch
from vllm.triton_utils import HAS_TRITON, tl, triton
__all__ = ["alias_free_snake"]
_BLOCK = 1024
if HAS_TRITON:
@triton.jit
def _upsample_snake_kernel(
x_ptr,
out_ptr,
taps_ptr,
exp_alpha_ptr,
inv_beta_ptr,
channels,
in_len,
out_len,
ratio: tl.constexpr,
taps: tl.constexpr,
pad: tl.constexpr,
crop: tl.constexpr,
block: tl.constexpr,
):
# out[m] = snake(ratio * sum_k taps[k] * xpad[(m + crop - k) / ratio]) over the k
# with (m + crop - k) divisible by ratio, where xpad is x replicate-padded by pad
# on both sides. That is conv_transpose1d(stride=ratio) followed by the crop.
row = tl.program_id(0)
offs = tl.program_id(1) * block + tl.arange(0, block)
mask = offs < out_len
x_row = x_ptr + row.to(tl.int64) * in_len
acc = tl.zeros((block,), dtype=tl.float32)
for k in tl.static_range(taps):
pos = offs + (crop - k)
hit = (pos % ratio) == 0
src = pos // ratio - pad
src = tl.minimum(tl.maximum(src, 0), in_len - 1)
w = tl.load(taps_ptr + k)
val = tl.load(x_row + src, mask=mask & hit, other=0.0)
acc += tl.where(hit, w * val, 0.0)
u = acc * ratio
channel = row % channels
alpha = tl.load(exp_alpha_ptr + channel)
inv_beta = tl.load(inv_beta_ptr + channel)
s = tl.sin(u * alpha)
u = u + inv_beta * (s * s)
tl.store(out_ptr + row.to(tl.int64) * out_len + offs, u, mask=mask)
@triton.jit
def _lowpass_kernel(
x_ptr,
out_ptr,
taps_ptr,
in_len,
out_len,
stride: tl.constexpr,
taps: tl.constexpr,
pad_left: tl.constexpr,
block: tl.constexpr,
):
# out[n] = sum_k taps[k] * x[clamp(stride * n + k - pad_left)], i.e. replicate
# padding followed by a strided conv1d.
row = tl.program_id(0)
offs = tl.program_id(1) * block + tl.arange(0, block)
mask = offs < out_len
x_row = x_ptr + row.to(tl.int64) * in_len
acc = tl.zeros((block,), dtype=tl.float32)
for k in tl.static_range(taps):
src = offs * stride + (k - pad_left)
src = tl.minimum(tl.maximum(src, 0), in_len - 1)
acc += tl.load(taps_ptr + k) * tl.load(x_row + src, mask=mask, other=0.0)
tl.store(out_ptr + row.to(tl.int64) * out_len + offs, acc, mask=mask)
@torch.library.triton_op("vllm_omni::auk_alias_free_snake", mutates_args=())
def alias_free_snake(
x: torch.Tensor,
up_taps: torch.Tensor,
down_taps: torch.Tensor,
exp_alpha: torch.Tensor,
inv_beta: torch.Tensor,
up_ratio: int,
up_pad: int,
up_crop: int,
down_stride: int,
down_pad_left: int,
down_pad_right: int,
) -> torch.Tensor:
"""Upsample by ``up_ratio``, SnakeBeta, low-pass and decimate: ``[B, C, T] -> [B, C, T_out]``."""
batch, channels, length = x.shape
x = x.contiguous()
up_len = length * up_ratio
oversampled = torch.empty((batch, channels, up_len), device=x.device, dtype=torch.float32)
rows = batch * channels
torch.library.wrap_triton(_upsample_snake_kernel)[(rows, triton.cdiv(up_len, _BLOCK))](
x,
oversampled,
up_taps,
exp_alpha,
inv_beta,
channels,
length,
up_len,
ratio=up_ratio,
taps=up_taps.numel(),
pad=up_pad,
crop=up_crop,
block=_BLOCK,
)
taps = down_taps.numel()
out_len = (up_len + down_pad_left + down_pad_right - taps) // down_stride + 1
out = torch.empty((batch, channels, out_len), device=x.device, dtype=torch.float32)
torch.library.wrap_triton(_lowpass_kernel)[(rows, triton.cdiv(out_len, _BLOCK))](
oversampled,
out,
down_taps,
up_len,
out_len,
stride=down_stride,
taps=taps,
pad_left=down_pad_left,
block=_BLOCK,
)
return out
else: # pragma: no cover
def alias_free_snake(*args, **kwargs):
raise RuntimeError("Triton is not available")
+126 -26
View File
@@ -153,6 +153,33 @@ class TimeEmbedding(nn.Module):
return self.time_mlp(hidden.to(self.time_mlp[0].weight.dtype))
def _grouped_conv1d_btc(x: torch.Tensor, conv: nn.Conv1d) -> torch.Tensor:
"""``conv`` applied to channels-last ``x`` ``[B, T, C]``, returning ``[B, T, C]``.
A same-padded grouped convolution written as one im2col copy and one
batched GEMM over the groups. cuDNN runs a grouped conv of this shape as
one kernel per group plus two layout transposes, which at AuK's sizes
(1536 channels in 16 groups, kernel 31, a few hundred frames) costs about
ten times the arithmetic.
"""
batch, frames, channels = x.shape
groups = conv.groups
kernel = conv.kernel_size[0]
per_group = channels // groups
pad = conv.padding[0]
padded = F.pad(x, (0, 0, pad, pad))
# unfold gives a [B, T, C, K] view; one copy lays it out as [B, T, G, C/G * K].
cols = padded.unfold(1, kernel, 1).reshape(batch * frames, groups, per_group * kernel)
# conv.weight [C_out, C/G, K] -> [G, C/G * K, C_out/G]; the reduction order matches the columns.
weight = conv.weight.reshape(groups, channels // groups, per_group * kernel).transpose(1, 2).to(cols.dtype)
# [G, B*T, C/G*K] @ [G, C/G*K, C_out/G] -> [G, B*T, C_out/G]
out = torch.bmm(cols.transpose(0, 1), weight)
out = out.transpose(0, 1).reshape(batch, frames, channels)
if conv.bias is not None:
out = out + conv.bias.to(out.dtype)
return out
class ConvPosEmbedding(nn.Module):
"""Depthwise-grouped conv stack that adds local position information."""
@@ -169,17 +196,19 @@ class ConvPosEmbedding(nn.Module):
)
def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
keep = None if mask is None else mask.unsqueeze(1)
x = x.transpose(1, 2)
keep = None if mask is None else mask.unsqueeze(-1)
if keep is not None:
x = x.masked_fill(~keep, 0.0)
for layer in self.conv1d:
x = layer(x)
# Re-zero padding after each convolution so it cannot bleed into
# valid frames through the kernel window.
if keep is not None and isinstance(layer, nn.Conv1d):
x = x.masked_fill(~keep, 0.0)
return x.transpose(1, 2)
if isinstance(layer, nn.Conv1d):
x = _grouped_conv1d_btc(x, layer)
# Re-zero padding after each convolution so it cannot bleed into
# valid frames through the kernel window.
if keep is not None:
x = x.masked_fill(~keep, 0.0)
else:
x = layer(x)
return x
class AudioEmbedding(nn.Module):
@@ -205,9 +234,12 @@ class AdaLayerNorm(nn.Module):
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
def forward(
self, x: torch.Tensor, emb: torch.Tensor
self, x: torch.Tensor, emb: torch.Tensor | None, mod: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.linear(self.silu(emb)).chunk(6, dim=1)
"""``mod`` is the precomputed ``linear(silu(emb))``; ``emb`` is then unused."""
if mod is None:
mod = self.linear(self.silu(emb))
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
@@ -221,8 +253,11 @@ class AdaLayerNormFinal(nn.Module):
self.linear = nn.Linear(dim, dim * 2)
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor:
scale, shift = self.linear(self.silu(emb)).chunk(2, dim=1)
def forward(self, x: torch.Tensor, emb: torch.Tensor | None, mod: torch.Tensor | None = None) -> torch.Tensor:
"""``mod`` is the precomputed ``linear(silu(emb))``; ``emb`` is then unused."""
if mod is None:
mod = self.linear(self.silu(emb))
scale, shift = mod.chunk(2, dim=1)
return self.norm(x) * (1 + scale)[:, None, :] + shift[:, None, :]
@@ -383,9 +418,11 @@ class DoubleBlock(nn.Module):
c_rope: RopeCache,
c_mask: torch.Tensor | None,
bias: torch.Tensor | None = None,
mod_c: torch.Tensor | None = None,
mod_x: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, t)
norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, t)
norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, t, mod_c)
norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, t, mod_x)
x_attn, c_attn = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope, c_mask=c_mask, bias=bias)
@@ -416,8 +453,9 @@ class SingleBlock(nn.Module):
mask: torch.Tensor | None,
rope: RopeCache,
bias: torch.Tensor | None = None,
mod: torch.Tensor | None = None,
) -> torch.Tensor:
norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, t)
norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, t, mod)
x = x + gate_msa[:, None] * self.attn(x=norm, mask=mask, rope=rope, bias=bias)
norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
return x + gate_mlp[:, None] * self.ff(norm)
@@ -491,6 +529,28 @@ class AuKTransformer(nn.Module):
self.norm_out = AdaLayerNormFinal(dim)
self.proj_out = nn.Linear(dim, latent_dim)
# Widths of the adaLN modulations in the order modulation_table() lays them out.
self._modulation_widths = [module.linear.out_features for module in self._adaln_modules()]
def _adaln_modules(self) -> list[nn.Module]:
"""Every timestep-conditioned norm, in the order :meth:`step` consumes them."""
modules: list[nn.Module] = []
for block in self.transformer_blocks:
modules += [block.attn_norm_c, block.attn_norm_x]
modules += [block.attn_norm for block in self.single_transformer_blocks]
modules.append(self.norm_out)
return modules
def modulation_table(self, timesteps: torch.Tensor) -> torch.Tensor:
"""adaLN modulations for every timestep, ``[len(timesteps), sum of widths]``.
The modulations depend only on the timestep, and a request's time grid
is known before its first step. Computing them for the whole grid at
once reads each modulation weight once per request instead of once per
step, which at batch 1 is what those matrix-vector products cost.
"""
emb = F.silu(self.time_embed(timesteps.reshape(-1).float()))
return torch.cat([module.linear(emb) for module in self._adaln_modules()], dim=1)
def project_text(self, text: torch.Tensor) -> torch.Tensor:
"""Project LLM hidden states ``[B, nt, text_hidden_dim]`` to model width."""
@@ -520,6 +580,7 @@ class AuKTransformer(nn.Module):
drop_text: bool = False,
cfg_infer: bool = False,
cache: bool = False,
timesteps: torch.Tensor | None = None,
) -> "AuKStepContext":
"""Everything a denoise step needs that does not depend on ``x`` or the time.
@@ -528,6 +589,9 @@ class AuKTransformer(nn.Module):
prompt embedding, the padding masks and biases and the rotary tables
are not recomputed on every step. Arguments are those of
:meth:`forward`; ``target_len`` is the target frame count ``n``.
``timesteps`` are the times the steps will run at (the grid without
its end point); when given, their adaLN modulations are precomputed
and :meth:`step` selects them by ``step_index``.
"""
batch = text.shape[0]
if c_mask is None:
@@ -593,25 +657,44 @@ class AuKTransformer(nn.Module):
rope_text=_rope_cos_sin(self.rotary_embed(text_len, c_mask)),
rope_single=_rope_cos_sin(self.rotary_embed(text_len + audio_len, single_mask)),
branches=branches,
modulation=None if timesteps is None else self.modulation_table(timesteps),
)
def step(self, x: torch.Tensor, time: torch.Tensor, ctx: "AuKStepContext") -> torch.Tensor:
def step(
self,
x: torch.Tensor,
time: torch.Tensor,
ctx: "AuKStepContext",
step_index: torch.Tensor | int | None = None,
) -> torch.Tensor:
"""Predict the velocity of ``x`` at ``time`` under a :meth:`prepare` context.
The target embedding does not depend on the CFG branch, so it is
computed once and shared by both branches.
computed once and shared by both branches. When the context carries a
modulation table, ``step_index`` selects this step's row of it and
``time`` is unused; the row broadcasts over the CFG branches.
"""
if time.ndim == 0:
time = time.repeat(x.shape[0])
t = self.time_embed(time)
target = self.audio_embed(x, mask=ctx.target_mask)
if ctx.modulation is not None and step_index is not None:
if isinstance(step_index, int):
row = ctx.modulation[step_index : step_index + 1]
else:
row = ctx.modulation.index_select(0, step_index.reshape(1))
mods = list(torch.split(row, self._modulation_widths, dim=1))
t = None
else:
if time.ndim == 0:
time = time.repeat(x.shape[0])
t = self.time_embed(time)
if ctx.branches == 2:
t = torch.cat((t, t), dim=0)
mods = [None] * len(self._modulation_widths)
if ctx.branches == 2:
t = torch.cat((t, t), dim=0)
target = torch.cat((target, target), dim=0)
audio = target if ctx.prompt is None else torch.cat([ctx.prompt, target], dim=1)
c = ctx.c
for block in self.transformer_blocks:
for i, block in enumerate(self.transformer_blocks):
c, audio = block(
audio,
c,
@@ -621,14 +704,24 @@ class AuKTransformer(nn.Module):
c_rope=ctx.rope_text,
c_mask=ctx.c_mask,
bias=ctx.joint_bias,
mod_c=mods[2 * i],
mod_x=mods[2 * i + 1],
)
h = torch.cat([c, audio], dim=1)
for block in self.single_transformer_blocks:
h = block(h, t, mask=ctx.single_mask, rope=ctx.rope_single, bias=ctx.single_bias)
offset = 2 * len(self.transformer_blocks)
for i, block in enumerate(self.single_transformer_blocks):
h = block(
h,
t,
mask=ctx.single_mask,
rope=ctx.rope_single,
bias=ctx.single_bias,
mod=mods[offset + i],
)
h = h[:, h.shape[1] - x.shape[1] :]
return self.proj_out(self.norm_out(h, t))
return self.proj_out(self.norm_out(h, t, mods[-1]))
def forward(
self,
@@ -706,6 +799,8 @@ class AuKStepContext:
rope_text: RopeCache
rope_single: RopeCache
branches: int
#: adaLN modulations per step, ``[steps, widths]``, when the time grid was known.
modulation: torch.Tensor | None = None
def tensors(self) -> list[torch.Tensor | None]:
"""Every tensor field in a fixed order, rope pairs flattened."""
@@ -721,6 +816,7 @@ class AuKStepContext:
*self.rope_audio,
*self.rope_text,
*self.rope_single,
self.modulation,
]
def copy_(self, other: "AuKStepContext") -> None:
@@ -752,6 +848,7 @@ class AuKStepContext:
rope_text=(self.rope_text[0].clone(), self.rope_text[1].clone()),
rope_single=(self.rope_single[0].clone(), self.rope_single[1].clone()),
branches=self.branches,
modulation=_clone(self.modulation),
)
@@ -881,6 +978,8 @@ def sample_latents(
timestep=t[i],
cfg_strength=cfg_strength,
new_request=i == 0,
timesteps=t[:-1],
step_index=i,
)
x = x + (t[i + 1] - t[i]) * velocity
finally:
@@ -922,9 +1021,10 @@ def _sample_latents(
ref_mask=ref_mask,
cfg_infer=guided,
cache=guided,
timesteps=timesteps[:-1],
)
for i in range(timesteps.shape[0] - 1):
v = dit.step(x, timesteps[i], ctx)
v = dit.step(x, timesteps[i], ctx, step_index=i)
if guided:
v_cond, v_uncond = v.chunk(2, dim=0)
v = v_cond + (v_cond - v_uncond) * cfg_strength
+39
View File
@@ -32,7 +32,9 @@ from torch import nn
from torch.nn.utils import remove_weight_norm as _fold_weight_norm
from torch.nn.utils import weight_norm as _apply_weight_norm
from vllm.logger import init_logger
from vllm.triton_utils import HAS_TRITON
from vllm_omni.diffusion.models.auk.alias_free_triton import alias_free_snake
from vllm_omni.model_executor.models.common.snake_activation import SnakeBeta as _SharedSnakeBeta
logger = init_logger(__name__)
@@ -319,8 +321,45 @@ class AliasFreeActivation(nn.Module):
self.downsample = Downsample(down_ratio, down_kernel_size, causal=causal)
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self._fused_eligible(x):
return self._fused_forward(x)
return self.downsample(self.act(self.upsample(x)))
def _fused_eligible(self, x: torch.Tensor) -> bool:
"""Whether ``x`` can take the Triton kernels in alias_free_triton (CUDA fp32, AuK's filter geometry)."""
up = self.upsample
taps = up.filter.numel()
return (
x.is_cuda
and x.dtype == torch.float32
and x.dim() == 3
and isinstance(self.act, SnakeBeta)
and HAS_TRITON
# The kernel indexes the crop from the start of the transposed
# conv output, so the crop must cover the filter's reach, and the
# cropped length must be exactly ratio * T.
and up.pad_left >= taps - 1
and (2 * up.pad - 1) * up.ratio + taps == up.pad_left + up.pad_right
)
def _fused_forward(self, x: torch.Tensor) -> torch.Tensor:
up, low, act = self.upsample, self.downsample.lowpass, self.act
if not act._cached:
act.precompute_exp_cache()
return alias_free_snake(
x,
up.filter.reshape(-1),
low.filter.reshape(-1),
act._exp_alpha,
act._inv_beta,
up.ratio,
up.pad,
up.pad_left,
low.stride,
low.pad_left,
low.pad_right,
)
class AmpBlock(nn.Module):
"""Anti-aliased multi-periodicity residual block: alternating alias-free snake and dilated convs."""
@@ -24,16 +24,19 @@ class _GraphEntry:
static_ctx: AuKStepContext
static_timestep: torch.Tensor
static_cfg: torch.Tensor | None
static_step: torch.Tensor | None
static_out: torch.Tensor
class AuKCUDAGraphWrapper:
"""Replay one DiT denoise step and leave Euler scheduling to the caller.
The graph is keyed by the target, text and reference sequence lengths plus
whether the CFG branch is enabled. Timestep and the CFG strength are
The graph is keyed by the target, text and reference sequence lengths,
whether the CFG branch is enabled and, when the caller passes the time
grid, its length. Timestep (or the step index), and the CFG strength are
mutable scalar buffers, so all Euler steps and CFG values within one path
reuse one graph.
reuse one graph. With the time grid, the adaLN modulations of every step
are part of the per-request context and the graph selects its row.
Only :meth:`AuKTransformer.step` is captured. The per-request context
(text projection, reference embedding, padding biases, rotary tables) is
@@ -85,8 +88,9 @@ class AuKCUDAGraphWrapper:
text: torch.Tensor,
ref: torch.Tensor,
uses_cfg: bool,
) -> tuple[int, int, int, bool]:
return (x.shape[1], text.shape[1], ref.shape[1], uses_cfg)
steps: int = 0,
) -> tuple[int, int, int, bool, int]:
return (x.shape[1], text.shape[1], ref.shape[1], uses_cfg, steps)
def _retire_graph_generation_if_full(self) -> None:
"""Retire all graphs together so none outlive shared workspaces."""
@@ -103,6 +107,7 @@ class AuKCUDAGraphWrapper:
ref: torch.Tensor,
ref_mask: torch.Tensor,
uses_cfg: bool,
timesteps: torch.Tensor | None = None,
) -> AuKStepContext:
return self.dit.prepare(
text,
@@ -112,6 +117,7 @@ class AuKCUDAGraphWrapper:
ref=ref,
ref_mask=ref_mask,
cfg_infer=uses_cfg,
timesteps=timesteps,
)
def _step(
@@ -120,9 +126,10 @@ class AuKCUDAGraphWrapper:
timestep: torch.Tensor,
ctx: AuKStepContext,
cfg_strength: torch.Tensor | float,
step_index: torch.Tensor | int | None = None,
) -> torch.Tensor:
"""One guided (or unguided) velocity; the CFG combine is part of the graph."""
velocity = self.dit.step(x, timestep, ctx)
velocity = self.dit.step(x, timestep, ctx, step_index=step_index)
if ctx.branches == 1:
return velocity
conditional, unconditional = velocity.chunk(2, dim=0)
@@ -140,6 +147,8 @@ class AuKCUDAGraphWrapper:
timestep: torch.Tensor,
cfg_strength: float,
new_request: bool = True,
timesteps: torch.Tensor | None = None,
step_index: int | None = None,
) -> torch.Tensor:
"""Velocity of ``x`` at ``timestep``.
@@ -147,26 +156,33 @@ class AuKCUDAGraphWrapper:
(``text``, ``c_mask``, ``ref``, ``ref_mask``) is then prepared anew; the
later steps of the request pass ``False`` and reuse it. The default
prepares on every call, which is always correct.
``timesteps`` (the times every step of the request runs at) and
``step_index`` (this step's position in them) let the adaLN
modulations be computed once per request instead of once per step.
"""
uses_cfg = cfg_strength >= 1e-5
if timesteps is None or step_index is None:
timesteps, step_index = None, None
if not self.enabled or x.device.type != "cuda" or torch.cuda.is_current_stream_capturing():
if new_request or self._eager_ctx is None:
self._eager_ctx = self._prepare(x, None, text, c_mask, ref, ref_mask, uses_cfg)
return self._step(x, timestep, self._eager_ctx, cfg_strength)
self._eager_ctx = self._prepare(x, None, text, c_mask, ref, ref_mask, uses_cfg, timesteps)
return self._step(x, timestep, self._eager_ctx, cfg_strength, step_index)
target_frames = x.shape[1]
x, x_mask, text, c_mask, ref, ref_mask = self._bucket_inputs(x, text, c_mask, ref, ref_mask)
key = self._key(x, text, ref, uses_cfg)
steps = 0 if timesteps is None else int(timesteps.numel())
key = self._key(x, text, ref, uses_cfg, steps)
entry = self._cache.get(key)
ctx = None
if entry is None or new_request or key != self._active_key:
ctx = self._prepare(x, x_mask, text, c_mask, ref, ref_mask, uses_cfg)
ctx = self._prepare(x, x_mask, text, c_mask, ref, ref_mask, uses_cfg, timesteps)
if entry is None:
# Same Retirement as #6587, details discussed in #7469
self._retire_graph_generation_if_full()
try:
assert ctx is not None
entry = self._capture(x, ctx, timestep, cfg_strength=cfg_strength)
entry = self._capture(x, ctx, timestep, cfg_strength=cfg_strength, step_index=step_index)
except Exception:
self._cache.clear()
self._active_key = None
@@ -186,6 +202,8 @@ class AuKCUDAGraphWrapper:
entry.static_x.copy_(x)
entry.static_timestep.copy_(timestep)
if entry.static_step is not None:
entry.static_step.fill_(step_index)
if entry.static_cfg is not None:
entry.static_cfg.fill_(cfg_strength)
entry.graph.replay()
@@ -198,10 +216,14 @@ class AuKCUDAGraphWrapper:
timestep: torch.Tensor,
*,
cfg_strength: float,
step_index: int | None = None,
) -> _GraphEntry:
static_x = x.clone()
static_ctx = ctx.clone()
static_timestep = timestep.clone()
static_step = None
if static_ctx.modulation is not None and step_index is not None:
static_step = torch.full((), step_index, device=static_x.device, dtype=torch.long)
static_cfg = None
if ctx.branches == 2:
static_cfg = torch.empty((), device=static_x.device, dtype=torch.float32)
@@ -209,13 +231,13 @@ class AuKCUDAGraphWrapper:
scale = static_cfg if static_cfg is not None else cfg_strength
# Warm-up runs any lazy torch.compile outside the capture.
for _ in range(3):
self._step(static_x, static_timestep, static_ctx, scale)
self._step(static_x, static_timestep, static_ctx, scale, static_step)
if self._pool_handle is None:
self._pool_handle = torch.cuda.graph_pool_handle()
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph, pool=self._pool_handle):
# The CFG scalar stays a mutable buffer, so cfg=2 and cfg=3 share this graph.
static_out = self._step(static_x, static_timestep, static_ctx, scale)
static_out = self._step(static_x, static_timestep, static_ctx, scale, static_step)
logger.info(
"Captured AuK DiT single-step CUDA graph: target_frames=%d text_tokens=%d ref_frames=%d cfg=%s",
@@ -230,6 +252,7 @@ class AuKCUDAGraphWrapper:
static_ctx=static_ctx,
static_timestep=static_timestep,
static_cfg=static_cfg,
static_step=static_step,
static_out=static_out,
)
+43 -3
View File
@@ -11,9 +11,11 @@ both, and the BigVGAN-flow decoder renders the waveform.
from __future__ import annotations
import hashlib
import json
import os
import time
from collections import OrderedDict
from collections.abc import Iterable
from typing import Any, ClassVar
@@ -27,7 +29,12 @@ from vllm.logger import init_logger
from vllm_omni.diffusion.compile import regionally_compile
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
from vllm_omni.diffusion.distributed.utils import get_local_device
from vllm_omni.diffusion.models.auk.auk_transformer import AuKTransformer, dit_state_dict, sample_latents
from vllm_omni.diffusion.models.auk.auk_transformer import (
AuKTransformer,
build_time_grid,
dit_state_dict,
sample_latents,
)
from vllm_omni.diffusion.models.auk.auk_vae import AuKVAE
from vllm_omni.diffusion.models.auk.cudagraph_wrapper import AuKCUDAGraphWrapper
from vllm_omni.diffusion.models.auk.vae_cudagraph import AuKVAEDecodeGraph
@@ -59,6 +66,10 @@ _FLASH_CFG = 0.0
_WARMUP_TARGET_FRAMES = (150, 300, 600)
_WARMUP_REF_FRAMES = (0, 150)
_WARMUP_TEXT_TOKENS = 96
# Reference clips whose posterior-mean latents are kept. Voice cloning reuses a
# small set of reference voices, so repeat requests skip the VAE encode.
# ``model_config.auk_ref_cache_size`` overrides it; 0 disables the cache.
_REF_CACHE_SIZE = 16
# Decorrelates the VAE posterior draw from the initial latent for the same request seed.
_VAE_SEED_OFFSET = 0x5EED_0A0C
@@ -152,6 +163,9 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
max_dit_graphs = od_config.model_config.get("max_dit_graphs", 32)
if isinstance(max_dit_graphs, bool) or not isinstance(max_dit_graphs, int) or max_dit_graphs < 1:
raise ValueError("AuK max_dit_graphs must be a positive integer")
ref_cache_size = od_config.model_config.get("auk_ref_cache_size", _REF_CACHE_SIZE)
if isinstance(ref_cache_size, bool) or not isinstance(ref_cache_size, int) or ref_cache_size < 0:
raise ValueError("AuK auk_ref_cache_size must be a non-negative integer (0 disables the cache)")
self.device = get_local_device()
self.dtype = getattr(od_config, "dtype", None) or torch.bfloat16
@@ -213,6 +227,8 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
if model_config.get("auk_vae_tile_frames") is not None:
vae_decode_kwargs["tile_frames"] = int(model_config["auk_vae_tile_frames"])
self.vae_decode = AuKVAEDecodeGraph(self.vae, enabled=not od_config.enforce_eager, **vae_decode_kwargs)
self._ref_cache_size = ref_cache_size
self._ref_cache: OrderedDict[str, torch.Tensor] = OrderedDict()
logger.info(
"AuK pipeline ready: variant=%s dtype=%s latent_dim=%d hop=%d sample_rate=%d",
@@ -267,6 +283,12 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
text = torch.zeros(1, _WARMUP_TEXT_TOKENS, self.text_hidden_dim, device=self.device, dtype=self.dtype)
c_mask = torch.ones(text.shape[:2], dtype=torch.bool, device=self.device)
timestep = torch.zeros((), device=self.device, dtype=torch.float32)
# The graphs are keyed by the number of steps, so warm the variant's default grid.
if self.is_flash:
grid = torch.tensor(self.flash_t_grid, device=self.device, dtype=torch.float32)
else:
sway = None if self.default_sway is None else float(self.default_sway)
grid = build_time_grid(nfe=self.default_nfe, sway_sampling_coef=sway, t_grid=None, device=self.device)
start = time.perf_counter()
with torch.inference_mode(), self._dit_autocast():
for ref_frames in _WARMUP_REF_FRAMES:
@@ -283,6 +305,8 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
timestep=timestep,
cfg_strength=cfg,
new_request=True,
timesteps=grid[:-1],
step_index=0,
)
if self.device.type != "cpu":
torch.accelerator.synchronize(self.device)
@@ -386,8 +410,24 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
if audio is None:
return torch.zeros(1, 0, self.latent_dim, device=self.device, dtype=torch.float32)
wav = self._prepare_waveform(_unwrap_single(audio)).to(self.device)
return self.vae.encode(wav, sample=sample, generator=generator)
wav = self._prepare_waveform(_unwrap_single(audio))
# The posterior mean is a pure function of the prepared waveform, so it
# is cached by content. A posterior draw depends on the generator and
# is always recomputed. Callers must not modify the returned tensor.
key = None
if not sample and self._ref_cache_size:
digest = hashlib.blake2b(wav.numpy().tobytes(), digest_size=16).hexdigest()
key = f"{wav.shape[-1]}:{digest}"
cached = self._ref_cache.get(key)
if cached is not None:
self._ref_cache.move_to_end(key)
return cached
latents = self.vae.encode(wav.to(self.device), sample=sample, generator=generator)
if key is not None:
self._ref_cache[key] = latents
while len(self._ref_cache) > self._ref_cache_size:
self._ref_cache.popitem(last=False)
return latents
def _target_frames(self, gen_seconds: Any, ref_frames: int) -> int:
"""Target latent length, capped by the remaining context."""
@@ -38,6 +38,7 @@ from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.diffusion.distributed.sp_plan import SequenceParallelInput, SequenceParallelOutput
from vllm_omni.diffusion.forward_context import get_forward_context, is_forward_context_available
from vllm_omni.diffusion.layers.norm import RMSNorm as _VllmRMSNorm
from vllm_omni.diffusion.models.utils import release_module_parameters_to_meta
from vllm_omni.diffusion.offloader.config import (
OffloadStrategy,
resolve_offload_strategy,
@@ -1084,6 +1085,7 @@ class Cosmos3LanguageModel(nn.Module):
rope_theta: float,
mrope_section: list[int],
quant_config: QuantizationConfig | None = None,
release_completed_blocks_to_meta: bool = False,
prefix: str = "",
) -> None:
super().__init__()
@@ -1093,21 +1095,21 @@ class Cosmos3LanguageModel(nn.Module):
rope_theta=rope_theta,
mrope_section=mrope_section,
)
self.layers = nn.ModuleList(
[
Cosmos3UndDecoderLayer(
hidden_size=hidden_size,
intermediate_size=intermediate_size,
num_attention_heads=num_attention_heads,
num_key_value_heads=num_key_value_heads,
head_dim=head_dim,
rms_norm_eps=rms_norm_eps,
quant_config=quant_config,
prefix=f"{prefix}.layers.{i}",
)
for i in range(num_hidden_layers)
]
)
self.layers = nn.ModuleList()
for i in range(num_hidden_layers):
layer = Cosmos3UndDecoderLayer(
hidden_size=hidden_size,
intermediate_size=intermediate_size,
num_attention_heads=num_attention_heads,
num_key_value_heads=num_key_value_heads,
head_dim=head_dim,
rms_norm_eps=rms_norm_eps,
quant_config=quant_config,
prefix=f"{prefix}.layers.{i}",
)
if release_completed_blocks_to_meta:
release_module_parameters_to_meta(layer)
self.layers.append(layer)
# TODO: Not used right now, will be used in the future for prompt upsampler.
self.norm = RMSNorm(hidden_size, eps=rms_norm_eps)
@@ -1228,6 +1230,10 @@ class Cosmos3VFMTransformer(nn.Module):
_hsdp_shard_conditions = [_is_transformer_block]
# Standard unquantized vLLM linears need no value-dependent post-load
# processing on accelerators. Edge inherits the same loading contract.
_hsdp_pre_sharded_meta_post_load = True
# Modules whose parameters must NOT be FSDP-sharded at the root level.
# time_embedder is cast to fp32 by post_load_weights for precision; if it
# were swept into the root flat-parameter under MixedPrecisionPolicy(param_dtype=bf16),
@@ -1346,6 +1352,10 @@ class Cosmos3VFMTransformer(nn.Module):
language_model_quant_config, gen_layers_quant_config = _resolve_cosmos3_quant_configs(
getattr(od_config, "quantization_config", None)
)
release_completed_blocks_to_meta = bool(
getattr(getattr(od_config, "parallel_config", None), "use_hsdp", False)
and getattr(od_config, "hsdp_weight_load_strategy", "full") == "pre_sharded"
)
mixed_precision_config, mixed_precision_source = resolve_mixed_precision_config(od_config)
if mixed_precision_config is None:
if mixed_precision_source == "additional_config_disabled":
@@ -1372,6 +1382,7 @@ class Cosmos3VFMTransformer(nn.Module):
rope_theta=self.rope_theta,
mrope_section=self.mrope_section,
quant_config=language_model_quant_config,
release_completed_blocks_to_meta=release_completed_blocks_to_meta,
prefix="language_model",
**self._language_model_kwargs(),
)
@@ -1399,24 +1410,24 @@ class Cosmos3VFMTransformer(nn.Module):
self.audio_proj_out = nn.Linear(self.hidden_size, self.sound_dim)
self.audio_modality_embed = nn.Parameter(torch.zeros(self.hidden_size))
self.gen_layers = nn.ModuleList(
[
Cosmos3GenDecoderLayer(
layer_idx=i,
hidden_size=self.hidden_size,
intermediate_size=self.intermediate_size,
num_attention_heads=self.num_attention_heads,
num_key_value_heads=self.num_key_value_heads,
head_dim=self.head_dim,
rms_norm_eps=self.rms_norm_eps,
quant_config=gen_layers_quant_config,
mlp_cls=self._gen_mlp_cls,
qk_norm=self.qk_norm_for_diffusion,
prefix=f"gen_layers.{i}",
)
for i in range(self.num_hidden_layers)
]
)
self.gen_layers = nn.ModuleList()
for i in range(self.num_hidden_layers):
layer = Cosmos3GenDecoderLayer(
layer_idx=i,
hidden_size=self.hidden_size,
intermediate_size=self.intermediate_size,
num_attention_heads=self.num_attention_heads,
num_key_value_heads=self.num_key_value_heads,
head_dim=self.head_dim,
rms_norm_eps=self.rms_norm_eps,
quant_config=gen_layers_quant_config,
mlp_cls=self._gen_mlp_cls,
qk_norm=self.qk_norm_for_diffusion,
prefix=f"gen_layers.{i}",
)
if release_completed_blocks_to_meta:
release_module_parameters_to_meta(layer)
self.gen_layers.append(layer)
self.mixed_precision_runtime: Cosmos3MixedPrecisionRuntime | None = None
if mixed_precision_config is not None:
@@ -14,6 +14,7 @@ from vllm.model_executor.layers.linear import ColumnParallelLinear, RowParallelL
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
from vllm_omni.diffusion.attention.layer import Attention as FrameworkAttention
from vllm_omni.diffusion.models.utils import release_module_parameters_to_meta
from .transformer_cosmos3 import (
Cosmos3VFMTransformer,
@@ -258,6 +259,7 @@ class Cosmos3EdgeLanguageModel(nn.Module):
mrope_section: list[int],
use_und_k_norm_for_gen: bool,
quant_config: QuantizationConfig | None = None,
release_completed_blocks_to_meta: bool = False,
prefix: str = "",
) -> None:
super().__init__()
@@ -267,22 +269,22 @@ class Cosmos3EdgeLanguageModel(nn.Module):
rope_theta=rope_theta,
mrope_section=mrope_section,
)
self.layers = nn.ModuleList(
[
Cosmos3EdgeUndDecoderLayer(
hidden_size=hidden_size,
intermediate_size=intermediate_size,
num_attention_heads=num_attention_heads,
num_key_value_heads=num_key_value_heads,
head_dim=head_dim,
rms_norm_eps=rms_norm_eps,
use_und_k_norm_for_gen=use_und_k_norm_for_gen,
quant_config=quant_config,
prefix=f"{prefix}.layers.{i}",
)
for i in range(num_hidden_layers)
]
)
self.layers = nn.ModuleList()
for i in range(num_hidden_layers):
layer = Cosmos3EdgeUndDecoderLayer(
hidden_size=hidden_size,
intermediate_size=intermediate_size,
num_attention_heads=num_attention_heads,
num_key_value_heads=num_key_value_heads,
head_dim=head_dim,
rms_norm_eps=rms_norm_eps,
use_und_k_norm_for_gen=use_und_k_norm_for_gen,
quant_config=quant_config,
prefix=f"{prefix}.layers.{i}",
)
if release_completed_blocks_to_meta:
release_module_parameters_to_meta(layer)
self.layers.append(layer)
self.norm = RMSNorm(hidden_size, eps=rms_norm_eps)
def forward(
@@ -288,13 +288,13 @@ def get_lingbot_video_post_process_func(od_config: OmniDiffusionConfig):
output_key = output_keys[0]
frames = frames[output_key]
output_type = getattr(sampling_params, "output_type", None) or "pt"
# The image serving path currently accepts PIL images or NumPy arrays,
# while LingBot decodes T2I outputs to tensors. Keep that compatibility
# conversion without discarding the model's image/video payload key.
# Decoded LingBot pixels are already in [0, 1]. The video API treats
# floating tensors as [-1, 1], so use its NumPy path to avoid normalizing
# twice. Images also need NumPy for serving; keep latent tensors intact.
if (
isinstance(frames, torch.Tensor)
and output_type != "latent"
and (output_type == "np" or output_key == "image")
and (output_type == "np" or output_key in {"image", "video"})
):
frames = frames.float().cpu().numpy()
return {output_key: frames} if output_key is not None else frames
+22
View File
@@ -168,6 +168,28 @@ def init_parameters(
init_parameters(child, dtype, device)
def release_module_parameters_to_meta(module: nn.Module) -> None:
"""Release a fully constructed module's parameter storage to meta.
The module must finish construction first so initialization code and vLLM
weight-loader metadata follow the ordinary CPU path. ``swap_tensors`` keeps
each registered Parameter object's identity while dropping only its storage.
Persistent buffers intentionally remain materialized.
"""
for param in module.parameters():
if param.device.type == "meta":
continue
meta_tensor = torch.empty_like(param, device="meta")
if type(param) is nn.Parameter:
meta_param = nn.Parameter(meta_tensor, requires_grad=param.requires_grad)
else:
meta_param = param.__class__.__new__(param.__class__, meta_tensor)
meta_param.requires_grad_(param.requires_grad)
meta_param.__dict__.update(getattr(param, "__dict__", {}))
torch.utils.swap_tensors(param, meta_param)
def init_parameters_preserving_vllm_linear(
module: nn.Module,
dtype: torch.dtype | None,